From 21a19a1f3d40a9263a87ed4ce66039444f9c0759 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Sun, 3 Apr 2022 23:20:53 +0300 Subject: [PATCH] Choose between 'and' and 'or' matching and filtering (#20) --- CHANGELOG.md | 3 ++- ffufrc.example | 2 ++ help.go | 4 ++-- main.go | 2 ++ pkg/ffuf/config.go | 4 ++++ pkg/ffuf/job.go | 19 ++++++++++++++++++- pkg/ffuf/optionsparser.go | 27 +++++++++++++++++++++++++++ 7 files changed, 57 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7ceaff..f25eb48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## Changelog - master - New + - New autocalibration options: `-ach`, `-ack` and `-acs`. Revamped the whole autocalibration process + - Configurable modes for matchers and filters (CLI flags: `fmode` and `mmode`): "and" and "or" - Changed - v1.4.1 @@ -16,7 +18,6 @@ - Added full line colors - Added `-json` to emit newline delimited JSON output - Added 500 Internal Server Error to list of status codes matched by default - - New autocalibration options: `-ach`, `-ack` and `-acs`. Revamped the whole autocalibration process - Changed - Fixed an issue where output file was created regardless of `-or` - Fixed an issue where output (often a lot of it) would be printed after entering interactive mode diff --git a/ffufrc.example b/ffufrc.example index 19ea23d..6d6b1ec 100644 --- a/ffufrc.example +++ b/ffufrc.example @@ -69,6 +69,7 @@ outputcreateemptyfile = false [filter] + mode = "or" lines = "" regexp = "" size = "" @@ -77,6 +78,7 @@ words = "" [matcher] + mode = "or" lines = "" regexp = "" size = "" diff --git a/help.go b/help.go index 42db3cf..4d429c4 100644 --- a/help.go +++ b/help.go @@ -75,14 +75,14 @@ func Usage() { Description: "Matchers for the response filtering.", Flags: make([]UsageFlag, 0), Hidden: false, - ExpectedFlags: []string{"mc", "ml", "mr", "ms", "mt", "mw"}, + ExpectedFlags: []string{"mmode", "mc", "ml", "mr", "ms", "mt", "mw"}, } u_filter := UsageSection{ Name: "FILTER OPTIONS", Description: "Filters for the response filtering.", Flags: make([]UsageFlag, 0), Hidden: false, - ExpectedFlags: []string{"fc", "fl", "fr", "fs", "ft", "fw"}, + ExpectedFlags: []string{"fmode", "fc", "fl", "fr", "fs", "ft", "fw"}, } u_input := UsageSection{ Name: "INPUT OPTIONS", diff --git a/main.go b/main.go index 85ee064..f51663f 100644 --- a/main.go +++ b/main.go @@ -88,6 +88,7 @@ func ParseFlags(opts *ffuf.ConfigOptions) *ffuf.ConfigOptions { flag.StringVar(&opts.General.AutoCalibrationKeyword, "ack", opts.General.AutoCalibrationKeyword, "Autocalibration keyword") flag.StringVar(&opts.General.AutoCalibrationStrategy, "acs", opts.General.AutoCalibrationStrategy, "Autocalibration strategy: \"basic\" or \"advanced\"") flag.StringVar(&opts.General.ConfigFile, "config", "", "Load configuration from a file") + flag.StringVar(&opts.Filter.Mode, "fmode", opts.Filter.Mode, "Filter set operator. Either of: and, or") flag.StringVar(&opts.Filter.Lines, "fl", opts.Filter.Lines, "Filter by amount of lines in response. Comma separated list of line counts and ranges") flag.StringVar(&opts.Filter.Regexp, "fr", opts.Filter.Regexp, "Filter regexp") flag.StringVar(&opts.Filter.Size, "fs", opts.Filter.Size, "Filter HTTP response size. Comma separated list of sizes and ranges") @@ -110,6 +111,7 @@ func ParseFlags(opts *ffuf.ConfigOptions) *ffuf.ConfigOptions { flag.StringVar(&opts.Input.InputShell, "input-shell", opts.Input.InputShell, "Shell to be used for running command") flag.StringVar(&opts.Input.Request, "request", opts.Input.Request, "File containing the raw http request") flag.StringVar(&opts.Input.RequestProto, "request-proto", opts.Input.RequestProto, "Protocol to use along with raw request") + flag.StringVar(&opts.Matcher.Mode, "mmode", opts.Matcher.Mode, "Matcher set operator. Either of: and, or") flag.StringVar(&opts.Matcher.Lines, "ml", opts.Matcher.Lines, "Match amount of lines in response") flag.StringVar(&opts.Matcher.Regexp, "mr", opts.Matcher.Regexp, "Match regexp") flag.StringVar(&opts.Matcher.Size, "ms", opts.Matcher.Size, "Match HTTP response size") diff --git a/pkg/ffuf/config.go b/pkg/ffuf/config.go index 7342db4..e2f21e3 100644 --- a/pkg/ffuf/config.go +++ b/pkg/ffuf/config.go @@ -20,6 +20,7 @@ type Config struct { Delay optRange `json:"delay"` DirSearchCompat bool `json:"dirsearch_compatibility"` Extensions []string `json:"extensions"` + FilterMode string `json:"fmode"` FollowRedirects bool `json:"follow_redirects"` Headers map[string]string `json:"headers"` IgnoreBody bool `json:"ignorebody"` @@ -30,6 +31,7 @@ type Config struct { InputShell string `json:"inputshell"` Json bool `json:"json"` MatcherManager MatcherManager `json:"matchers"` + MatcherMode string `json:"mmode"` MaxTime int `json:"maxtime"` MaxTimeJob int `json:"maxtime_job"` Method string `json:"method"` @@ -76,6 +78,7 @@ func NewConfig(ctx context.Context, cancel context.CancelFunc) Config { conf.Delay = optRange{0, 0, false, false} conf.DirSearchCompat = false conf.Extensions = make([]string, 0) + conf.FilterMode = "or" conf.FollowRedirects = false conf.Headers = make(map[string]string) conf.IgnoreWordlistComments = false @@ -84,6 +87,7 @@ func NewConfig(ctx context.Context, cancel context.CancelFunc) Config { conf.InputShell = "" conf.InputProviders = make([]InputProviderConfig, 0) conf.Json = false + conf.MatcherMode = "or" conf.MaxTime = 0 conf.MaxTimeJob = 0 conf.Method = "GET" diff --git a/pkg/ffuf/job.go b/pkg/ffuf/job.go index 89d4f75..8e4a1e9 100644 --- a/pkg/ffuf/job.go +++ b/pkg/ffuf/job.go @@ -341,6 +341,10 @@ func (j *Job) isMatch(resp Response) bool { } if match { matched = true + } else if j.Config.MatcherMode == "and" { + // we already know this isn't "and" match + return false + } } // The response was not matched, return before running filters @@ -353,9 +357,22 @@ func (j *Job) isMatch(resp Response) bool { continue } if fv { - return false + // return false + if j.Config.FilterMode == "or" { + // return early, as filter matched + return false + } + } else { + if j.Config.FilterMode == "and" { + // return early as not all filters matched in "and" mode + return true + } } } + if len(filters) > 0 && j.Config.FilterMode == "and" { + // we did not return early, so all filters were matched + return false + } return true } diff --git a/pkg/ffuf/optionsparser.go b/pkg/ffuf/optionsparser.go index cb3ebb3..6d2dc98 100644 --- a/pkg/ffuf/optionsparser.go +++ b/pkg/ffuf/optionsparser.go @@ -88,6 +88,7 @@ type OutputOptions struct { } type FilterOptions struct { + Mode string Lines string Regexp string Size string @@ -97,6 +98,7 @@ type FilterOptions struct { } type MatcherOptions struct { + Mode string Lines string Regexp string Size string @@ -108,6 +110,7 @@ type MatcherOptions struct { //NewConfigOptions returns a newly created ConfigOptions struct with default values func NewConfigOptions() *ConfigOptions { c := &ConfigOptions{} + c.Filter.Mode = "or" c.Filter.Lines = "" c.Filter.Regexp = "" c.Filter.Size = "" @@ -151,6 +154,7 @@ func NewConfigOptions() *ConfigOptions { c.Input.InputNum = 100 c.Input.Request = "" c.Input.RequestProto = "https" + c.Matcher.Mode = "or" c.Matcher.Lines = "" c.Matcher.Regexp = "" c.Matcher.Size = "" @@ -461,6 +465,29 @@ func ConfigFromOptions(parseOpts *ConfigOptions, ctx context.Context, cancel con conf.Json = parseOpts.General.Json conf.Http2 = parseOpts.HTTP.Http2 + // Check that fmode and mmode have sane values + valid_opmodes := []string{"and", "or"} + fmode_found := false + mmode_found := false + for _, v := range valid_opmodes { + if v == parseOpts.Filter.Mode { + fmode_found = true + } + if v == parseOpts.Matcher.Mode { + mmode_found = true + } + } + if !fmode_found { + errmsg := fmt.Sprintf("Unrecognized value for parameter fmode: %s, valid values are: and, or", parseOpts.Filter.Mode) + errs.Add(fmt.Errorf(errmsg)) + } + if !mmode_found { + errmsg := fmt.Sprintf("Unrecognized value for parameter mmode: %s, valid values are: and, or", parseOpts.Matcher.Mode) + errs.Add(fmt.Errorf(errmsg)) + } + conf.FilterMode = parseOpts.Filter.Mode + conf.MatcherMode = parseOpts.Matcher.Mode + if conf.AutoCalibrationPerHost { // AutoCalibrationPerHost implies AutoCalibration conf.AutoCalibration = true