mirror of
				https://github.com/chainreactors/spray.git
				synced 2025-11-04 09:58:03 +00:00 
			
		
		
		
	实装对probes的支持
新增自动检测退出任务时的细节输出 优化错误请求的输出 优化options的结构
This commit is contained in:
		
							parent
							
								
									78b3dcda75
								
							
						
					
					
						commit
						c9e16aa36a
					
				@ -165,6 +165,27 @@ func (bl *baseline) Additional(key string) string {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (bl *baseline) Format(probes []string) string {
 | 
			
		||||
	var line strings.Builder
 | 
			
		||||
	line.WriteString(bl.Url)
 | 
			
		||||
	if bl.Host != "" {
 | 
			
		||||
		line.WriteString(" (" + bl.Host + ")")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if bl.Err != nil {
 | 
			
		||||
		line.WriteString("err: ")
 | 
			
		||||
		line.WriteString(bl.Err.Error())
 | 
			
		||||
		return line.String()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, p := range probes {
 | 
			
		||||
		line.WriteString(" ")
 | 
			
		||||
		line.WriteString(bl.Additional(p))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return line.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (bl *baseline) String() string {
 | 
			
		||||
	var line strings.Builder
 | 
			
		||||
	//line.WriteString("[+] ")
 | 
			
		||||
@ -172,6 +193,13 @@ func (bl *baseline) String() string {
 | 
			
		||||
	if bl.Host != "" {
 | 
			
		||||
		line.WriteString(" (" + bl.Host + ")")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if bl.Err != nil {
 | 
			
		||||
		line.WriteString("err: ")
 | 
			
		||||
		line.WriteString(bl.Err.Error())
 | 
			
		||||
		return line.String()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	line.WriteString(" - ")
 | 
			
		||||
	line.WriteString(strconv.Itoa(bl.Status))
 | 
			
		||||
	line.WriteString(" - ")
 | 
			
		||||
@ -182,14 +210,8 @@ func (bl *baseline) String() string {
 | 
			
		||||
		line.WriteString(" ")
 | 
			
		||||
	}
 | 
			
		||||
	line.WriteString(bl.Additional("title"))
 | 
			
		||||
	line.WriteString(bl.Additional("mmh3"))
 | 
			
		||||
	line.WriteString(bl.Frameworks.ToString())
 | 
			
		||||
	//line.WriteString(bl.Extracteds)
 | 
			
		||||
	//line.WriteString("\n")
 | 
			
		||||
	if bl.Err != nil {
 | 
			
		||||
		line.WriteString("err: ")
 | 
			
		||||
		line.WriteString(bl.Err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return line.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,31 +13,52 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Option struct {
 | 
			
		||||
	URL               string            `short:"u" long:"url"`
 | 
			
		||||
	URLFile           string            `short:"l" long:"list"`
 | 
			
		||||
	Dictionaries      []string          `short:"d" long:"dict"`
 | 
			
		||||
	Word              string            `short:"w" long:"word"`
 | 
			
		||||
	Extensions        string            `short:"e" long:"extension"`
 | 
			
		||||
	ExcludeExtensions string            `long:"exclude-extension"`
 | 
			
		||||
	RemoveExtensions  string            `long:"remove-extension"`
 | 
			
		||||
	Uppercase         bool              `short:"U" long:"uppercase"`
 | 
			
		||||
	Lowercase         bool              `short:"L" long:"lowercase"`
 | 
			
		||||
	Prefixes          []string          `long:"prefix"`
 | 
			
		||||
	Suffixes          []string          `long:"suffix"`
 | 
			
		||||
	Replaces          map[string]string `long:"replace"`
 | 
			
		||||
	Deadline          int               `long:"deadline" default:"600"` // todo 总的超时时间,适配云函数的deadline
 | 
			
		||||
	Timeout           int               `long:"timeout" default:"2"`
 | 
			
		||||
	InputOptions
 | 
			
		||||
	OutputOptions
 | 
			
		||||
	RequestOptions
 | 
			
		||||
	MiscOptions
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type InputOptions struct {
 | 
			
		||||
	URL               string            `short:"u" long:"url" description:"String, input baseurl (separated by commas), e.g.: http://google.com, http://baidu.com"`
 | 
			
		||||
	URLFile           string            `short:"l" long:"list" description:"File, input filename"`
 | 
			
		||||
	Offset            int               `long:"offset" description:"Int, wordlist offset"`
 | 
			
		||||
	Limit             int               `long:"limit" description:"Int, wordlist limit, start with offset. e.g.: --offset 1000 --limit 100"`
 | 
			
		||||
	Dictionaries      []string          `short:"d" long:"dict" description:"Files, dict files, e.g.: -d 1.txt -d 2.txt"`
 | 
			
		||||
	Word              string            `short:"w" long:"word" description:"String, word generate dsl, e.g.: -w test{?ld#4}"`
 | 
			
		||||
	Extensions        string            `short:"e" long:"extension" description:"String, add extensions (separated by commas), e.g.: -e jsp,jspx"`
 | 
			
		||||
	ExcludeExtensions string            `long:"exclude-extension" description:"String, exclude extensions (separated by commas), e.g.: --exclude-extension jsp,jspx"`
 | 
			
		||||
	RemoveExtensions  string            `long:"remove-extension" description:"String, remove extensions (separated by commas), e.g.: --remove-extension jsp,jspx"`
 | 
			
		||||
	Uppercase         bool              `short:"U" long:"uppercase" description:"Bool, upper wordlist, e.g.: --uppercase"`
 | 
			
		||||
	Lowercase         bool              `short:"L" long:"lowercase" description:"Bool, lower wordlist, e.g.: --lowercase"`
 | 
			
		||||
	Prefixes          []string          `long:"prefix" description:"Strings, add prefix, e.g.: --prefix aaa --prefix bbb"`
 | 
			
		||||
	Suffixes          []string          `long:"suffix" description:"Strings, add suffix, e.g.: --suffix aaa --suffix bbb"`
 | 
			
		||||
	Replaces          map[string]string `long:"replace" description:"Strings, replace string, e.g.: --replace aaa:bbb --replace ccc:ddd"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type OutputOptions struct {
 | 
			
		||||
	Matches     map[string]string `short:"m" long:"match" description:"String, "`
 | 
			
		||||
	Filters     map[string]string `long:"filter" description:"String, "`
 | 
			
		||||
	Extracts    []string          `long:"extract" description:"String, "`
 | 
			
		||||
	OutputFile  string            `short:"f" description:"String, output filename"`
 | 
			
		||||
	OutputProbe string            `long:"probe" description:"String, output format"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RequestOptions struct {
 | 
			
		||||
	Headers []string `long:"header"`
 | 
			
		||||
	OutputFile        string            `short:"f"`
 | 
			
		||||
	OutputProbe       string            `long:"probe"`
 | 
			
		||||
	Offset            int               `long:"offset"`
 | 
			
		||||
	Limit             int               `long:"limit"`
 | 
			
		||||
	Threads           int               `short:"t" long:"thread" default:"20"`
 | 
			
		||||
	PoolSize          int               `short:"p" long:"pool" default:"5"`
 | 
			
		||||
	Debug             bool              `long:"debug"`
 | 
			
		||||
	Quiet             bool              `short:"q" long:"quiet"`
 | 
			
		||||
	Mod               string            `short:"m" long:"mod" default:"path"`
 | 
			
		||||
	Client            string            `short:"c" long:"client" default:"auto"`
 | 
			
		||||
	Method  string   `long:"method"`
 | 
			
		||||
	Cookie  string   `long:"cookie"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MiscOptions struct {
 | 
			
		||||
	Deadline int    `long:"deadline" default:"600" description:"Int, deadline (seconds)"` // todo 总的超时时间,适配云函数的deadline
 | 
			
		||||
	Timeout  int    `long:"timeout" default:"2" description:"Int, timeout with request (seconds)"`
 | 
			
		||||
	PoolSize int    `short:"p" long:"pool" default:"5" description:"Int, Pool size"`
 | 
			
		||||
	Threads  int    `short:"t" long:"thread" default:"20" description:"Int, number of threads per pool (seconds)"`
 | 
			
		||||
	Debug    bool   `long:"debug" description:"Bool, output debug info"`
 | 
			
		||||
	Quiet    bool   `short:"q" long:"quiet" description:"Bool, Quiet"`
 | 
			
		||||
	Mod      string `short:"m" long:"mod" default:"path" choice:"path" choice:"host" description:"String, path/host spray"`
 | 
			
		||||
	Client   string `short:"c" long:"client" default:"auto" choice:"fast" choice:"standard" choice:"auto" description:"String, Client type"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opt *Option) PrepareRunner() (*Runner, error) {
 | 
			
		||||
@ -52,6 +73,7 @@ func (opt *Option) PrepareRunner() (*Runner, error) {
 | 
			
		||||
		PoolSize: opt.PoolSize,
 | 
			
		||||
		Mod:      opt.Mod,
 | 
			
		||||
		Timeout:  opt.Timeout,
 | 
			
		||||
		Probes:   strings.Split(opt.OutputProbe, ","),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = pkg.LoadTemplates()
 | 
			
		||||
 | 
			
		||||
@ -48,6 +48,7 @@ func NewPool(ctx context.Context, config *pkg.Config, outputCh chan *baseline) (
 | 
			
		||||
 | 
			
		||||
			if pool.failedCount > breakThreshold {
 | 
			
		||||
				// 当报错次数超过上限是, 结束任务
 | 
			
		||||
				pool.Recover()
 | 
			
		||||
				pool.cancel()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@ -62,6 +63,7 @@ func NewPool(ctx context.Context, config *pkg.Config, outputCh chan *baseline) (
 | 
			
		||||
 | 
			
		||||
			if pool.failedCount > breakThreshold {
 | 
			
		||||
				// 当报错次数超过上限是, 结束任务
 | 
			
		||||
				pool.Recover()
 | 
			
		||||
				pool.cancel()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@ -86,7 +88,6 @@ func NewPool(ctx context.Context, config *pkg.Config, outputCh chan *baseline) (
 | 
			
		||||
			pool.failedCount++
 | 
			
		||||
			bl = &baseline{Url: pool.BaseURL + unit.path, Err: reqerr}
 | 
			
		||||
		} else {
 | 
			
		||||
			pool.failedCount = 0 // 如果后续访问正常, 重置错误次数
 | 
			
		||||
			if err = pool.PreCompare(resp); err == nil || unit.source == CheckSource || unit.source == InitSource {
 | 
			
		||||
				// 通过预对比跳过一些无用数据, 减少性能消耗
 | 
			
		||||
				bl = NewBaseline(req.URI(), req.Host(), resp)
 | 
			
		||||
@ -99,14 +100,18 @@ func NewPool(ctx context.Context, config *pkg.Config, outputCh chan *baseline) (
 | 
			
		||||
		case InitSource:
 | 
			
		||||
			pool.base = bl
 | 
			
		||||
			pool.initwg.Done()
 | 
			
		||||
			logs.Log.Important("[init] " + bl.String())
 | 
			
		||||
			logs.Log.Important("[baseline] " + bl.String())
 | 
			
		||||
			return
 | 
			
		||||
		case CheckSource:
 | 
			
		||||
			logs.Log.Debugf("[check] " + bl.String())
 | 
			
		||||
			if bl.Err != nil {
 | 
			
		||||
				logs.Log.Warnf("maybe ip has banned by waf, break (%d/%d), error: %s", pool.failedCount, breakThreshold, bl.Err.Error())
 | 
			
		||||
				logs.Log.Warnf("[check.error] maybe ip had banned by waf, break (%d/%d), error: %s", pool.failedCount, breakThreshold, bl.Err.Error())
 | 
			
		||||
				pool.failedBaselines = append(pool.failedBaselines, bl)
 | 
			
		||||
			} else if pool.base.Compare(bl) < 1 {
 | 
			
		||||
				logs.Log.Warn("maybe trigger risk control")
 | 
			
		||||
				logs.Log.Warn("[check.failed] maybe trigger risk control, " + bl.String())
 | 
			
		||||
				pool.failedBaselines = append(pool.failedBaselines, bl)
 | 
			
		||||
			} else {
 | 
			
		||||
				pool.ResetFailed() // 如果后续访问正常, 重置错误次数
 | 
			
		||||
				logs.Log.Debug("[check.pass] " + bl.String())
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case WordSource:
 | 
			
		||||
@ -119,9 +124,9 @@ func NewPool(ctx context.Context, config *pkg.Config, outputCh chan *baseline) (
 | 
			
		||||
			} else if pool.failedCount%pool.errPeriod == 0 {
 | 
			
		||||
				go pool.check()
 | 
			
		||||
			}
 | 
			
		||||
			pool.bar.Done()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pool.bar.Done()
 | 
			
		||||
		pool.wg.Done()
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
@ -145,6 +150,7 @@ type Pool struct {
 | 
			
		||||
	failedCount     int
 | 
			
		||||
	checkPeriod     int
 | 
			
		||||
	errPeriod       int
 | 
			
		||||
	failedBaselines []*baseline
 | 
			
		||||
	analyzeDone     bool
 | 
			
		||||
	genReq          func(s string) (*ihttp.Request, error)
 | 
			
		||||
	check           func()
 | 
			
		||||
@ -247,10 +253,22 @@ func (p *Pool) comparing() {
 | 
			
		||||
	p.analyzeDone = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Pool) ResetFailed() {
 | 
			
		||||
	p.failedCount = 0
 | 
			
		||||
	p.failedBaselines = nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Pool) Recover() {
 | 
			
		||||
	logs.Log.Errorf("failed request exceeds the threshold , task will exit. Breakpoint %d", p.reqCount)
 | 
			
		||||
	logs.Log.Error("collecting failed check")
 | 
			
		||||
	for _, bl := range p.failedBaselines {
 | 
			
		||||
		logs.Log.Error(bl.String())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Pool) Close() {
 | 
			
		||||
	p.wg.Wait()
 | 
			
		||||
	p.bar.Close()
 | 
			
		||||
 | 
			
		||||
	close(p.tempCh)
 | 
			
		||||
	for !p.analyzeDone {
 | 
			
		||||
		time.Sleep(time.Duration(100) * time.Millisecond)
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,7 @@ type Runner struct {
 | 
			
		||||
	poolwg   sync.WaitGroup
 | 
			
		||||
	Timeout  int
 | 
			
		||||
	Mod      string
 | 
			
		||||
	Probes   []string
 | 
			
		||||
	OutputCh chan *baseline
 | 
			
		||||
	Progress *uiprogress.Progress
 | 
			
		||||
}
 | 
			
		||||
@ -104,7 +105,11 @@ func (r *Runner) Outputting() {
 | 
			
		||||
		select {
 | 
			
		||||
		case bl := <-r.OutputCh:
 | 
			
		||||
			if bl.IsValid {
 | 
			
		||||
				if len(r.Probes) > 0 {
 | 
			
		||||
					logs.Log.Console("[+]" + bl.Format(r.Probes) + "\n")
 | 
			
		||||
				} else {
 | 
			
		||||
					logs.Log.Console("[+] " + bl.String() + "\n")
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				logs.Log.Debug(bl.String())
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user