Connection error handling, and options to stop execution (#15)
This commit is contained in:
parent
d5fe00e330
commit
b9c9c92418
@ -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
|
||||||
|
|||||||
2
main.go
2
main.go
@ -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.")
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -10,10 +10,13 @@ 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
|
||||||
|
ErrorMutex sync.Mutex
|
||||||
Input InputProvider
|
Input InputProvider
|
||||||
Runner RunnerProvider
|
Runner RunnerProvider
|
||||||
Output OutputProvider
|
Output OutputProvider
|
||||||
Counter int
|
Counter int
|
||||||
|
ErrorCounter int
|
||||||
|
SpuriousErrorCounter int
|
||||||
Total int
|
Total int
|
||||||
Running bool
|
Running bool
|
||||||
Count403 int
|
Count403 int
|
||||||
@ -24,10 +27,34 @@ type Job struct {
|
|||||||
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,12 +172,22 @@ 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 j.Config.StopOn403 || j.Config.StopOnAll {
|
||||||
if float64(j.Count403)/float64(j.Counter) > 0.95 {
|
if float64(j.Count403)/float64(j.Counter) > 0.95 {
|
||||||
// Over 95% of requests are 403
|
// Over 95% of requests are 403
|
||||||
j.Error = "Getting unusual amount of 403 responses, exiting."
|
j.Error = "Getting an unusual amount of 403 responses, exiting."
|
||||||
j.Stop()
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Stop the execution of the Job
|
//Stop the execution of the Job
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user