mirror of
https://github.com/chainreactors/spray.git
synced 2026-10-24 17:04:26 +00:00
chore: normalize line endings (CRLF → LF)
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
1 parent
b4e9b10c84
commit
0a6132ca02
20 files changed
+2643
-2643
No files matched your search
+34
-34
@@ -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
|
||||||
@@ -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
@@ -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
@@ -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
|
||||||
@@ -1,128 +1,128 @@
|
|||||||
# SPRAY
|
# SPRAY
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+73
-73
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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("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"))
|
return encode.MustDeflateDeCompress(encode.Base64Decode("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"))
|
||||||
}
|
}
|
||||||
return []byte{}
|
return []byte{}
|
||||||
}
|
}
|
||||||
+122
-122
@@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user