From cee6b2f25bd2664b287a2f94791acbbaa0d0f174 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Fri, 9 Nov 2018 14:26:55 +0200 Subject: [PATCH] Progress and duration --- main.go | 2 +- pkg/ffuf/interfaces.go | 5 +++- pkg/ffuf/job.go | 58 ++++++++++++++++++++++++++++++------- pkg/input/wordlist.go | 9 +++++- pkg/output/const.go | 7 +++++ pkg/output/const_windows.go | 7 +++++ pkg/output/stdout.go | 24 ++++++++++++--- 7 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 pkg/output/const.go create mode 100644 pkg/output/const_windows.go diff --git a/main.go b/main.go index 77ba645..0379c1d 100644 --- a/main.go +++ b/main.go @@ -53,7 +53,7 @@ func main() { flag.StringVar(&conf.Data, "d", "", "POST data.") //flag.StringVar(&opts.filterRegex, "fr", "", "Filter regex") //flag.StringVar(&opts.filterReflect, "fref", "", "Filter reflected payload") - flag.StringVar(&opts.matcherStatus, "mc", "200,204,301,302,307", "Match HTTP status codes from respose") + flag.StringVar(&opts.matcherStatus, "mc", "200,204,301,302,307,401", "Match HTTP status codes from respose") flag.StringVar(&opts.matcherSize, "ms", "", "Match HTTP response size") //flag.StringVar(&opts.matcherRegex, "mr", "", "Match regex") flag.StringVar(&conf.Method, "X", "GET", "HTTP method to use.") diff --git a/pkg/ffuf/interfaces.go b/pkg/ffuf/interfaces.go index 04ac49b..3d684b5 100644 --- a/pkg/ffuf/interfaces.go +++ b/pkg/ffuf/interfaces.go @@ -16,10 +16,13 @@ type RunnerProvider interface { type InputProvider interface { Next() bool Value() []byte + Total() int } //OutputProvider is responsible of providing output from the RunnerProvider type OutputProvider interface { Banner() error - Result(resp Response) + Finalize() error + Error(errstring string) + Result(resp Response) bool } diff --git a/pkg/ffuf/job.go b/pkg/ffuf/job.go index d5fee82..60f2dc5 100644 --- a/pkg/ffuf/job.go +++ b/pkg/ffuf/job.go @@ -2,18 +2,20 @@ package ffuf import ( "fmt" - "os" "sync" + "time" ) //Job ties together Config, Runner, Input and Output type Job struct { - Config *Config - Input InputProvider - Runner RunnerProvider - Output OutputProvider - Counter int - Running bool + Config *Config + Input InputProvider + Runner RunnerProvider + Output OutputProvider + Counter int + Total int + Running bool + startTime time.Time } func NewJob(conf *Config) Job { @@ -25,18 +27,23 @@ func NewJob(conf *Config) Job { //Start the execution of the Job func (j *Job) Start() { + j.Total = j.Input.Total() defer j.Stop() + //Show banner if not running in silent mode if !j.Config.Quiet { j.Output.Banner() } j.Running = true var wg sync.WaitGroup + wg.Add(1) + go j.runProgress(&wg) //Limiter blocks after reaching the buffer, ensuring limited concurrency limiter := make(chan bool, j.Config.Threads) for j.Input.Next() { limiter <- true - wg.Add(1) nextInput := j.Input.Value() + wg.Add(1) + j.Counter++ go func() { defer func() { <-limiter }() defer wg.Done() @@ -44,21 +51,50 @@ func (j *Job) Start() { }() } wg.Wait() + j.updateProgress() + j.Output.Finalize() return } +func (j *Job) runProgress(wg *sync.WaitGroup) { + defer wg.Done() + j.startTime = time.Now() + totalProgress := j.Input.Total() + for j.Counter <= totalProgress { + j.updateProgress() + if j.Counter == totalProgress { + return + } + time.Sleep(time.Millisecond * 100) + } +} + +func (j *Job) updateProgress() { + dur := time.Now().Sub(j.startTime) + hours := dur / time.Hour + dur -= hours * time.Hour + mins := dur / time.Minute + dur -= mins * time.Minute + secs := dur / time.Second + progString := fmt.Sprintf(":: Progress: [%d/%d] :: Duration: [%d:%02d:%02d] ::", j.Counter, j.Total, hours, mins, secs) + j.Output.Error(progString) +} + func (j *Job) runTask(input []byte) { req, err := j.Runner.Prepare(input) if err != nil { - fmt.Fprintf(os.Stderr, "Encountered error while preparing request: %s\n", err) + j.Output.Error(fmt.Sprintf("Encountered error while preparing request: %s\n", err)) return } resp, err := j.Runner.Execute(&req) if err != nil { - fmt.Fprintf(os.Stderr, "Error in runner: %s\n", err) + j.Output.Error(fmt.Sprintf("Error in runner: %s\n", err)) return } - j.Output.Result(resp) + if j.Output.Result(resp) { + // Refresh the progress indicator as we printed something out + j.updateProgress() + } return } diff --git a/pkg/input/wordlist.go b/pkg/input/wordlist.go index 9ecac34..86fcbae 100644 --- a/pkg/input/wordlist.go +++ b/pkg/input/wordlist.go @@ -27,18 +27,25 @@ func NewWordlistInput(conf *ffuf.Config) (*WordlistInput, error) { return &wl, err } +//Next will increment the cursor position, and return a boolean telling if there's words left in the list func (w *WordlistInput) Next() bool { w.position++ - if w.position >= len(w.data)-1 { + if w.position >= len(w.data) { return false } return true } +//Value returns the value from wordlist at current cursor position func (w *WordlistInput) Value() []byte { return w.data[w.position] } +//Total returns the size of wordlist +func (w *WordlistInput) Total() int { + return len(w.data) +} + //validFile checks that the wordlist file exists and can be read func (w *WordlistInput) validFile(path string) (bool, error) { _, err := os.Stat(path) diff --git a/pkg/output/const.go b/pkg/output/const.go new file mode 100644 index 0000000..8e89a11 --- /dev/null +++ b/pkg/output/const.go @@ -0,0 +1,7 @@ +// +build !windows + +package output + +const ( + TERMINAL_CLEAR_LINE = "\r\x1b[2K" +) diff --git a/pkg/output/const_windows.go b/pkg/output/const_windows.go new file mode 100644 index 0000000..de0dbf5 --- /dev/null +++ b/pkg/output/const_windows.go @@ -0,0 +1,7 @@ +// +build windows + +package output + +const ( + TERMINAL_CLEAR_LINE = "\r\r" +) diff --git a/pkg/output/stdout.go b/pkg/output/stdout.go index 70603c6..1414450 100644 --- a/pkg/output/stdout.go +++ b/pkg/output/stdout.go @@ -2,6 +2,7 @@ package output import ( "fmt" + "os" "github.com/ffuf/ffuf/pkg/ffuf" ) @@ -42,7 +43,20 @@ func (s *Stdoutput) Banner() error { return nil } -func (s *Stdoutput) Result(resp ffuf.Response) { +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) + } +} + +func (s *Stdoutput) Finalize() error { + fmt.Fprintf(os.Stderr, "\n") + return nil +} + +func (s *Stdoutput) Result(resp ffuf.Response) bool { matched := false for _, m := range s.config.Matchers { match, err := m.Filter(&resp) @@ -55,7 +69,7 @@ func (s *Stdoutput) Result(resp ffuf.Response) { } // The response was not matched, return before running filters if !matched { - return + return false } for _, f := range s.config.Filters { fv, err := f.Filter(&resp) @@ -63,11 +77,12 @@ func (s *Stdoutput) Result(resp ffuf.Response) { continue } if fv { - return + return false } } // Response survived the filtering, output the result s.printResult(resp) + return true } func (s *Stdoutput) printResult(resp ffuf.Response) { @@ -83,9 +98,10 @@ func (s *Stdoutput) resultQuiet(resp ffuf.Response) { } func (s *Stdoutput) resultNormal(resp ffuf.Response) { - res_str := fmt.Sprintf("%-23s [Status: %d, Size: %d]", resp.Request.Input, resp.StatusCode, resp.ContentLength) + res_str := fmt.Sprintf("%s%-23s [Status: %d, Size: %d]", TERMINAL_CLEAR_LINE, resp.Request.Input, resp.StatusCode, resp.ContentLength) fmt.Println(res_str) } + func printOption(name []byte, value []byte) { fmt.Printf(" :: %-12s : %s\n", name, value) }