mirror of
https://github.com/ffuf/ffuf
synced 2024-11-10 14:14:24 +00:00
Progress and duration
This commit is contained in:
parent
d869393b81
commit
cee6b2f25b
7 changed files with 94 additions and 18 deletions
2
main.go
2
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.")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
7
pkg/output/const.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
// +build !windows
|
||||
|
||||
package output
|
||||
|
||||
const (
|
||||
TERMINAL_CLEAR_LINE = "\r\x1b[2K"
|
||||
)
|
7
pkg/output/const_windows.go
Normal file
7
pkg/output/const_windows.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
// +build windows
|
||||
|
||||
package output
|
||||
|
||||
const (
|
||||
TERMINAL_CLEAR_LINE = "\r\r"
|
||||
)
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue