Skip to content

Commit

Permalink
Better usage information and -help. Ignore -mc default value if any m…
Browse files Browse the repository at this point in the history
…atcher is manually set (#143)
  • Loading branch information
joohoi committed Jan 29, 2020
1 parent b0a632e commit afece7b
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 14 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
160 changes: 160 additions & 0 deletions help.go
@@ -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 changes: 36 additions & 14 deletions main.go
Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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.")
Expand All @@ -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)
Expand All @@ -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)
}

Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit afece7b

Please sign in to comment.