From b0a632e6cd83a68d6e12e26bb81686e3b9155614 Mon Sep 17 00:00:00 2001 From: Joona Hoikkala Date: Fri, 17 Jan 2020 09:49:25 +0200 Subject: [PATCH] Replay matches using a chosen proxy (#140) * Replay matches using a custom proxy * Add changelog entry --- CHANGELOG.md | 1 + main.go | 30 ++++++++++++++++++++++-------- pkg/ffuf/config.go | 1 + pkg/ffuf/job.go | 13 +++++++++++++ pkg/output/stdout.go | 10 ++++++++++ pkg/runner/runner.go | 4 ++-- pkg/runner/simple.go | 12 +++++++++--- 7 files changed, 58 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5a8016..529c66c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - New CLI flag `-od` (output directory) to enable writing requests and responses for matched results to a file for postprocessing or debugging purposes. - New CLI flag `-maxtime` to limit the running time of ffuf - New CLI flags `-recursion` and `-recursion-depth` to control recursive ffuf jobs if directories are found. This requires the `-u` to end with FUZZ keyword. + - New CLI flag `-replay-proxy` to replay matched requests using a custom proxy. - Changed - Limit the use of `-e` (extensions) to a single keyword: FUZZ - Regexp matching and filtering (-mr/-fr) allow using keywords in patterns diff --git a/main.go b/main.go index 1bdab5e..4c38f4b 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,7 @@ type cliOptions struct { matcherWords string matcherLines string proxyURL string + replayProxyURL string request string requestProto string outputFormat string @@ -106,6 +107,7 @@ func main() { 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.") + flag.StringVar(&opts.replayProxyURL, "replay-proxy", "", "Replay matched requests using this proxy.") flag.BoolVar(&conf.AutoCalibration, "ac", false, "Automatically calibrate filtering options") flag.Var(&opts.AutoCalibrationStrings, "acc", "Custom auto-calibration string. Can be used multiple times. Implies -ac") flag.IntVar(&conf.Threads, "t", 40, "Number of concurrent threads.") @@ -158,6 +160,9 @@ func main() { } func prepareJob(conf *ffuf.Config) (*ffuf.Job, error) { + job := &ffuf.Job{ + Config: conf, + } errs := ffuf.NewMultierror() var err error inputprovider, err := input.NewInputProvider(conf) @@ -166,7 +171,10 @@ func prepareJob(conf *ffuf.Config) (*ffuf.Job, error) { } // TODO: implement error handling for runnerprovider and outputprovider // We only have http runner right now - runprovider := runner.NewRunnerByName("http", conf) + job.Runner = runner.NewRunnerByName("http", conf, false) + if len(conf.ReplayProxyURL) > 0 { + job.ReplayRunner = runner.NewRunnerByName("http", conf, true) + } // Initialize the correct inputprovider for _, v := range conf.InputProviders { err = inputprovider.AddProvider(v) @@ -174,14 +182,10 @@ func prepareJob(conf *ffuf.Config) (*ffuf.Job, error) { errs.Add(err) } } + job.Input = inputprovider // We only have stdout outputprovider right now - outprovider := output.NewOutputProviderByName("stdout", conf) - return &ffuf.Job{ - Config: conf, - Runner: runprovider, - Output: outprovider, - Input: inputprovider, - }, errs.ErrorOrNil() + job.Output = output.NewOutputProviderByName("stdout", conf) + return job, errs.ErrorOrNil() } func prepareFilters(parseOpts *cliOptions, conf *ffuf.Config) error { @@ -349,6 +353,16 @@ func prepareConfig(parseOpts *cliOptions, conf *ffuf.Config) error { } } + // Verify replayproxy url format + if len(parseOpts.replayProxyURL) > 0 { + _, err := url.Parse(parseOpts.replayProxyURL) + if err != nil { + errs.Add(fmt.Errorf("Bad replay-proxy url (-replay-proxy) format: %s", err)) + } else { + conf.ReplayProxyURL = parseOpts.replayProxyURL + } + } + //Check the output file format option if conf.OutputFile != "" { //No need to check / error out if output file isn't defined diff --git a/pkg/ffuf/config.go b/pkg/ffuf/config.go index a65337e..3c13b3b 100644 --- a/pkg/ffuf/config.go +++ b/pkg/ffuf/config.go @@ -35,6 +35,7 @@ type Config struct { Threads int `json:"threads"` Context context.Context `json:"-"` ProxyURL string `json:"proxyurl"` + ReplayProxyURL string `json:"replayproxyurl"` CommandLine string `json:"cmdline"` Verbose bool `json:"verbose"` MaxTime int `json:"maxtime"` diff --git a/pkg/ffuf/job.go b/pkg/ffuf/job.go index bc89513..090cb85 100644 --- a/pkg/ffuf/job.go +++ b/pkg/ffuf/job.go @@ -17,6 +17,7 @@ type Job struct { ErrorMutex sync.Mutex Input InputProvider Runner RunnerProvider + ReplayRunner RunnerProvider Output OutputProvider Counter int ErrorCounter int @@ -261,6 +262,18 @@ func (j *Job) runTask(input map[string][]byte, position int, retried bool) { } } if j.isMatch(resp) { + // Re-send request through replay-proxy if needed + if j.ReplayRunner != nil { + replayreq, err := j.ReplayRunner.Prepare(input) + replayreq.Position = position + if err != nil { + j.Output.Error(fmt.Sprintf("Encountered an error while preparing replayproxy request: %s\n", err)) + j.incError() + log.Printf("%s", err) + } else { + _, _ = j.ReplayRunner.Execute(&replayreq) + } + } j.Output.Result(resp) // Refresh the progress indicator as we printed something out j.updateProgress() diff --git a/pkg/output/stdout.go b/pkg/output/stdout.go index 3244252..2183b96 100644 --- a/pkg/output/stdout.go +++ b/pkg/output/stdout.go @@ -87,6 +87,16 @@ func (s *Stdoutput) Banner() error { autocalib := fmt.Sprintf("%t", s.config.AutoCalibration) printOption([]byte("Calibration"), []byte(autocalib)) + // Proxies + if len(s.config.ProxyURL) > 0 { + proxy := fmt.Sprintf("%s", s.config.ProxyURL) + printOption([]byte("Proxy"), []byte(proxy)) + } + if len(s.config.ReplayProxyURL) > 0 { + replayproxy := fmt.Sprintf("%s", s.config.ReplayProxyURL) + printOption([]byte("ReplayProxy"), []byte(replayproxy)) + } + // Timeout timeout := fmt.Sprintf("%d", s.config.Timeout) printOption([]byte("Timeout"), []byte(timeout)) diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 2c37890..092d1e0 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -4,7 +4,7 @@ import ( "github.com/ffuf/ffuf/pkg/ffuf" ) -func NewRunnerByName(name string, conf *ffuf.Config) ffuf.RunnerProvider { +func NewRunnerByName(name string, conf *ffuf.Config, replay bool) ffuf.RunnerProvider { // We have only one Runner at the moment - return NewSimpleRunner(conf) + return NewSimpleRunner(conf, replay) } diff --git a/pkg/runner/simple.go b/pkg/runner/simple.go index 0649808..2164edb 100644 --- a/pkg/runner/simple.go +++ b/pkg/runner/simple.go @@ -23,12 +23,18 @@ type SimpleRunner struct { client *http.Client } -func NewSimpleRunner(conf *ffuf.Config) ffuf.RunnerProvider { +func NewSimpleRunner(conf *ffuf.Config, replay bool) ffuf.RunnerProvider { var simplerunner SimpleRunner proxyURL := http.ProxyFromEnvironment + customProxy := "" - if len(conf.ProxyURL) > 0 { - pu, err := url.Parse(conf.ProxyURL) + if replay { + customProxy = conf.ReplayProxyURL + } else { + customProxy = conf.ProxyURL + } + if len(customProxy) > 0 { + pu, err := url.Parse(customProxy) if err == nil { proxyURL = http.ProxyURL(pu) }