Choose between 'and' and 'or' matching and filtering (#20)
This commit is contained in:
parent
9fa0a5d20a
commit
21a19a1f3d
@ -1,6 +1,8 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
- master
|
- master
|
||||||
- New
|
- 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
|
- Changed
|
||||||
|
|
||||||
- v1.4.1
|
- v1.4.1
|
||||||
@ -16,7 +18,6 @@
|
|||||||
- Added full line colors
|
- Added full line colors
|
||||||
- Added `-json` to emit newline delimited JSON output
|
- Added `-json` to emit newline delimited JSON output
|
||||||
- Added 500 Internal Server Error to list of status codes matched by default
|
- 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
|
- Changed
|
||||||
- Fixed an issue where output file was created regardless of `-or`
|
- 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
|
- Fixed an issue where output (often a lot of it) would be printed after entering interactive mode
|
||||||
|
|||||||
@ -69,6 +69,7 @@
|
|||||||
outputcreateemptyfile = false
|
outputcreateemptyfile = false
|
||||||
|
|
||||||
[filter]
|
[filter]
|
||||||
|
mode = "or"
|
||||||
lines = ""
|
lines = ""
|
||||||
regexp = ""
|
regexp = ""
|
||||||
size = ""
|
size = ""
|
||||||
@ -77,6 +78,7 @@
|
|||||||
words = ""
|
words = ""
|
||||||
|
|
||||||
[matcher]
|
[matcher]
|
||||||
|
mode = "or"
|
||||||
lines = ""
|
lines = ""
|
||||||
regexp = ""
|
regexp = ""
|
||||||
size = ""
|
size = ""
|
||||||
|
|||||||
4
help.go
4
help.go
@ -75,14 +75,14 @@ func Usage() {
|
|||||||
Description: "Matchers for the response filtering.",
|
Description: "Matchers for the response filtering.",
|
||||||
Flags: make([]UsageFlag, 0),
|
Flags: make([]UsageFlag, 0),
|
||||||
Hidden: false,
|
Hidden: false,
|
||||||
ExpectedFlags: []string{"mc", "ml", "mr", "ms", "mt", "mw"},
|
ExpectedFlags: []string{"mmode", "mc", "ml", "mr", "ms", "mt", "mw"},
|
||||||
}
|
}
|
||||||
u_filter := UsageSection{
|
u_filter := UsageSection{
|
||||||
Name: "FILTER OPTIONS",
|
Name: "FILTER OPTIONS",
|
||||||
Description: "Filters for the response filtering.",
|
Description: "Filters for the response filtering.",
|
||||||
Flags: make([]UsageFlag, 0),
|
Flags: make([]UsageFlag, 0),
|
||||||
Hidden: false,
|
Hidden: false,
|
||||||
ExpectedFlags: []string{"fc", "fl", "fr", "fs", "ft", "fw"},
|
ExpectedFlags: []string{"fmode", "fc", "fl", "fr", "fs", "ft", "fw"},
|
||||||
}
|
}
|
||||||
u_input := UsageSection{
|
u_input := UsageSection{
|
||||||
Name: "INPUT OPTIONS",
|
Name: "INPUT OPTIONS",
|
||||||
|
|||||||
2
main.go
2
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.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.AutoCalibrationStrategy, "acs", opts.General.AutoCalibrationStrategy, "Autocalibration strategy: \"basic\" or \"advanced\"")
|
||||||
flag.StringVar(&opts.General.ConfigFile, "config", "", "Load configuration from a file")
|
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.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.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")
|
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.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.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.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.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.Regexp, "mr", opts.Matcher.Regexp, "Match regexp")
|
||||||
flag.StringVar(&opts.Matcher.Size, "ms", opts.Matcher.Size, "Match HTTP response size")
|
flag.StringVar(&opts.Matcher.Size, "ms", opts.Matcher.Size, "Match HTTP response size")
|
||||||
|
|||||||
@ -20,6 +20,7 @@ type Config struct {
|
|||||||
Delay optRange `json:"delay"`
|
Delay optRange `json:"delay"`
|
||||||
DirSearchCompat bool `json:"dirsearch_compatibility"`
|
DirSearchCompat bool `json:"dirsearch_compatibility"`
|
||||||
Extensions []string `json:"extensions"`
|
Extensions []string `json:"extensions"`
|
||||||
|
FilterMode string `json:"fmode"`
|
||||||
FollowRedirects bool `json:"follow_redirects"`
|
FollowRedirects bool `json:"follow_redirects"`
|
||||||
Headers map[string]string `json:"headers"`
|
Headers map[string]string `json:"headers"`
|
||||||
IgnoreBody bool `json:"ignorebody"`
|
IgnoreBody bool `json:"ignorebody"`
|
||||||
@ -30,6 +31,7 @@ type Config struct {
|
|||||||
InputShell string `json:"inputshell"`
|
InputShell string `json:"inputshell"`
|
||||||
Json bool `json:"json"`
|
Json bool `json:"json"`
|
||||||
MatcherManager MatcherManager `json:"matchers"`
|
MatcherManager MatcherManager `json:"matchers"`
|
||||||
|
MatcherMode string `json:"mmode"`
|
||||||
MaxTime int `json:"maxtime"`
|
MaxTime int `json:"maxtime"`
|
||||||
MaxTimeJob int `json:"maxtime_job"`
|
MaxTimeJob int `json:"maxtime_job"`
|
||||||
Method string `json:"method"`
|
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.Delay = optRange{0, 0, false, false}
|
||||||
conf.DirSearchCompat = false
|
conf.DirSearchCompat = false
|
||||||
conf.Extensions = make([]string, 0)
|
conf.Extensions = make([]string, 0)
|
||||||
|
conf.FilterMode = "or"
|
||||||
conf.FollowRedirects = false
|
conf.FollowRedirects = false
|
||||||
conf.Headers = make(map[string]string)
|
conf.Headers = make(map[string]string)
|
||||||
conf.IgnoreWordlistComments = false
|
conf.IgnoreWordlistComments = false
|
||||||
@ -84,6 +87,7 @@ func NewConfig(ctx context.Context, cancel context.CancelFunc) Config {
|
|||||||
conf.InputShell = ""
|
conf.InputShell = ""
|
||||||
conf.InputProviders = make([]InputProviderConfig, 0)
|
conf.InputProviders = make([]InputProviderConfig, 0)
|
||||||
conf.Json = false
|
conf.Json = false
|
||||||
|
conf.MatcherMode = "or"
|
||||||
conf.MaxTime = 0
|
conf.MaxTime = 0
|
||||||
conf.MaxTimeJob = 0
|
conf.MaxTimeJob = 0
|
||||||
conf.Method = "GET"
|
conf.Method = "GET"
|
||||||
|
|||||||
@ -341,6 +341,10 @@ func (j *Job) isMatch(resp Response) bool {
|
|||||||
}
|
}
|
||||||
if match {
|
if match {
|
||||||
matched = true
|
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
|
// The response was not matched, return before running filters
|
||||||
@ -353,8 +357,21 @@ func (j *Job) isMatch(resp Response) bool {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if fv {
|
if fv {
|
||||||
|
// return false
|
||||||
|
if j.Config.FilterMode == "or" {
|
||||||
|
// return early, as filter matched
|
||||||
return false
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,6 +88,7 @@ type OutputOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FilterOptions struct {
|
type FilterOptions struct {
|
||||||
|
Mode string
|
||||||
Lines string
|
Lines string
|
||||||
Regexp string
|
Regexp string
|
||||||
Size string
|
Size string
|
||||||
@ -97,6 +98,7 @@ type FilterOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MatcherOptions struct {
|
type MatcherOptions struct {
|
||||||
|
Mode string
|
||||||
Lines string
|
Lines string
|
||||||
Regexp string
|
Regexp string
|
||||||
Size string
|
Size string
|
||||||
@ -108,6 +110,7 @@ type MatcherOptions struct {
|
|||||||
//NewConfigOptions returns a newly created ConfigOptions struct with default values
|
//NewConfigOptions returns a newly created ConfigOptions struct with default values
|
||||||
func NewConfigOptions() *ConfigOptions {
|
func NewConfigOptions() *ConfigOptions {
|
||||||
c := &ConfigOptions{}
|
c := &ConfigOptions{}
|
||||||
|
c.Filter.Mode = "or"
|
||||||
c.Filter.Lines = ""
|
c.Filter.Lines = ""
|
||||||
c.Filter.Regexp = ""
|
c.Filter.Regexp = ""
|
||||||
c.Filter.Size = ""
|
c.Filter.Size = ""
|
||||||
@ -151,6 +154,7 @@ func NewConfigOptions() *ConfigOptions {
|
|||||||
c.Input.InputNum = 100
|
c.Input.InputNum = 100
|
||||||
c.Input.Request = ""
|
c.Input.Request = ""
|
||||||
c.Input.RequestProto = "https"
|
c.Input.RequestProto = "https"
|
||||||
|
c.Matcher.Mode = "or"
|
||||||
c.Matcher.Lines = ""
|
c.Matcher.Lines = ""
|
||||||
c.Matcher.Regexp = ""
|
c.Matcher.Regexp = ""
|
||||||
c.Matcher.Size = ""
|
c.Matcher.Size = ""
|
||||||
@ -461,6 +465,29 @@ func ConfigFromOptions(parseOpts *ConfigOptions, ctx context.Context, cancel con
|
|||||||
conf.Json = parseOpts.General.Json
|
conf.Json = parseOpts.General.Json
|
||||||
conf.Http2 = parseOpts.HTTP.Http2
|
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 {
|
if conf.AutoCalibrationPerHost {
|
||||||
// AutoCalibrationPerHost implies AutoCalibration
|
// AutoCalibrationPerHost implies AutoCalibration
|
||||||
conf.AutoCalibration = true
|
conf.AutoCalibration = true
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user