2018-11-08 09:26:32 +00:00
package main
import (
"context"
"flag"
"fmt"
2021-05-13 20:46:29 +00:00
"io/ioutil"
"log"
"os"
"strings"
2018-11-08 09:26:32 +00:00
"github.com/ffuf/ffuf/pkg/ffuf"
"github.com/ffuf/ffuf/pkg/filter"
"github.com/ffuf/ffuf/pkg/input"
2021-04-18 09:54:17 +00:00
"github.com/ffuf/ffuf/pkg/interactive"
2018-11-08 09:26:32 +00:00
"github.com/ffuf/ffuf/pkg/output"
"github.com/ffuf/ffuf/pkg/runner"
)
2018-12-05 22:57:42 +00:00
type multiStringFlag [ ] string
2020-09-16 08:08:09 +00:00
type wordlistFlag [ ] string
2018-11-08 09:26:32 +00:00
2018-12-05 22:57:42 +00:00
func ( m * multiStringFlag ) String ( ) string {
2018-11-08 09:26:32 +00:00
return ""
}
2020-09-16 08:08:09 +00:00
func ( m * wordlistFlag ) String ( ) string {
return ""
}
2018-12-05 22:57:42 +00:00
func ( m * multiStringFlag ) Set ( value string ) error {
2020-09-16 08:08:09 +00:00
* m = append ( * m , value )
return nil
}
func ( m * wordlistFlag ) Set ( value string ) error {
2020-09-14 20:13:21 +00:00
delimited := strings . Split ( value , "," )
if len ( delimited ) > 1 {
* m = append ( * m , delimited ... )
} else {
* m = append ( * m , value )
}
2018-11-08 09:26:32 +00:00
return nil
}
2020-09-27 16:24:06 +00:00
//ParseFlags parses the command line flags and (re)populates the ConfigOptions struct
func ParseFlags ( opts * ffuf . ConfigOptions ) * ffuf . ConfigOptions {
2019-06-04 21:26:27 +00:00
var ignored bool
2020-09-27 16:24:06 +00:00
var cookies , autocalibrationstrings , headers , inputcommands multiStringFlag
var wordlists wordlistFlag
cookies = opts . HTTP . Cookies
autocalibrationstrings = opts . General . AutoCalibrationStrings
headers = opts . HTTP . Headers
inputcommands = opts . Input . Inputcommands
2019-06-04 21:26:27 +00:00
flag . BoolVar ( & ignored , "compressed" , true , "Dummy flag for copy as curl functionality (ignored)" )
2019-06-26 19:44:52 +00:00
flag . BoolVar ( & ignored , "i" , true , "Dummy flag for copy as curl functionality (ignored)" )
2020-09-27 16:24:06 +00:00
flag . BoolVar ( & ignored , "k" , false , "Dummy flag for backwards compatibility" )
2021-05-13 16:07:00 +00:00
flag . BoolVar ( & opts . Output . OutputSkipEmptyFile , "or" , opts . Output . OutputSkipEmptyFile , "Don't create the output file if we don't have results" )
2020-09-27 16:24:06 +00:00
flag . BoolVar ( & opts . General . AutoCalibration , "ac" , opts . General . AutoCalibration , "Automatically calibrate filtering options" )
flag . BoolVar ( & opts . General . Colors , "c" , opts . General . Colors , "Colorize output." )
2021-04-26 20:04:12 +00:00
flag . BoolVar ( & opts . General . Noninteractive , "noninteractive" , opts . General . Noninteractive , "Disable the interactive console functionality" )
2020-09-27 16:24:06 +00:00
flag . BoolVar ( & opts . General . Quiet , "s" , opts . General . Quiet , "Do not print additional information (silent mode)" )
flag . BoolVar ( & opts . General . ShowVersion , "V" , opts . General . ShowVersion , "Show version information." )
flag . BoolVar ( & opts . General . StopOn403 , "sf" , opts . General . StopOn403 , "Stop when > 95% of responses return 403 Forbidden" )
flag . BoolVar ( & opts . General . StopOnAll , "sa" , opts . General . StopOnAll , "Stop on all error cases. Implies -sf and -se." )
flag . BoolVar ( & opts . General . StopOnErrors , "se" , opts . General . StopOnErrors , "Stop on spurious errors" )
flag . BoolVar ( & opts . General . Verbose , "v" , opts . General . Verbose , "Verbose output, printing full URL and redirect location (if any) with the results." )
flag . BoolVar ( & opts . HTTP . FollowRedirects , "r" , opts . HTTP . FollowRedirects , "Follow redirects" )
flag . BoolVar ( & opts . HTTP . IgnoreBody , "ignore-body" , opts . HTTP . IgnoreBody , "Do not fetch the response content." )
flag . BoolVar ( & opts . HTTP . Recursion , "recursion" , opts . HTTP . Recursion , "Scan recursively. Only FUZZ keyword is supported, and URL (-u) has to end in it." )
flag . BoolVar ( & opts . Input . DirSearchCompat , "D" , opts . Input . DirSearchCompat , "DirSearch wordlist compatibility mode. Used in conjunction with -e flag." )
flag . BoolVar ( & opts . Input . IgnoreWordlistComments , "ic" , opts . Input . IgnoreWordlistComments , "Ignore wordlist comments" )
flag . IntVar ( & opts . General . MaxTime , "maxtime" , opts . General . MaxTime , "Maximum running time in seconds for entire process." )
flag . IntVar ( & opts . General . MaxTimeJob , "maxtime-job" , opts . General . MaxTimeJob , "Maximum running time in seconds per job." )
flag . IntVar ( & opts . General . Rate , "rate" , opts . General . Rate , "Rate of requests per second" )
flag . IntVar ( & opts . General . Threads , "t" , opts . General . Threads , "Number of concurrent threads." )
flag . IntVar ( & opts . HTTP . RecursionDepth , "recursion-depth" , opts . HTTP . RecursionDepth , "Maximum recursion depth." )
flag . IntVar ( & opts . HTTP . Timeout , "timeout" , opts . HTTP . Timeout , "HTTP request timeout in seconds." )
flag . IntVar ( & opts . Input . InputNum , "input-num" , opts . Input . InputNum , "Number of inputs to test. Used in conjunction with --input-cmd." )
flag . StringVar ( & opts . General . ConfigFile , "config" , "" , "Load configuration from a file" )
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" )
flag . StringVar ( & opts . Filter . Status , "fc" , opts . Filter . Status , "Filter HTTP status codes from response. Comma separated list of codes and ranges" )
2021-05-16 21:10:56 +00:00
flag . StringVar ( & opts . Filter . Time , "ft" , opts . Filter . Time , "Filter by number of milliseconds to the first response byte, either greater or less than. EG: >100 or <100" )
2020-09-27 16:24:06 +00:00
flag . StringVar ( & opts . Filter . Words , "fw" , opts . Filter . Words , "Filter by amount of words in response. Comma separated list of word counts and ranges" )
flag . StringVar ( & opts . General . Delay , "p" , opts . General . Delay , "Seconds of `delay` between requests, or a range of random delay. For example \"0.1\" or \"0.1-2.0\"" )
flag . StringVar ( & opts . HTTP . Data , "d" , opts . HTTP . Data , "POST data" )
flag . StringVar ( & opts . HTTP . Data , "data" , opts . HTTP . Data , "POST data (alias of -d)" )
flag . StringVar ( & opts . HTTP . Data , "data-ascii" , opts . HTTP . Data , "POST data (alias of -d)" )
flag . StringVar ( & opts . HTTP . Data , "data-binary" , opts . HTTP . Data , "POST data (alias of -d)" )
flag . StringVar ( & opts . HTTP . Method , "X" , opts . HTTP . Method , "HTTP method to use" )
2021-04-18 09:54:17 +00:00
flag . StringVar ( & opts . HTTP . ProxyURL , "x" , opts . HTTP . ProxyURL , "Proxy URL (SOCKS5 or HTTP). For example: http://127.0.0.1:8080 or socks5://127.0.0.1:8080" )
2020-09-27 16:24:06 +00:00
flag . StringVar ( & opts . HTTP . ReplayProxyURL , "replay-proxy" , opts . HTTP . ReplayProxyURL , "Replay matched requests using this proxy." )
2021-04-18 09:54:17 +00:00
flag . StringVar ( & opts . HTTP . RecursionStrategy , "recursion-strategy" , opts . HTTP . RecursionStrategy , "Recursion strategy: \"default\" for a redirect based, and \"greedy\" to recurse on all matches" )
2020-09-27 16:24:06 +00:00
flag . StringVar ( & opts . HTTP . URL , "u" , opts . HTTP . URL , "Target URL" )
2021-05-13 20:46:29 +00:00
flag . StringVar ( & opts . HTTP . SNI , "sni" , opts . HTTP . SNI , "Target TLS SNI, does not support FUZZ keyword" )
2020-09-27 16:24:06 +00:00
flag . StringVar ( & opts . Input . Extensions , "e" , opts . Input . Extensions , "Comma separated list of extensions. Extends FUZZ keyword." )
flag . StringVar ( & opts . Input . InputMode , "mode" , opts . Input . InputMode , "Multi-wordlist operation mode. Available modes: clusterbomb, pitchfork" )
2021-01-24 16:55:23 +00:00
flag . StringVar ( & opts . Input . InputShell , "input-shell" , opts . Input . InputShell , "Shell to be used for running command" )
2020-09-27 16:24:06 +00:00
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 . 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" )
flag . StringVar ( & opts . Matcher . Status , "mc" , opts . Matcher . Status , "Match HTTP status codes, or \"all\" for everything." )
2021-05-16 21:10:56 +00:00
flag . StringVar ( & opts . Matcher . Time , "mt" , opts . Matcher . Time , "Match how many milliseconds to the first response byte, either greater or less than. EG: >100 or <100" )
2020-09-27 16:24:06 +00:00
flag . StringVar ( & opts . Matcher . Words , "mw" , opts . Matcher . Words , "Match amount of words in response" )
flag . StringVar ( & opts . Output . DebugLog , "debug-log" , opts . Output . DebugLog , "Write all of the internal logging to the specified file." )
flag . StringVar ( & opts . Output . OutputDirectory , "od" , opts . Output . OutputDirectory , "Directory path to store matched results to." )
flag . StringVar ( & opts . Output . OutputFile , "o" , opts . Output . OutputFile , "Write output to file" )
flag . StringVar ( & opts . Output . OutputFormat , "of" , opts . Output . OutputFormat , "Output file format. Available formats: json, ejson, html, md, csv, ecsv (or, 'all' for all formats)" )
flag . Var ( & autocalibrationstrings , "acc" , "Custom auto-calibration string. Can be used multiple times. Implies -ac" )
flag . Var ( & cookies , "b" , "Cookie data `\"NAME1=VALUE1; NAME2=VALUE2\"` for copy as curl functionality." )
flag . Var ( & cookies , "cookie" , "Cookie data (alias of -b)" )
flag . Var ( & headers , "H" , "Header `\"Name: Value\"`, separated by colon. Multiple -H flags are accepted." )
flag . Var ( & inputcommands , "input-cmd" , "Command producing the input. --input-num is required when using this input method. Overrides -w." )
flag . Var ( & wordlists , "w" , "Wordlist file path and (optional) keyword separated by colon. eg. '/path/to/wordlist:KEYWORD'" )
2020-01-29 22:23:58 +00:00
flag . Usage = Usage
2018-11-08 09:26:32 +00:00
flag . Parse ( )
2020-09-27 16:24:06 +00:00
opts . General . AutoCalibrationStrings = autocalibrationstrings
opts . HTTP . Cookies = cookies
opts . HTTP . Headers = headers
opts . Input . Inputcommands = inputcommands
opts . Input . Wordlists = wordlists
return opts
}
func main ( ) {
var err , optserr error
// prepare the default config options from default config file
var opts * ffuf . ConfigOptions
opts , optserr = ffuf . ReadDefaultConfig ( )
opts = ParseFlags ( opts )
if opts . General . ShowVersion {
2021-03-04 20:04:04 +00:00
fmt . Printf ( "ffuf version: %s\n" , ffuf . Version ( ) )
2018-11-15 08:47:43 +00:00
os . Exit ( 0 )
}
2020-09-27 16:24:06 +00:00
if len ( opts . Output . DebugLog ) != 0 {
f , err := os . OpenFile ( opts . Output . DebugLog , os . O_APPEND | os . O_CREATE | os . O_WRONLY , 0644 )
2019-10-20 15:38:11 +00:00
if err != nil {
fmt . Fprintf ( os . Stderr , "Disabling logging, encountered error(s): %s\n" , err )
log . SetOutput ( ioutil . Discard )
} else {
log . SetOutput ( f )
defer f . Close ( )
}
} else {
log . SetOutput ( ioutil . Discard )
}
2020-09-27 16:24:06 +00:00
if optserr != nil {
log . Printf ( "Error while opening default config file: %s" , optserr )
}
if opts . General . ConfigFile != "" {
opts , err = ffuf . ReadConfig ( opts . General . ConfigFile )
if err != nil {
fmt . Fprintf ( os . Stderr , "Encoutered error(s): %s\n" , err )
Usage ( )
fmt . Fprintf ( os . Stderr , "Encoutered error(s): %s\n" , err )
os . Exit ( 1 )
}
// Reset the flag package state
flag . CommandLine = flag . NewFlagSet ( os . Args [ 0 ] , flag . ExitOnError )
// Re-parse the cli options
opts = ParseFlags ( opts )
}
// Prepare context and set up Config struct
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
conf , err := ffuf . ConfigFromOptions ( opts , ctx , cancel )
if err != nil {
2018-11-08 13:54:12 +00:00
fmt . Fprintf ( os . Stderr , "Encountered error(s): %s\n" , err )
2020-01-29 22:23:58 +00:00
Usage ( )
2020-08-30 12:22:06 +00:00
fmt . Fprintf ( os . Stderr , "Encountered error(s): %s\n" , err )
2018-11-08 09:26:32 +00:00
os . Exit ( 1 )
}
2020-09-27 16:24:06 +00:00
job , err := prepareJob ( conf )
2019-04-28 16:36:48 +00:00
if err != nil {
2018-11-08 13:54:12 +00:00
fmt . Fprintf ( os . Stderr , "Encountered error(s): %s\n" , err )
2020-01-29 22:23:58 +00:00
Usage ( )
2020-08-30 12:22:06 +00:00
fmt . Fprintf ( os . Stderr , "Encountered error(s): %s\n" , err )
2018-11-08 09:26:32 +00:00
os . Exit ( 1 )
}
2020-09-27 16:24:06 +00:00
if err := filter . SetupFilters ( opts , conf ) ; err != nil {
2018-11-08 13:54:12 +00:00
fmt . Fprintf ( os . Stderr , "Encountered error(s): %s\n" , err )
2020-01-29 22:23:58 +00:00
Usage ( )
2020-08-30 12:22:06 +00:00
fmt . Fprintf ( os . Stderr , "Encountered error(s): %s\n" , err )
2018-11-08 09:26:32 +00:00
os . Exit ( 1 )
}
2019-04-20 17:46:43 +00:00
2019-04-28 16:36:48 +00:00
if err := filter . CalibrateIfNeeded ( job ) ; err != nil {
fmt . Fprintf ( os . Stderr , "Error in autocalibration, exiting: %s\n" , err )
os . Exit ( 1 )
2019-04-20 17:46:43 +00:00
}
2021-04-26 20:04:12 +00:00
if ! conf . Noninteractive {
go func ( ) {
err := interactive . Handle ( job )
if err != nil {
log . Printf ( "Error while trying to initialize interactive session: %s" , err )
}
} ( )
}
2019-04-20 17:46:43 +00:00
2018-11-08 09:26:32 +00:00
// Job handles waiting for goroutines to complete itself
job . Start ( )
}
func prepareJob ( conf * ffuf . Config ) ( * ffuf . Job , error ) {
2020-08-30 10:51:41 +00:00
job := ffuf . NewJob ( conf )
2020-09-27 16:24:06 +00:00
var errs ffuf . Multierror
job . Input , errs = input . NewInputProvider ( conf )
2018-11-08 09:26:32 +00:00
// TODO: implement error handling for runnerprovider and outputprovider
// We only have http runner right now
2020-01-17 07:49:25 +00:00
job . Runner = runner . NewRunnerByName ( "http" , conf , false )
if len ( conf . ReplayProxyURL ) > 0 {
job . ReplayRunner = runner . NewRunnerByName ( "http" , conf , true )
}
2018-11-08 09:26:32 +00:00
// We only have stdout outputprovider right now
2020-01-17 07:49:25 +00:00
job . Output = output . NewOutputProviderByName ( "stdout" , conf )
return job , errs . ErrorOrNil ( )
2018-11-08 09:26:32 +00:00
}