From e9f2dfd1c9f4077c99303a2801aeba2da1147563 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Thu, 28 Mar 2019 19:31:02 +0200 Subject: [PATCH] New flag to stop on spurious 403 responses --- main.go | 1 + pkg/ffuf/config.go | 2 ++ pkg/ffuf/interfaces.go | 2 ++ pkg/ffuf/job.go | 35 +++++++++++++++++++++++++++++------ pkg/output/stdout.go | 27 ++++++++++++++++++++++++++- 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/main.go b/main.go index c531a4f8..87e73199 100644 --- a/main.go +++ b/main.go @@ -66,6 +66,7 @@ func main() { flag.StringVar(&opts.proxyURL, "x", "", "HTTP Proxy URL") flag.StringVar(&conf.Method, "X", "GET", "HTTP method to use") 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.IntVar(&conf.Threads, "t", 40, "Number of concurrent threads.") flag.BoolVar(&opts.showVersion, "V", false, "Show version information.") flag.Parse() diff --git a/pkg/ffuf/config.go b/pkg/ffuf/config.go index a689b35e..8fbadaaa 100644 --- a/pkg/ffuf/config.go +++ b/pkg/ffuf/config.go @@ -25,6 +25,7 @@ type Config struct { Quiet bool Colors bool Wordlist string + StopOn403 bool Delay optRange Filters []FilterProvider Matchers []FilterProvider @@ -43,6 +44,7 @@ func NewConfig(ctx context.Context) Config { conf.TLSSkipVerify = false conf.Data = "" conf.Quiet = false + conf.StopOn403 = false conf.ProxyURL = http.ProxyFromEnvironment conf.Filters = make([]FilterProvider, 0) conf.Delay = optRange{0, 0, false, false} diff --git a/pkg/ffuf/interfaces.go b/pkg/ffuf/interfaces.go index 3d684b55..6e9776c4 100644 --- a/pkg/ffuf/interfaces.go +++ b/pkg/ffuf/interfaces.go @@ -23,6 +23,8 @@ type InputProvider interface { type OutputProvider interface { Banner() error Finalize() error + Progress(status string) Error(errstring string) + Warning(warnstring string) Result(resp Response) bool } diff --git a/pkg/ffuf/job.go b/pkg/ffuf/job.go index c275e9b4..eb82ed9e 100644 --- a/pkg/ffuf/job.go +++ b/pkg/ffuf/job.go @@ -16,6 +16,8 @@ type Job struct { Counter int Total int Running bool + Count403 int + Error string startTime time.Time } @@ -42,6 +44,12 @@ func (j *Job) Start() { //Limiter blocks after reaching the buffer, ensuring limited concurrency limiter := make(chan bool, j.Config.Threads) for j.Input.Next() { + // Check if we should stop the process + j.CheckStop() + if !j.Running { + defer j.Output.Warning(j.Error) + break + } limiter <- true nextInput := j.Input.Value() wg.Add(1) @@ -73,6 +81,9 @@ func (j *Job) runProgress(wg *sync.WaitGroup) { j.startTime = time.Now() totalProgress := j.Input.Total() for j.Counter <= totalProgress { + if !j.Running { + break + } j.updateProgress() if j.Counter == totalProgress { return @@ -82,11 +93,6 @@ func (j *Job) runProgress(wg *sync.WaitGroup) { } func (j *Job) updateProgress() { - //TODO: refactor to use a defined progress struct for future output modules - if j.Config.Quiet { - // Do not print progress status in silent mode - return - } runningSecs := int((time.Now().Sub(j.startTime)) / time.Second) var reqRate int if runningSecs > 0 { @@ -101,7 +107,7 @@ func (j *Job) updateProgress() { 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) - j.Output.Error(progString) + j.Output.Progress(progString) } func (j *Job) runTask(input []byte) { @@ -115,6 +121,12 @@ func (j *Job) runTask(input []byte) { j.Output.Error(fmt.Sprintf("Error in runner: %s\n", err)) return } + if j.Config.StopOn403 { + // Incremnt Forbidden counter if we encountered one + if resp.StatusCode == 403 { + j.Count403 += 1 + } + } if j.Output.Result(resp) { // Refresh the progress indicator as we printed something out j.updateProgress() @@ -122,6 +134,17 @@ func (j *Job) runTask(input []byte) { return } +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() + } + } +} + //Stop the execution of the Job func (j *Job) Stop() { j.Running = false diff --git a/pkg/output/stdout.go b/pkg/output/stdout.go index cedd848d..f93dc33e 100644 --- a/pkg/output/stdout.go +++ b/pkg/output/stdout.go @@ -43,11 +43,36 @@ func (s *Stdoutput) Banner() error { return nil } +func (s *Stdoutput) Progress(status string) { + if s.config.Quiet { + // No progress for quiet mode + return + } else { + fmt.Fprintf(os.Stderr, "%s%s", TERMINAL_CLEAR_LINE, status) + } +} + func (s *Stdoutput) Error(errstring string) { if s.config.Quiet { fmt.Fprintf(os.Stderr, "%s", errstring) } else { - fmt.Fprintf(os.Stderr, "%s%s", TERMINAL_CLEAR_LINE, errstring) + if !s.config.Colors { + fmt.Fprintf(os.Stderr, "%s[ERR] %s\n", TERMINAL_CLEAR_LINE, errstring) + } else { + fmt.Fprintf(os.Stderr, "%s[%sERR%s] %s\n", TERMINAL_CLEAR_LINE, ANSI_RED, ANSI_CLEAR, errstring) + } + } +} + +func (s *Stdoutput) Warning(warnstring string) { + if s.config.Quiet { + fmt.Fprintf(os.Stderr, "%s", warnstring) + } else { + if !s.config.Colors { + fmt.Fprintf(os.Stderr, "%s[WARN] %s", TERMINAL_CLEAR_LINE, warnstring) + } else { + fmt.Fprintf(os.Stderr, "%s[%sWARN%s] %s\n", TERMINAL_CLEAR_LINE, ANSI_RED, ANSI_CLEAR, warnstring) + } } }