Fix the multi wordlist bug and add a new mode of operation (#93)

* Fix the multi wordlist bug and add a new mode

* Add a README entry
This commit is contained in:
Joona Hoikkala 2019-11-16 00:40:04 +02:00 committed by GitHub
parent 73922822f9
commit ac141e5e34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 114 additions and 15 deletions

View file

@ -144,6 +144,8 @@ Usage of ./ffuf:
Match HTTP status codes from respose, use "all" to match every response code. (default "200,204,301,302,307,401,403") Match HTTP status codes from respose, use "all" to match every response code. (default "200,204,301,302,307,401,403")
-ml string -ml string
Match amount of lines in response Match amount of lines in response
-mode string
Multi-wordlist operation mode. Available modes: clusterbomb, pitchfork (default "clusterbomb")
-mr string -mr string
Match regexp Match regexp
-ms string -ms string
@ -190,7 +192,9 @@ The only dependency of ffuf is Go 1.11. No dependencies outside of Go standard l
- master - master
- New - New
- Added a new flag to select a multi wordlist operation mode: `--mode`, possible values: `clusterbomb` and `pitchfork`.
- Changed - Changed
- Fixed a bug in the default multi wordlist mode
- v0.11 - v0.11

View file

@ -80,6 +80,7 @@ func main() {
flag.BoolVar(&ignored, "compressed", true, "Dummy flag for copy as curl functionality (ignored)") flag.BoolVar(&ignored, "compressed", true, "Dummy flag for copy as curl functionality (ignored)")
flag.Var(&opts.inputcommands, "input-cmd", "Command producing the input. --input-num is required when using this input method. Overrides -w.") flag.Var(&opts.inputcommands, "input-cmd", "Command producing the input. --input-num is required when using this input method. Overrides -w.")
flag.IntVar(&conf.InputNum, "input-num", 100, "Number of inputs to test. Used in conjunction with --input-cmd.") 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.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.\nResults unpredictable when combined with -H \"Cookie: ...\"")
flag.Var(&opts.cookies, "cookie", "Cookie data (alias of -b)") flag.Var(&opts.cookies, "cookie", "Cookie data (alias of -b)")
@ -150,7 +151,10 @@ func main() {
func prepareJob(conf *ffuf.Config) (*ffuf.Job, error) { func prepareJob(conf *ffuf.Config) (*ffuf.Job, error) {
errs := ffuf.NewMultierror() errs := ffuf.NewMultierror()
var err error var err error
inputprovider := input.NewInputProvider(conf) inputprovider, err := input.NewInputProvider(conf)
if err != nil {
errs.Add(err)
}
// TODO: implement error handling for runnerprovider and outputprovider // TODO: implement error handling for runnerprovider and outputprovider
// We only have http runner right now // We only have http runner right now
runprovider := runner.NewRunnerByName("http", conf) runprovider := runner.NewRunnerByName("http", conf)
@ -158,7 +162,7 @@ func prepareJob(conf *ffuf.Config) (*ffuf.Job, error) {
for _, v := range conf.InputProviders { for _, v := range conf.InputProviders {
err = inputprovider.AddProvider(v) err = inputprovider.AddProvider(v)
if err != nil { if err != nil {
errs.Add(fmt.Errorf("%s", err)) errs.Add(err)
} }
} }
// We only have stdout outputprovider right now // We only have stdout outputprovider right now

View file

@ -28,6 +28,7 @@ type Config struct {
InputProviders []InputProviderConfig InputProviders []InputProviderConfig
CommandKeywords []string CommandKeywords []string
InputNum int InputNum int
InputMode string
OutputFile string OutputFile string
OutputFormat string OutputFormat string
StopOn403 bool StopOn403 bool
@ -71,6 +72,7 @@ func NewConfig(ctx context.Context) Config {
conf.InputProviders = make([]InputProviderConfig, 0) conf.InputProviders = make([]InputProviderConfig, 0)
conf.CommandKeywords = make([]string, 0) conf.CommandKeywords = make([]string, 0)
conf.InputNum = 0 conf.InputNum = 0
conf.InputMode = "clusterbomb"
conf.ProxyURL = http.ProxyFromEnvironment conf.ProxyURL = http.ProxyFromEnvironment
conf.Filters = make([]FilterProvider, 0) conf.Filters = make([]FilterProvider, 0)
conf.Delay = optRange{0, 0, false, false} conf.Delay = optRange{0, 0, false, false}

View file

@ -27,6 +27,7 @@ type InternalInputProvider interface {
Next() bool Next() bool
Position() int Position() int
ResetPosition() ResetPosition()
IncrementPosition()
Value() []byte Value() []byte
Total() int Total() int
} }

View file

@ -20,7 +20,7 @@ func NewCommandInput(keyword string, value string, conf *ffuf.Config) (*CommandI
var cmd CommandInput var cmd CommandInput
cmd.keyword = keyword cmd.keyword = keyword
cmd.config = conf cmd.config = conf
cmd.count = -1 cmd.count = 0
cmd.command = value cmd.command = value
return &cmd, nil return &cmd, nil
} }
@ -40,9 +40,13 @@ func (c *CommandInput) ResetPosition() {
c.count = 0 c.count = 0
} }
//IncrementPosition increments the current position in the inputprovider
func (c *CommandInput) IncrementPosition() {
c.count += 1
}
//Next will increment the cursor position, and return a boolean telling if there's iterations left //Next will increment the cursor position, and return a boolean telling if there's iterations left
func (c *CommandInput) Next() bool { func (c *CommandInput) Next() bool {
c.count++
if c.count >= c.config.InputNum { if c.count >= c.config.InputNum {
return false return false
} }

View file

@ -1,6 +1,8 @@
package input package input
import ( import (
"fmt"
"github.com/ffuf/ffuf/pkg/ffuf" "github.com/ffuf/ffuf/pkg/ffuf"
) )
@ -8,10 +10,20 @@ type MainInputProvider struct {
Providers []ffuf.InternalInputProvider Providers []ffuf.InternalInputProvider
Config *ffuf.Config Config *ffuf.Config
position int position int
msbIterator int
} }
func NewInputProvider(conf *ffuf.Config) ffuf.InputProvider { func NewInputProvider(conf *ffuf.Config) (ffuf.InputProvider, error) {
return &MainInputProvider{Config: conf} validmode := false
for _, mode := range []string{"clusterbomb", "pitchfork"} {
if conf.InputMode == mode {
validmode = true
}
}
if !validmode {
return &MainInputProvider{}, fmt.Errorf("Input mode (-mode) %s not recognized", conf.InputMode)
}
return &MainInputProvider{Config: conf, msbIterator: 0}, nil
} }
func (i *MainInputProvider) AddProvider(provider ffuf.InputProviderConfig) error { func (i *MainInputProvider) AddProvider(provider ffuf.InputProviderConfig) error {
@ -43,8 +55,21 @@ func (i *MainInputProvider) Next() bool {
return true return true
} }
//Value returns a map of keyword:value pairs including all inputs //Value returns a map of inputs for keywords
func (i *MainInputProvider) Value() map[string][]byte { func (i *MainInputProvider) Value() map[string][]byte {
retval := make(map[string][]byte)
if i.Config.InputMode == "clusterbomb" {
retval = i.clusterbombValue()
}
if i.Config.InputMode == "pitchfork" {
retval = i.pitchforkValue()
}
return retval
}
//pitchforkValue returns a map of keyword:value pairs including all inputs.
//This mode will iterate through wordlists in lockstep.
func (i *MainInputProvider) pitchforkValue() map[string][]byte {
values := make(map[string][]byte) values := make(map[string][]byte)
for _, p := range i.Providers { for _, p := range i.Providers {
if !p.Next() { if !p.Next() {
@ -52,15 +77,70 @@ func (i *MainInputProvider) Value() map[string][]byte {
p.ResetPosition() p.ResetPosition()
} }
values[p.Keyword()] = p.Value() values[p.Keyword()] = p.Value()
p.IncrementPosition()
} }
return values return values
} }
//clusterbombValue returns map of keyword:value pairs including all inputs.
//this mode will iterate through all possible combinations.
func (i *MainInputProvider) clusterbombValue() map[string][]byte {
values := make(map[string][]byte)
// Should we signal the next InputProvider in the slice to increment
signalNext := false
first := true
for index, p := range i.Providers {
if signalNext {
p.IncrementPosition()
signalNext = false
}
if !p.Next() {
// No more inputs in this inputprovider
if index == i.msbIterator {
// Reset all previous wordlists and increment the msb counter
i.msbIterator += 1
i.clusterbombIteratorReset()
// Start again
return i.clusterbombValue()
}
p.ResetPosition()
signalNext = true
}
values[p.Keyword()] = p.Value()
if first {
p.IncrementPosition()
first = false
}
}
return values
}
func (i *MainInputProvider) clusterbombIteratorReset() {
for index, p := range i.Providers {
if index < i.msbIterator {
p.ResetPosition()
}
if index == i.msbIterator {
p.IncrementPosition()
}
}
}
//Total returns the amount of input combinations available //Total returns the amount of input combinations available
func (i *MainInputProvider) Total() int { func (i *MainInputProvider) Total() int {
count := 1 count := 0
if i.Config.InputMode == "pitchfork" {
for _, p := range i.Providers {
if p.Total() > count {
count = p.Total()
}
}
}
if i.Config.InputMode == "clusterbomb" {
count = 1
for _, p := range i.Providers { for _, p := range i.Providers {
count = count * p.Total() count = count * p.Total()
} }
}
return count return count
} }

View file

@ -19,7 +19,7 @@ func NewWordlistInput(keyword string, value string, conf *ffuf.Config) (*Wordlis
var wl WordlistInput var wl WordlistInput
wl.keyword = keyword wl.keyword = keyword
wl.config = conf wl.config = conf
wl.position = -1 wl.position = 0
var valid bool var valid bool
var err error var err error
// stdin? // stdin?
@ -56,13 +56,17 @@ func (w *WordlistInput) Keyword() string {
//Next will increment the cursor position, and return a boolean telling if there's words left in the list //Next will increment the cursor position, and return a boolean telling if there's words left in the list
func (w *WordlistInput) Next() bool { func (w *WordlistInput) Next() bool {
w.position++
if w.position >= len(w.data) { if w.position >= len(w.data) {
return false return false
} }
return true return true
} }
//IncrementPosition will increment the current position in the inputprovider data slice
func (w *WordlistInput) IncrementPosition() {
w.position += 1
}
//Value returns the value from wordlist at current cursor position //Value returns the value from wordlist at current cursor position
func (w *WordlistInput) Value() []byte { func (w *WordlistInput) Value() []byte {
return w.data[w.position] return w.data[w.position]