Skip to content

Commit

Permalink
Replay matches using a chosen proxy (#140)
Browse files Browse the repository at this point in the history
* Replay matches using a custom proxy

* Add changelog entry
  • Loading branch information
joohoi committed Jan 17, 2020
1 parent 3d8e233 commit b0a632e
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
30 changes: 22 additions & 8 deletions main.go
Expand Up @@ -33,6 +33,7 @@ type cliOptions struct {
matcherWords string
matcherLines string
proxyURL string
replayProxyURL string
request string
requestProto string
outputFormat string
Expand Down Expand Up @@ -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.")
Expand Down Expand Up @@ -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)
Expand All @@ -166,22 +171,21 @@ 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)
if err != nil {
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 {
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions pkg/ffuf/config.go
Expand Up @@ -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"`
Expand Down
13 changes: 13 additions & 0 deletions pkg/ffuf/job.go
Expand Up @@ -17,6 +17,7 @@ type Job struct {
ErrorMutex sync.Mutex
Input InputProvider
Runner RunnerProvider
ReplayRunner RunnerProvider
Output OutputProvider
Counter int
ErrorCounter int
Expand Down Expand Up @@ -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()
Expand Down
10 changes: 10 additions & 0 deletions pkg/output/stdout.go
Expand Up @@ -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))
Expand Down
4 changes: 2 additions & 2 deletions pkg/runner/runner.go
Expand Up @@ -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)
}
12 changes: 9 additions & 3 deletions pkg/runner/simple.go
Expand Up @@ -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)
}
Expand Down

0 comments on commit b0a632e

Please sign in to comment.