2022-10-27 18:53:26 +08:00
|
|
|
package internal
|
|
|
|
|
|
|
|
import (
|
2022-11-10 04:22:42 +08:00
|
|
|
"fmt"
|
2022-11-21 20:44:02 +08:00
|
|
|
"github.com/antonmedv/expr"
|
2022-11-29 15:08:10 +08:00
|
|
|
"github.com/chainreactors/files"
|
2022-10-27 18:53:26 +08:00
|
|
|
"github.com/chainreactors/logs"
|
|
|
|
"github.com/chainreactors/spray/pkg"
|
2022-10-27 23:40:00 +08:00
|
|
|
"github.com/chainreactors/words/mask"
|
2022-10-27 18:53:26 +08:00
|
|
|
"github.com/gosuri/uiprogress"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2022-11-10 04:48:07 +08:00
|
|
|
"strconv"
|
2022-10-27 18:53:26 +08:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Option struct {
|
2022-11-10 15:43:25 +08:00
|
|
|
InputOptions
|
|
|
|
OutputOptions
|
|
|
|
RequestOptions
|
2022-11-17 05:40:02 +08:00
|
|
|
ModeOptions
|
2022-11-10 15:43:25 +08:00
|
|
|
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 {
|
2022-11-21 20:44:02 +08:00
|
|
|
Match string `long:"match" description:"String, "`
|
|
|
|
Filter string `long:"filter" description:"String, "`
|
|
|
|
Extracts []string `long:"extract" description:"String, "`
|
|
|
|
OutputFile string `short:"f" description:"String, output filename"`
|
|
|
|
FuzzyFile string `long:"fuzzy-file" description:"String, fuzzy output filename"`
|
|
|
|
Fuzzy bool `long:"fuzzy" description:"String, open fuzzy output"`
|
|
|
|
OutputProbe string `long:"probe" description:"String, output format"`
|
2022-11-10 15:43:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type RequestOptions struct {
|
2022-11-10 21:26:07 +08:00
|
|
|
Headers []string `long:"header"`
|
|
|
|
Method string `long:"method"`
|
|
|
|
Cookie string `long:"cookie"`
|
2022-11-17 05:40:02 +08:00
|
|
|
SimhashDistance int `long:"distance" default:"5"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ModeOptions struct {
|
2022-11-17 16:27:44 +08:00
|
|
|
Force bool `long:"force"`
|
|
|
|
CheckOnly bool `long:"check-only"`
|
|
|
|
CheckPeriod int `long:"check-period" default:"100"`
|
|
|
|
ErrPeriod int `long:"error-period" default:"10"`
|
|
|
|
BreakThreshold int `long:"error-threshold" default:"20"`
|
2022-11-29 15:16:33 +08:00
|
|
|
BlackStatus string `long:"black-status" default:"404,400,410"`
|
|
|
|
WhiteStatus string `long:"white-status" default:"200"`
|
|
|
|
FuzzyStatus string `long:"fuzzy-status" default:"403,500,501,502,503"`
|
2022-11-10 15:43:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type MiscOptions struct {
|
2022-11-18 20:27:29 +08:00
|
|
|
Deadline int `long:"deadline" default:"999999" description:"Int, deadline (seconds)"` // todo 总的超时时间,适配云函数的deadline
|
2022-11-10 15:43:25 +08:00
|
|
|
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"`
|
2022-10-27 18:53:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (opt *Option) PrepareRunner() (*Runner, error) {
|
2022-11-10 04:22:42 +08:00
|
|
|
ok := opt.Validate()
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("validate failed")
|
|
|
|
}
|
2022-10-28 00:46:54 +08:00
|
|
|
var err error
|
2022-10-27 18:53:26 +08:00
|
|
|
r := &Runner{
|
2022-11-17 05:40:02 +08:00
|
|
|
Progress: uiprogress.New(),
|
|
|
|
Threads: opt.Threads,
|
|
|
|
PoolSize: opt.PoolSize,
|
|
|
|
Mod: opt.Mod,
|
|
|
|
Timeout: opt.Timeout,
|
|
|
|
Deadline: opt.Deadline,
|
|
|
|
Offset: opt.Offset,
|
|
|
|
Limit: opt.Limit,
|
2022-11-21 11:35:38 +08:00
|
|
|
urlCh: make(chan string),
|
2022-11-17 05:40:02 +08:00
|
|
|
OutputCh: make(chan *pkg.Baseline, 100),
|
|
|
|
FuzzyCh: make(chan *pkg.Baseline, 100),
|
|
|
|
Fuzzy: opt.Fuzzy,
|
|
|
|
Force: opt.Force,
|
|
|
|
CheckOnly: opt.CheckOnly,
|
|
|
|
CheckPeriod: opt.CheckPeriod,
|
|
|
|
ErrPeriod: opt.ErrPeriod,
|
|
|
|
BreakThreshold: opt.BreakThreshold,
|
2022-10-27 18:53:26 +08:00
|
|
|
}
|
|
|
|
|
2022-10-28 00:46:54 +08:00
|
|
|
err = pkg.LoadTemplates()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 一些全局变量初始化
|
2022-10-27 18:53:26 +08:00
|
|
|
if opt.Debug {
|
|
|
|
logs.Log.Level = logs.Debug
|
|
|
|
}
|
|
|
|
if !opt.Quiet {
|
|
|
|
r.Progress.Start()
|
|
|
|
logs.Log.Writer = r.Progress.Bypass()
|
2022-11-29 15:39:16 +08:00
|
|
|
} else {
|
|
|
|
logs.Log.Level = 100
|
2022-10-27 18:53:26 +08:00
|
|
|
}
|
|
|
|
|
2022-11-10 21:26:07 +08:00
|
|
|
if opt.SimhashDistance != 0 {
|
|
|
|
pkg.Distance = uint8(opt.SimhashDistance)
|
|
|
|
}
|
|
|
|
|
2022-11-10 17:19:05 +08:00
|
|
|
if opt.Force {
|
2022-11-17 05:40:02 +08:00
|
|
|
// 如果开启了force模式, 将关闭check机制, err积累到一定数量自动退出机制
|
|
|
|
r.BreakThreshold = max
|
|
|
|
r.CheckPeriod = max
|
|
|
|
r.ErrPeriod = max
|
2022-11-10 17:19:05 +08:00
|
|
|
}
|
|
|
|
|
2022-11-29 15:16:33 +08:00
|
|
|
if opt.BlackStatus != "" {
|
2022-11-17 16:27:44 +08:00
|
|
|
for _, s := range strings.Split(opt.BlackStatus, ",") {
|
|
|
|
si, err := strconv.Atoi(s)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
BlackStatus = append(BlackStatus, si)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-17 17:09:37 +08:00
|
|
|
if opt.WhiteStatus != "" {
|
|
|
|
for _, s := range strings.Split(opt.WhiteStatus, ",") {
|
|
|
|
si, err := strconv.Atoi(s)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
WhiteStatus = append(WhiteStatus, si)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-29 15:16:33 +08:00
|
|
|
if opt.FuzzyStatus != "" {
|
|
|
|
for _, s := range strings.Split(opt.FuzzyStatus, ",") {
|
|
|
|
si, err := strconv.Atoi(s)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
FuzzyStatus = append(FuzzyStatus, si)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-27 18:53:26 +08:00
|
|
|
// prepare url
|
2022-11-10 16:52:00 +08:00
|
|
|
var urls []string
|
2022-10-27 18:53:26 +08:00
|
|
|
var file *os.File
|
|
|
|
urlfrom := opt.URLFile
|
|
|
|
if opt.URL != "" {
|
2022-11-10 16:52:00 +08:00
|
|
|
urls = append(urls, opt.URL)
|
2022-10-27 18:53:26 +08:00
|
|
|
urlfrom = "cmd"
|
|
|
|
} else if opt.URLFile != "" {
|
|
|
|
file, err = os.Open(opt.URLFile)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else if pkg.HasStdin() {
|
|
|
|
file = os.Stdin
|
|
|
|
urlfrom = "stdin"
|
|
|
|
}
|
|
|
|
|
|
|
|
if file != nil {
|
|
|
|
content, err := ioutil.ReadAll(file)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-11-21 11:52:14 +08:00
|
|
|
urls = strings.Split(strings.TrimSpace(string(content)), "\n")
|
2022-10-27 18:53:26 +08:00
|
|
|
}
|
|
|
|
|
2022-11-21 11:35:38 +08:00
|
|
|
r.URLList = urls
|
2022-11-21 11:52:14 +08:00
|
|
|
logs.Log.Importantf("Loaded %d urls from %s", len(urls), urlfrom)
|
2022-10-27 18:53:26 +08:00
|
|
|
|
|
|
|
// prepare word
|
2022-10-27 19:07:17 +08:00
|
|
|
dicts := make([][]string, len(opt.Dictionaries))
|
|
|
|
for i, f := range opt.Dictionaries {
|
|
|
|
dicts[i], err = loadFileToSlice(f)
|
2022-10-27 18:53:26 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-11-21 11:45:48 +08:00
|
|
|
logs.Log.Importantf("Loaded %d word from %s", len(dicts[i]), f)
|
2022-10-27 18:53:26 +08:00
|
|
|
}
|
|
|
|
|
2022-10-27 19:07:17 +08:00
|
|
|
if opt.Word == "" {
|
2022-11-10 04:48:07 +08:00
|
|
|
opt.Word = "{?"
|
|
|
|
for i, _ := range dicts {
|
|
|
|
opt.Word += strconv.Itoa(i)
|
2022-10-27 23:40:00 +08:00
|
|
|
}
|
2022-11-10 16:52:00 +08:00
|
|
|
opt.Word += "}"
|
2022-11-10 04:48:07 +08:00
|
|
|
}
|
|
|
|
|
2022-11-10 16:52:00 +08:00
|
|
|
if opt.Suffixes != nil {
|
2022-11-10 04:48:07 +08:00
|
|
|
dicts = append(dicts, opt.Suffixes)
|
|
|
|
opt.Word += fmt.Sprintf("{?%d}", len(dicts)-1)
|
|
|
|
}
|
|
|
|
if opt.Prefixes != nil {
|
|
|
|
dicts = append(dicts, opt.Prefixes)
|
|
|
|
opt.Word = fmt.Sprintf("{?%d}", len(dicts)-1) + opt.Word
|
|
|
|
}
|
|
|
|
|
|
|
|
if opt.Extensions != "" {
|
|
|
|
dicts = append(dicts, strings.Split(opt.Extensions, ","))
|
|
|
|
opt.Word += fmt.Sprintf("{?%d}", len(dicts)-1)
|
|
|
|
}
|
|
|
|
|
|
|
|
mask.CustomWords = dicts
|
|
|
|
r.Wordlist, err = mask.Run(opt.Word)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2022-10-27 18:53:26 +08:00
|
|
|
}
|
2022-11-21 11:45:48 +08:00
|
|
|
logs.Log.Importantf("Parsed %d words by %s", len(r.Wordlist), opt.Word)
|
2022-11-21 23:56:27 +08:00
|
|
|
pkg.DefaultStatistor.Total = len(r.Wordlist)
|
|
|
|
pkg.DefaultStatistor.Word = opt.Word
|
|
|
|
pkg.DefaultStatistor.Dictionaries = opt.Dictionaries
|
2022-11-21 11:45:48 +08:00
|
|
|
|
2022-11-10 16:52:00 +08:00
|
|
|
if r.Limit == 0 {
|
2022-11-18 20:27:29 +08:00
|
|
|
if r.CheckOnly {
|
|
|
|
r.Limit = len(r.URLList)
|
|
|
|
} else {
|
|
|
|
r.Limit = len(r.Wordlist)
|
|
|
|
}
|
2022-11-11 11:40:53 +08:00
|
|
|
} else {
|
|
|
|
r.Limit = r.Offset + opt.Limit
|
2022-11-10 16:52:00 +08:00
|
|
|
}
|
2022-10-27 19:07:17 +08:00
|
|
|
|
2022-11-10 04:22:42 +08:00
|
|
|
if opt.Uppercase {
|
|
|
|
r.Fns = append(r.Fns, strings.ToUpper)
|
|
|
|
}
|
|
|
|
if opt.Lowercase {
|
|
|
|
r.Fns = append(r.Fns, strings.ToLower)
|
|
|
|
}
|
2022-11-10 04:48:07 +08:00
|
|
|
|
|
|
|
if opt.RemoveExtensions != "" {
|
|
|
|
rexts := strings.Split(opt.ExcludeExtensions, ",")
|
|
|
|
r.Fns = append(r.Fns, func(s string) string {
|
2022-11-10 21:03:07 +08:00
|
|
|
if ext := parseExtension(s); StringsContains(rexts, ext) {
|
2022-11-10 04:48:07 +08:00
|
|
|
return strings.TrimSuffix(s, "."+ext)
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-11-10 04:22:42 +08:00
|
|
|
if opt.ExcludeExtensions != "" {
|
|
|
|
exexts := strings.Split(opt.ExcludeExtensions, ",")
|
|
|
|
r.Fns = append(r.Fns, func(s string) string {
|
2022-11-10 21:03:07 +08:00
|
|
|
if ext := parseExtension(s); StringsContains(exexts, ext) {
|
2022-11-10 04:22:42 +08:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
})
|
|
|
|
}
|
2022-11-10 04:48:07 +08:00
|
|
|
|
2022-11-10 16:52:00 +08:00
|
|
|
if len(opt.Replaces) > 0 {
|
2022-11-10 04:22:42 +08:00
|
|
|
r.Fns = append(r.Fns, func(s string) string {
|
2022-11-10 04:48:07 +08:00
|
|
|
for k, v := range opt.Replaces {
|
|
|
|
s = strings.Replace(s, k, v, -1)
|
2022-11-10 04:22:42 +08:00
|
|
|
}
|
|
|
|
return s
|
|
|
|
})
|
|
|
|
}
|
2022-11-21 11:45:48 +08:00
|
|
|
logs.Log.Importantf("Loaded %d dictionaries and %d decorators", len(opt.Dictionaries), len(r.Fns))
|
2022-11-21 20:44:02 +08:00
|
|
|
|
|
|
|
if opt.Match != "" {
|
|
|
|
exp, err := expr.Compile(opt.Match)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
r.MatchExpr = exp
|
|
|
|
}
|
|
|
|
|
|
|
|
if opt.Filter != "" {
|
|
|
|
exp, err := expr.Compile(opt.Filter)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
r.FilterExpr = exp
|
|
|
|
}
|
|
|
|
|
2022-10-27 18:53:26 +08:00
|
|
|
// prepare header
|
|
|
|
for _, h := range opt.Headers {
|
|
|
|
i := strings.Index(h, ":")
|
|
|
|
if i == -1 {
|
|
|
|
logs.Log.Warn("invalid header")
|
|
|
|
} else {
|
|
|
|
r.Headers.Add(h[:i], h[i+2:])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-10 17:19:05 +08:00
|
|
|
if opt.OutputProbe != "" {
|
|
|
|
r.Probes = strings.Split(opt.OutputProbe, ",")
|
|
|
|
}
|
2022-11-29 15:08:10 +08:00
|
|
|
|
|
|
|
if opt.OutputFile != "" {
|
|
|
|
r.OutputFile, err = files.NewFile(opt.OutputFile, false, false, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-11-29 20:24:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
r.FuzzyFile, err = files.NewFile(opt.FuzzyFile, false, false, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
r.StatFile, err = files.NewFile("stat.json", false, false, true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2022-11-29 15:08:10 +08:00
|
|
|
}
|
2022-10-27 18:53:26 +08:00
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
2022-11-10 04:22:42 +08:00
|
|
|
func (opt *Option) Validate() bool {
|
|
|
|
if opt.Uppercase && opt.Lowercase {
|
|
|
|
logs.Log.Error("Cannot set -U and -L at the same time")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-10-27 18:53:26 +08:00
|
|
|
func loadFileToSlice(filename string) ([]string, error) {
|
|
|
|
var ss []string
|
|
|
|
content, err := ioutil.ReadFile(filename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
ss = strings.Split(string(content), "\n")
|
|
|
|
|
|
|
|
// 统一windows与linux的回车换行差异
|
|
|
|
for i, word := range ss {
|
|
|
|
ss[i] = strings.TrimSpace(word)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ss, nil
|
|
|
|
}
|
2022-11-10 04:22:42 +08:00
|
|
|
|
|
|
|
func parseExtension(s string) string {
|
|
|
|
if i := strings.Index(s, "."); i != -1 {
|
|
|
|
return s[i+1:]
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2022-11-10 21:03:07 +08:00
|
|
|
func StringsContains(s []string, e string) bool {
|
|
|
|
for _, v := range s {
|
|
|
|
if v == e {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func IntsContains(s []int, e int) bool {
|
2022-11-10 04:22:42 +08:00
|
|
|
for _, v := range s {
|
|
|
|
if v == e {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|