Connection error handling, and options to stop execution (#15)

This commit is contained in:
Joona Hoikkala 2019-04-03 23:11:49 +03:00 committed by GitHub
parent d5fe00e330
commit b9c9c92418
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 79 additions and 22 deletions

View File

@ -69,7 +69,6 @@ ffuf -w /path/to/postdata.txt -X POST -d "username=admin\&password=FUZZ" https:/
## Usage ## Usage
To define the test case for ffuf, use the keyword `FUZZ` anywhere in the URL (`-u`), headers (`-H`), or POST data (`-d`). 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" -H "Name: Value"
Header "Name: Value", separated by colon. Multiple -H flags are accepted. 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" Seconds of delay between requests, or a range of random delay. For example "0.1" or "0.1-2.0"
-r Follow redirects -r Follow redirects
-s Do not print additional information (silent mode) -s Do not print additional information (silent mode)
-sa
Stop on all error cases. Implies -sf and -se
-se
Stop on spurious errors
-sf -sf
Stop when > 90% of responses return 403 Forbidden Stop when > 90% of responses return 403 Forbidden
-t int -t int
@ -132,6 +135,9 @@ The only dependency of ffuf is Go 1.11. No dependencies outside of Go standard l
- New - New
- New output file formats: CSV and eCSV (CSV with base64 encoded input field to avoid CSV breakage with payloads containing a comma) - 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 - 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 - v0.8
- New - New
- New CLI flag to write output to a file in JSON format - New CLI flag to write output to a file in JSON format

View File

@ -70,6 +70,8 @@ func main() {
flag.StringVar(&opts.outputFormat, "of", "json", "Output file format. Available formats: json, csv, ecsv") 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.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.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.BoolVar(&conf.FollowRedirects, "r", false, "Follow redirects")
flag.IntVar(&conf.Threads, "t", 40, "Number of concurrent threads.") flag.IntVar(&conf.Threads, "t", 40, "Number of concurrent threads.")
flag.BoolVar(&opts.showVersion, "V", false, "Show version information.") flag.BoolVar(&opts.showVersion, "V", false, "Show version information.")

View File

@ -28,6 +28,8 @@ type Config struct {
OutputFile string OutputFile string
OutputFormat string OutputFormat string
StopOn403 bool StopOn403 bool
StopOnErrors bool
StopOnAll bool
FollowRedirects bool FollowRedirects bool
Delay optRange Delay optRange
Filters []FilterProvider Filters []FilterProvider
@ -49,6 +51,8 @@ func NewConfig(ctx context.Context) Config {
conf.Data = "" conf.Data = ""
conf.Quiet = false conf.Quiet = false
conf.StopOn403 = false conf.StopOn403 = false
conf.StopOnErrors = false
conf.StopOnAll = false
conf.FollowRedirects = false conf.FollowRedirects = false
conf.ProxyURL = http.ProxyFromEnvironment conf.ProxyURL = http.ProxyFromEnvironment
conf.Filters = make([]FilterProvider, 0) conf.Filters = make([]FilterProvider, 0)

View File

@ -9,25 +9,52 @@ import (
//Job ties together Config, Runner, Input and Output //Job ties together Config, Runner, Input and Output
type Job struct { type Job struct {
Config *Config Config *Config
Input InputProvider ErrorMutex sync.Mutex
Runner RunnerProvider Input InputProvider
Output OutputProvider Runner RunnerProvider
Counter int Output OutputProvider
Total int Counter int
Running bool ErrorCounter int
Count403 int SpuriousErrorCounter int
Error string Total int
startTime time.Time Running bool
Count403 int
Error string
startTime time.Time
} }
func NewJob(conf *Config) Job { func NewJob(conf *Config) Job {
var j Job var j Job
j.Counter = 0 j.Counter = 0
j.ErrorCounter = 0
j.SpuriousErrorCounter = 0
j.Running = false j.Running = false
return j 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 //Start the execution of the Job
func (j *Job) Start() { func (j *Job) Start() {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
@ -57,7 +84,7 @@ func (j *Job) Start() {
go func() { go func() {
defer func() { <-limiter }() defer func() { <-limiter }()
defer wg.Done() defer wg.Done()
j.runTask([]byte(nextInput)) j.runTask([]byte(nextInput), false)
if j.Config.Delay.HasDelay { if j.Config.Delay.HasDelay {
var sleepDurationMS time.Duration var sleepDurationMS time.Duration
if j.Config.Delay.IsRange { if j.Config.Delay.IsRange {
@ -106,25 +133,33 @@ func (j *Job) updateProgress() {
mins := dur / time.Minute mins := dur / time.Minute
dur -= mins * time.Minute dur -= mins * time.Minute
secs := dur / time.Second 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) j.Output.Progress(progString)
} }
func (j *Job) runTask(input []byte) { func (j *Job) runTask(input []byte, retried bool) {
req, err := j.Runner.Prepare(input) req, err := j.Runner.Prepare(input)
if err != nil { 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 return
} }
resp, err := j.Runner.Execute(&req) resp, err := j.Runner.Execute(&req)
if err != nil { if err != nil {
j.Output.Error(fmt.Sprintf("Error in runner: %s\n", err)) if retried {
j.incError()
} else {
j.runTask(input, true)
}
return 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 // Incremnt Forbidden counter if we encountered one
if resp.StatusCode == 403 { if resp.StatusCode == 403 {
j.Count403 += 1 j.inc403()
} }
} }
if j.Output.Result(resp) { if j.Output.Result(resp) {
@ -137,10 +172,20 @@ func (j *Job) runTask(input []byte) {
func (j *Job) CheckStop() { func (j *Job) CheckStop() {
if j.Counter > 50 { if j.Counter > 50 {
// We have enough samples // We have enough samples
if float64(j.Count403)/float64(j.Counter) > 0.95 { if j.Config.StopOn403 || j.Config.StopOnAll {
// Over 95% of requests are 403 if float64(j.Count403)/float64(j.Counter) > 0.95 {
j.Error = "Getting unusual amount of 403 responses, exiting." // Over 95% of requests are 403
j.Stop() 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()
}
} }
} }
} }