diff --git a/main.go b/main.go index fc507bf..8bb499a 100644 --- a/main.go +++ b/main.go @@ -21,10 +21,12 @@ type cliOptions struct { filterSize string filterReflect string filterRegex string + filterWords string matcherStatus string matcherSize string matcherReflect string matcherRegex string + matcherWords string headers headerFlags } @@ -50,12 +52,14 @@ func main() { flag.BoolVar(&conf.TLSSkipVerify, "k", false, "Skip TLS identity verification (insecure)") flag.StringVar(&opts.filterStatus, "fc", "", "Filter HTTP status codes from response") flag.StringVar(&opts.filterSize, "fs", "", "Filter HTTP response size") + flag.StringVar(&opts.filterWords, "fw", "", "Filter by amount of words in response") flag.StringVar(&conf.Data, "d", "", "POST data.") flag.BoolVar(&conf.Colors, "c", false, "Colorize output.") //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,401", "Match HTTP status codes from respose") flag.StringVar(&opts.matcherSize, "ms", "", "Match HTTP response size") + flag.StringVar(&opts.matcherWords, "mw", "", "Match amount of words in response") //flag.StringVar(&opts.matcherRegex, "mr", "", "Match regex") flag.StringVar(&conf.Method, "X", "GET", "HTTP method to use.") flag.BoolVar(&conf.Quiet, "s", false, "Do not print additional information (silent mode)") @@ -160,6 +164,11 @@ func prepareFilters(parseOpts *cliOptions, conf *ffuf.Config) error { errlist = multierror.Append(errlist, err) } } + if parseOpts.filterWords != "" { + if err := addFilter(conf, "word", parseOpts.filterWords); err != nil { + errlist = multierror.Append(errlist, err) + } + } if parseOpts.matcherStatus != "" { if err := addMatcher(conf, "status", parseOpts.matcherStatus); err != nil { errlist = multierror.Append(errlist, err) @@ -170,6 +179,11 @@ func prepareFilters(parseOpts *cliOptions, conf *ffuf.Config) error { errlist = multierror.Append(errlist, err) } } + if parseOpts.matcherWords != "" { + if err := addMatcher(conf, "word", parseOpts.matcherWords); err != nil { + errlist = multierror.Append(errlist, err) + } + } return errlist.ErrorOrNil() } diff --git a/pkg/ffuf/response.go b/pkg/ffuf/response.go index e83d474..c67b7ba 100644 --- a/pkg/ffuf/response.go +++ b/pkg/ffuf/response.go @@ -10,6 +10,7 @@ type Response struct { Headers map[string][]string Data []byte ContentLength int64 + ContentWords int64 Cancelled bool Request *Request } diff --git a/pkg/filter/filter.go b/pkg/filter/filter.go index fe9c8e1..bcdc53f 100644 --- a/pkg/filter/filter.go +++ b/pkg/filter/filter.go @@ -1,6 +1,8 @@ package filter import ( + "fmt" + "github.com/ffuf/ffuf/pkg/ffuf" ) @@ -11,5 +13,8 @@ func NewFilterByName(name string, value string) (ffuf.FilterProvider, error) { if name == "size" { return NewSizeFilter(value) } - return nil, nil + if name == "word" { + return NewWordFilter(value) + } + return nil, fmt.Errorf("Could not create filter with name %s", name) } diff --git a/pkg/filter/words.go b/pkg/filter/words.go new file mode 100644 index 0000000..160f0d4 --- /dev/null +++ b/pkg/filter/words.go @@ -0,0 +1,43 @@ +package filter + +import ( + "fmt" + "strconv" + "strings" + + "github.com/ffuf/ffuf/pkg/ffuf" +) + +type WordFilter struct { + Value []int64 +} + +func NewWordFilter(value string) (ffuf.FilterProvider, error) { + var intvals []int64 + for _, sv := range strings.Split(value, ",") { + intval, err := strconv.ParseInt(sv, 10, 0) + if err != nil { + return &WordFilter{}, fmt.Errorf("Word filter or matcher (-fw / -mw): invalid value: %s", value) + } + intvals = append(intvals, intval) + } + return &WordFilter{Value: intvals}, nil +} + +func (f *WordFilter) Filter(response *ffuf.Response) (bool, error) { + wordsSize := len(strings.Split(string(response.Data), " ")) + for _, iv := range f.Value { + if iv == int64(wordsSize) { + return true, nil + } + } + return false, nil +} + +func (f *WordFilter) Repr() string { + var strval []string + for _, iv := range f.Value { + strval = append(strval, strconv.Itoa(int(iv))) + } + return fmt.Sprintf("Response words: %s", strings.Join(strval, ",")) +} diff --git a/pkg/output/stdout.go b/pkg/output/stdout.go index 4add71f..cedd848 100644 --- a/pkg/output/stdout.go +++ b/pkg/output/stdout.go @@ -98,7 +98,7 @@ func (s *Stdoutput) resultQuiet(resp ffuf.Response) { } func (s *Stdoutput) resultNormal(resp ffuf.Response) { - res_str := fmt.Sprintf("%s%-23s [Status: %s, Size: %d]", TERMINAL_CLEAR_LINE, resp.Request.Input, s.colorizeStatus(resp.StatusCode), resp.ContentLength) + res_str := fmt.Sprintf("%s%-23s [Status: %s, Size: %d, Words: %d]", TERMINAL_CLEAR_LINE, resp.Request.Input, s.colorizeStatus(resp.StatusCode), resp.ContentLength, resp.ContentWords) fmt.Println(res_str) } diff --git a/pkg/runner/simple.go b/pkg/runner/simple.go index 0a49821..8ca8e8d 100644 --- a/pkg/runner/simple.go +++ b/pkg/runner/simple.go @@ -94,5 +94,8 @@ func (r *SimpleRunner) Execute(req *ffuf.Request) (ffuf.Response, error) { resp.Data = respbody } + wordsSize := len(strings.Split(string(resp.Data), " ")) + resp.ContentWords = int64(wordsSize) + return resp, nil }