Better usage information and -help. Ignore -mc default value if any matcher is manually set (#143)
This commit is contained in:
parent
b0a632e6cd
commit
afece7bf2b
@ -14,6 +14,8 @@
|
||||
- Take 429 responses into account when -sa (stop on all error cases) is used
|
||||
- Remove -k flag support, convert to dummy flag #134
|
||||
- Write configuration to output JSON
|
||||
- Better help text.
|
||||
- If any matcher is set, ignore -mc default value.
|
||||
|
||||
- v0.12
|
||||
- New
|
||||
|
||||
160
help.go
Normal file
160
help.go
Normal file
@ -0,0 +1,160 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ffuf/ffuf/pkg/ffuf"
|
||||
)
|
||||
|
||||
type UsageSection struct {
|
||||
Name string
|
||||
Description string
|
||||
Flags []UsageFlag
|
||||
Hidden bool
|
||||
ExpectedFlags []string
|
||||
}
|
||||
|
||||
//PrintSection prints out the section name, description and each of the flags
|
||||
func (u *UsageSection) PrintSection(max_length int, extended bool) {
|
||||
// Do not print if extended usage not requested and section marked as hidden
|
||||
if !extended && u.Hidden {
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s:\n", u.Name)
|
||||
for _, f := range u.Flags {
|
||||
f.PrintFlag(max_length)
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
type UsageFlag struct {
|
||||
Name string
|
||||
Description string
|
||||
Default string
|
||||
}
|
||||
|
||||
//PrintFlag prints out the flag name, usage string and default value
|
||||
func (f *UsageFlag) PrintFlag(max_length int) {
|
||||
// Create format string, used for padding
|
||||
format := fmt.Sprintf(" -%%-%ds %%s", max_length)
|
||||
if f.Default != "" {
|
||||
format = format + " (default: %s)\n"
|
||||
fmt.Printf(format, f.Name, f.Description, f.Default)
|
||||
} else {
|
||||
format = format + "\n"
|
||||
fmt.Printf(format, f.Name, f.Description)
|
||||
}
|
||||
}
|
||||
|
||||
func Usage() {
|
||||
u_http := UsageSection{
|
||||
Name: "HTTP OPTIONS",
|
||||
Description: "Options controlling the HTTP request and its parts.",
|
||||
Flags: make([]UsageFlag, 0),
|
||||
Hidden: false,
|
||||
ExpectedFlags: []string{"H", "X", "b", "d", "r", "u", "recursion", "recursion-depth", "replay-proxy", "timeout", "x"},
|
||||
}
|
||||
u_general := UsageSection{
|
||||
Name: "GENERAL OPTIONS",
|
||||
Description: "",
|
||||
Flags: make([]UsageFlag, 0),
|
||||
Hidden: false,
|
||||
ExpectedFlags: []string{"ac", "acc", "c", "maxtime", "p", "s", "sa", "se", "sf", "t", "v", "V"},
|
||||
}
|
||||
u_compat := UsageSection{
|
||||
Name: "COMPATIBILITY OPTIONS",
|
||||
Description: "Options to ensure compatibility with other pieces of software.",
|
||||
Flags: make([]UsageFlag, 0),
|
||||
Hidden: true,
|
||||
ExpectedFlags: []string{"compressed", "cookie", "data", "data-ascii", "data-binary", "i", "k"},
|
||||
}
|
||||
u_matcher := UsageSection{
|
||||
Name: "MATCHER OPTIONS",
|
||||
Description: "Matchers for the response filtering.",
|
||||
Flags: make([]UsageFlag, 0),
|
||||
Hidden: false,
|
||||
ExpectedFlags: []string{"mc", "ml", "mr", "ms", "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", "fw"},
|
||||
}
|
||||
u_input := UsageSection{
|
||||
Name: "INPUT OPTIONS",
|
||||
Description: "Options for input data for fuzzing. Wordlists and input generators.",
|
||||
Flags: make([]UsageFlag, 0),
|
||||
Hidden: false,
|
||||
ExpectedFlags: []string{"D", "ic", "input-cmd", "input-num", "mode", "request", "request-proto", "e", "w"},
|
||||
}
|
||||
u_output := UsageSection{
|
||||
Name: "OUTPUT OPTIONS",
|
||||
Description: "Options for output. Output file formats, file names and debug file locations.",
|
||||
Flags: make([]UsageFlag, 0),
|
||||
Hidden: false,
|
||||
ExpectedFlags: []string{"debug-log", "o", "of", "od"},
|
||||
}
|
||||
sections := []UsageSection{u_http, u_general, u_compat, u_matcher, u_filter, u_input, u_output}
|
||||
|
||||
// Populate the flag sections
|
||||
max_length := 0
|
||||
flag.VisitAll(func(f *flag.Flag) {
|
||||
found := false
|
||||
for i, section := range sections {
|
||||
if strInSlice(f.Name, section.ExpectedFlags) {
|
||||
sections[i].Flags = append(sections[i].Flags, UsageFlag{
|
||||
Name: f.Name,
|
||||
Description: f.Usage,
|
||||
Default: f.DefValue,
|
||||
})
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
fmt.Printf("DEBUG: Flag %s was found but not defined in usage.go.\n", f.Name)
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(f.Name) > max_length {
|
||||
max_length = len(f.Name)
|
||||
}
|
||||
})
|
||||
|
||||
fmt.Printf("Fuzz Faster U Fool - v%s\n\n", ffuf.VERSION)
|
||||
|
||||
// Print out the sections
|
||||
for _, section := range sections {
|
||||
section.PrintSection(max_length, false)
|
||||
}
|
||||
|
||||
// Usage examples.
|
||||
fmt.Printf("EXAMPLE USAGE:\n")
|
||||
|
||||
fmt.Printf(" Fuzz file paths from wordlist.txt, match all responses but filter out those with content-size 42.\n")
|
||||
fmt.Printf(" Colored, verbose output.\n")
|
||||
fmt.Printf(" ffuf -w wordlist.txt -u https://example.org/FUZZ -mc all -fs 42 -c -v\n\n")
|
||||
|
||||
fmt.Printf(" Fuzz Host-header, match HTTP 200 responses.\n")
|
||||
fmt.Printf(" ffuf -w hosts.txt -u https://example.org/ -H \"Host: FUZZ\" -mc 200\n\n")
|
||||
|
||||
fmt.Printf(" Fuzz POST JSON data. Match all responses not containing text \"error\".\n")
|
||||
fmt.Printf(" ffuf -w entries.txt -u https://example.org/ -X POST -H \"Content-Type: application/json\" \\\n")
|
||||
fmt.Printf(" -d '{\"name\": \"FUZZ\", \"anotherkey\": \"anothervalue\"}' -fr \"error\"\n\n")
|
||||
|
||||
fmt.Printf(" Fuzz multiple locations. Match only responses reflecting the value of \"VAL\" keyword. Colored.\n")
|
||||
fmt.Printf(" ffuf -w params.txt:PARAM -w values.txt:VAL -u https://example.org/?PARAM=VAL -mr \"VAL\" -c\n\n")
|
||||
|
||||
fmt.Printf(" More information and examples: https://github.com/ffuf/ffuf\n\n")
|
||||
}
|
||||
|
||||
func strInSlice(val string, slice []string) bool {
|
||||
for _, v := range slice {
|
||||
if v == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
50
main.go
50
main.go
@ -64,11 +64,11 @@ func main() {
|
||||
opts := cliOptions{}
|
||||
var ignored bool
|
||||
flag.BoolVar(&conf.IgnoreWordlistComments, "ic", false, "Ignore wordlist comments")
|
||||
flag.StringVar(&opts.extensions, "e", "", "Comma separated list of extensions to apply. Each extension provided will extend the wordlist entry once. Only extends a wordlist with (default) FUZZ keyword.")
|
||||
flag.BoolVar(&conf.DirSearchCompat, "D", false, "DirSearch style wordlist compatibility mode. Used in conjunction with -e flag. Replaces %EXT% in wordlist entry with each of the extensions provided by -e.")
|
||||
flag.StringVar(&opts.extensions, "e", "", "Comma separated list of extensions. Extends FUZZ keyword.")
|
||||
flag.BoolVar(&conf.DirSearchCompat, "D", false, "DirSearch wordlist compatibility mode. Used in conjunction with -e flag.")
|
||||
flag.Var(&opts.headers, "H", "Header `\"Name: Value\"`, separated by colon. Multiple -H flags are accepted.")
|
||||
flag.StringVar(&conf.Url, "u", "", "Target URL")
|
||||
flag.Var(&opts.wordlists, "w", "Wordlist file path and (optional) custom fuzz keyword, using colon as delimiter. Use file path '-' to read from standard input. Can be supplied multiple times. Format: '/path/to/wordlist:KEYWORD'")
|
||||
flag.Var(&opts.wordlists, "w", "Wordlist file path and (optional) keyword separated by colon. eg. '/path/to/wordlist:KEYWORD'")
|
||||
flag.BoolVar(&ignored, "k", false, "Dummy flag for backwards compatibility")
|
||||
flag.StringVar(&opts.delay, "p", "", "Seconds of `delay` between requests, or a range of random delay. For example \"0.1\" or \"0.1-2.0\"")
|
||||
flag.StringVar(&opts.filterStatus, "fc", "", "Filter HTTP status codes from response. Comma separated list of codes and ranges")
|
||||
@ -86,9 +86,9 @@ func main() {
|
||||
flag.IntVar(&conf.InputNum, "input-num", 100, "Number of inputs to test. Used in conjunction with --input-cmd.")
|
||||
flag.StringVar(&conf.InputMode, "mode", "clusterbomb", "Multi-wordlist operation mode. Available modes: clusterbomb, pitchfork")
|
||||
flag.BoolVar(&ignored, "i", true, "Dummy flag for copy as curl functionality (ignored)")
|
||||
flag.Var(&opts.cookies, "b", "Cookie data `\"NAME1=VALUE1; NAME2=VALUE2\"` for copy as curl functionality.\nResults unpredictable when combined with -H \"Cookie: ...\"")
|
||||
flag.Var(&opts.cookies, "b", "Cookie data `\"NAME1=VALUE1; NAME2=VALUE2\"` for copy as curl functionality.")
|
||||
flag.Var(&opts.cookies, "cookie", "Cookie data (alias of -b)")
|
||||
flag.StringVar(&opts.matcherStatus, "mc", "200,204,301,302,307,401,403", "Match HTTP status codes from respose, use \"all\" to match every response code.")
|
||||
flag.StringVar(&opts.matcherStatus, "mc", "200,204,301,302,307,401,403", "Match HTTP status codes, or \"all\" for everything.")
|
||||
flag.StringVar(&opts.matcherSize, "ms", "", "Match HTTP response size")
|
||||
flag.StringVar(&opts.matcherRegexp, "mr", "", "Match regexp")
|
||||
flag.StringVar(&opts.matcherWords, "mw", "", "Match amount of words in response")
|
||||
@ -103,7 +103,7 @@ func main() {
|
||||
flag.BoolVar(&conf.Quiet, "s", false, "Do not print additional information (silent mode)")
|
||||
flag.BoolVar(&conf.StopOn403, "sf", false, "Stop when > 95% of responses return 403 Forbidden")
|
||||
flag.BoolVar(&conf.StopOnErrors, "se", false, "Stop on spurious errors")
|
||||
flag.BoolVar(&conf.StopOnAll, "sa", false, "Stop on all error cases. Implies -sf and -se. Also stops on spurious 429 response codes.")
|
||||
flag.BoolVar(&conf.StopOnAll, "sa", false, "Stop on all error cases. Implies -sf and -se.")
|
||||
flag.BoolVar(&conf.FollowRedirects, "r", false, "Follow redirects")
|
||||
flag.BoolVar(&conf.Recursion, "recursion", false, "Scan recursively. Only FUZZ keyword is supported, and URL (-u) has to end in it.")
|
||||
flag.IntVar(&conf.RecursionDepth, "recursion-depth", 0, "Maximum recursion depth.")
|
||||
@ -116,6 +116,7 @@ func main() {
|
||||
flag.BoolVar(&conf.Verbose, "v", false, "Verbose output, printing full URL and redirect location (if any) with the results.")
|
||||
flag.BoolVar(&opts.showVersion, "V", false, "Show version information.")
|
||||
flag.StringVar(&opts.debugLog, "debug-log", "", "Write all of the internal logging to the specified file.")
|
||||
flag.Usage = Usage
|
||||
flag.Parse()
|
||||
if opts.showVersion {
|
||||
fmt.Printf("ffuf version: %s\n", ffuf.VERSION)
|
||||
@ -135,18 +136,18 @@ func main() {
|
||||
}
|
||||
if err := prepareConfig(&opts, &conf); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Encountered error(s): %s\n", err)
|
||||
flag.Usage()
|
||||
Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
job, err := prepareJob(&conf)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Encountered error(s): %s\n", err)
|
||||
flag.Usage()
|
||||
Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := prepareFilters(&opts, &conf); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Encountered error(s): %s\n", err)
|
||||
flag.Usage()
|
||||
Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@ -190,6 +191,32 @@ func prepareJob(conf *ffuf.Config) (*ffuf.Job, error) {
|
||||
|
||||
func prepareFilters(parseOpts *cliOptions, conf *ffuf.Config) error {
|
||||
errs := ffuf.NewMultierror()
|
||||
// If any other matcher is set, ignore -mc default value
|
||||
matcherSet := false
|
||||
statusSet := false
|
||||
flag.Visit(func(f *flag.Flag) {
|
||||
if f.Name == "mc" {
|
||||
statusSet = true
|
||||
}
|
||||
if f.Name == "ms" {
|
||||
matcherSet = true
|
||||
}
|
||||
if f.Name == "ml" {
|
||||
matcherSet = true
|
||||
}
|
||||
if f.Name == "mr" {
|
||||
matcherSet = true
|
||||
}
|
||||
if f.Name == "mw" {
|
||||
matcherSet = true
|
||||
}
|
||||
})
|
||||
if statusSet || !matcherSet {
|
||||
if err := filter.AddMatcher(conf, "status", parseOpts.matcherStatus); err != nil {
|
||||
errs.Add(err)
|
||||
}
|
||||
}
|
||||
|
||||
if parseOpts.filterStatus != "" {
|
||||
if err := filter.AddFilter(conf, "status", parseOpts.filterStatus); err != nil {
|
||||
errs.Add(err)
|
||||
@ -215,11 +242,6 @@ func prepareFilters(parseOpts *cliOptions, conf *ffuf.Config) error {
|
||||
errs.Add(err)
|
||||
}
|
||||
}
|
||||
if parseOpts.matcherStatus != "" {
|
||||
if err := filter.AddMatcher(conf, "status", parseOpts.matcherStatus); err != nil {
|
||||
errs.Add(err)
|
||||
}
|
||||
}
|
||||
if parseOpts.matcherSize != "" {
|
||||
if err := filter.AddMatcher(conf, "size", parseOpts.matcherSize); err != nil {
|
||||
errs.Add(err)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user