mirror of
https://github.com/ffuf/ffuf
synced 2024-11-10 06:04:17 +00:00
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:
parent
73922822f9
commit
ac141e5e34
7 changed files with 114 additions and 15 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
8
main.go
8
main.go
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
Loading…
Reference in a new issue