Add Sniper Mode (#469)

* Modify SimpleRunner to take a Request parameter, add base and copy functions for Requests

* Add Request structs to run queues

* Implemented sniper mode

* Added request and optionsparser tests for sniper mode

* Removed unneccesary print statements

* Updated readme.md and terminal output

* Enabled command inputs for sniper mode

* correctly initialize validmode in optionsparser

* Remove unnecessary print data in TestScrubTemplates

* Use InputProvider for sniper template characters

* Add a sniper-mode specific queue job execution log
This commit is contained in:
DoI 2022-03-07 03:14:45 +13:00 committed by GitHub
parent 80a4aa7783
commit 9aeae16a08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 654 additions and 52 deletions

View File

@ -218,7 +218,7 @@ INPUT OPTIONS:
-input-cmd Command producing the input. --input-num is required when using this input method. Overrides -w.
-input-num Number of inputs to test. Used in conjunction with --input-cmd. (default: 100)
-input-shell Shell to be used for running command
-mode Multi-wordlist operation mode. Available modes: clusterbomb, pitchfork (default: clusterbomb)
-mode Multi-wordlist operation mode. Available modes: clusterbomb, pitchfork, sniper (default: clusterbomb)
-request File containing the raw http request
-request-proto Protocol to use along with raw request (default: https)
-w Wordlist file path and (optional) keyword separated by colon. eg. '/path/to/wordlist:KEYWORD'

View File

@ -102,7 +102,7 @@ func ParseFlags(opts *ffuf.ConfigOptions) *ffuf.ConfigOptions {
flag.StringVar(&opts.HTTP.URL, "u", opts.HTTP.URL, "Target URL")
flag.StringVar(&opts.HTTP.SNI, "sni", opts.HTTP.SNI, "Target TLS SNI, does not support FUZZ keyword")
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")
flag.StringVar(&opts.Input.InputMode, "mode", opts.Input.InputMode, "Multi-wordlist operation mode. Available modes: clusterbomb, pitchfork, sniper")
flag.StringVar(&opts.Input.InputShell, "input-shell", opts.Input.InputShell, "Shell to be used for running command")
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")

View File

@ -58,6 +58,7 @@ type InputProviderConfig struct {
Name string `json:"name"`
Keyword string `json:"keyword"`
Value string `json:"value"`
Template string `json:"template"` // the templating string used for sniper mode (usually "§")
}
func NewConfig(ctx context.Context, cancel context.CancelFunc) Config {

View File

@ -11,7 +11,7 @@ type FilterProvider interface {
//RunnerProvider is an interface for request executors
type RunnerProvider interface {
Prepare(input map[string][]byte) (Request, error)
Prepare(input map[string][]byte, basereq *Request) (Request, error)
Execute(req *Request) (Response, error)
}

View File

@ -42,6 +42,7 @@ type Job struct {
type QueueJob struct {
Url string
depth int
req Request
}
func NewJob(conf *Config) *Job {
@ -107,10 +108,22 @@ func (j *Job) Start() {
j.startTime = time.Now()
}
basereq := BaseRequest(j.Config)
if j.Config.InputMode == "sniper" {
// process multiple payload locations and create a queue job for each location
reqs := SniperRequests(&basereq, j.Config.InputProviders[0].Template)
for _, r := range reqs {
j.queuejobs = append(j.queuejobs, QueueJob{Url: j.Config.Url, depth: 0, req: r})
}
j.Total = j.Input.Total() * len(reqs)
} else {
// Add the default job to job queue
j.queuejobs = append(j.queuejobs, QueueJob{Url: j.Config.Url, depth: 0})
rand.Seed(time.Now().UnixNano())
j.queuejobs = append(j.queuejobs, QueueJob{Url: j.Config.Url, depth: 0, req: BaseRequest(j.Config)})
j.Total = j.Input.Total()
}
rand.Seed(time.Now().UnixNano())
defer j.Stop()
j.Running = true
@ -203,10 +216,14 @@ func (j *Job) startExecution() {
wg.Add(1)
go j.runBackgroundTasks(&wg)
// Print the base URL when starting a new recursion queue job
// Print the base URL when starting a new recursion or sniper queue job
if j.queuepos > 1 {
if j.Config.InputMode == "sniper" {
j.Output.Info(fmt.Sprintf("Starting queued sniper job (%d of %d) on target: %s", j.queuepos, len(j.queuejobs), j.Config.Url))
} else {
j.Output.Info(fmt.Sprintf("Starting queued job on target: %s", j.Config.Url))
}
}
//Limiter blocks after reaching the buffer, ensuring limited concurrency
limiter := make(chan bool, j.Config.Threads)
@ -323,7 +340,8 @@ func (j *Job) isMatch(resp Response) bool {
}
func (j *Job) runTask(input map[string][]byte, position int, retried bool) {
req, err := j.Runner.Prepare(input)
basereq := j.queuejobs[j.queuepos-1].req
req, err := j.Runner.Prepare(input, &basereq)
req.Position = position
if err != nil {
j.Output.Error(fmt.Sprintf("Encountered an error while preparing request: %s\n", err))
@ -360,7 +378,7 @@ 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, err := j.ReplayRunner.Prepare(input, &basereq)
replayreq.Position = position
if err != nil {
j.Output.Error(fmt.Sprintf("Encountered an error while preparing replayproxy request: %s\n", err))
@ -407,7 +425,7 @@ func (j *Job) handleDefaultRecursionJob(resp Response) {
}
if j.Config.RecursionDepth == 0 || j.currentDepth < j.Config.RecursionDepth {
// We have yet to reach the maximum recursion depth
newJob := QueueJob{Url: recUrl, depth: j.currentDepth + 1}
newJob := QueueJob{Url: recUrl, depth: j.currentDepth + 1, req: BaseRequest(j.Config)}
j.queuejobs = append(j.queuejobs, newJob)
j.Output.Info(fmt.Sprintf("Adding a new job to the queue: %s", recUrl))
} else {
@ -417,6 +435,7 @@ func (j *Job) handleDefaultRecursionJob(resp Response) {
//CalibrateResponses returns slice of Responses for randomly generated filter autocalibration requests
func (j *Job) CalibrateResponses() ([]Response, error) {
basereq := BaseRequest(j.Config)
cInputs := make([]string, 0)
rand.Seed(time.Now().UnixNano())
if len(j.Config.AutoCalibrationStrings) < 1 {
@ -435,7 +454,7 @@ func (j *Job) CalibrateResponses() ([]Response, error) {
inputs[v.Keyword] = []byte(input)
}
req, err := j.Runner.Prepare(inputs)
req, err := j.Runner.Prepare(inputs, &basereq)
if err != nil {
j.Output.Error(fmt.Sprintf("Encountered an error while preparing request: %s\n", err))
j.incError()

View File

@ -183,6 +183,32 @@ func ConfigFromOptions(parseOpts *ConfigOptions, ctx context.Context, cancel con
}
//Prepare inputproviders
conf.InputMode = parseOpts.Input.InputMode
validmode := false
for _, mode := range []string{"clusterbomb", "pitchfork", "sniper"} {
if conf.InputMode == mode {
validmode = true
}
}
if !validmode {
errs.Add(fmt.Errorf("Input mode (-mode) %s not recognized", conf.InputMode))
}
template := ""
// sniper mode needs some additional checking
if conf.InputMode == "sniper" {
template = "§"
if len(parseOpts.Input.Wordlists) > 1 {
errs.Add(fmt.Errorf("sniper mode only supports one wordlist"))
}
if len(parseOpts.Input.Inputcommands) > 1 {
errs.Add(fmt.Errorf("sniper mode only supports one input command"))
}
}
for _, v := range parseOpts.Input.Wordlists {
var wl []string
if runtime.GOOS == "windows" {
@ -207,33 +233,44 @@ func ConfigFromOptions(parseOpts *ConfigOptions, ctx context.Context, cancel con
wl = strings.SplitN(v, ":", 2)
}
if len(wl) == 2 {
if conf.InputMode == "sniper" {
errs.Add(fmt.Errorf("sniper mode does not support wordlist keywords"))
} else {
conf.InputProviders = append(conf.InputProviders, InputProviderConfig{
Name: "wordlist",
Value: wl[0],
Keyword: wl[1],
})
}
} else {
conf.InputProviders = append(conf.InputProviders, InputProviderConfig{
Name: "wordlist",
Value: wl[0],
Keyword: "FUZZ",
Template: template,
})
}
}
for _, v := range parseOpts.Input.Inputcommands {
ic := strings.SplitN(v, ":", 2)
if len(ic) == 2 {
if conf.InputMode == "sniper" {
errs.Add(fmt.Errorf("sniper mode does not support command keywords"))
} else {
conf.InputProviders = append(conf.InputProviders, InputProviderConfig{
Name: "command",
Value: ic[0],
Keyword: ic[1],
})
conf.CommandKeywords = append(conf.CommandKeywords, ic[0])
}
} else {
conf.InputProviders = append(conf.InputProviders, InputProviderConfig{
Name: "command",
Value: ic[0],
Keyword: "FUZZ",
Template: template,
})
conf.CommandKeywords = append(conf.CommandKeywords, "FUZZ")
}
@ -391,7 +428,7 @@ func ConfigFromOptions(parseOpts *ConfigOptions, ctx context.Context, cancel con
conf.DirSearchCompat = parseOpts.Input.DirSearchCompat
conf.Colors = parseOpts.General.Colors
conf.InputNum = parseOpts.Input.InputNum
conf.InputMode = parseOpts.Input.InputMode
conf.InputShell = parseOpts.Input.InputShell
conf.OutputFile = parseOpts.Output.OutputFile
conf.OutputDirectory = parseOpts.Output.OutputDirectory
@ -426,11 +463,18 @@ func ConfigFromOptions(parseOpts *ConfigOptions, ctx context.Context, cancel con
conf.CommandLine = strings.Join(os.Args, " ")
for _, provider := range conf.InputProviders {
if provider.Template != "" {
if !templatePresent(provider.Template, &conf) {
errmsg := fmt.Sprintf("Template %s defined, but not found in pairs in headers, method, URL or POST data.", provider.Template)
errs.Add(fmt.Errorf(errmsg))
}
} else {
if !keywordPresent(provider.Keyword, &conf) {
errmsg := fmt.Sprintf("Keyword %s defined, but not found in headers, method, URL or POST data.", provider.Keyword)
errs.Add(fmt.Errorf(errmsg))
}
}
}
// Do checks for recursion mode
if parseOpts.HTTP.Recursion {
@ -534,6 +578,46 @@ func keywordPresent(keyword string, conf *Config) bool {
return false
}
func templatePresent(template string, conf *Config) bool {
// Search for input location identifiers, these must exist in pairs
sane := false
if c := strings.Count(conf.Method, template); c > 0 {
if c%2 != 0 {
return false
}
sane = true
}
if c := strings.Count(conf.Url, template); c > 0 {
if c%2 != 0 {
return false
}
sane = true
}
if c := strings.Count(conf.Data, template); c > 0 {
if c%2 != 0 {
return false
}
sane = true
}
for k, v := range conf.Headers {
if c := strings.Count(k, template); c > 0 {
if c%2 != 0 {
return false
}
sane = true
}
if c := strings.Count(v, template); c > 0 {
if c%2 != 0 {
return false
}
sane = true
}
}
return sane
}
func ReadConfig(configFile string) (*ConfigOptions, error) {
conf := NewConfigOptions()
configData, err := ioutil.ReadFile(configFile)

View File

@ -0,0 +1,85 @@
package ffuf
import (
"testing"
)
func TestTemplatePresent(t *testing.T) {
template := "§"
headers := make(map[string]string)
headers["foo"] = "§bar§"
headers["omg"] = "bbq"
headers["§world§"] = "Ooo"
goodConf := Config{
Url: "https://example.com/fooo/bar?test=§value§&order[§0§]=§foo§",
Method: "PO§ST§",
Headers: headers,
Data: "line=Can we pull back the §veil§ of §static§ and reach in to the source of §all§ being?&commit=true",
}
if !templatePresent(template, &goodConf) {
t.Errorf("Expected-good config failed validation")
}
badConfMethod := Config{
Url: "https://example.com/fooo/bar?test=§value§&order[§0§]=§foo§",
Method: "POST§",
Headers: headers,
Data: "line=Can we pull back the §veil§ of §static§ and reach in to the source of §all§ being?&commit=§true§",
}
if templatePresent(template, &badConfMethod) {
t.Errorf("Expected-bad config (Method) failed validation")
}
badConfURL := Config{
Url: "https://example.com/fooo/bar?test=§value§&order[0§]=§foo§",
Method: "§POST§",
Headers: headers,
Data: "line=Can we pull back the §veil§ of §static§ and reach in to the source of §all§ being?&commit=§true§",
}
if templatePresent(template, &badConfURL) {
t.Errorf("Expected-bad config (URL) failed validation")
}
badConfData := Config{
Url: "https://example.com/fooo/bar?test=§value§&order[§0§]=§foo§",
Method: "§POST§",
Headers: headers,
Data: "line=Can we pull back the §veil of §static§ and reach in to the source of §all§ being?&commit=§true§",
}
if templatePresent(template, &badConfData) {
t.Errorf("Expected-bad config (Data) failed validation")
}
headers["kingdom"] = "§candy"
badConfHeaderValue := Config{
Url: "https://example.com/fooo/bar?test=§value§&order[§0§]=§foo§",
Method: "PO§ST§",
Headers: headers,
Data: "line=Can we pull back the §veil§ of §static§ and reach in to the source of §all§ being?&commit=true",
}
if templatePresent(template, &badConfHeaderValue) {
t.Errorf("Expected-bad config (Header value) failed validation")
}
headers["kingdom"] = "candy"
headers["§kingdom"] = "candy"
badConfHeaderKey := Config{
Url: "https://example.com/fooo/bar?test=§value§&order[§0§]=§foo§",
Method: "PO§ST§",
Headers: headers,
Data: "line=Can we pull back the §veil§ of §static§ and reach in to the source of §all§ being?&commit=true",
}
if templatePresent(template, &badConfHeaderKey) {
t.Errorf("Expected-bad config (Header key) failed validation")
}
}

View File

@ -1,5 +1,9 @@
package ffuf
import (
"strings"
)
// Request holds the meaningful data that is passed for runner for making the query
type Request struct {
Method string
@ -19,3 +23,171 @@ func NewRequest(conf *Config) Request {
req.Headers = make(map[string]string)
return req
}
// BaseRequest returns a base request struct populated from the main config
func BaseRequest(conf *Config) Request {
req := NewRequest(conf)
req.Headers = conf.Headers
req.Data = []byte(conf.Data)
return req
}
// CopyRequest performs a deep copy of a request and returns a new struct
func CopyRequest(basereq *Request) Request {
var req Request
req.Method = basereq.Method
req.Host = basereq.Host
req.Url = basereq.Url
req.Headers = make(map[string]string, len(basereq.Headers))
for h, v := range basereq.Headers {
req.Headers[h] = v
}
req.Data = make([]byte, len(basereq.Data))
copy(req.Data, basereq.Data)
if len(basereq.Input) > 0 {
req.Input = make(map[string][]byte, len(basereq.Input))
for k, v := range basereq.Input {
req.Input[k] = v
}
}
req.Position = basereq.Position
req.Raw = basereq.Raw
return req
}
// SniperRequests returns an array of requests, each with one of the templated locations replaced by a keyword
func SniperRequests(basereq *Request, template string) []Request {
var reqs []Request
keyword := "FUZZ"
// Search for input location identifiers, these must exist in pairs
if c := strings.Count(basereq.Method, template); c > 0 {
if c%2 == 0 {
tokens := templateLocations(template, basereq.Method)
for i := 0; i < len(tokens); i = i + 2 {
newreq := CopyRequest(basereq)
newreq.Method = injectKeyword(basereq.Method, keyword, tokens[i], tokens[i+1])
scrubTemplates(&newreq, template)
reqs = append(reqs, newreq)
}
}
}
if c := strings.Count(basereq.Url, template); c > 0 {
if c%2 == 0 {
tokens := templateLocations(template, basereq.Url)
for i := 0; i < len(tokens); i = i + 2 {
newreq := CopyRequest(basereq)
newreq.Url = injectKeyword(basereq.Url, keyword, tokens[i], tokens[i+1])
scrubTemplates(&newreq, template)
reqs = append(reqs, newreq)
}
}
}
data := string(basereq.Data)
if c := strings.Count(data, template); c > 0 {
if c%2 == 0 {
tokens := templateLocations(template, data)
for i := 0; i < len(tokens); i = i + 2 {
newreq := CopyRequest(basereq)
newreq.Data = []byte(injectKeyword(data, keyword, tokens[i], tokens[i+1]))
scrubTemplates(&newreq, template)
reqs = append(reqs, newreq)
}
}
}
for k, v := range basereq.Headers {
if c := strings.Count(k, template); c > 0 {
if c%2 == 0 {
tokens := templateLocations(template, k)
for i := 0; i < len(tokens); i = i + 2 {
newreq := CopyRequest(basereq)
newreq.Headers[injectKeyword(k, keyword, tokens[i], tokens[i+1])] = v
delete(newreq.Headers, k)
scrubTemplates(&newreq, template)
reqs = append(reqs, newreq)
}
}
}
if c := strings.Count(v, template); c > 0 {
if c%2 == 0 {
tokens := templateLocations(template, v)
for i := 0; i < len(tokens); i = i + 2 {
newreq := CopyRequest(basereq)
newreq.Headers[k] = injectKeyword(v, keyword, tokens[i], tokens[i+1])
scrubTemplates(&newreq, template)
reqs = append(reqs, newreq)
}
}
}
}
return reqs
}
// templateLocations returns an array of template character locations in input
func templateLocations(template string, input string) []int {
var tokens []int
for k, i := range []rune(input) {
if i == []rune(template)[0] {
tokens = append(tokens, k)
}
}
return tokens
}
// injectKeyword takes a string, a keyword, and a start/end offset. The data between
// the start/end offset in string is removed, and replaced by keyword
func injectKeyword(input string, keyword string, startOffset int, endOffset int) string {
// some basic sanity checking, return the original string unchanged if offsets didnt make sense
if startOffset > len(input) || endOffset > len(input) || startOffset > endOffset {
return input
}
inputslice := []rune(input)
keywordslice := []rune(keyword)
prefix := inputslice[:startOffset]
suffix := inputslice[endOffset+1:]
inputslice = append(prefix, keywordslice...)
inputslice = append(inputslice, suffix...)
return string(inputslice)
}
// scrubTemplates removes all template (§) strings from the request struct
func scrubTemplates(req *Request, template string) {
req.Method = strings.Join(strings.Split(req.Method, template), "")
req.Url = strings.Join(strings.Split(req.Url, template), "")
req.Data = []byte(strings.Join(strings.Split(string(req.Data), template), ""))
for k, v := range req.Headers {
if c := strings.Count(k, template); c > 0 {
if c%2 == 0 {
delete(req.Headers, k)
req.Headers[strings.Join(strings.Split(k, template), "")] = v
}
}
if c := strings.Count(v, template); c > 0 {
if c%2 == 0 {
req.Headers[k] = strings.Join(strings.Split(v, template), "")
}
}
}
}

246
pkg/ffuf/request_test.go Normal file
View File

@ -0,0 +1,246 @@
package ffuf
import (
"reflect"
"testing"
)
func TestBaseRequest(t *testing.T) {
headers := make(map[string]string)
headers["foo"] = "bar"
headers["baz"] = "wibble"
headers["Content-Type"] = "application/json"
data := "{\"quote\":\"I'll still be here tomorrow to high five you yesterday, my friend. Peace.\"}"
expectedreq := Request{Method: "POST", Url: "http://example.com/aaaa", Headers: headers, Data: []byte(data)}
config := Config{Method: "POST", Url: "http://example.com/aaaa", Headers: headers, Data: data}
basereq := BaseRequest(&config)
if !reflect.DeepEqual(basereq, expectedreq) {
t.Errorf("BaseRequest does not return a struct with expected values")
}
}
func TestCopyRequest(t *testing.T) {
headers := make(map[string]string)
headers["foo"] = "bar"
headers["omg"] = "bbq"
data := "line=Is+that+where+creativity+comes+from?+From+sad+biz?"
input := make(map[string][]byte)
input["matthew"] = []byte("If you are the head that floats atop the §ziggurat§, then the stairs that lead to you must be infinite.")
basereq := Request{Method: "POST",
Host: "testhost.local",
Url: "http://example.com/aaaa",
Headers: headers,
Data: []byte(data),
Input: input,
Position: 2,
Raw: "We're not oil and water, we're oil and vinegar! It's good. It's yummy.",
}
copiedreq := CopyRequest(&basereq)
if !reflect.DeepEqual(basereq, copiedreq) {
t.Errorf("CopyRequest does not return an equal struct")
}
}
func TestSniperRequests(t *testing.T) {
headers := make(map[string]string)
headers["foo"] = "§bar§"
headers["§omg§"] = "bbq"
testreq := Request{
Method: "§POST§",
Url: "http://example.com/aaaa?param=§lemony§",
Headers: headers,
Data: []byte("line=§yo yo, it's grease§"),
}
requests := SniperRequests(&testreq, "§")
if len(requests) != 5 {
t.Errorf("SniperRequests returned an incorrect number of requests")
}
headers = make(map[string]string)
headers["foo"] = "bar"
headers["omg"] = "bbq"
var expected Request
expected = Request{ // Method
Method: "FUZZ",
Url: "http://example.com/aaaa?param=lemony",
Headers: headers,
Data: []byte("line=yo yo, it's grease"),
}
pass := false
for _, req := range requests {
if reflect.DeepEqual(req, expected) {
pass = true
}
}
if !pass {
t.Errorf("SniperRequests does not return expected values (Method)")
}
expected = Request{ // URL
Method: "POST",
Url: "http://example.com/aaaa?param=FUZZ",
Headers: headers,
Data: []byte("line=yo yo, it's grease"),
}
pass = false
for _, req := range requests {
if reflect.DeepEqual(req, expected) {
pass = true
}
}
if !pass {
t.Errorf("SniperRequests does not return expected values (Url)")
}
expected = Request{ // Data
Method: "POST",
Url: "http://example.com/aaaa?param=lemony",
Headers: headers,
Data: []byte("line=FUZZ"),
}
pass = false
for _, req := range requests {
if reflect.DeepEqual(req, expected) {
pass = true
}
}
if !pass {
t.Errorf("SniperRequests does not return expected values (Data)")
}
headers = make(map[string]string)
headers["foo"] = "FUZZ"
headers["omg"] = "bbq"
expected = Request{ // Header value
Method: "POST",
Url: "http://example.com/aaaa?param=lemony",
Headers: headers,
Data: []byte("line=yo yo, it's grease"),
}
pass = false
for _, req := range requests {
if reflect.DeepEqual(req, expected) {
pass = true
}
}
if !pass {
t.Errorf("SniperRequests does not return expected values (Header value)")
}
headers = make(map[string]string)
headers["foo"] = "bar"
headers["FUZZ"] = "bbq"
expected = Request{ // Header key
Method: "POST",
Url: "http://example.com/aaaa?param=lemony",
Headers: headers,
Data: []byte("line=yo yo, it's grease"),
}
pass = false
for _, req := range requests {
if reflect.DeepEqual(req, expected) {
pass = true
}
}
if !pass {
t.Errorf("SniperRequests does not return expected values (Header key)")
}
}
func TestTemplateLocations(t *testing.T) {
test := "this is my 1§template locator§ test"
arr := templateLocations("§", test)
expected := []int{12, 29}
if !reflect.DeepEqual(arr, expected) {
t.Errorf("templateLocations does not return expected values")
}
test2 := "§template locator§"
arr = templateLocations("§", test2)
expected = []int{0, 17}
if !reflect.DeepEqual(arr, expected) {
t.Errorf("templateLocations does not return expected values")
}
if len(templateLocations("§", "te§st2")) != 1 {
t.Errorf("templateLocations does not return expected values")
}
}
func TestInjectKeyword(t *testing.T) {
input := "§Greetings, creator§"
offsetTuple := templateLocations("§", input)
expected := "FUZZ"
result := injectKeyword(input, "FUZZ", offsetTuple[0], offsetTuple[1])
if result != expected {
t.Errorf("injectKeyword returned unexpected result: " + result)
}
if injectKeyword(input, "FUZZ", -32, 44) != input {
t.Errorf("injectKeyword offset validation failed")
}
if injectKeyword(input, "FUZZ", 12, 2) != input {
t.Errorf("injectKeyword offset validation failed")
}
if injectKeyword(input, "FUZZ", 0, 25) != input {
t.Errorf("injectKeyword offset validation failed")
}
}
func TestScrubTemplates(t *testing.T) {
headers := make(map[string]string)
headers["foo"] = "§bar§"
headers["§omg§"] = "bbq"
testreq := Request{Method: "§POST§",
Url: "http://example.com/aaaa?param=§lemony§",
Headers: headers,
Data: []byte("line=§yo yo, it's grease§"),
}
headers = make(map[string]string)
headers["foo"] = "bar"
headers["omg"] = "bbq"
expectedreq := Request{Method: "POST",
Url: "http://example.com/aaaa?param=lemony",
Headers: headers,
Data: []byte("line=yo yo, it's grease"),
}
scrubTemplates(&testreq, "§")
if !reflect.DeepEqual(testreq, expectedreq) {
t.Errorf("scrubTemplates does not return expected values")
}
}

View File

@ -16,7 +16,7 @@ type MainInputProvider struct {
func NewInputProvider(conf *ffuf.Config) (ffuf.InputProvider, ffuf.Multierror) {
validmode := false
errs := ffuf.NewMultierror()
for _, mode := range []string{"clusterbomb", "pitchfork"} {
for _, mode := range []string{"clusterbomb", "pitchfork", "sniper"} {
if conf.InputMode == mode {
validmode = true
}
@ -68,7 +68,7 @@ func (i *MainInputProvider) Next() bool {
//Value returns a map of inputs for keywords
func (i *MainInputProvider) Value() map[string][]byte {
retval := make(map[string][]byte)
if i.Config.InputMode == "clusterbomb" {
if i.Config.InputMode == "clusterbomb" || i.Config.InputMode == "sniper" {
retval = i.clusterbombValue()
}
if i.Config.InputMode == "pitchfork" {
@ -155,7 +155,7 @@ func (i *MainInputProvider) Total() int {
}
}
}
if i.Config.InputMode == "clusterbomb" {
if i.Config.InputMode == "clusterbomb" || i.Config.InputMode == "sniper" {
count = 1
for _, p := range i.Providers {
count = count * p.Total()

View File

@ -232,7 +232,7 @@ func (i *interactive) appendFilter(name, value string) {
func (i *interactive) printQueue() {
if len(i.Job.QueuedJobs()) > 0 {
i.Job.Output.Raw("Queued recursion jobs:\n")
i.Job.Output.Raw("Queued jobs:\n")
for index, job := range i.Job.QueuedJobs() {
postfix := ""
if index == 0 {
@ -241,7 +241,7 @@ func (i *interactive) printQueue() {
i.Job.Output.Raw(fmt.Sprintf(" [%d] : %s%s\n", index, job.Url, postfix))
}
} else {
i.Job.Output.Info("Recursion job queue is empty")
i.Job.Output.Info("Job queue is empty")
}
}
@ -256,7 +256,7 @@ func (i *interactive) deleteQueue(in string) {
i.Job.Output.Warning("Cannot delete the currently running job. Use \"queueskip\" to advance to the next one")
} else {
i.Job.DeleteQueueItem(index)
i.Job.Output.Info("Recursion job successfully deleted!")
i.Job.Output.Info("Job successfully deleted!")
}
}
}
@ -296,9 +296,9 @@ available commands:
fs [value] - (re)configure size filter %s
aft [value] - append to time filter %s
ft [value] - (re)configure time filter %s
queueshow - show recursive job queue
queuedel [number] - delete a recursion job in the queue
queueskip - advance to the next queued recursion job
queueshow - show job queue
queuedel [number] - delete a job in the queue
queueskip - advance to the next queued job
restart - restart and resume the current ffuf job
resume - resume current ffuf job (or: ENTER)
show - show results for the current job

View File

@ -69,13 +69,8 @@ func NewSimpleRunner(conf *ffuf.Config, replay bool) ffuf.RunnerProvider {
return &simplerunner
}
func (r *SimpleRunner) Prepare(input map[string][]byte) (ffuf.Request, error) {
req := ffuf.NewRequest(r.config)
req.Headers = r.config.Headers
req.Url = r.config.Url
req.Method = r.config.Method
req.Data = []byte(r.config.Data)
func (r *SimpleRunner) Prepare(input map[string][]byte, basereq *ffuf.Request) (ffuf.Request, error) {
req := ffuf.CopyRequest(basereq)
for keyword, inputitem := range input {
req.Method = strings.ReplaceAll(req.Method, keyword, string(inputitem))