diff --git a/README.md b/README.md index b0f9773..d95a2ad 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,9 @@ ffuf -w /path/to/postdata.txt -X POST -d "username=admin\&password=FUZZ" https:/ To define the test case for ffuf, use the keyword `FUZZ` anywhere in the URL (`-u`), headers (`-H`), or POST data (`-d`). ``` -Usage of ./ffuf: -H "Name: Value" Header "Name: Value", separated by colon. Multiple -H flags are accepted. + -V Show version information. -X string HTTP method to use. (default "GET") -c Colorize output. @@ -89,13 +89,15 @@ Usage of ./ffuf: Filter by amount of words in response -k Skip TLS identity verification (insecure) -mc string - Match HTTP status codes from respose (default "200,204,301,302,307,401") + Match HTTP status codes from respose (default "200,204,301,302,307,401,403") -mr string Match regexp -ms string Match HTTP response size -mw string Match amount of words in response + -p delay + Seconds of delay between requests, or a range of random delay. For example "0.1" or "0.1-2.0" -s Do not print additional information (silent mode) -t int Number of concurrent threads. (default 40) diff --git a/main.go b/main.go index 4c2fef0..ae4bb12 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "os" + "strconv" "strings" "github.com/ffuf/ffuf/pkg/ffuf" @@ -15,6 +16,7 @@ import ( ) type cliOptions struct { + delay string filterStatus string filterSize string filterRegexp string @@ -23,18 +25,18 @@ type cliOptions struct { matcherSize string matcherRegexp string matcherWords string - headers headerFlags + headers multiStringFlag showVersion bool } -type headerFlags []string +type multiStringFlag []string -func (h *headerFlags) String() string { +func (m *multiStringFlag) String() string { return "" } -func (h *headerFlags) Set(value string) error { - *h = append(*h, value) +func (m *multiStringFlag) Set(value string) error { + *m = append(*m, value) return nil } @@ -47,6 +49,7 @@ func main() { flag.StringVar(&conf.Url, "u", "", "Target URL") flag.StringVar(&conf.Wordlist, "w", "", "Wordlist path") flag.BoolVar(&conf.TLSSkipVerify, "k", false, "Skip TLS identity verification (insecure)") + flag.StringVar(&opts.delay, "p", "", "Seconds of `delay` between requests, or a range of random delay. For example \"0.1\" or \"0.1-2.0\"") flag.StringVar(&opts.filterStatus, "fc", "", "Filter HTTP status codes from response") flag.StringVar(&opts.filterSize, "fs", "", "Filter HTTP response size") flag.StringVar(&opts.filterRegexp, "fr", "", "Filter regexp") @@ -111,6 +114,9 @@ func prepareConfig(parseOpts *cliOptions, conf *ffuf.Config) error { //TODO: refactor in a proper flag library that can handle things like required flags errs := ffuf.NewMultierror() foundkeyword := false + + var err error + var err2 error if len(conf.Url) == 0 { errs.Add(fmt.Errorf("-u flag is required")) } @@ -138,6 +144,27 @@ func prepareConfig(parseOpts *cliOptions, conf *ffuf.Config) error { errs.Add(fmt.Errorf("Header defined by -H needs to have a value. \":\" should be used as a separator")) } } + //Prepare delay + d := strings.Split(parseOpts.delay, "-") + if len(d) > 2 { + errs.Add(fmt.Errorf("Delay needs to be either a single float: \"0.1\" or a range of floats, delimited by dash: \"0.1-0.8\"")) + } else if len(d) == 2 { + conf.Delay.IsRange = true + conf.Delay.HasDelay = true + conf.Delay.Min, err = strconv.ParseFloat(d[0], 64) + conf.Delay.Max, err2 = strconv.ParseFloat(d[1], 64) + if err != nil || err2 != nil { + errs.Add(fmt.Errorf("Delay range min and max values need to be valid floats. For example: 0.1-0.5")) + } + } else if len(parseOpts.delay) > 0 { + conf.Delay.IsRange = false + conf.Delay.HasDelay = true + conf.Delay.Min, err = strconv.ParseFloat(parseOpts.delay, 64) + if err != nil { + errs.Add(fmt.Errorf("Delay needs to be either a single float: \"0.1\" or a range of floats, delimited by dash: \"0.1-0.8\"")) + } + } + //Search for keyword from URL and POST data too if strings.Index(conf.Url, "FUZZ") != -1 { foundkeyword = true diff --git a/pkg/ffuf/config.go b/pkg/ffuf/config.go index 7d205b3..8f52f29 100644 --- a/pkg/ffuf/config.go +++ b/pkg/ffuf/config.go @@ -4,6 +4,15 @@ import ( "context" ) +//optRange stores either a single float, in which case the value is stored in min and IsRange is false, +//or a range of floats, in which case IsRange is true +type optRange struct { + Min float64 + Max float64 + IsRange bool + HasDelay bool +} + type Config struct { StaticHeaders map[string]string FuzzHeaders map[string]string @@ -14,6 +23,7 @@ type Config struct { Quiet bool Colors bool Wordlist string + Delay optRange Filters []FilterProvider Matchers []FilterProvider Threads int @@ -31,5 +41,6 @@ func NewConfig(ctx context.Context) Config { conf.Data = "" conf.Quiet = false conf.Filters = make([]FilterProvider, 0) + conf.Delay = optRange{0, 0, false, false} return conf } diff --git a/pkg/ffuf/job.go b/pkg/ffuf/job.go index 767b57b..c275e9b 100644 --- a/pkg/ffuf/job.go +++ b/pkg/ffuf/job.go @@ -2,6 +2,7 @@ package ffuf import ( "fmt" + "math/rand" "sync" "time" ) @@ -27,6 +28,7 @@ func NewJob(conf *Config) Job { //Start the execution of the Job func (j *Job) Start() { + rand.Seed(time.Now().UnixNano()) j.Total = j.Input.Total() defer j.Stop() //Show banner if not running in silent mode @@ -48,6 +50,16 @@ func (j *Job) Start() { defer func() { <-limiter }() defer wg.Done() j.runTask([]byte(nextInput)) + if j.Config.Delay.HasDelay { + var sleepDurationMS time.Duration + if j.Config.Delay.IsRange { + sTime := j.Config.Delay.Min + rand.Float64()*(j.Config.Delay.Max-j.Config.Delay.Min) + sleepDurationMS = time.Duration(sTime * 1000) + } else { + sleepDurationMS = time.Duration(j.Config.Delay.Min * 1000) + } + time.Sleep(sleepDurationMS * time.Millisecond) + } }() } wg.Wait()