2022-09-08 15:57:17 +08:00
|
|
|
package internal
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-09-20 04:01:38 +08:00
|
|
|
"github.com/chainreactors/logs"
|
2022-09-08 17:04:41 +08:00
|
|
|
"github.com/chainreactors/spray/pkg"
|
2022-09-15 19:27:07 +08:00
|
|
|
"github.com/chainreactors/words"
|
2022-09-08 15:57:17 +08:00
|
|
|
"github.com/panjf2000/ants/v2"
|
2022-09-23 01:20:01 +08:00
|
|
|
"github.com/valyala/fasthttp"
|
2022-09-08 15:57:17 +08:00
|
|
|
"net/http"
|
|
|
|
"sync"
|
2022-09-15 19:27:07 +08:00
|
|
|
"time"
|
2022-09-08 15:57:17 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
CheckStatusCode func(int) bool
|
2022-09-23 01:20:01 +08:00
|
|
|
CheckRedirect func(string) bool
|
2022-09-08 15:57:17 +08:00
|
|
|
CheckWaf func(*http.Response) bool
|
|
|
|
)
|
|
|
|
|
2022-09-23 01:47:24 +08:00
|
|
|
var breakThreshold int = 10
|
|
|
|
|
2022-09-15 19:27:07 +08:00
|
|
|
func NewPool(ctx context.Context, config *pkg.Config, outputCh chan *baseline) (*Pool, error) {
|
2022-09-19 14:42:29 +08:00
|
|
|
pctx, cancel := context.WithCancel(ctx)
|
2022-09-08 15:57:17 +08:00
|
|
|
pool := &Pool{
|
2022-09-23 01:39:00 +08:00
|
|
|
Config: config,
|
|
|
|
ctx: pctx,
|
2022-09-23 01:47:24 +08:00
|
|
|
cancel: cancel,
|
2022-09-23 01:39:00 +08:00
|
|
|
client: pkg.NewClient(config.Thread, 2),
|
|
|
|
worder: words.NewWorder(config.Wordlist),
|
|
|
|
outputCh: outputCh,
|
|
|
|
tempCh: make(chan *baseline, config.Thread),
|
2022-09-23 11:20:41 +08:00
|
|
|
wg: sync.WaitGroup{},
|
|
|
|
initwg: sync.WaitGroup{},
|
2022-09-23 01:39:00 +08:00
|
|
|
checkPeriod: 100,
|
|
|
|
errPeriod: 10,
|
2022-09-23 11:20:41 +08:00
|
|
|
//reqCount: 1,
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
switch config.Mod {
|
|
|
|
case pkg.PathSpray:
|
2022-09-23 01:20:01 +08:00
|
|
|
pool.genReq = func(s string) (*fasthttp.Request, error) {
|
2022-09-23 11:20:41 +08:00
|
|
|
return pool.buildPathRequest(s)
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
|
|
|
case pkg.HostSpray:
|
2022-09-23 01:20:01 +08:00
|
|
|
pool.genReq = func(s string) (*fasthttp.Request, error) {
|
2022-09-23 11:20:41 +08:00
|
|
|
return pool.buildHostRequest(s)
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
p, _ := ants.NewPoolWithFunc(config.Thread, func(i interface{}) {
|
|
|
|
unit := i.(*Unit)
|
2022-09-20 04:01:38 +08:00
|
|
|
req, err := pool.genReq(unit.path)
|
|
|
|
if err != nil {
|
|
|
|
logs.Log.Error(err.Error())
|
|
|
|
return
|
|
|
|
}
|
2022-09-23 01:20:01 +08:00
|
|
|
|
|
|
|
var bl *baseline
|
2022-09-23 11:20:41 +08:00
|
|
|
resp, reqerr := pool.client.Do(pctx, req)
|
2022-09-25 21:04:14 +08:00
|
|
|
defer fasthttp.ReleaseResponse(resp)
|
|
|
|
defer fasthttp.ReleaseRequest(req)
|
2022-09-23 11:20:41 +08:00
|
|
|
if reqerr != nil && reqerr != fasthttp.ErrBodyTooLarge {
|
2022-09-08 15:57:17 +08:00
|
|
|
//logs.Log.Debugf("%s request error, %s", strurl, err.Error())
|
|
|
|
pool.errorCount++
|
2022-09-23 11:20:41 +08:00
|
|
|
bl = &baseline{UrlString: pool.BaseURL + unit.path, Err: reqerr}
|
2022-09-08 15:57:17 +08:00
|
|
|
} else {
|
2022-09-23 01:20:01 +08:00
|
|
|
//defer resp.Body.Close() // 必须要关闭body ,否则keep-alive无法生效
|
2022-09-20 18:09:06 +08:00
|
|
|
if err = pool.PreCompare(resp); err == nil || unit.source == CheckSource {
|
2022-09-08 15:57:17 +08:00
|
|
|
// 通过预对比跳过一些无用数据, 减少性能消耗
|
2022-09-23 01:47:24 +08:00
|
|
|
bl = NewBaseline(req.URI(), resp)
|
2022-09-08 15:57:17 +08:00
|
|
|
} else {
|
2022-09-23 01:20:01 +08:00
|
|
|
bl = NewInvalidBaseline(req.URI(), resp)
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
2022-09-23 11:20:41 +08:00
|
|
|
bl.Err = reqerr
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
switch unit.source {
|
2022-09-20 18:09:06 +08:00
|
|
|
case CheckSource:
|
2022-09-23 11:20:41 +08:00
|
|
|
logs.Log.Debugf("check: " + bl.String())
|
|
|
|
if pool.base == nil {
|
2022-09-23 01:39:00 +08:00
|
|
|
//初次check覆盖baseline
|
2022-09-23 11:20:41 +08:00
|
|
|
pool.base = bl
|
|
|
|
pool.initwg.Done()
|
2022-09-23 01:39:00 +08:00
|
|
|
} else if bl.Err != nil {
|
|
|
|
logs.Log.Warn("maybe ip banned by waf")
|
2022-09-23 11:20:41 +08:00
|
|
|
} else if !pool.base.Equal(bl) {
|
2022-09-23 01:39:00 +08:00
|
|
|
logs.Log.Warn("maybe trigger risk control")
|
|
|
|
}
|
|
|
|
|
2022-09-08 15:57:17 +08:00
|
|
|
case WordSource:
|
2022-09-23 01:20:01 +08:00
|
|
|
// 异步进行性能消耗较大的深度对比
|
2022-09-23 11:20:41 +08:00
|
|
|
pool.reqCount++
|
2022-09-23 01:20:01 +08:00
|
|
|
pool.tempCh <- bl
|
2022-09-23 11:20:41 +08:00
|
|
|
|
|
|
|
if pool.reqCount%pool.checkPeriod == 0 {
|
|
|
|
go pool.check()
|
|
|
|
} else if pool.reqCount%pool.errPeriod == 0 {
|
|
|
|
go pool.check()
|
|
|
|
}
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
|
|
|
//todo connectivity check
|
2022-09-20 04:01:38 +08:00
|
|
|
pool.bar.Done()
|
2022-09-08 15:57:17 +08:00
|
|
|
pool.wg.Done()
|
|
|
|
})
|
|
|
|
|
|
|
|
pool.pool = p
|
2022-09-23 01:20:01 +08:00
|
|
|
go pool.Comparing()
|
2022-09-08 15:57:17 +08:00
|
|
|
return pool, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type Pool struct {
|
|
|
|
//url string
|
|
|
|
//thread int
|
|
|
|
*pkg.Config
|
|
|
|
client *pkg.Client
|
|
|
|
pool *ants.PoolWithFunc
|
2022-09-20 04:01:38 +08:00
|
|
|
bar *pkg.Bar
|
2022-09-19 14:42:29 +08:00
|
|
|
ctx context.Context
|
2022-09-23 01:47:24 +08:00
|
|
|
cancel context.CancelFunc
|
2022-09-08 15:57:17 +08:00
|
|
|
//baseReq *http.Request
|
2022-09-23 11:20:41 +08:00
|
|
|
base *baseline
|
2022-09-23 01:39:00 +08:00
|
|
|
outputCh chan *baseline
|
|
|
|
tempCh chan *baseline
|
|
|
|
reqCount int
|
|
|
|
errorCount int
|
2022-09-23 01:47:24 +08:00
|
|
|
failedCount int
|
2022-09-23 01:39:00 +08:00
|
|
|
checkPeriod int
|
|
|
|
errPeriod int
|
2022-09-23 11:20:41 +08:00
|
|
|
analyzeDone bool
|
2022-09-23 01:39:00 +08:00
|
|
|
genReq func(s string) (*fasthttp.Request, error)
|
2022-09-23 11:20:41 +08:00
|
|
|
worder *words.Worder
|
|
|
|
wg sync.WaitGroup
|
|
|
|
initwg sync.WaitGroup
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
|
|
|
|
2022-09-23 01:39:00 +08:00
|
|
|
func (p *Pool) check() {
|
2022-09-23 11:20:41 +08:00
|
|
|
p.wg.Add(1)
|
2022-09-20 18:09:06 +08:00
|
|
|
_ = p.pool.Invoke(newUnit(pkg.RandPath(), CheckSource))
|
2022-09-23 01:47:24 +08:00
|
|
|
|
|
|
|
if p.failedCount > breakThreshold {
|
|
|
|
p.cancel()
|
|
|
|
}
|
2022-09-23 01:39:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Pool) Init() error {
|
2022-09-23 11:20:41 +08:00
|
|
|
p.initwg.Add(1)
|
2022-09-23 01:39:00 +08:00
|
|
|
p.check()
|
2022-09-23 11:20:41 +08:00
|
|
|
p.initwg.Wait()
|
2022-09-08 15:57:17 +08:00
|
|
|
// todo 分析baseline
|
|
|
|
// 检测基本访问能力
|
|
|
|
|
2022-09-23 11:20:41 +08:00
|
|
|
if p.base != nil && p.base.Err != nil {
|
2022-09-23 01:47:24 +08:00
|
|
|
p.cancel()
|
2022-09-23 11:20:41 +08:00
|
|
|
return p.base.Err
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
|
|
|
|
2022-09-23 11:20:41 +08:00
|
|
|
p.base.Collect()
|
|
|
|
if p.base.RedirectURL != "" {
|
2022-09-23 01:20:01 +08:00
|
|
|
CheckRedirect = func(redirectURL string) bool {
|
2022-09-23 11:20:41 +08:00
|
|
|
if redirectURL == p.base.RedirectURL {
|
2022-09-08 15:57:17 +08:00
|
|
|
// 相同的RedirectURL将被认为是无效数据
|
|
|
|
return false
|
2022-09-23 01:20:01 +08:00
|
|
|
} else {
|
|
|
|
// path为3xx, 且与baseline中的RedirectURL不同时, 为有效数据
|
|
|
|
return true
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-09-15 19:27:07 +08:00
|
|
|
func (p *Pool) Run(ctx context.Context) {
|
|
|
|
|
|
|
|
Loop:
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case u, ok := <-p.worder.C:
|
|
|
|
if !ok {
|
|
|
|
break Loop
|
|
|
|
}
|
2022-09-20 04:01:38 +08:00
|
|
|
p.wg.Add(1)
|
|
|
|
_ = p.pool.Invoke(newUnit(u, WordSource))
|
|
|
|
case <-time.NewTimer(time.Duration(p.DeadlineTime) * time.Second).C:
|
2022-09-15 19:27:07 +08:00
|
|
|
break Loop
|
|
|
|
case <-ctx.Done():
|
|
|
|
break Loop
|
2022-09-19 14:42:29 +08:00
|
|
|
case <-p.ctx.Done():
|
|
|
|
break Loop
|
2022-09-15 19:27:07 +08:00
|
|
|
}
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
2022-09-23 11:20:41 +08:00
|
|
|
|
|
|
|
p.Close()
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
|
|
|
|
2022-09-23 01:20:01 +08:00
|
|
|
func (p *Pool) PreCompare(resp *fasthttp.Response) error {
|
|
|
|
if !CheckStatusCode(resp.StatusCode()) {
|
2022-09-15 19:27:07 +08:00
|
|
|
return ErrBadStatus
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
|
|
|
|
2022-09-23 01:20:01 +08:00
|
|
|
if CheckRedirect != nil && !CheckRedirect(string(resp.Header.Peek("Location"))) {
|
2022-09-15 19:27:07 +08:00
|
|
|
return ErrRedirect
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
|
|
|
|
2022-09-23 01:20:01 +08:00
|
|
|
//if CheckWaf != nil && !CheckWaf(resp) {
|
|
|
|
// return ErrWaf
|
|
|
|
//}
|
2022-09-08 15:57:17 +08:00
|
|
|
|
2022-09-15 19:27:07 +08:00
|
|
|
return nil
|
2022-09-08 15:57:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Pool) RunWithWord(words []string) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-09-23 01:20:01 +08:00
|
|
|
func (p *Pool) Comparing() {
|
|
|
|
for bl := range p.tempCh {
|
2022-09-23 11:20:41 +08:00
|
|
|
if p.base.Equal(bl) {
|
2022-09-23 01:20:01 +08:00
|
|
|
// 如果是同一个包则设置为无效包
|
|
|
|
bl.IsValid = false
|
|
|
|
p.outputCh <- bl
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
bl.Collect()
|
2022-09-23 11:20:41 +08:00
|
|
|
if p.EnableFuzzy && p.base.FuzzyEqual(bl) {
|
2022-09-23 01:20:01 +08:00
|
|
|
bl.IsValid = false
|
|
|
|
p.outputCh <- bl
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
p.outputCh <- bl
|
2022-09-20 04:01:38 +08:00
|
|
|
}
|
2022-09-23 11:20:41 +08:00
|
|
|
|
|
|
|
p.analyzeDone = true
|
2022-09-23 01:20:01 +08:00
|
|
|
}
|
2022-09-23 11:20:41 +08:00
|
|
|
func (p *Pool) Close() {
|
|
|
|
p.wg.Wait()
|
|
|
|
p.bar.Close()
|
2022-09-23 01:20:01 +08:00
|
|
|
|
2022-09-23 11:20:41 +08:00
|
|
|
close(p.tempCh)
|
|
|
|
for !p.analyzeDone {
|
|
|
|
time.Sleep(time.Duration(100) * time.Millisecond)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func (p *Pool) buildPathRequest(path string) (*fasthttp.Request, error) {
|
2022-09-23 01:20:01 +08:00
|
|
|
req := fasthttp.AcquireRequest()
|
|
|
|
req.SetRequestURI(p.BaseURL + path)
|
2022-09-20 04:01:38 +08:00
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
2022-09-23 11:20:41 +08:00
|
|
|
func (p *Pool) buildHostRequest(host string) (*fasthttp.Request, error) {
|
2022-09-23 01:20:01 +08:00
|
|
|
req := fasthttp.AcquireRequest()
|
|
|
|
req.SetRequestURI(p.BaseURL)
|
|
|
|
req.SetHost(host)
|
2022-09-20 04:01:38 +08:00
|
|
|
return req, nil
|
|
|
|
}
|
|
|
|
|
2022-09-08 15:57:17 +08:00
|
|
|
type sourceType int
|
|
|
|
|
|
|
|
const (
|
2022-09-20 18:09:06 +08:00
|
|
|
CheckSource sourceType = iota + 1
|
2022-09-08 15:57:17 +08:00
|
|
|
WordSource
|
|
|
|
WafSource
|
|
|
|
)
|
|
|
|
|
|
|
|
//var sourceMap = map[int]string{
|
|
|
|
//
|
|
|
|
//}
|
|
|
|
|
|
|
|
func newUnit(path string, source sourceType) *Unit {
|
|
|
|
return &Unit{path: path, source: source}
|
|
|
|
}
|
|
|
|
|
|
|
|
type Unit struct {
|
|
|
|
path string
|
|
|
|
source sourceType
|
|
|
|
//callback func()
|
|
|
|
}
|