Progress and duration

This commit is contained in:
Joona Hoikkala 2018-11-09 14:26:55 +02:00
parent d869393b81
commit cee6b2f25b
No known key found for this signature in database
GPG key ID: D5AA86BBF9B29A5C
7 changed files with 94 additions and 18 deletions

View file

@ -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.")

View file

@ -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
}

View file

@ -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
}

View file

@ -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)

7
pkg/output/const.go Normal file
View file

@ -0,0 +1,7 @@
// +build !windows
package output
const (
TERMINAL_CLEAR_LINE = "\r\x1b[2K"
)

View file

@ -0,0 +1,7 @@
// +build windows
package output
const (
TERMINAL_CLEAR_LINE = "\r\r"
)

View file

@ -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)
}