mirror of
https://github.com/ffuf/ffuf
synced 2024-11-10 06:04:17 +00:00
Connection error handling, and options to stop execution (#15)
This commit is contained in:
parent
d5fe00e330
commit
b9c9c92418
4 changed files with 79 additions and 22 deletions
|
@ -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
|
||||
|
|
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.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.")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue