Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 2987 fuzz options #3355

Merged
merged 5 commits into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ INTERACTSH:
-interactions-cooldown-period int extra time for interaction polling before exiting (default 5)
-ni, -no-interactsh disable interactsh server for OAST testing, exclude OAST based templates

FUZZING:
-ft, -fuzzing-type string overrides fuzzing type set in template (replace, prefix, postfix, infix)
-fm, -fuzzing-mode string overrides fuzzing mode set in template (multiple, single)

UNCOVER:
-uc, -uncover enable uncover engine
-uq, -uncover-query string[] uncover search query
Expand Down
27 changes: 27 additions & 0 deletions integration_tests/fuzz/fuzz-mode.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
id: fuzz-query

info:
name: Basic Fuzz URL Query
author: pdteam
severity: info

requests:
- method: GET
path:
- "{{BaseURL}}"
fuzzing:
- part: query
type: postfix
mode: multiple
keys: ["id","name"]
fuzz: ["fuzz-word"]
matchers-condition: and
matchers:
- type: word
part: body
words:
- "fuzz-word"
- type: word
part: header
words:
- "text/html"
27 changes: 27 additions & 0 deletions integration_tests/fuzz/fuzz-type.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
id: fuzz-type

info:
name: Basic Fuzz URL Query
author: pdteam
severity: info

requests:
- method: GET
path:
- "{{BaseURL}}"
fuzzing:
- part: query
type: postfix
mode: single
keys: ["id"]
fuzz: ["fuzz-word"]
matchers-condition: and
matchers:
- type: word
part: body
words:
- "fuzz-word"
- type: word
part: header
words:
- "text/html"
128 changes: 128 additions & 0 deletions v2/cmd/integration-test/fuzz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package main

import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"

"github.com/julienschmidt/httprouter"
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
)

var fuzzingTestCases = map[string]testutils.TestCase{
"fuzz/fuzz-mode.yaml": &fuzzModeOverride{},
"fuzz/fuzz-type.yaml": &fuzzTypeOverride{},
"fuzz/fuzz-query.yaml": &httpFuzzQuery{},
}

type httpFuzzQuery struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpFuzzQuery) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.Header().Set("Content-Type", "text/html")
value := r.URL.Query().Get("id")
fmt.Fprintf(w, "This is test matcher text: %v", value)

Check warning

Code scanning / CodeQL

Reflected cross-site scripting

Cross-site scripting vulnerability due to [user-provided value](1).
})
ts := httptest.NewTLSServer(router)
defer ts.Close()

results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/?id=example", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}

type fuzzModeOverride struct{}

// Execute executes a test case and returns an error if occurred
func (h *fuzzModeOverride) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.Header().Set("Content-Type", "text/html")
value := r.URL.Query().Get("id")
fmt.Fprintf(w, "This is test matcher text: %v", value)

Check warning

Code scanning / CodeQL

Reflected cross-site scripting

Cross-site scripting vulnerability due to [user-provided value](1).
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/?id=example&name=nuclei", false, "-fuzzing-mode", "single", "-json")
if err != nil {
return err
}
if err = expectResultsCount(results, 1); err != nil {
return err
}
var event output.ResultEvent
err = json.Unmarshal([]byte(results[0]), &event)
if err != nil {
return fmt.Errorf("could not unmarshal event: %s", err)
}

// Check whether the matched value url query params are correct
// default fuzzing mode is multiple in template, so all query params should be fuzzed
// but using -fm flag we are overriding fuzzing mode to single,
// so only one query param should be fuzzed, and the other should be the same

//parse url to get query params
matchedURL, err := url.Parse(event.Matched)
if err != nil {
return err
}
values, err := url.ParseQuery(matchedURL.RawQuery)
if err != nil {
return err
}
if values.Get("name") != "nuclei" {
return fmt.Errorf("expected fuzzing should not override the name nuclei got %s", values.Get("name"))
}
return nil
}

type fuzzTypeOverride struct{}

// Execute executes a test case and returns an error if occurred
func (h *fuzzTypeOverride) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.Header().Set("Content-Type", "text/html")
value := r.URL.Query().Get("id")
fmt.Fprintf(w, "This is test matcher text: %v", value)

Check warning

Code scanning / CodeQL

Reflected cross-site scripting

Cross-site scripting vulnerability due to [user-provided value](1).
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"?id=example", false, "-fuzzing-type", "replace", "-json")
if err != nil {
return err
}
if err = expectResultsCount(results, 1); err != nil {
return err
}
var event output.ResultEvent
err = json.Unmarshal([]byte(results[0]), &event)
if err != nil {
return fmt.Errorf("could not unmarshal event: %s", err)
}

// check whether the matched url query params are fuzzed
// default fuzzing type in template is postfix but we are overriding it to replace
// so the matched url query param should be replaced with fuzz-word

//parse url to get query params
matchedURL, err := url.Parse(event.Matched)
if err != nil {
return err
}
values, err := url.ParseQuery(matchedURL.RawQuery)
if err != nil {
return err
}
if values.Get("id") != "fuzz-word" {
return fmt.Errorf("expected id to be fuzz-word, got %s", values.Get("id"))
}
return nil
}
21 changes: 0 additions & 21 deletions v2/cmd/integration-test/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ var httpTestcases = map[string]testutils.TestCase{
"http/get-host-redirects.yaml": &httpGetHostRedirects{},
"http/disable-redirects.yaml": &httpDisableRedirects{},
"http/get.yaml": &httpGet{},
"http/fuzz-query.yaml": &httpFuzzQuery{},
"http/post-body.yaml": &httpPostBody{},
"http/post-json-body.yaml": &httpPostJSONBody{},
"http/post-multipart-body.yaml": &httpPostMultipartBody{},
Expand Down Expand Up @@ -1128,26 +1127,6 @@ func (h *annotationTimeout) Execute(filePath string) error {
return expectResultsCount(results, 1)
}

type httpFuzzQuery struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpFuzzQuery) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.Header().Set("Content-Type", "text/html")
value := r.URL.Query().Get("id")
fmt.Fprintf(w, "This is test matcher text: %v", value)
})
ts := httptest.NewTLSServer(router)
defer ts.Close()

results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/?id=example", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}

type customAttackType struct{}

// Execute executes a test case and returns an error if occurred
Expand Down
1 change: 1 addition & 0 deletions v2/cmd/integration-test/integration-test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
"file": fileTestcases,
"offlineHttp": offlineHttpTestcases,
"customConfigDir": customConfigDirTestCases,
"fuzzing": fuzzingTestCases,
}

// For debug purposes
Expand Down
5 changes: 5 additions & 0 deletions v2/cmd/nuclei/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.BoolVarP(&options.NoInteractsh, "no-interactsh", "ni", false, "disable interactsh server for OAST testing, exclude OAST based templates"),
)

flagSet.CreateGroup("fuzzing", "Fuzzing",
flagSet.StringVarP(&options.FuzzingType, "fuzzing-type", "ft", "", "overrides fuzzing type set in template (replace, prefix, postfix, infix)"),
flagSet.StringVarP(&options.FuzzingMode, "fuzzing-mode", "fm", "", "overrides fuzzing mode set in template (multiple, single)"),
)

flagSet.CreateGroup("uncover", "Uncover",
flagSet.BoolVarP(&options.Uncover, "uncover", "uc", false, "enable uncover engine"),
flagSet.StringSliceVarP(&options.UncoverQuery, "uncover-query", "uq", nil, "uncover search query", goflags.FileStringSliceOptions),
Expand Down
5 changes: 3 additions & 2 deletions v2/pkg/protocols/http/fuzz/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,17 @@ func (rule *Rule) Compile(generator *generators.PayloadGenerator, options *proto
}
if rule.Part != "" {
if valueType, ok := stringToPartType[rule.Part]; !ok {
return errors.Errorf("invalid part value specified: %s", rule.Mode)
return errors.Errorf("invalid part value specified: %s", rule.Part)
} else {
rule.partType = valueType
}
} else {
rule.partType = queryPartType
}

if rule.Type != "" {
if valueType, ok := stringToRuleType[rule.Type]; !ok {
return errors.Errorf("invalid type value specified: %s", rule.Mode)
return errors.Errorf("invalid type value specified: %s", rule.Type)
} else {
rule.ruleType = valueType
}
Expand Down
6 changes: 6 additions & 0 deletions v2/pkg/protocols/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,12 @@ func (request *Request) Compile(options *protocols.ExecuterOptions) error {
return errors.New("cannot use unsafe with http fuzzing templates")
}
for _, rule := range request.Fuzzing {
if fuzzingMode := options.Options.FuzzingMode; fuzzingMode != "" {
rule.Mode = fuzzingMode
}
if fuzzingType := options.Options.FuzzingType; fuzzingType != "" {
rule.Type = fuzzingType
}
if err := rule.Compile(request.generator, request.options); err != nil {
return errors.Wrap(err, "could not compile fuzzing rule")
}
Expand Down
4 changes: 4 additions & 0 deletions v2/pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,10 @@ type Options struct {
AwsRegion string
// Scan Strategy (auto,hosts-spray,templates-spray)
ScanStrategy string
// Fuzzing Type overrides template level fuzzing-type configuration
FuzzingType string
// Fuzzing Mode overrides template level fuzzing-mode configuration
FuzzingMode string
}

// ShouldLoadResume resume file
Expand Down