From b9c9c924181dee8461ccf9bcdb285dc30a9d5790 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Wed, 3 Apr 2019 23:11:49 +0300 Subject: [PATCH] Connection error handling, and options to stop execution (#15) --- README.md | 8 ++++- main.go | 2 ++ pkg/ffuf/config.go | 4 +++ pkg/ffuf/job.go | 87 +++++++++++++++++++++++++++++++++++----------- 4 files changed, 79 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 36a45e3..8c31c96 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,6 @@ ffuf -w /path/to/postdata.txt -X POST -d "username=admin\&password=FUZZ" https:/ ## Usage To define the test case for ffuf, use the keyword `FUZZ` anywhere in the URL (`-u`), headers (`-H`), or POST data (`-d`). - ``` -H "Name: Value" Header "Name: Value", separated by colon. Multiple -H flags are accepted. @@ -104,6 +103,10 @@ To define the test case for ffuf, use the keyword `FUZZ` anywhere in the URL (`- Seconds of delay between requests, or a range of random delay. For example "0.1" or "0.1-2.0" -r Follow redirects -s Do not print additional information (silent mode) + -sa + Stop on all error cases. Implies -sf and -se + -se + Stop on spurious errors -sf Stop when > 90% of responses return 403 Forbidden -t int @@ -132,6 +135,9 @@ The only dependency of ffuf is Go 1.11. No dependencies outside of Go standard l - New - New output file formats: CSV and eCSV (CSV with base64 encoded input field to avoid CSV breakage with payloads containing a comma) - New CLI flag to follow redirects + - Erroring connections will be retried once + - Error counter in status bar + - New CLI flags: -se (stop on spurious errors) and -sa (stop on all errors, implies -se and -sf) - v0.8 - New - New CLI flag to write output to a file in JSON format diff --git a/main.go b/main.go index 221c811..078f3fa 100644 --- a/main.go +++ b/main.go @@ -70,6 +70,8 @@ func main() { flag.StringVar(&opts.outputFormat, "of", "json", "Output file format. Available formats: json, csv, ecsv") flag.BoolVar(&conf.Quiet, "s", false, "Do not print additional information (silent mode)") flag.BoolVar(&conf.StopOn403, "sf", false, "Stop when > 90% of responses return 403 Forbidden") + flag.BoolVar(&conf.StopOnErrors, "se", false, "Stop on spurious errors") + flag.BoolVar(&conf.StopOnAll, "sa", false, "Stop on all error cases. Implies -sf and -se") flag.BoolVar(&conf.FollowRedirects, "r", false, "Follow redirects") flag.IntVar(&conf.Threads, "t", 40, "Number of concurrent threads.") flag.BoolVar(&opts.showVersion, "V", false, "Show version information.") diff --git a/pkg/ffuf/config.go b/pkg/ffuf/config.go index 9df4f9c..e3713a1 100644 --- a/pkg/ffuf/config.go +++ b/pkg/ffuf/config.go @@ -28,6 +28,8 @@ type Config struct { OutputFile string OutputFormat string StopOn403 bool + StopOnErrors bool + StopOnAll bool FollowRedirects bool Delay optRange Filters []FilterProvider @@ -49,6 +51,8 @@ func NewConfig(ctx context.Context) Config { conf.Data = "" conf.Quiet = false conf.StopOn403 = false + conf.StopOnErrors = false + conf.StopOnAll = false conf.FollowRedirects = false conf.ProxyURL = http.ProxyFromEnvironment conf.Filters = make([]FilterProvider, 0) diff --git a/pkg/ffuf/job.go b/pkg/ffuf/job.go index eb82ed9..cbe5944 100644 --- a/pkg/ffuf/job.go +++ b/pkg/ffuf/job.go @@ -9,25 +9,52 @@ import ( //Job ties together Config, Runner, Input and Output type Job struct { - Config *Config - Input InputProvider - Runner RunnerProvider - Output OutputProvider - Counter int - Total int - Running bool - Count403 int - Error string - startTime time.Time + Config *Config + ErrorMutex sync.Mutex + Input InputProvider + Runner RunnerProvider + Output OutputProvider + Counter int + ErrorCounter int + SpuriousErrorCounter int + Total int + Running bool + Count403 int + Error string + startTime time.Time } func NewJob(conf *Config) Job { var j Job j.Counter = 0 + j.ErrorCounter = 0 + j.SpuriousErrorCounter = 0 j.Running = false return j } +//incError increments the error counter +func (j *Job) incError() { + j.ErrorMutex.Lock() + defer j.ErrorMutex.Unlock() + j.ErrorCounter++ + j.SpuriousErrorCounter++ +} + +//inc403 increments the 403 response counter +func (j *Job) inc403() { + j.ErrorMutex.Lock() + defer j.ErrorMutex.Unlock() + j.Count403++ +} + +//resetSpuriousErrors resets the spurious error counter +func (j *Job) resetSpuriousErrors() { + j.ErrorMutex.Lock() + defer j.ErrorMutex.Unlock() + j.SpuriousErrorCounter = 0 +} + //Start the execution of the Job func (j *Job) Start() { rand.Seed(time.Now().UnixNano()) @@ -57,7 +84,7 @@ func (j *Job) Start() { go func() { defer func() { <-limiter }() defer wg.Done() - j.runTask([]byte(nextInput)) + j.runTask([]byte(nextInput), false) if j.Config.Delay.HasDelay { var sleepDurationMS time.Duration if j.Config.Delay.IsRange { @@ -106,25 +133,33 @@ func (j *Job) updateProgress() { mins := dur / time.Minute dur -= mins * time.Minute secs := dur / time.Second - progString := fmt.Sprintf(":: Progress: [%d/%d] :: %d req/sec :: Duration: [%d:%02d:%02d] ::", j.Counter, j.Total, int(reqRate), hours, mins, secs) + progString := fmt.Sprintf(":: Progress: [%d/%d] :: %d req/sec :: Duration: [%d:%02d:%02d] :: Errors: %d ::", j.Counter, j.Total, int(reqRate), hours, mins, secs, j.ErrorCounter) j.Output.Progress(progString) } -func (j *Job) runTask(input []byte) { +func (j *Job) runTask(input []byte, retried bool) { req, err := j.Runner.Prepare(input) if err != nil { - j.Output.Error(fmt.Sprintf("Encountered error while preparing request: %s\n", err)) + j.Output.Error(fmt.Sprintf("Encountered an error while preparing request: %s\n", err)) + j.incError() return } resp, err := j.Runner.Execute(&req) if err != nil { - j.Output.Error(fmt.Sprintf("Error in runner: %s\n", err)) + if retried { + j.incError() + } else { + j.runTask(input, true) + } return } - if j.Config.StopOn403 { + if j.SpuriousErrorCounter > 0 { + j.resetSpuriousErrors() + } + if j.Config.StopOn403 || j.Config.StopOnAll { // Incremnt Forbidden counter if we encountered one if resp.StatusCode == 403 { - j.Count403 += 1 + j.inc403() } } if j.Output.Result(resp) { @@ -137,10 +172,20 @@ func (j *Job) runTask(input []byte) { func (j *Job) CheckStop() { if j.Counter > 50 { // We have enough samples - if float64(j.Count403)/float64(j.Counter) > 0.95 { - // Over 95% of requests are 403 - j.Error = "Getting unusual amount of 403 responses, exiting." - j.Stop() + if j.Config.StopOn403 || j.Config.StopOnAll { + if float64(j.Count403)/float64(j.Counter) > 0.95 { + // Over 95% of requests are 403 + j.Error = "Getting an unusual amount of 403 responses, exiting." + j.Stop() + } + } + if j.Config.StopOnErrors || j.Config.StopOnAll { + if j.SpuriousErrorCounter > j.Config.Threads*2 { + // Most of the requests are erroring + j.Error = "Recieving spurious errors, exiting." + j.Stop() + } + } } }