chore: normalize line endings (CRLF → LF)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
M09IcandClaude Opus 4.6 committed 2026-05-08 10:12:56 -07:00
1 parent b4e9b10c84
commit 0a6132ca02
20 files changed
+2643 -2643

No files matched your search

+34 -34
View File
@@ -1,34 +1,34 @@
name: CI name: CI
on: on:
pull_request: pull_request:
branches: [ master ] branches: [ master ]
push: push:
branches: [ master ] branches: [ master ]
jobs: jobs:
test: test:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, windows-latest] os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: "1.20" go-version: "1.20"
- name: Build - name: Build
run: go build ./... run: go build ./...
- name: Test (race) - name: Test (race)
run: go test ./core/pool/ ./core/baseline/ ./core/ihttp/ ./pkg/ -v -race -count=1 -timeout=120s run: go test ./core/pool/ ./core/baseline/ ./core/ihttp/ ./pkg/ -v -race -count=1 -timeout=120s
- name: Vet - name: Vet
run: go vet ./core/... ./pkg/... ./cmd/... run: go vet ./core/... ./pkg/... ./cmd/...
continue-on-error: true continue-on-error: true
+40 -40
View File
@@ -1,40 +1,40 @@
name: goreleaser name: goreleaser
on: on:
push: push:
tags: tags:
- 'v*.*.*' - 'v*.*.*'
workflow_dispatch: workflow_dispatch:
jobs: jobs:
goreleaser: goreleaser:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
submodules: recursive submodules: recursive
- name: Install upx - name: Install upx
run: sudo apt install upx -y run: sudo apt install upx -y
continue-on-error: true continue-on-error: true
- -
name: Set up Go name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: "1.20" go-version: "1.20"
- -
name: Run GoReleaser name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4 uses: goreleaser/goreleaser-action@v4
with: with:
distribution: goreleaser distribution: goreleaser
version: latest version: latest
args: release args: release
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOPATH: "/home/runner/go" GOPATH: "/home/runner/go"
+19 -19
View File
@@ -1,19 +1,19 @@
# Binaries for programs and plugins # Binaries for programs and plugins
*.exe *.exe
*.exe~ *.exe~
*.dll *.dll
*.so *.so
*.dylib *.dylib
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
.idea/ .idea/
bin/ bin/
*.stat *.stat
spray spray
+68 -68
View File
@@ -1,68 +1,68 @@
project_name: spray project_name: spray
before: before:
hooks: hooks:
- go mod tidy - go mod tidy
- go generate - go generate
builds: builds:
- -
main: . main: .
binary: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" binary: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
goos: goos:
- windows - windows
- linux - linux
- darwin - darwin
goarch: goarch:
- amd64 - amd64
- "386" - "386"
- arm64 - arm64
ignore: ignore:
- goos: windows - goos: windows
goarch: arm64 goarch: arm64
- goos: darwin - goos: darwin
goarch: "386" goarch: "386"
ldflags: "-s -w -X 'github.com/chainreactors/spray/cmd.ver=={{ .Tag }}'" ldflags: "-s -w -X 'github.com/chainreactors/spray/cmd.ver=={{ .Tag }}'"
flags: flags:
- -trimpath - -trimpath
asmflags: asmflags:
- all=-trimpath={{.Env.GOPATH}} - all=-trimpath={{.Env.GOPATH}}
gcflags: gcflags:
- all=-trimpath={{.Env.GOPATH}} - all=-trimpath={{.Env.GOPATH}}
no_unique_dist_dir: true no_unique_dist_dir: true
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
tags: tags:
- forceposix - forceposix
- osusergo - osusergo
- netgo - netgo
upx: upx:
- -
enabled: true enabled: true
goos: [linux, windows] goos: [linux, windows]
goarch: goarch:
- amd64 - amd64
- "386" - "386"
archives: archives:
- -
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
format: binary format: binary
checksum: checksum:
name_template: "{{ .ProjectName }}_checksums.txt" name_template: "{{ .ProjectName }}_checksums.txt"
changelog: changelog:
sort: desc sort: desc
filters: filters:
exclude: exclude:
- '^MERGE' - '^MERGE'
- "{{ .Tag }}" - "{{ .Tag }}"
- "^docs" - "^docs"
release: release:
github: github:
owner: chainreactors owner: chainreactors
name: spray name: spray
draft: true draft: true
+674 -674
View File
File diff suppressed because it is too large. Load diff
+127 -127
View File
@@ -1,128 +1,128 @@
# SPRAY # SPRAY
![](https://socialify.git.ci/chainreactors/spray/image?description=1&font=Inter&forks=1&issues=1&language=1&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Light) ![](https://socialify.git.ci/chainreactors/spray/image?description=1&font=Inter&forks=1&issues=1&language=1&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Light)
<p align="center"> <p align="center">
<a href="#features">Features</a> • <a href="#features">Features</a> •
<a href="#quickstart">QuickStart</a> • <a href="#quickstart">QuickStart</a> •
<a href="#make">Make</a> • <a href="#make">Make</a> •
<a href="https://chainreactors.github.io/wiki/spray/">Wiki</a> <a href="https://chainreactors.github.io/wiki/spray/">Wiki</a>
</p> </p>
## Features ## Features
* 超强的性能, 在本地测试极限性能的场景下, 能超过ffuf与feroxbruster的性能50%以上. 实际情况受到网络的影响, 感受没有这么明确. 但在多目标下可以感受到明显的区别. * 超强的性能, 在本地测试极限性能的场景下, 能超过ffuf与feroxbruster的性能50%以上. 实际情况受到网络的影响, 感受没有这么明确. 但在多目标下可以感受到明显的区别.
* 基于掩码的字典生成 * 基于掩码的字典生成
* 基于规则的字典生成 * 基于规则的字典生成
* 动态智能过滤, 自定义过滤策略 * 动态智能过滤, 自定义过滤策略
* 全量[gogo](https://github.com/chainreactors/gogo)的指纹识别, 全量的[fingerprinthub](https://github.com/0x727/FingerprintHub),[wappalyzer](https://github.com/projectdiscovery/wappalyzergo)指纹 * 全量[gogo](https://github.com/chainreactors/gogo)的指纹识别, 全量的[fingerprinthub](https://github.com/0x727/FingerprintHub),[wappalyzer](https://github.com/projectdiscovery/wappalyzergo)指纹
* 自定义信息提取, 内置敏感信息提取规则 * 自定义信息提取, 内置敏感信息提取规则
* 自定义输出格式与内容 * 自定义输出格式与内容
* *nix的命令行设计, 轻松与其他工具联动 * *nix的命令行设计, 轻松与其他工具联动
* 多角度的自动被ban,被waf判断 * 多角度的自动被ban,被waf判断
* 断点续传 * 断点续传
## QuickStart ## QuickStart
[**Document**](https://chainreactors.github.io/wiki/spray/start) [**Document**](https://chainreactors.github.io/wiki/spray/start)
### 基本使用 ### 基本使用
**从字典中读取目录进行爆破** **从字典中读取目录进行爆破**
`spray -u http://example.com -d wordlist1.txt -d wordlist2.txt` `spray -u http://example.com -d wordlist1.txt -d wordlist2.txt`
**通过掩码生成字典进行爆破** **通过掩码生成字典进行爆破**
`spray -u http://example.com -w "/aaa/bbb{?l#4}/ccc"` `spray -u http://example.com -w "/aaa/bbb{?l#4}/ccc"`
**通过规则生成字典爆破** **通过规则生成字典爆破**
规则文件格式参考hashcat的字典生成规则 规则文件格式参考hashcat的字典生成规则
`spray -u http://example.com -r rule.txt -d 1.txt` `spray -u http://example.com -r rule.txt -d 1.txt`
**批量爆破多个目标** **批量爆破多个目标**
`spray -l url.txt -r rule.txt -d 1.txt` `spray -l url.txt -r rule.txt -d 1.txt`
**断点续传** **断点续传**
`spray --resume stat.json` `spray --resume stat.json`
### 高级用法 ### 高级用法
**check-only 模式** **check-only 模式**
类似ehole/httpx这类对单页面信息收集的模式. 会有针对性的性能优化. 默认使用[templates](https://github.com/chainreactors/templates/tree/master/fingers)指纹库. 可以使用`--finger`打开第三方指纹库的匹配 类似ehole/httpx这类对单页面信息收集的模式. 会有针对性的性能优化. 默认使用[templates](https://github.com/chainreactors/templates/tree/master/fingers)指纹库. 可以使用`--finger`打开第三方指纹库的匹配
`spray -l url.txt --check-only` `spray -l url.txt --check-only`
**启用拓展指纹识别** **启用拓展指纹识别**
会进行主动探测常见的指纹目录, 并额外启用fingerprinthub与wappalyzer拓展指纹库 会进行主动探测常见的指纹目录, 并额外启用fingerprinthub与wappalyzer拓展指纹库
`spray -u http://example.com --finger ` `spray -u http://example.com --finger `
**启用爬虫** **启用爬虫**
`spray -u http://example.com --crawl` `spray -u http://example.com --crawl`
**扫描备份文件与常见通用文件** **扫描备份文件与常见通用文件**
`spray -u http://example.com --bak --common` `spray -u http://example.com --bak --common`
**启用所有插件** **启用所有插件**
`spray -u http://example.com -a` `spray -u http://example.com -a`
**被动url收集** **被动url收集**
参见: https://github.com/chainreactors/urlfounder 参见: https://github.com/chainreactors/urlfounder
## Wiki ## Wiki
详细用法请见[wiki](https://chainreactors.github.io/wiki/spray/) 详细用法请见[wiki](https://chainreactors.github.io/wiki/spray/)
https://chainreactors.github.io/wiki/spray/ https://chainreactors.github.io/wiki/spray/
## Make ## Make
``` ```
git clone --recurse-submodules https://github.com/chainreactors/spray git clone --recurse-submodules https://github.com/chainreactors/spray
cd spray cd spray
go mod tidy go mod tidy
go generate go generate
go build . go build .
``` ```
## Similar or related works ## Similar or related works
* [ffuf](https://github.com/ffuf/ffuf) 一款优秀的http fuzz工具, 与spray的功能有一定重合但并不完全相同 * [ffuf](https://github.com/ffuf/ffuf) 一款优秀的http fuzz工具, 与spray的功能有一定重合但并不完全相同
* [feroxbuster](https://github.com/epi052/feroxbuster) 在编写spray之前我最常使用的目录爆破工具, 但因为批量扫描与过滤配置不便的原因选择自行编写 * [feroxbuster](https://github.com/epi052/feroxbuster) 在编写spray之前我最常使用的目录爆破工具, 但因为批量扫描与过滤配置不便的原因选择自行编写
* [dirsearch](https://github.com/maurosoria/dirsearch) 较早的目录爆破工具, 参考了部分字典生成与配色 * [dirsearch](https://github.com/maurosoria/dirsearch) 较早的目录爆破工具, 参考了部分字典生成与配色
* [httpx](https://github.com/projectdiscovery/httpx) http信息收集功能, 参考了通过脚本语言编写任意过滤条件的功能 * [httpx](https://github.com/projectdiscovery/httpx) http信息收集功能, 参考了通过脚本语言编写任意过滤条件的功能
* [gobuster](https://github.com/OJ/gobuster) 一款统一是go编写的爆破工具, 但不仅限于目录爆破 * [gobuster](https://github.com/OJ/gobuster) 一款统一是go编写的爆破工具, 但不仅限于目录爆破
## TODO ## TODO
1. [x] 模糊对比 1. [x] 模糊对比
2. [x] 断点续传 2. [x] 断点续传
3. [x] 简易爬虫 3. [x] 简易爬虫
4. [x] 支持http2 4. [x] 支持http2
5. [ ] auto-tune, 自动调整并发数量 5. [ ] auto-tune, 自动调整并发数量
6. [x] 可自定义的递归配置 6. [x] 可自定义的递归配置
7. [x] 参考[feroxbuster](https://github.com/epi052/feroxbuster)的`--collect-backups`, 自动爆破有效目录的备份 7. [x] 参考[feroxbuster](https://github.com/epi052/feroxbuster)的`--collect-backups`, 自动爆破有效目录的备份
8. [x] 支持socks/http代理, 不建议使用, 优先级较低. 代理的keep-alive会带来严重的性能下降 8. [x] 支持socks/http代理, 不建议使用, 优先级较低. 代理的keep-alive会带来严重的性能下降
9. [ ] 云函数化, chainreactors工具链的通用分布式解决方案. 9. [ ] 云函数化, chainreactors工具链的通用分布式解决方案.
## Thanks ## Thanks
* [fuzzuli](https://github.com/musana/fuzzuli) 提供了一个备份文件字典生成思路 * [fuzzuli](https://github.com/musana/fuzzuli) 提供了一个备份文件字典生成思路
* [fingerprinthub](https://github.com/0x727/FingerprintHub) 作为指纹库的补充 * [fingerprinthub](https://github.com/0x727/FingerprintHub) 作为指纹库的补充
* [wappalyzer](https://github.com/projectdiscovery/wappalyzergo) 作为指纹库补充 * [wappalyzer](https://github.com/projectdiscovery/wappalyzergo) 作为指纹库补充
* [dirsearch](https://github.com/maurosoria/dirsearch) 提供了默认字典 * [dirsearch](https://github.com/maurosoria/dirsearch) 提供了默认字典
+154 -154
View File
@@ -1,156 +1,156 @@
input: input:
# Files, Multi,dict files, e.g.: -d 1.txt -d 2.txt # Files, Multi,dict files, e.g.: -d 1.txt -d 2.txt
dictionaries: [] dictionaries: []
# Bool, no dictionary # Bool, no dictionary
no-dict: false no-dict: false
# String, word generate dsl, e.g.: -w test{?ld#4} # String, word generate dsl, e.g.: -w test{?ld#4}
word: "" word: ""
# Files, rule files, e.g.: -r rule1.txt -r rule2.txt # Files, rule files, e.g.: -r rule1.txt -r rule2.txt
rules: [] rules: []
# Files, when found valid path , use append rule generator new word with current path # Files, when found valid path , use append rule generator new word with current path
append-rules: [] append-rules: []
# String, filter rule, e.g.: --rule-filter '>8 <4' # String, filter rule, e.g.: --rule-filter '>8 <4'
filter-rule: "" filter-rule: ""
# Files, when found valid path , use append file new word with current path # Files, when found valid path , use append file new word with current path
append-files: [] append-files: []
functions: functions:
# String, add extensions (separated by commas), e.g.: -e jsp,jspx # String, add extensions (separated by commas), e.g.: -e jsp,jspx
extension: "" extension: ""
# Bool, force add extensions # Bool, force add extensions
force-extension: false force-extension: false
# String, exclude extensions (separated by commas), e.g.: --exclude-extension jsp,jspx # String, exclude extensions (separated by commas), e.g.: --exclude-extension jsp,jspx
exclude-extension: "" exclude-extension: ""
# String, remove extensions (separated by commas), e.g.: --remove-extension jsp,jspx # String, remove extensions (separated by commas), e.g.: --remove-extension jsp,jspx
remove-extension: "" remove-extension: ""
# Bool, upper wordlist, e.g.: --uppercase # Bool, upper wordlist, e.g.: --uppercase
upper: false upper: false
# Bool, lower wordlist, e.g.: --lowercase # Bool, lower wordlist, e.g.: --lowercase
lower: false lower: false
# Strings, add prefix, e.g.: --prefix aaa --prefix bbb # Strings, add prefix, e.g.: --prefix aaa --prefix bbb
prefix: [] prefix: []
# Strings, add suffix, e.g.: --suffix aaa --suffix bbb # Strings, add suffix, e.g.: --suffix aaa --suffix bbb
suffix: [] suffix: []
# Strings, replace string, e.g.: --replace aaa:bbb --replace ccc:ddd # Strings, replace string, e.g.: --replace aaa:bbb --replace ccc:ddd
replace: {} replace: {}
# String, skip word when generate. rule, e.g.: --skip aaa # String, skip word when generate. rule, e.g.: --skip aaa
skip: [] skip: []
output: output:
# String, custom match function, e.g.: --match 'current.Status != 200'' # String, custom match function, e.g.: --match 'current.Status != 200''
match: "" match: ""
# String, custom filter function, e.g.: --filter 'current.Body contains "hello"' # String, custom filter function, e.g.: --filter 'current.Body contains "hello"'
filter: "" filter: ""
# String, open fuzzy output # String, open fuzzy output
fuzzy: false fuzzy: false
# String, output filename # String, output filename
output-file: "" output-file: ""
# String, fuzzy output filename # String, fuzzy output filename
fuzzy-file: "" fuzzy-file: ""
# String, dump all request, and write to filename # String, dump all request, and write to filename
dump-file: "" dump-file: ""
# Bool, dump all request # Bool, dump all request
dump: false dump: false
# Bool, auto generator output and fuzzy filename # Bool, auto generator output and fuzzy filename
auto-file: false auto-file: false
# String, output format, e.g.: --format 1.json # String, output format, e.g.: --format 1.json
format: "" format: ""
# String, output format # String, output format
output_probe: "" output_probe: ""
# Bool, Quiet # Bool, Quiet
quiet: false quiet: false
# Bool, no color # Bool, no color
no-color: false no-color: false
# Bool, No progress bar # Bool, No progress bar
no-bar: false no-bar: false
# Bool, No stat # Bool, No stat
no-stat: false no-stat: false
plugins: plugins:
# Bool, enable all plugin # Bool, enable all plugin
all: false all: false
# Strings, extract response, e.g.: --extract js --extract ip --extract version:(.*?) # Strings, extract response, e.g.: --extract js --extract ip --extract version:(.*?)
extract: [] extract: []
# String, extract config filename # String, extract config filename
extract-config: "" extract-config: ""
# Bool, enable recon # Bool, enable recon
recon: false recon: false
# Bool, enable active finger detect # Bool, enable active finger detect
finger: false finger: false
# Bool, enable bak found # Bool, enable bak found
bak: false bak: false
# Bool, enable valid result bak found, equal --append-rule rule/filebak.txt # Bool, enable valid result bak found, equal --append-rule rule/filebak.txt
file-bak: false file-bak: false
# Bool, enable common file found # Bool, enable common file found
common: false common: false
# Bool, enable crawl # Bool, enable crawl
crawl: false crawl: false
# Int, crawl depth # Int, crawl depth
crawl-depth: 3 crawl-depth: 3
request: request:
# Strings, custom headers, e.g.: --headers 'Auth: example_auth' # Strings, custom headers, e.g.: --headers 'Auth: example_auth'
headers: [] headers: []
# String, custom user-agent, e.g.: --user-agent Custom # String, custom user-agent, e.g.: --user-agent Custom
useragent: "" useragent: ""
# Bool, use random with default user-agent # Bool, use random with default user-agent
random-useragent: false random-useragent: false
# Strings, custom cookie # Strings, custom cookie
cookies: [] cookies: []
# Bool, read all response body # Bool, read all response body
read-all: false read-all: false
# Int, max response body length (kb), -1 read-all, 0 not read body, default 100k, e.g. --max-length 1000 # Int, max response body length (kb), -1 read-all, 0 not read body, default 100k, e.g. --max-length 1000
max-length: 100 max-length: 100
mode: mode:
# Int, request rate limit (rate/s), e.g.: --rate-limit 100 # Int, request rate limit (rate/s), e.g.: --rate-limit 100
rate-limit: 0 rate-limit: 0
# Bool, skip error break # Bool, skip error break
force: false force: false
# Bool, check only # Bool, check only
default: false default: false
# Bool, no scope # Bool, no scope
no-scope: false no-scope: false
# String, custom scope, e.g.: --scope *.example.com # String, custom scope, e.g.: --scope *.example.com
scope: [] scope: []
# String,custom recursive rule, e.g.: --recursive current.IsDir() # String,custom recursive rule, e.g.: --recursive current.IsDir()
recursive: current.IsDir() recursive: current.IsDir()
# Int, recursive depth # Int, recursive depth
depth: 0 depth: 0
# String, custom index path # String, custom index path
index: / index: /
# String, custom random path # String, custom random path
random: "" random: ""
# Int, check period when request # Int, check period when request
check-period: 200 check-period: 200
# Int, check period when error # Int, check period when error
error-period: 10 error-period: 10
# Int, break when the error exceeds the threshold # Int, break when the error exceeds the threshold
error-threshold: 20 error-threshold: 20
# Strings (comma split),custom black status # Strings (comma split),custom black status
black-status: 400,410 black-status: 400,410
# Strings (comma split), custom white status # Strings (comma split), custom white status
white-status: 200 white-status: 200
# Strings (comma split), custom fuzzy status # Strings (comma split), custom fuzzy status
fuzzy-status: 500,501,502,503 fuzzy-status: 500,501,502,503
# Strings (comma split), custom unique status # Strings (comma split), custom unique status
unique-status: 403,200,404 unique-status: 403,200,404
# Bool, unique response # Bool, unique response
unique: false unique: false
# Int, retry count # Int, retry count
retry: 0 retry: 0
sim-distance: 5 sim-distance: 5
misc: misc:
# String, path/host spray # String, path/host spray
mod: path mod: path
# String, Client type # String, Client type
client: auto client: auto
# Int, deadline (seconds) # Int, deadline (seconds)
deadline: 999999 deadline: 999999
# Int, timeout with request (seconds) # Int, timeout with request (seconds)
timeout: 5 timeout: 5
# Int, Pool size # Int, Pool size
pool: 5 pool: 5
# Int, number of threads per pool # Int, number of threads per pool
thread: 20 thread: 20
# Bool, output debug info # Bool, output debug info
debug: false debug: false
# Bool, log verbose level ,default 0, level1: -v level2 -vv # Bool, log verbose level ,default 0, level1: -v level2 -vv
verbose: [] verbose: []
# String, proxy address, e.g.: --proxy socks5://127.0.0.1:1080 # String, proxy address, e.g.: --proxy socks5://127.0.0.1:1080
proxy: "" proxy: ""
+278 -278
View File
@@ -1,278 +1,278 @@
package core package core
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/chainreactors/logs" "github.com/chainreactors/logs"
"github.com/chainreactors/spray/core/baseline" "github.com/chainreactors/spray/core/baseline"
"github.com/chainreactors/spray/pkg" "github.com/chainreactors/spray/pkg"
"github.com/chainreactors/words/mask" "github.com/chainreactors/words/mask"
"io" "io"
"net/url" "net/url"
"os" "os"
"sort" "sort"
"strings" "strings"
) )
// TreeNode represents a node in the path tree // TreeNode represents a node in the path tree
type TreeNode struct { type TreeNode struct {
Name string Name string
Children map[string]*TreeNode Children map[string]*TreeNode
Result *baseline.Baseline // nil for directory nodes Result *baseline.Baseline // nil for directory nodes
IsLeaf bool IsLeaf bool
} }
// NewTreeNode creates a new tree node // NewTreeNode creates a new tree node
func NewTreeNode(name string) *TreeNode { func NewTreeNode(name string) *TreeNode {
return &TreeNode{ return &TreeNode{
Name: name, Name: name,
Children: make(map[string]*TreeNode), Children: make(map[string]*TreeNode),
IsLeaf: false, IsLeaf: false,
} }
} }
// AddPath adds a path to the tree // AddPath adds a path to the tree
func (tn *TreeNode) AddPath(parts []string, result *baseline.Baseline) { func (tn *TreeNode) AddPath(parts []string, result *baseline.Baseline) {
if len(parts) == 0 { if len(parts) == 0 {
tn.Result = result tn.Result = result
tn.IsLeaf = true tn.IsLeaf = true
return return
} }
part := parts[0] part := parts[0]
if part == "" { if part == "" {
if len(parts) > 1 { if len(parts) > 1 {
tn.AddPath(parts[1:], result) tn.AddPath(parts[1:], result)
} else { } else {
tn.Result = result tn.Result = result
tn.IsLeaf = true tn.IsLeaf = true
} }
return return
} }
if _, exists := tn.Children[part]; !exists { if _, exists := tn.Children[part]; !exists {
tn.Children[part] = NewTreeNode(part) tn.Children[part] = NewTreeNode(part)
} }
tn.Children[part].AddPath(parts[1:], result) tn.Children[part].AddPath(parts[1:], result)
} }
// RenderTree renders the tree structure // RenderTree renders the tree structure
func (tn *TreeNode) RenderTree(prefix string, isLast bool, color bool) string { func (tn *TreeNode) RenderTree(prefix string, isLast bool, color bool) string {
var sb strings.Builder var sb strings.Builder
if tn.Name != "" { if tn.Name != "" {
connector := "├── " connector := "├── "
if isLast { if isLast {
connector = "└── " connector = "└── "
} }
sb.WriteString(prefix) sb.WriteString(prefix)
sb.WriteString(connector) sb.WriteString(connector)
if tn.IsLeaf && tn.Result != nil { if tn.IsLeaf && tn.Result != nil {
// Leaf node with result // Leaf node with result
if color { if color {
sb.WriteString(tn.Result.ColorString()) sb.WriteString(tn.Result.ColorString())
} else { } else {
sb.WriteString(tn.Result.String()) sb.WriteString(tn.Result.String())
} }
} else { } else {
// Directory node // Directory node
sb.WriteString(tn.Name) sb.WriteString(tn.Name)
if !strings.HasSuffix(tn.Name, "/") { if !strings.HasSuffix(tn.Name, "/") {
sb.WriteString("/") sb.WriteString("/")
} }
} }
sb.WriteString("\n") sb.WriteString("\n")
} }
// Sort children for consistent output // Sort children for consistent output
var keys []string var keys []string
for k := range tn.Children { for k := range tn.Children {
keys = append(keys, k) keys = append(keys, k)
} }
sort.Strings(keys) sort.Strings(keys)
for i, key := range keys { for i, key := range keys {
child := tn.Children[key] child := tn.Children[key]
isLastChild := i == len(keys)-1 isLastChild := i == len(keys)-1
newPrefix := prefix newPrefix := prefix
if tn.Name != "" { if tn.Name != "" {
if isLast { if isLast {
newPrefix += " " newPrefix += " "
} else { } else {
newPrefix += "│ " newPrefix += "│ "
} }
} }
sb.WriteString(child.RenderTree(newPrefix, isLastChild, color)) sb.WriteString(child.RenderTree(newPrefix, isLastChild, color))
} }
return sb.String() return sb.String()
} }
func Format(opts Option) { func Format(opts Option) {
var content []byte var content []byte
var err error var err error
if opts.Format == "stdin" { if opts.Format == "stdin" {
content, err = io.ReadAll(os.Stdin) content, err = io.ReadAll(os.Stdin)
} else { } else {
content, err = os.ReadFile(opts.Format) content, err = os.ReadFile(opts.Format)
} }
if err != nil { if err != nil {
return return
} }
// Group by host+path for deduplication. // Group by host+path for deduplication.
// Prefer the HTTP Host header when present so that // Prefer the HTTP Host header when present so that
// different virtual hosts on the same IP:port do not // different virtual hosts on the same IP:port do not
// get merged together. // get merged together.
group := make(map[string]map[string]*baseline.Baseline) group := make(map[string]map[string]*baseline.Baseline)
for _, line := range bytes.Split(bytes.TrimSpace(content), []byte("\n")) { for _, line := range bytes.Split(bytes.TrimSpace(content), []byte("\n")) {
var result baseline.Baseline var result baseline.Baseline
err := json.Unmarshal(line, &result) err := json.Unmarshal(line, &result)
if err != nil { if err != nil {
logs.Log.Error(err.Error()) logs.Log.Error(err.Error())
return return
} }
result.Url, err = url.Parse(result.UrlString) result.Url, err = url.Parse(result.UrlString)
if err != nil { if err != nil {
continue continue
} }
// Determine host key: prefer Host header when available. // Determine host key: prefer Host header when available.
hostKey := result.Host hostKey := result.Host
if hostKey == "" { if hostKey == "" {
hostKey = result.Url.Host hostKey = result.Url.Host
// Normalize default ports when URL host does not include one. // Normalize default ports when URL host does not include one.
if result.Url.Port() == "" { if result.Url.Port() == "" {
if result.Url.Scheme == "https" { if result.Url.Scheme == "https" {
hostKey += ":443" hostKey += ":443"
} else if result.Url.Scheme == "http" { } else if result.Url.Scheme == "http" {
hostKey += ":80" hostKey += ":80"
} }
} }
} }
if hostKey == "" { if hostKey == "" {
continue continue
} }
if _, exists := group[hostKey]; !exists { if _, exists := group[hostKey]; !exists {
group[hostKey] = make(map[string]*baseline.Baseline) group[hostKey] = make(map[string]*baseline.Baseline)
} }
group[hostKey][result.Path] = &result group[hostKey][result.Path] = &result
} }
// Default to tree mode if not specified // Default to tree mode if not specified
outputProbe := opts.OutputProbe outputProbe := opts.OutputProbe
if outputProbe == "" { if outputProbe == "" {
outputProbe = "full" outputProbe = "full"
} }
if outputProbe == "tree" { if outputProbe == "tree" {
formatTree(group, opts) formatTree(group, opts)
} else if outputProbe == "full" { } else if outputProbe == "full" {
formatFull(group, opts) formatFull(group, opts)
} else { } else {
// Custom probe output // Custom probe output
formatProbe(group, opts, outputProbe) formatProbe(group, opts, outputProbe)
} }
} }
// formatTree renders results in tree structure // formatTree renders results in tree structure
func formatTree(group map[string]map[string]*baseline.Baseline, opts Option) { func formatTree(group map[string]map[string]*baseline.Baseline, opts Option) {
// Sort host:port for consistent output // Sort host:port for consistent output
var hosts []string var hosts []string
for host := range group { for host := range group {
hosts = append(hosts, host) hosts = append(hosts, host)
} }
sort.Strings(hosts) sort.Strings(hosts)
for _, host := range hosts { for _, host := range hosts {
results := group[host] results := group[host]
// Build tree for this host // Build tree for this host
root := NewTreeNode("") root := NewTreeNode("")
for path, result := range results { for path, result := range results {
if !opts.Fuzzy && result.IsFuzzy { if !opts.Fuzzy && result.IsFuzzy {
continue continue
} }
// Split path into parts // Split path into parts
parts := strings.Split(strings.Trim(path, "/"), "/") parts := strings.Split(strings.Trim(path, "/"), "/")
if path == "/" { if path == "/" {
parts = []string{"/"} parts = []string{"/"}
} }
root.AddPath(parts, result) root.AddPath(parts, result)
} }
// Print host header // Print host header
logs.Log.Console(fmt.Sprintf("\n%s\n", host)) logs.Log.Console(fmt.Sprintf("\n%s\n", host))
// Render tree // Render tree
logs.Log.Console(root.RenderTree("", true, !opts.NoColor)) logs.Log.Console(root.RenderTree("", true, !opts.NoColor))
} }
} }
// formatFull renders results in full/original format // formatFull renders results in full/original format
func formatFull(group map[string]map[string]*baseline.Baseline, opts Option) { func formatFull(group map[string]map[string]*baseline.Baseline, opts Option) {
for _, results := range group { for _, results := range group {
for _, result := range results { for _, result := range results {
if !opts.Fuzzy && result.IsFuzzy { if !opts.Fuzzy && result.IsFuzzy {
continue continue
} }
if !opts.NoColor { if !opts.NoColor {
logs.Log.Console(result.ColorString() + "\n") logs.Log.Console(result.ColorString() + "\n")
} else { } else {
logs.Log.Console(result.String() + "\n") logs.Log.Console(result.String() + "\n")
} }
} }
} }
} }
// formatProbe renders results with custom probe fields // formatProbe renders results with custom probe fields
func formatProbe(group map[string]map[string]*baseline.Baseline, opts Option, probeFields string) { func formatProbe(group map[string]map[string]*baseline.Baseline, opts Option, probeFields string) {
probes := strings.Split(probeFields, ",") probes := strings.Split(probeFields, ",")
for _, results := range group { for _, results := range group {
for _, result := range results { for _, result := range results {
if !opts.Fuzzy && result.IsFuzzy { if !opts.Fuzzy && result.IsFuzzy {
continue continue
} }
logs.Log.Console(result.ProbeOutput(probes) + "\n") logs.Log.Console(result.ProbeOutput(probes) + "\n")
} }
} }
} }
func PrintPreset() { func PrintPreset() {
logs.Log.Console("internal rules:\n") logs.Log.Console("internal rules:\n")
for name, rule := range pkg.Rules { for name, rule := range pkg.Rules {
logs.Log.Consolef("\t%s\t%d rules\n", name, len(strings.Split(rule, "\n"))) logs.Log.Consolef("\t%s\t%d rules\n", name, len(strings.Split(rule, "\n")))
} }
logs.Log.Console("\ninternal dicts:\n") logs.Log.Console("\ninternal dicts:\n")
for name, dict := range pkg.Dicts { for name, dict := range pkg.Dicts {
logs.Log.Consolef("\t%s\t%d items\n", name, len(dict)) logs.Log.Consolef("\t%s\t%d items\n", name, len(dict))
} }
logs.Log.Console("\ninternal words keyword:\n") logs.Log.Console("\ninternal words keyword:\n")
for name, words := range mask.SpecialWords { for name, words := range mask.SpecialWords {
logs.Log.Consolef("\t%s\t%d words\n", name, len(words)) logs.Log.Consolef("\t%s\t%d words\n", name, len(words))
} }
logs.Log.Console("\ninternal extractor:\n") logs.Log.Console("\ninternal extractor:\n")
for name, _ := range pkg.ExtractRegexps { for name, _ := range pkg.ExtractRegexps {
logs.Log.Consolef("\t%s\n", name) logs.Log.Consolef("\t%s\n", name)
} }
logs.Log.Console("\ninternal fingers:\n") logs.Log.Console("\ninternal fingers:\n")
for name, engine := range pkg.FingerEngine.EnginesImpl { for name, engine := range pkg.FingerEngine.EnginesImpl {
logs.Log.Consolef("\t%s\t%d fingerprints \n", name, engine.Len()) logs.Log.Consolef("\t%s\t%d fingerprints \n", name, engine.Len())
} }
logs.Log.Consolef("\nload %d active path\n", len(pkg.ActivePath)) logs.Log.Consolef("\nload %d active path\n", len(pkg.ActivePath))
} }
+136 -136
View File
@@ -1,136 +1,136 @@
package ihttp package ihttp
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"github.com/chainreactors/proxyclient" "github.com/chainreactors/proxyclient"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"net" "net"
"net/http" "net/http"
"time" "time"
) )
var ( var (
DefaultMaxBodySize int64 = 1024 * 100 // 100k DefaultMaxBodySize int64 = 1024 * 100 // 100k
) )
func CheckBodySize(size int64) bool { func CheckBodySize(size int64) bool {
if DefaultMaxBodySize == -1 { if DefaultMaxBodySize == -1 {
return true return true
} }
if DefaultMaxBodySize == 0 { if DefaultMaxBodySize == 0 {
return false return false
} }
return size < DefaultMaxBodySize return size < DefaultMaxBodySize
} }
const ( const (
Auto = iota Auto = iota
FAST FAST
STANDARD STANDARD
) )
func NewClient(config *ClientConfig) *Client { func NewClient(config *ClientConfig) *Client {
var client *Client var client *Client
if config.Type == FAST { if config.Type == FAST {
client = &Client{ client = &Client{
fastClient: &fasthttp.Client{ fastClient: &fasthttp.Client{
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
Renegotiation: tls.RenegotiateOnceAsClient, Renegotiation: tls.RenegotiateOnceAsClient,
InsecureSkipVerify: true, InsecureSkipVerify: true,
}, },
Dial: customDialFunc(config.ProxyClient, config.Timeout), Dial: customDialFunc(config.ProxyClient, config.Timeout),
MaxConnsPerHost: config.Thread * 3 / 2, MaxConnsPerHost: config.Thread * 3 / 2,
MaxIdleConnDuration: config.Timeout, MaxIdleConnDuration: config.Timeout,
//MaxConnWaitTimeout: time.Duration(timeout) * time.Second, //MaxConnWaitTimeout: time.Duration(timeout) * time.Second,
ReadTimeout: config.Timeout, ReadTimeout: config.Timeout,
WriteTimeout: config.Timeout, WriteTimeout: config.Timeout,
ReadBufferSize: 16384, // 16k ReadBufferSize: 16384, // 16k
MaxResponseBodySize: int(DefaultMaxBodySize), MaxResponseBodySize: int(DefaultMaxBodySize),
NoDefaultUserAgentHeader: true, NoDefaultUserAgentHeader: true,
DisablePathNormalizing: true, DisablePathNormalizing: true,
DisableHeaderNamesNormalizing: true, DisableHeaderNamesNormalizing: true,
}, },
ClientConfig: config, ClientConfig: config,
} }
} else { } else {
client = &Client{ client = &Client{
standardClient: &http.Client{ standardClient: &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
DialContext: config.ProxyClient, DialContext: config.ProxyClient,
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
Renegotiation: tls.RenegotiateNever, Renegotiation: tls.RenegotiateNever,
InsecureSkipVerify: true, InsecureSkipVerify: true,
}, },
TLSHandshakeTimeout: config.Timeout, TLSHandshakeTimeout: config.Timeout,
MaxConnsPerHost: config.Thread * 3 / 2, MaxConnsPerHost: config.Thread * 3 / 2,
IdleConnTimeout: config.Timeout, IdleConnTimeout: config.Timeout,
ReadBufferSize: 16384, // 16k ReadBufferSize: 16384, // 16k
}, },
Timeout: config.Timeout, Timeout: config.Timeout,
CheckRedirect: func(req *http.Request, via []*http.Request) error { CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse return http.ErrUseLastResponse
}, },
}, },
ClientConfig: config, ClientConfig: config,
} }
} }
return client return client
} }
type ClientConfig struct { type ClientConfig struct {
Type int Type int
Timeout time.Duration Timeout time.Duration
Thread int Thread int
ProxyClient proxyclient.Dial ProxyClient proxyclient.Dial
} }
type Client struct { type Client struct {
fastClient *fasthttp.Client fastClient *fasthttp.Client
standardClient *http.Client standardClient *http.Client
*ClientConfig *ClientConfig
} }
func (c *Client) TransToCheck() { func (c *Client) TransToCheck() {
if c.fastClient != nil { if c.fastClient != nil {
c.fastClient.MaxConnsPerHost = -1 // disable keepalive c.fastClient.MaxConnsPerHost = -1 // disable keepalive
} else if c.standardClient != nil { } else if c.standardClient != nil {
c.standardClient.Transport.(*http.Transport).DisableKeepAlives = true // disable keepalive c.standardClient.Transport.(*http.Transport).DisableKeepAlives = true // disable keepalive
} }
} }
func (c *Client) FastDo(req *fasthttp.Request) (*fasthttp.Response, error) { func (c *Client) FastDo(req *fasthttp.Request) (*fasthttp.Response, error) {
resp := fasthttp.AcquireResponse() resp := fasthttp.AcquireResponse()
err := c.fastClient.DoTimeout(req, resp, c.Timeout) err := c.fastClient.DoTimeout(req, resp, c.Timeout)
return resp, err return resp, err
} }
func (c *Client) StandardDo(req *http.Request) (*http.Response, error) { func (c *Client) StandardDo(req *http.Request) (*http.Response, error) {
return c.standardClient.Do(req) return c.standardClient.Do(req)
} }
func (c *Client) Do(req *Request) (*Response, error) { func (c *Client) Do(req *Request) (*Response, error) {
if c.fastClient != nil { if c.fastClient != nil {
resp, err := c.FastDo(req.FastRequest) resp, err := c.FastDo(req.FastRequest)
return &Response{FastResponse: resp, ClientType: FAST}, err return &Response{FastResponse: resp, ClientType: FAST}, err
} else if c.standardClient != nil { } else if c.standardClient != nil {
resp, err := c.StandardDo(req.StandardRequest) resp, err := c.StandardDo(req.StandardRequest)
return &Response{StandardResponse: resp, ClientType: STANDARD}, err return &Response{StandardResponse: resp, ClientType: STANDARD}, err
} else { } else {
return nil, fmt.Errorf("not found client") return nil, fmt.Errorf("not found client")
} }
} }
func customDialFunc(dialer proxyclient.Dial, timeout time.Duration) fasthttp.DialFunc { func customDialFunc(dialer proxyclient.Dial, timeout time.Duration) fasthttp.DialFunc {
if dialer == nil { if dialer == nil {
return func(addr string) (net.Conn, error) { return func(addr string) (net.Conn, error) {
return fasthttp.DialTimeout(addr, timeout) return fasthttp.DialTimeout(addr, timeout)
} }
} }
return func(addr string) (net.Conn, error) { return func(addr string) (net.Conn, error) {
ctx, _ := context.WithTimeout(context.Background(), timeout) ctx, _ := context.WithTimeout(context.Background(), timeout)
return dialer.DialContext(ctx, "tcp", addr) return dialer.DialContext(ctx, "tcp", addr)
} }
} }
+110 -110
View File
@@ -1,110 +1,110 @@
package ihttp package ihttp
import ( import (
"github.com/chainreactors/logs" "github.com/chainreactors/logs"
"github.com/chainreactors/utils/httputils" "github.com/chainreactors/utils/httputils"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"io" "io"
"net/http" "net/http"
"strings" "strings"
) )
type Response struct { type Response struct {
StandardResponse *http.Response StandardResponse *http.Response
FastResponse *fasthttp.Response FastResponse *fasthttp.Response
ClientType int ClientType int
} }
func (r *Response) StatusCode() int { func (r *Response) StatusCode() int {
if r.FastResponse != nil { if r.FastResponse != nil {
return r.FastResponse.StatusCode() return r.FastResponse.StatusCode()
} else if r.StandardResponse != nil { } else if r.StandardResponse != nil {
return r.StandardResponse.StatusCode return r.StandardResponse.StatusCode
} else { } else {
return 0 return 0
} }
} }
func (r *Response) Body() []byte { func (r *Response) Body() []byte {
if r.FastResponse != nil { if r.FastResponse != nil {
return r.FastResponse.Body() return r.FastResponse.Body()
} else if r.StandardResponse != nil { } else if r.StandardResponse != nil {
if r.StandardResponse.ContentLength == -1 { if r.StandardResponse.ContentLength == -1 {
body, err := io.ReadAll(r.StandardResponse.Body) body, err := io.ReadAll(r.StandardResponse.Body)
if err != nil { if err != nil {
return nil return nil
} }
return body return body
} else { } else {
var body []byte var body []byte
if r.StandardResponse.ContentLength > 0 && CheckBodySize(r.StandardResponse.ContentLength) { if r.StandardResponse.ContentLength > 0 && CheckBodySize(r.StandardResponse.ContentLength) {
body = make([]byte, r.StandardResponse.ContentLength) body = make([]byte, r.StandardResponse.ContentLength)
} else { } else {
return nil return nil
} }
n, err := io.ReadFull(r.StandardResponse.Body, body) n, err := io.ReadFull(r.StandardResponse.Body, body)
_ = r.StandardResponse.Body.Close() _ = r.StandardResponse.Body.Close()
if err == nil { if err == nil {
return body return body
} else if err == io.ErrUnexpectedEOF { } else if err == io.ErrUnexpectedEOF {
return body[:n] return body[:n]
} else if err == io.EOF { } else if err == io.EOF {
return nil return nil
} else { } else {
logs.Log.Error("readfull failed, " + err.Error()) logs.Log.Error("readfull failed, " + err.Error())
return nil return nil
} }
} }
return nil return nil
} else { } else {
return nil return nil
} }
} }
func (r *Response) ContentLength() int64 { func (r *Response) ContentLength() int64 {
if r.FastResponse != nil { if r.FastResponse != nil {
return int64(r.FastResponse.Header.ContentLength()) return int64(r.FastResponse.Header.ContentLength())
} else if r.StandardResponse != nil { } else if r.StandardResponse != nil {
return r.StandardResponse.ContentLength return r.StandardResponse.ContentLength
} else { } else {
return 0 return 0
} }
} }
func (r *Response) ContentType() string { func (r *Response) ContentType() string {
var t string var t string
if r.FastResponse != nil { if r.FastResponse != nil {
t = string(r.FastResponse.Header.ContentType()) t = string(r.FastResponse.Header.ContentType())
} else if r.StandardResponse != nil { } else if r.StandardResponse != nil {
t = r.StandardResponse.Header.Get("Content-Type") t = r.StandardResponse.Header.Get("Content-Type")
} else { } else {
return "" return ""
} }
if i := strings.Index(t, ";"); i > 0 { if i := strings.Index(t, ";"); i > 0 {
return t[:i] return t[:i]
} else { } else {
return t return t
} }
} }
func (r *Response) Header() []byte { func (r *Response) Header() []byte {
if r.FastResponse != nil { if r.FastResponse != nil {
return r.FastResponse.Header.Header() return r.FastResponse.Header.Header()
} else if r.StandardResponse != nil { } else if r.StandardResponse != nil {
return append(httputils.ReadRawHeader(r.StandardResponse), []byte("\r\n")...) return append(httputils.ReadRawHeader(r.StandardResponse), []byte("\r\n")...)
} else { } else {
return nil return nil
} }
} }
func (r *Response) GetHeader(key string) string { func (r *Response) GetHeader(key string) string {
if r.FastResponse != nil { if r.FastResponse != nil {
return string(r.FastResponse.Header.Peek(key)) return string(r.FastResponse.Header.Peek(key))
} else if r.StandardResponse != nil { } else if r.StandardResponse != nil {
return r.StandardResponse.Header.Get(key) return r.StandardResponse.Header.Get(key)
} else { } else {
return "" return ""
} }
} }
+70 -70
View File
@@ -1,70 +1,70 @@
package pool package pool
import ( import (
"github.com/chainreactors/logs" "github.com/chainreactors/logs"
"github.com/chainreactors/proxyclient" "github.com/chainreactors/proxyclient"
"github.com/chainreactors/spray/core/baseline" "github.com/chainreactors/spray/core/baseline"
"github.com/chainreactors/spray/core/ihttp" "github.com/chainreactors/spray/core/ihttp"
"github.com/chainreactors/words" "github.com/chainreactors/words"
"github.com/chainreactors/words/rule" "github.com/chainreactors/words/rule"
"github.com/expr-lang/expr/vm" "github.com/expr-lang/expr/vm"
"sync" "sync"
"time" "time"
) )
type Config struct { type Config struct {
BaseURL string BaseURL string
ProxyClient proxyclient.Dial ProxyClient proxyclient.Dial
Thread int Thread int
Wordlist []string Wordlist []string
Timeout time.Duration Timeout time.Duration
ProcessCh chan *baseline.Baseline ProcessCh chan *baseline.Baseline
OutputCh chan *baseline.Baseline OutputCh chan *baseline.Baseline
FuzzyCh chan *baseline.Baseline FuzzyCh chan *baseline.Baseline
Outwg *sync.WaitGroup Outwg *sync.WaitGroup
RateLimit int RateLimit int
CheckPeriod int CheckPeriod int
ErrPeriod int32 ErrPeriod int32
BreakThreshold int32 BreakThreshold int32
Mod SprayMod Mod SprayMod
Request *ihttp.RequestConfig Request *ihttp.RequestConfig
ClientType int ClientType int
MatchExpr *vm.Program MatchExpr *vm.Program
FilterExpr *vm.Program FilterExpr *vm.Program
RecuExpr *vm.Program RecuExpr *vm.Program
AppendRule *rule.Program AppendRule *rule.Program
Fns []words.WordFunc Fns []words.WordFunc
AppendWords []string AppendWords []string
Fuzzy bool Fuzzy bool
IgnoreWaf bool IgnoreWaf bool
Crawl bool Crawl bool
Scope []string Scope []string
Active bool Active bool
Bak bool Bak bool
Fuzzuli bool Fuzzuli bool
Common bool Common bool
RetryLimit int RetryLimit int
Random string Random string
Index string Index string
MaxRedirect int MaxRedirect int
MaxCrawlDepth int MaxCrawlDepth int
MaxRecursionDepth int MaxRecursionDepth int
MaxAppendDepth int MaxAppendDepth int
} }
func NewBruteWords(config *Config, list []string) *words.Worder { func NewBruteWords(config *Config, list []string) *words.Worder {
word := words.NewWorderWithList(list) word := words.NewWorderWithList(list)
word.Fns = config.Fns word.Fns = config.Fns
word.Run() word.Run()
return word return word
} }
func NewBruteDSL(config *Config, dsl string, params [][]string) *words.Worder { func NewBruteDSL(config *Config, dsl string, params [][]string) *words.Worder {
word, err := words.NewWorderWithDsl(dsl, params, nil) word, err := words.NewWorderWithDsl(dsl, params, nil)
if err != nil { if err != nil {
logs.Log.Error(err.Error()) logs.Log.Error(err.Error())
} }
word.Fns = config.Fns word.Fns = config.Fns
word.Run() word.Run()
return word return word
} }
+526 -526
View File
File diff suppressed because it is too large. Load diff
+73 -73
View File
@@ -1,73 +1,73 @@
package core package core
import ( import (
"fmt" "fmt"
"github.com/chainreactors/logs" "github.com/chainreactors/logs"
"github.com/chainreactors/utils" "github.com/chainreactors/utils"
"github.com/chainreactors/words/rule" "github.com/chainreactors/words/rule"
"net/url" "net/url"
) )
type Task struct { type Task struct {
baseUrl string baseUrl string
depth int depth int
rule []rule.Expression rule []rule.Expression
origin *Origin origin *Origin
} }
func NewTaskGenerator(port string) *TaskGenerator { func NewTaskGenerator(port string) *TaskGenerator {
gen := &TaskGenerator{ gen := &TaskGenerator{
ports: utils.ParsePortsString(port), ports: utils.ParsePortsString(port),
tasks: make(chan *Task), tasks: make(chan *Task),
In: make(chan *Task), In: make(chan *Task),
} }
go func() { go func() {
for task := range gen.In { for task := range gen.In {
gen.tasks <- task gen.tasks <- task
} }
close(gen.tasks) close(gen.tasks)
}() }()
return gen return gen
} }
type TaskGenerator struct { type TaskGenerator struct {
Name string Name string
ports []string ports []string
tasks chan *Task tasks chan *Task
In chan *Task In chan *Task
} }
func (gen *TaskGenerator) Run(baseurl string) { func (gen *TaskGenerator) Run(baseurl string) {
parsed, err := url.Parse(baseurl) parsed, err := url.Parse(baseurl)
if err != nil { if err != nil {
logs.Log.Warnf("parse %s, %s ", baseurl, err.Error()) logs.Log.Warnf("parse %s, %s ", baseurl, err.Error())
return return
} }
if parsed.Scheme == "" { if parsed.Scheme == "" {
if parsed.Port() == "443" { if parsed.Port() == "443" {
parsed.Scheme = "https" parsed.Scheme = "https"
} else { } else {
parsed.Scheme = "http" parsed.Scheme = "http"
} }
} }
if len(gen.ports) == 0 { if len(gen.ports) == 0 {
gen.In <- &Task{baseUrl: parsed.String()} gen.In <- &Task{baseUrl: parsed.String()}
return return
} }
for _, p := range gen.ports { for _, p := range gen.ports {
if parsed.Host == "" { if parsed.Host == "" {
gen.In <- &Task{baseUrl: fmt.Sprintf("%s://%s:%s", parsed.Scheme, parsed.Path, p)} gen.In <- &Task{baseUrl: fmt.Sprintf("%s://%s:%s", parsed.Scheme, parsed.Path, p)}
} else { } else {
gen.In <- &Task{baseUrl: fmt.Sprintf("%s://%s:%s/%s", parsed.Scheme, parsed.Host, p, parsed.Path)} gen.In <- &Task{baseUrl: fmt.Sprintf("%s://%s:%s/%s", parsed.Scheme, parsed.Host, p, parsed.Path)}
} }
} }
} }
func (gen *TaskGenerator) Close() { func (gen *TaskGenerator) Close() {
close(gen.tasks) close(gen.tasks)
} }
+37 -37
View File
@@ -1,37 +1,37 @@
package core package core
import ( import (
"github.com/chainreactors/spray/pkg" "github.com/chainreactors/spray/pkg"
"github.com/chainreactors/words" "github.com/chainreactors/words"
) )
func NewOrigin(stat *pkg.Statistor) *Origin { func NewOrigin(stat *pkg.Statistor) *Origin {
return &Origin{Statistor: stat} return &Origin{Statistor: stat}
} }
type Origin struct { type Origin struct {
*pkg.Statistor *pkg.Statistor
sum int sum int
} }
func (o *Origin) InitWorder(fns []words.WordFunc) (*words.Worder, error) { func (o *Origin) InitWorder(fns []words.WordFunc) (*words.Worder, error) {
var worder *words.Worder var worder *words.Worder
wl, err := pkg.LoadWordlist(o.Word, o.Dictionaries) wl, err := pkg.LoadWordlist(o.Word, o.Dictionaries)
if err != nil { if err != nil {
return nil, err return nil, err
} }
worder = words.NewWorderWithList(wl) worder = words.NewWorderWithList(wl)
worder.Fns = fns worder.Fns = fns
rules, err := pkg.LoadRuleWithFiles(o.RuleFiles, o.RuleFilter) rules, err := pkg.LoadRuleWithFiles(o.RuleFiles, o.RuleFilter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
worder.Rules = rules worder.Rules = rules
if len(rules) > 0 { if len(rules) > 0 {
o.sum = len(rules) * len(wl) o.sum = len(rules) * len(wl)
} else { } else {
o.sum = len(wl) o.sum = len(wl)
} }
return worder, nil return worder, nil
} }
+61 -61
View File
@@ -1,61 +1,61 @@
package pkg package pkg
import ( import (
"fmt" "fmt"
"github.com/vbauerster/mpb/v8" "github.com/vbauerster/mpb/v8"
"github.com/vbauerster/mpb/v8/decor" "github.com/vbauerster/mpb/v8/decor"
"time" "time"
) )
func NewBar(u string, total int, stat *Statistor, p *mpb.Progress) *Bar { func NewBar(u string, total int, stat *Statistor, p *mpb.Progress) *Bar {
if p == nil { if p == nil {
return &Bar{ return &Bar{
url: u, url: u,
} }
} }
bar := p.AddBar(int64(total), bar := p.AddBar(int64(total),
mpb.BarFillerClearOnComplete(), mpb.BarFillerClearOnComplete(),
mpb.BarRemoveOnComplete(), mpb.BarRemoveOnComplete(),
mpb.PrependDecorators( mpb.PrependDecorators(
decor.Name(u, decor.WC{W: len(u) + 1, C: decor.DindentRight}), // 这里调整了装饰器的参数 decor.Name(u, decor.WC{W: len(u) + 1, C: decor.DindentRight}), // 这里调整了装饰器的参数
decor.NewAverageSpeed(0, "% .0f/s ", time.Now()), decor.NewAverageSpeed(0, "% .0f/s ", time.Now()),
decor.Counters(0, "%d/%d"), decor.Counters(0, "%d/%d"),
decor.Any(func(s decor.Statistics) string { decor.Any(func(s decor.Statistics) string {
return fmt.Sprintf(" found: %d", stat.FoundNumber) return fmt.Sprintf(" found: %d", stat.FoundNumber)
}), }),
), ),
mpb.AppendDecorators( mpb.AppendDecorators(
decor.Percentage(), decor.Percentage(),
decor.Elapsed(decor.ET_STYLE_GO, decor.WC{W: 4}), decor.Elapsed(decor.ET_STYLE_GO, decor.WC{W: 4}),
), ),
) )
return &Bar{ return &Bar{
url: u, url: u,
bar: bar, bar: bar,
//m: m, //m: m,
} }
} }
type Bar struct { type Bar struct {
url string url string
bar *mpb.Bar bar *mpb.Bar
//m metrics.Meter //m metrics.Meter
} }
func (bar *Bar) Done() { func (bar *Bar) Done() {
//bar.m.Mark(1) //bar.m.Mark(1)
if bar.bar == nil { if bar.bar == nil {
return return
} }
bar.bar.Increment() bar.bar.Increment()
} }
func (bar *Bar) Close() { func (bar *Bar) Close() {
//metrics.Unregister(bar.url) //metrics.Unregister(bar.url)
// 标记进度条为完成状态 // 标记进度条为完成状态
if bar.bar == nil { if bar.bar == nil {
return return
} }
bar.bar.Abort(true) bar.bar.Abort(true)
} }
+41 -41
View File
@@ -1,41 +1,41 @@
package pkg package pkg
type ErrorType uint type ErrorType uint
const ( const (
NoErr ErrorType = iota NoErr ErrorType = iota
ErrBadStatus ErrBadStatus
ErrSameStatus ErrSameStatus
ErrRequestFailed ErrRequestFailed
ErrWaf ErrWaf
ErrRedirect ErrRedirect
ErrCompareFailed ErrCompareFailed
ErrCustomCompareFailed ErrCustomCompareFailed
ErrCustomFilter ErrCustomFilter
ErrFuzzyCompareFailed ErrFuzzyCompareFailed
ErrFuzzyRedirect ErrFuzzyRedirect
ErrFuzzyNotUnique ErrFuzzyNotUnique
ErrUrlError ErrUrlError
ErrResponseError ErrResponseError
) )
var ErrMap = map[ErrorType]string{ var ErrMap = map[ErrorType]string{
NoErr: "", NoErr: "",
ErrBadStatus: "blacklist status", ErrBadStatus: "blacklist status",
ErrSameStatus: "same status with random baseline", ErrSameStatus: "same status with random baseline",
ErrRequestFailed: "request failed", ErrRequestFailed: "request failed",
ErrWaf: "maybe banned by waf", ErrWaf: "maybe banned by waf",
ErrRedirect: "duplicate redirect url", ErrRedirect: "duplicate redirect url",
ErrCompareFailed: "compare failed", ErrCompareFailed: "compare failed",
ErrCustomCompareFailed: "custom compare failed", ErrCustomCompareFailed: "custom compare failed",
ErrCustomFilter: "custom filtered", ErrCustomFilter: "custom filtered",
ErrFuzzyCompareFailed: "fuzzy compare failed", ErrFuzzyCompareFailed: "fuzzy compare failed",
ErrFuzzyRedirect: "fuzzy redirect", ErrFuzzyRedirect: "fuzzy redirect",
ErrFuzzyNotUnique: "not unique", ErrFuzzyNotUnique: "not unique",
ErrUrlError: "url parse error", ErrUrlError: "url parse error",
ErrResponseError: "response parse error", ErrResponseError: "response parse error",
} }
func (e ErrorType) Error() string { func (e ErrorType) Error() string {
return ErrMap[e] return ErrMap[e]
} }
+26 -26
View File
@@ -1,26 +1,26 @@
package pkg package pkg
import "strings" import "strings"
var ( var (
SkipChar = "%SKIP%" SkipChar = "%SKIP%"
EXTChar = "%EXT%" EXTChar = "%EXT%"
) )
func ParseEXTPlaceholderFunc(exts []string) func(string) []string { func ParseEXTPlaceholderFunc(exts []string) func(string) []string {
return func(s string) []string { return func(s string) []string {
ss := make([]string, len(exts)) ss := make([]string, len(exts))
var n int var n int
for i, e := range exts { for i, e := range exts {
if strings.Contains(s, EXTChar) { if strings.Contains(s, EXTChar) {
n++ n++
ss[i] = strings.Replace(s, EXTChar, e, -1) ss[i] = strings.Replace(s, EXTChar, e, -1)
} }
} }
if n == 0 { if n == 0 {
return []string{s} return []string{s}
} else { } else {
return ss return ss
} }
} }
} }
+20 -20
View File
@@ -2,24 +2,24 @@
// +build !emptytemplates // +build !emptytemplates
package pkg package pkg
import ( import (
"github.com/chainreactors/utils/encode" "github.com/chainreactors/utils/encode"
) )
var RandomDir = "/g8kZMwp4oeKsL2in" var RandomDir = "/g8kZMwp4oeKsL2in"
func loadEmbeddedConfig(typ string) []byte { func loadEmbeddedConfig(typ string) []byte {
if typ == "spray_rule" { if typ == "spray_rule" {
return encode.MustDeflateDeCompress(encode.Base64Decode("hFPNbqs8EN3zFEfJsMQYU5rwsfpa9RXuKrLktKQlTUsUkvvTxX32K48xcQJRJQRzxsdnPMOxOR3f1n/2puv+w2yOtenq1WH1Sal971wI/p56wN/nHvD3sQdkXBABlNlwju6tObRwBWxGp9ACOsX88cdTomQmk3x5nwdLvKo76C0G0l0i5eLOkSJAV7eYSiZZWSy83IhH6RSzcm+rnEKnfrdhAYYUgxRo0/d1hgK0BXV+ADEoB609jScx5ELuFfEWR4Je0AfBcA36ExReXnFwb5+gwaxclqOjmZCRlzkPP0gtikyeS4lgvwLVVxDOKBeZW0wbWMnxJomL+oqP1GQGTQaJJouHdZXkqsiVXXcG25qfJgKe24+PllN2hMYH4b/5y8Gc31VwBBE0KXy+4t4vBVKvKjAIeNP6eEimZ/PYeANte9dxkKsuYxHAGlqdybOo/n0cLmlvB8nn2IM+Qa+BX/4f5VcrBqOsbfHktSS7amLnwBgMOsEo2Hu5VfyGM1knBj15M5egJQdL14vV/Jb7wM+VajVVJwc99P82WJlFm2ZXr827na8zCt8Uevf/fBqClM+0oB3oxcOdLdL6wsJfdBHs+AI1oL2HR5Y8ePgK+mI7OG+JfhQd6BdoP4v+AQAA//8=")) return encode.MustDeflateDeCompress(encode.Base64Decode("hFPNbqs8EN3zFEfJsMQYU5rwsfpa9RXuKrLktKQlTUsUkvvTxX32K48xcQJRJQRzxsdnPMOxOR3f1n/2puv+w2yOtenq1WH1Sal971wI/p56wN/nHvD3sQdkXBABlNlwju6tObRwBWxGp9ACOsX88cdTomQmk3x5nwdLvKo76C0G0l0i5eLOkSJAV7eYSiZZWSy83IhH6RSzcm+rnEKnfrdhAYYUgxRo0/d1hgK0BXV+ADEoB609jScx5ELuFfEWR4Je0AfBcA36ExReXnFwb5+gwaxclqOjmZCRlzkPP0gtikyeS4lgvwLVVxDOKBeZW0wbWMnxJomL+oqP1GQGTQaJJouHdZXkqsiVXXcG25qfJgKe24+PllN2hMYH4b/5y8Gc31VwBBE0KXy+4t4vBVKvKjAIeNP6eEimZ/PYeANte9dxkKsuYxHAGlqdybOo/n0cLmlvB8nn2IM+Qa+BX/4f5VcrBqOsbfHktSS7amLnwBgMOsEo2Hu5VfyGM1knBj15M5egJQdL14vV/Jb7wM+VajVVJwc99P82WJlFm2ZXr827na8zCt8Uevf/fBqClM+0oB3oxcOdLdL6wsJfdBHs+AI1oL2HR5Y8ePgK+mI7OG+JfhQd6BdoP4v+AQAA//8="))
} else if typ == "spray_common" { } else if typ == "spray_common" {
return encode.MustDeflateDeCompress(encode.Base64Decode("nJOxbtRAFEV7S/4HKxUUIM84m93hZ9DYHuI49tp4hjhJR5UWmkh0FOloqJDIKn+Tzf4G2sy7FxSlAKp3ZFv3zN55e+Hs9CZNsuxVpswqJymSJhWkQ9KCdERaklYkAzJ0GDoMHYYOQ4ehw9Bh6DB0GHHoPM9JiqRJBWlBOiItSSsSkxWTFZMVkxWT1SGJDkWHokPRoejQdGg6NB2aDk2HpkPToenQdGiTJv2wDo3cONpBN2gml2T0g3bQDZpBL2gFnewbqe3F31r+I/1xYlewKdgTbAl2hBuCHmSiXXSLZtErWkWnaBR9Sl4heYVKk9KevnXnQX75ZKcIlydjhGCn18eX5Ah4sJTp33cRSnsaYd5/us9e295J+DzPAq5UJL6bhiFI3ElwYnQ+PEkubSVU22BL6+XTWpJCj5M7EK4zDhvHQXbwr/v7+z7++Bcsn/m3yD18GLvB1pG74fhJVw/X37eb24frq/vNj+3Nz2cfZi/Uy/hie3N1v7mTj24/7b5+jLy7+7z79iVN1g53aP0IsIRzviM1pJ5UCdWdHLJycuWVx2xC36VJa8+sCFsIW6a3fga84ztQDHhEeyZHrDrrfZqMzSihYzMSCtICxIixCWnSeh4lTgcIODYe9K3/BQAA//8=")) return encode.MustDeflateDeCompress(encode.Base64Decode("nJOxbtRAFEV7S/4HKxUUIM84m93hZ9DYHuI49tp4hjhJR5UWmkh0FOloqJDIKn+Tzf4G2sy7FxSlAKp3ZFv3zN55e+Hs9CZNsuxVpswqJymSJhWkQ9KCdERaklYkAzJ0GDoMHYYOQ4ehw9Bh6DB0GHHoPM9JiqRJBWlBOiItSSsSkxWTFZMVkxWT1SGJDkWHokPRoejQdGg6NB2aDk2HpkPToenQdGiTJv2wDo3cONpBN2gml2T0g3bQDZpBL2gFnewbqe3F31r+I/1xYlewKdgTbAl2hBuCHmSiXXSLZtErWkWnaBR9Sl4heYVKk9KevnXnQX75ZKcIlydjhGCn18eX5Ah4sJTp33cRSnsaYd5/us9e295J+DzPAq5UJL6bhiFI3ElwYnQ+PEkubSVU22BL6+XTWpJCj5M7EK4zDhvHQXbwr/v7+z7++Bcsn/m3yD18GLvB1pG74fhJVw/X37eb24frq/vNj+3Nz2cfZi/Uy/hie3N1v7mTj24/7b5+jLy7+7z79iVN1g53aP0IsIRzviM1pJ5UCdWdHLJycuWVx2xC36VJa8+sCFsIW6a3fga84ztQDHhEeyZHrDrrfZqMzSihYzMSCtICxIixCWnSeh4lTgcIODYe9K3/BQAA//8="))
} else if typ == "spray_dict" { } else if typ == "spray_dict" {
return encode.MustDeflateDeCompress(encode.Base64Decode("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 truncated return encode.MustDeflateDeCompress(encode.Base64Decode("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 truncated
} else if typ == "extract" { } else if typ == "extract" {
return encode.MustDeflateDeCompress(encode.Base64Decode("nFbdbts2FL4fsHfw3LQmLVPWT5Layo+srUXhFeiKtii28tCBIjGNm9gyTBlqIwrI/a4HbMD2DLvaC+Vi2GMMlH8kx0oWD0isw8PzfeeXlEht7I+4U/skvv6qVpvyj/zzRDhKrtVIrY7O43giUqNlZg4dAIjGzfVvUL+5/uP4EGHZBKAstVrWnpG5dHDUXFgcHzoHEmHKAPRPAtdXfEcAim0/o1BvsJx3qUE5/0DBW9rx4cFt+jlVjivxPQxVAHL8HNRoFbnkOOdud8syzaaXlXVa5bVZMKhvlGojnnY5ICePqLdzO5fbuc+huT93zVdvowTV+ZaNVuSPyuxFbx61dNUuK2svPbWODw96Oxs8aw0uggX4bBnnU3623F+bhJvrX2+uf6+ah0br+HCnd1BRwNXkJrHyEvsfi35M+DjmIq5qFeJfvqce+eCTK4N0TwhLTaOVgV7o9KVSrptCW6u0LvS4iGrkDysnpYEKKMBslxsGAZh1z/y9E51hDXqo7JJArtSXSlWAToYbGxkPx2eRkgNf8EKritAzLVsPotFS9/dfP//z5y+FdhnxMAz8aXgn8UYidGCQLkMIwrSTIQNCaRrSNKVpYUQNYjEIpW1I28QQpna2g6Uy3c+Q2ZFmV1qG0lsZMqhJuux+MIJQ/ig/Y4znXhtF2JPzaMwfHjUCzTXczj4QF7smtUmXQZh2sxLjcHIn3X/NFrLQHjXIHpPUILsMQoyVZDIXwtRsWRlGoD/ACqd2VgpoHPMpqQjrnlBW7TGtp6Ab+Z+JJTIN0HMvdrYpqP2nlorQpPuky7BEFoRYIpsaJsO4GtG1QDf3OxWbuFTVSIitjin1pM+CgAtBX8oLxr/QvhwyGspnTG7uvZWC8WDK49IRFPZWDhFC1CdXHvmwuAM00IUtha16JoFg7da2vMMcdH/kX0VjPxGgjhiWSNgOtKFdAAjocMK0fEsPorGILrnuJ2IBVTBoCxvap7PggseiGuuPrsagj5QfapCuT85Y2snISt59gGxaqok8sHIVYZoehITO7598FY0ms5gXOS1Smgl6QpjLfRHngjlfXp7mz5V1vlKIUmP8RBD/4n9Ns58IpKv3kJFhF7lkiGmjAXU2T0cVCNoaS3eNbK7HEiHPfkfnlWPSe9n3pPfitSe9/jNPem9+UJJavsp/3nvSe9v3cFFtlpr7WcW9MxU+ueBftptpwrRvn7/ov6rRAQim1V6/6b/33j2vvXz+EyWsVKJkOA6jRJCz4SXfslCQsNUkMwcAuQ4kGoCLm1hWbeCmeuiQaGsHduILkUQVb4R7M5ww5Ashk1Aq+Pw3moYYUxBH9YbDtOL19k3v0c7jgas3n7DUVi2l9QYrfWsJPlXSgy93RGcMCT6VCiXV/4LhDu+P9aVfEGueEx5Eo+27G8jvWDSlE/maDUO5WE6okG837qZP4WmwVV8VwGm3FwcTdCAnzsERtHtu6wnTSm37OIzPZ6ckji74eLvwV19ETYeuvj+Y1psz5ldZEyvzfwEAAP//")) return encode.MustDeflateDeCompress(encode.Base64Decode("nFbdbts2FL4fsHfw3LQmLVPWT5Layo+srUXhFeiKtii28tCBIjGNm9gyTBlqIwrI/a4HbMD2DLvaC+Vi2GMMlH8kx0oWD0isw8PzfeeXlEht7I+4U/skvv6qVpvyj/zzRDhKrtVIrY7O43giUqNlZg4dAIjGzfVvUL+5/uP4EGHZBKAstVrWnpG5dHDUXFgcHzoHEmHKAPRPAtdXfEcAim0/o1BvsJx3qUE5/0DBW9rx4cFt+jlVjivxPQxVAHL8HNRoFbnkOOdud8syzaaXlXVa5bVZMKhvlGojnnY5ICePqLdzO5fbuc+huT93zVdvowTV+ZaNVuSPyuxFbx61dNUuK2svPbWODw96Oxs8aw0uggX4bBnnU3623F+bhJvrX2+uf6+ah0br+HCnd1BRwNXkJrHyEvsfi35M+DjmIq5qFeJfvqce+eCTK4N0TwhLTaOVgV7o9KVSrptCW6u0LvS4iGrkDysnpYEKKMBslxsGAZh1z/y9E51hDXqo7JJArtSXSlWAToYbGxkPx2eRkgNf8EKritAzLVsPotFS9/dfP//z5y+FdhnxMAz8aXgn8UYidGCQLkMIwrSTIQNCaRrSNKVpYUQNYjEIpW1I28QQpna2g6Uy3c+Q2ZFmV1qG0lsZMqhJuux+MIJQ/ig/Y4znXhtF2JPzaMwfHjUCzTXczj4QF7smtUmXQZh2sxLjcHIn3X/NFrLQHjXIHpPUILsMQoyVZDIXwtRsWRlGoD/ACqd2VgpoHPMpqQjrnlBW7TGtp6Ab+Z+JJTIN0HMvdrYpqP2nlorQpPuky7BEFoRYIpsaJsO4GtG1QDf3OxWbuFTVSIitjin1pM+CgAtBX8oLxr/QvhwyGspnTG7uvZWC8WDK49IRFPZWDhFC1CdXHvmwuAM00IUtha16JoFg7da2vMMcdH/kX0VjPxGgjhiWSNgOtKFdAAjocMK0fEsPorGILrnuJ2IBVTBoCxvap7PggseiGuuPrsagj5QfapCuT85Y2snISt59gGxaqok8sHIVYZoehITO7598FY0ms5gXOS1Smgl6QpjLfRHngjlfXp7mz5V1vlKIUmP8RBD/4n9Ns58IpKv3kJFhF7lkiGmjAXU2T0cVCNoaS3eNbK7HEiHPfkfnlWPSe9n3pPfitSe9/jNPem9+UJJavsp/3nvSe9v3cFFtlpr7WcW9MxU+ueBftptpwrRvn7/ov6rRAQim1V6/6b/33j2vvXz+EyWsVKJkOA6jRJCz4SXfslCQsNUkMwcAuQ4kGoCLm1hWbeCmeuiQaGsHduILkUQVb4R7M5ww5Ashk1Aq+Pw3moYYUxBH9YbDtOL19k3v0c7jgas3n7DUVi2l9QYrfWsJPlXSgy93RGcMCT6VCiXV/4LhDu+P9aVfEGueEx5Eo+27G8jvWDSlE/maDUO5WE6okG837qZP4WmwVV8VwGm3FwcTdCAnzsERtHtu6wnTSm37OIzPZ6ckji74eLvwV19ETYeuvj+Y1psz5ldZEyvzfwEAAP//"))
} else if typ == "port" { } else if typ == "port" {
return encode.MustDeflateDeCompress(encode.Base64Decode("pFlBbvPKDd4/4N0h8MavCwHkjGaG85ZFLyJLSqzElvxLcvynq16gFyiKAi3Q7rvq7vUyxd/2Fg9KbItMKMvxvzHMkYbkkB85JJUkyY8/JHd1ti1/vuubHf74w93drmn77ufh391dcrckWJ7/p6ldsgfHR332ML6/7vud4GkUnmHkSZBEYAJMkqbpKASByTcAkBgAPC3YYcGyhSUKFdN3lB15uWGr41udBzw/9sNjD2BPC2FYCGwhDguRLQz2SAgijgswLJjxeESGWdNLcwoqymeBwpJZj/uEgFlzGSQleYIHTrFnEV79QG60hnOOqeq9Z2xD4OoEIsY2xsj2kWfPiEhQQT5j+wJFpqrz/E0f2JsRIiQRAZgCQh0KntvRO265iIIa2S4oeLtgyqVcVfRiH7cqET8+gTgwCLehOPyZQBhggwAIIgoSBDI8MAQKELirFziAccFJkCSRIOP4dImvNh1+Rw0XDiC8vaNE+9v/vNlum1rEvlViXyLbi1OIiFjEk8ijo00SIfKAtEkARBaQNolsgXCIPxwXpOcIDCYE9mxmoiGAaUxHBCgDeDBUQgxthMMWJCYiEveDRWQwsp4HtcRDEKCiIN7k0UdR7DMcFSSijyIJMAouRnBBFJC2AqkpTMIdJQjFkQaY4dwN0TX5U6fA5Jj4T69VlfZSGgBmJTyumk7d7Bw3SJqmjLLCAU4V0+blKGXXdP1DW3ZfNoool1rzcX+xeh86b9Sq3feM87bTmWJqbYKpHTPa65t1v9neLOpFFWUt+MRaoNE8VsBgIP3NB2zaLN+U2gnRsfSJzjCMoiQNwvXyK5al8mZf5+u3N997LRKrRPyJ+iCCscrXq6wr1SoqCuUn9T0Dvr7f7L/+7rcqL/KXN6+L+07dyMuPAdQzSuSbKn9aN3v9SGjs5e1tWahh622I13vrNcxGx+nALbd5lq91EBnES9JGphdRWmx/+t8vv3z72z9+o0HFWD8n4wz3Ls/an/7797/8/w9/0ngZAHstr+5lAm9DectyG8KEp5kJm/qhUePABMCQDL/x5hifigv0QlH0J0weSQM4A9Gs67K6aDOt1IDU8NIYZuDeZqtV1W+/aHo6H4yoSK9K6CfOddmkj1ojlAaWYoI/lvnTgV12XZXVGqdTiTp9RT3WRfVxJ0KkBHFshPBUf54W0jRNh64ssMtZ1BkYgVezsgUgIF7KePKiiAWRFnkNfunWncvqj8Vhp0cFr/9TZ2/kfyhX3W5dtmpeJF4TGYJ4VQu2jCgiAQH88mLBLZ27/arpIsxNEHnLh0PLh4ZZx3ENPHr0N9pnt94lX4tytX9Qw1ILxHdp/hw5TVE+dskUL0e8RIjGxJkgeNhkXXdfdWutIpyPobbJn0o9RchWEyFyxyNEXmEPpLnRtlneV8+lqoLsTL/HgU/Z/ZOeUqOZMVFedXmjFuvk56y7aR4qJbs5NInD9PLmtRrxYozhnJtToezzQrsDTyXLkdJGXx+teVzfNPuCVXZdXzXqFSPvPgcCMQ40xFwps6yfmxddppZjr+T6mJUPZau2JobPfdB7zfVXSnkivT80ToSbcSBJd7vET4H/SqZ11qtdqDG8fz9Rt0jY7TddprnDe3c7XPOm7vZa/0nitiALkJCF7zFQ3qhdC6WUEHH3xgC3H6gYUrhiJmODS4wN9LmMqbuiafusqtXg+NT1lz3u1EYO4o2JvdxkXV/lXZm1uXYHRjEXivYKXd8Xv8fSX6+ED1VdNAfFz1W+3Y0Jlk862YRjiTbwkY3jMwg27DpUdbs9Ec3XqvhwhsNU28eN1ZebuuzV62B5G8dNkWkOPek+xfCaScZT2a7KdiKEblS3q7c7tWvDiwzfNX+7qn6YdvhHJqd9970mXI59DH7iaO8HT/1aTQSYGMvmPssP5z7Wz0kEIzpuJVaq+r5RLbvVD8erFf7RJ+VfLowzyu22zaoNT0Hq+B15jRHjLJtqqwIWRecS7Ryb3zfNU1nu1IRokLhLxUciSxp2uVGnwdt2L3WuRUOwl1mqCUyJ5abfq6WJdeZ6INTqqM5AGq++JN4GqMlUuAAfpHdf9pVW4Fo0c73PLs/ql8NE1+u8nTz0WbbadfFcb0Q1hADG3Or9Z9X3Tnw8cZGPHxxpATwDBq0RyYqm0a9tDu3XT2raOPZEGpgz6PP2kKneiBRYukhN0DhdGLee75RqldWZ6m6tb9A8c47GQr34+K29REkOFPsmawd6cXO2L/arldp3nefpr1IMEEEy/JqFwOVsZSss9+1f//zPv//67c9/7LpN8rzTRnYxso/cizS17BMtkfa5VanwzoWi/gnDaW2QwuZXAAAA//8=")) return encode.MustDeflateDeCompress(encode.Base64Decode("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"))
} }
return []byte{} return []byte{}
} }
+122 -122
View File
@@ -1,122 +1,122 @@
package pkg package pkg
import ( import (
"strings" "strings"
"testing" "testing"
) )
func TestParseRawResponse(t *testing.T) { func TestParseRawResponse(t *testing.T) {
t.Run("valid complete response", func(t *testing.T) { t.Run("valid complete response", func(t *testing.T) {
raw := "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 5\r\n\r\nhello" raw := "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 5\r\n\r\nhello"
resp, err := ParseRawResponse([]byte(raw)) resp, err := ParseRawResponse([]byte(raw))
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
t.Fatalf("expected status 200, got %d", resp.StatusCode) t.Fatalf("expected status 200, got %d", resp.StatusCode)
} }
if ct := resp.Header.Get("Content-Type"); ct != "text/html" { if ct := resp.Header.Get("Content-Type"); ct != "text/html" {
t.Fatalf("expected Content-Type text/html, got %s", ct) t.Fatalf("expected Content-Type text/html, got %s", ct)
} }
}) })
t.Run("nil input", func(t *testing.T) { t.Run("nil input", func(t *testing.T) {
_, err := ParseRawResponse(nil) _, err := ParseRawResponse(nil)
if err == nil { if err == nil {
t.Fatal("expected error for nil input") t.Fatal("expected error for nil input")
} }
}) })
t.Run("empty input", func(t *testing.T) { t.Run("empty input", func(t *testing.T) {
_, err := ParseRawResponse([]byte{}) _, err := ParseRawResponse([]byte{})
if err == nil { if err == nil {
t.Fatal("expected error for empty input") t.Fatal("expected error for empty input")
} }
}) })
t.Run("status line only no headers", func(t *testing.T) { t.Run("status line only no headers", func(t *testing.T) {
raw := "HTTP/1.1 200 OK\r\n\r\n" raw := "HTTP/1.1 200 OK\r\n\r\n"
resp, err := ParseRawResponse([]byte(raw)) resp, err := ParseRawResponse([]byte(raw))
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
t.Fatalf("expected status 200, got %d", resp.StatusCode) t.Fatalf("expected status 200, got %d", resp.StatusCode)
} }
}) })
t.Run("truncated status line", func(t *testing.T) { t.Run("truncated status line", func(t *testing.T) {
raw := "HTTP/1." raw := "HTTP/1."
_, err := ParseRawResponse([]byte(raw)) _, err := ParseRawResponse([]byte(raw))
if err == nil { if err == nil {
t.Fatal("expected error for truncated status line") t.Fatal("expected error for truncated status line")
} }
}) })
t.Run("redirect response no body", func(t *testing.T) { t.Run("redirect response no body", func(t *testing.T) {
raw := "HTTP/1.1 302 Found\r\nLocation: https://example.com/new\r\n\r\n" raw := "HTTP/1.1 302 Found\r\nLocation: https://example.com/new\r\n\r\n"
resp, err := ParseRawResponse([]byte(raw)) resp, err := ParseRawResponse([]byte(raw))
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if resp.StatusCode != 302 { if resp.StatusCode != 302 {
t.Fatalf("expected status 302, got %d", resp.StatusCode) t.Fatalf("expected status 302, got %d", resp.StatusCode)
} }
if loc := resp.Header.Get("Location"); loc != "https://example.com/new" { if loc := resp.Header.Get("Location"); loc != "https://example.com/new" {
t.Fatalf("expected Location https://example.com/new, got %s", loc) t.Fatalf("expected Location https://example.com/new, got %s", loc)
} }
}) })
t.Run("chunked transfer encoding header", func(t *testing.T) { t.Run("chunked transfer encoding header", func(t *testing.T) {
raw := "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n" raw := "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
resp, err := ParseRawResponse([]byte(raw)) resp, err := ParseRawResponse([]byte(raw))
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if te := resp.Header.Get("Transfer-Encoding"); te == "" { if te := resp.Header.Get("Transfer-Encoding"); te == "" {
// Transfer-Encoding may be consumed by http.ReadResponse, just verify no panic // Transfer-Encoding may be consumed by http.ReadResponse, just verify no panic
} }
_ = resp _ = resp
}) })
t.Run("large header value", func(t *testing.T) { t.Run("large header value", func(t *testing.T) {
largeValue := strings.Repeat("A", 8192) largeValue := strings.Repeat("A", 8192)
raw := "HTTP/1.1 200 OK\r\nX-Large: " + largeValue + "\r\n\r\n" raw := "HTTP/1.1 200 OK\r\nX-Large: " + largeValue + "\r\n\r\n"
resp, err := ParseRawResponse([]byte(raw)) resp, err := ParseRawResponse([]byte(raw))
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if got := resp.Header.Get("X-Large"); got != largeValue { if got := resp.Header.Get("X-Large"); got != largeValue {
t.Fatalf("large header value mismatch, got length %d", len(got)) t.Fatalf("large header value mismatch, got length %d", len(got))
} }
}) })
t.Run("invalid status code", func(t *testing.T) { t.Run("invalid status code", func(t *testing.T) {
raw := "HTTP/1.1 xyz OK\r\n\r\n" raw := "HTTP/1.1 xyz OK\r\n\r\n"
_, err := ParseRawResponse([]byte(raw)) _, err := ParseRawResponse([]byte(raw))
if err == nil { if err == nil {
t.Fatal("expected error for invalid status code") t.Fatal("expected error for invalid status code")
} }
}) })
t.Run("incomplete header no terminator", func(t *testing.T) { t.Run("incomplete header no terminator", func(t *testing.T) {
raw := "HTTP/1.1 200 OK\r\nContent-Type: text/html" raw := "HTTP/1.1 200 OK\r\nContent-Type: text/html"
_, err := ParseRawResponse([]byte(raw)) _, err := ParseRawResponse([]byte(raw))
// Should return error or at least not panic // Should return error or at least not panic
// http.ReadResponse may or may not error on missing \r\n\r\n, // http.ReadResponse may or may not error on missing \r\n\r\n,
// the key requirement is no panic // the key requirement is no panic
_ = err _ = err
}) })
t.Run("binary body", func(t *testing.T) { t.Run("binary body", func(t *testing.T) {
body := []byte{0x00, 0x01, 0x02, 0xff, 0xfe, 0xfd} body := []byte{0x00, 0x01, 0x02, 0xff, 0xfe, 0xfd}
raw := append([]byte("HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\n"), body...) raw := append([]byte("HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\n"), body...)
resp, err := ParseRawResponse(raw) resp, err := ParseRawResponse(raw)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
t.Fatalf("expected status 200, got %d", resp.StatusCode) t.Fatalf("expected status 200, got %d", resp.StatusCode)
} }
}) })
} }
+27 -27
View File
@@ -1,27 +1,27 @@
//go:generate go run templates/templates_gen.go -t templates -o pkg/templates.go -need spray //go:generate go run templates/templates_gen.go -t templates -o pkg/templates.go -need spray
package main package main
import ( import (
"github.com/chainreactors/spray/cmd" "github.com/chainreactors/spray/cmd"
"github.com/gookit/config/v2" "github.com/gookit/config/v2"
"github.com/gookit/config/v2/yaml" "github.com/gookit/config/v2/yaml"
//_ "net/http/pprof" //_ "net/http/pprof"
) )
func init() { func init() {
config.WithOptions(func(opt *config.Options) { config.WithOptions(func(opt *config.Options) {
opt.DecoderConfig.TagName = "config" opt.DecoderConfig.TagName = "config"
opt.ParseDefault = true opt.ParseDefault = true
}) })
config.AddDriver(yaml.Driver) config.AddDriver(yaml.Driver)
} }
func main() { func main() {
// 启动 pprof HTTP 服务器,用于调试 goroutine 泄露等问题 // 启动 pprof HTTP 服务器,用于调试 goroutine 泄露等问题
//go func() { //go func() {
// // 仅监听本地 6060 端口,避免对外暴露 // // 仅监听本地 6060 端口,避免对外暴露
// _ = http.ListenAndServe("127.0.0.1:6060", nil) // _ = http.ListenAndServe("127.0.0.1:6060", nil)
//}() //}()
cmd.Spray() cmd.Spray()
} }