Skip to content

Commit 2cfd7f0

Browse files
committed
feat: add spray sdk
1 parent 7703982 commit 2cfd7f0

5 files changed

Lines changed: 794 additions & 15 deletions

File tree

core/option.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,10 +302,10 @@ func (opt *Option) NewRunner() (*Runner, error) {
302302
r := &Runner{
303303
Option: opt,
304304
taskCh: make(chan *Task),
305-
outputCh: make(chan *baseline.Baseline, 256),
305+
OutputCh: make(chan *baseline.Baseline, 256),
306306
poolwg: &sync.WaitGroup{},
307-
outwg: &sync.WaitGroup{},
308-
fuzzyCh: make(chan *baseline.Baseline, 256),
307+
OutWg: &sync.WaitGroup{},
308+
FuzzyCh: make(chan *baseline.Baseline, 256),
309309
Headers: make(map[string]string),
310310
Total: opt.Limit,
311311
Color: true,

core/runner.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ type Runner struct {
3030

3131
taskCh chan *Task
3232
poolwg *sync.WaitGroup
33-
outwg *sync.WaitGroup
34-
outputCh chan *baseline.Baseline
35-
fuzzyCh chan *baseline.Baseline
33+
OutWg *sync.WaitGroup
34+
OutputCh chan *baseline.Baseline
35+
FuzzyCh chan *baseline.Baseline
3636
bar *mpb.Bar
3737
bruteMod bool
3838

@@ -93,9 +93,9 @@ func (r *Runner) PrepareConfig() *pool.Config {
9393
RateLimit: r.RateLimit,
9494
Mod: pool.ModMap[r.Mod],
9595
Request: requestConfig,
96-
OutputCh: r.outputCh,
97-
FuzzyCh: r.fuzzyCh,
98-
Outwg: r.outwg,
96+
OutputCh: r.OutputCh,
97+
FuzzyCh: r.FuzzyCh,
98+
Outwg: r.OutWg,
9999
Fuzzy: r.Fuzzy,
100100
CheckPeriod: r.CheckPeriod,
101101
ErrPeriod: int32(r.ErrPeriod),
@@ -302,7 +302,7 @@ Loop:
302302
r.bar.Wait()
303303
}
304304
r.poolwg.Wait()
305-
r.outwg.Wait()
305+
r.OutWg.Wait()
306306
}
307307

308308
func (r *Runner) RunWithCheck(ctx context.Context) {
@@ -328,7 +328,7 @@ Loop:
328328
}
329329
}
330330

331-
r.outwg.Wait()
331+
r.OutWg.Wait()
332332
}
333333

334334
func (r *Runner) AddRecursive(bl *baseline.Baseline) {
@@ -444,7 +444,7 @@ func (r *Runner) OutputHandler() {
444444
go func() {
445445
for {
446446
select {
447-
case bl, ok := <-r.outputCh:
447+
case bl, ok := <-r.OutputCh:
448448
if !ok {
449449
return
450450
}
@@ -464,20 +464,20 @@ func (r *Runner) OutputHandler() {
464464
logs.Log.Debug(bl.String())
465465
}
466466
}
467-
r.outwg.Done()
467+
r.OutWg.Done()
468468
}
469469
}
470470
}()
471471

472472
go func() {
473473
for {
474474
select {
475-
case bl, ok := <-r.fuzzyCh:
475+
case bl, ok := <-r.FuzzyCh:
476476
if !ok {
477477
return
478478
}
479479
r.Output(bl)
480-
r.outwg.Done()
480+
r.OutWg.Done()
481481
}
482482
}
483483
}()

sdk/README.md

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
# Spray SDK
2+
3+
Spray SDK 提供了简洁的 Go API,用于 HTTP URL 检测和路径暴力破解。
4+
5+
## 核心概念
6+
7+
SDK 由两部分组成:
8+
9+
1. **SprayEngine**: 管理持久化状态(指纹库等)
10+
2. **两个核心 API**:
11+
- `CheckStream`: URL 批量检测,返回 channel
12+
- `BruteStream`: 路径暴力破解,返回 channel
13+
14+
其他 API(`Check``Brute`)都是对 Stream API 的简单封装,你也可以根据需要自行封装。
15+
16+
## 快速开始
17+
18+
```go
19+
import "github.com/chainreactors/spray/sdk"
20+
21+
// 1. 创建 SprayEngine
22+
engine := sdk.NewSprayEngine(nil)
23+
24+
// 2. 初始化(加载指纹库等)
25+
engine.Init()
26+
27+
// 3. 使用
28+
ctx := context.Background()
29+
30+
// URL 检测
31+
urls := []string{"http://example.com", "http://httpbin.org"}
32+
resultCh, _ := engine.CheckStream(ctx, urls)
33+
for result := range resultCh {
34+
fmt.Printf("%s [%d]\n", result.UrlString, result.Status)
35+
}
36+
37+
// 路径暴力破解
38+
wordlist := []string{"admin", "api", "test"}
39+
resultCh, _ := engine.BruteStream(ctx, "http://example.com", wordlist)
40+
for result := range resultCh {
41+
fmt.Printf("%s [%d]\n", result.UrlString, result.Status)
42+
}
43+
```
44+
45+
## 配置
46+
47+
### 使用默认配置
48+
49+
```go
50+
engine := sdk.NewSprayEngine(nil)
51+
```
52+
53+
默认配置针对 SDK 场景优化:静默模式、无进度条、单 pool、20 线程。
54+
55+
### 自定义配置
56+
57+
```go
58+
opt := sdk.DefaultConfig()
59+
opt.Threads = 100
60+
opt.Timeout = 10
61+
opt.Filter = "current.Status == 404" // 过滤 404
62+
opt.Headers = []string{"Authorization: Bearer token"}
63+
64+
engine := sdk.NewSprayEngine(opt)
65+
```
66+
67+
### 运行时修改
68+
69+
```go
70+
engine.SetThreads(50)
71+
engine.SetTimeout(10)
72+
```
73+
74+
## API 参考
75+
76+
### SprayEngine
77+
78+
```go
79+
// 创建实例
80+
engine := sdk.NewSprayEngine(opt) // opt 为 nil 时使用默认配置
81+
82+
// 初始化(必须调用)
83+
engine.Init()
84+
85+
// 设置参数
86+
engine.SetThreads(threads)
87+
engine.SetTimeout(timeout)
88+
```
89+
90+
### 核心 API
91+
92+
```go
93+
// URL 检测(流式)
94+
CheckStream(ctx, urls) -> channel
95+
96+
// 暴力破解(流式)
97+
BruteStream(ctx, baseURL, wordlist) -> channel
98+
```
99+
100+
### 便捷 API
101+
102+
```go
103+
// URL 检测(批量)
104+
Check(ctx, urls) -> []result
105+
106+
// 暴力破解(批量)
107+
Brute(ctx, baseURL, wordlist) -> []result
108+
```
109+
110+
## 配置选项
111+
112+
`core.Option` 包含所有 spray 的配置项,常用的有:
113+
114+
**请求配置**
115+
- `Method`: HTTP 方法
116+
- `Timeout`: 超时时间(秒)
117+
- `Threads`: 线程数
118+
- `Headers`: 自定义请求头
119+
- `MaxBodyLength`: 最大响应体长度(KB)
120+
121+
**模式配置**
122+
- `Mod`: 扫描模式(`path``host`
123+
- `Filter`: 过滤规则(expr 表达式)
124+
- `Match`: 匹配规则(expr 表达式)
125+
- `BlackStatus`: 黑名单状态码
126+
- `WhiteStatus`: 白名单状态码
127+
- `RateLimit`: 速率限制(请求/秒)
128+
129+
**插件配置**
130+
- `Finger`: 启用指纹识别
131+
- `CrawlPlugin`: 启用爬虫
132+
- `BakPlugin`: 启用备份文件检测
133+
134+
完整配置项参考 `core.Option` 结构体。
135+
136+
## 结果结构
137+
138+
```go
139+
type SprayResult struct {
140+
UrlString string // URL
141+
Status int // HTTP 状态码
142+
BodyLength int // 响应体长度
143+
Title string // 页面标题
144+
IsValid bool // 是否有效
145+
Frameworks common.Frameworks // 识别的框架
146+
Extracts map[string][]string // 提取的信息
147+
}
148+
```
149+
150+
## 完整示例
151+
152+
### 示例 1: URL 批量检测
153+
154+
```go
155+
package main
156+
157+
import (
158+
"context"
159+
"fmt"
160+
"github.com/chainreactors/spray/sdk"
161+
)
162+
163+
func main() {
164+
engine := sdk.NewSprayEngine(nil)
165+
engine.Init()
166+
engine.SetThreads(50)
167+
168+
urls := []string{
169+
"http://example.com",
170+
"http://httpbin.org/get",
171+
}
172+
173+
ctx := context.Background()
174+
results, _ := engine.Check(ctx, urls)
175+
176+
for _, r := range results {
177+
if r.IsValid {
178+
fmt.Printf("[+] %s [%d] %s\n", r.UrlString, r.Status, r.Title)
179+
}
180+
}
181+
}
182+
```
183+
184+
### 示例 2: 路径暴力破解(流式)
185+
186+
```go
187+
package main
188+
189+
import (
190+
"context"
191+
"fmt"
192+
"github.com/chainreactors/spray/sdk"
193+
)
194+
195+
func main() {
196+
opt := sdk.DefaultConfig()
197+
opt.Threads = 100
198+
opt.Filter = "current.Status == 404"
199+
200+
engine := sdk.NewSprayEngine(opt)
201+
engine.Init()
202+
203+
wordlist := []string{"admin", "api", "test", ".git"}
204+
ctx := context.Background()
205+
206+
resultCh, _ := engine.BruteStream(ctx, "http://example.com", wordlist)
207+
for result := range resultCh {
208+
fmt.Printf("[+] %s [%d] %d bytes\n",
209+
result.UrlString, result.Status, result.BodyLength)
210+
}
211+
}
212+
```
213+
214+
### 示例 3: 多实例共享指纹库
215+
216+
```go
217+
package main
218+
219+
import (
220+
"context"
221+
"github.com/chainreactors/spray/sdk"
222+
)
223+
224+
func main() {
225+
// 第一个实例初始化指纹库
226+
engine1 := sdk.NewSprayEngine(nil)
227+
engine1.Init()
228+
229+
// 第二个实例共享已加载的指纹库
230+
opt2 := sdk.DefaultConfig()
231+
opt2.Threads = 100
232+
engine2 := sdk.NewSprayEngine(opt2)
233+
// 不需要再次 Init
234+
235+
ctx := context.Background()
236+
237+
// 并发使用
238+
go engine1.Check(ctx, urls1)
239+
go engine2.Check(ctx, urls2)
240+
}
241+
```
242+
243+
## 注意事项
244+
245+
1. **必须初始化**: 创建 `SprayEngine` 后必须调用 `Init()` 加载指纹库
246+
2. **配置克隆**: SDK 内部会克隆配置,避免并发修改
247+
3. **共享状态**: 多个 `SprayEngine` 实例共享已加载的指纹库
248+
4. **自动清理**: Stream 模式会自动清理资源
249+
250+
## 许可证
251+
252+
MIT License

0 commit comments

Comments
 (0)