Issue 3339 headless fuzz (#3790)

* Basic headless fuzzing

* Remove debug statements

* Add integration tests

* Update template

* Fix recognize payload value in matcher

* Update tempalte

* use req.SetURL()

---------

Co-authored-by: Tarun Koyalwar <tarun@projectdiscovery.io>
dev
Shubham Rasal 2023-06-09 05:50:44 +05:30 committed by GitHub
parent b4e4715d36
commit a34b94e62f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 119 additions and 10 deletions

View File

@ -0,0 +1,31 @@
id: headless-query-fuzzing
info:
name: Example Query Fuzzing
author: pdteam
severity: info
headless:
- steps:
- action: navigate
args:
url: "{{BaseURL}}"
- action: waitload
payloads:
redirect:
- "blog.com"
- "portal.com"
fuzzing:
- part: query
mode: single
type: replace
fuzz:
- "https://{{redirect}}"
matchers:
- type: word
part: body
words:
- "{{redirect}}"

View File

@ -13,9 +13,10 @@ import (
) )
var fuzzingTestCases = map[string]testutils.TestCase{ var fuzzingTestCases = map[string]testutils.TestCase{
"fuzz/fuzz-mode.yaml": &fuzzModeOverride{}, "fuzz/fuzz-mode.yaml": &fuzzModeOverride{},
"fuzz/fuzz-type.yaml": &fuzzTypeOverride{}, "fuzz/fuzz-type.yaml": &fuzzTypeOverride{},
"fuzz/fuzz-query.yaml": &httpFuzzQuery{}, "fuzz/fuzz-query.yaml": &httpFuzzQuery{},
"fuzz/fuzz-headless.yaml": &HeadlessFuzzingQuery{},
} }
type httpFuzzQuery struct{} type httpFuzzQuery struct{}
@ -126,3 +127,23 @@ func (h *fuzzTypeOverride) Execute(filePath string) error {
} }
return nil return nil
} }
// HeadlessFuzzingQuery tests fuzzing is working not in headless mode
type HeadlessFuzzingQuery struct{}
// Execute executes a test case and returns an error if occurred
func (h *HeadlessFuzzingQuery) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
resp := fmt.Sprintf("<html><body>%s</body></html>", r.URL.Query().Get("url"))
fmt.Fprint(w, resp)
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"?url=https://scanme.sh", debug, "-headless")
if err != nil {
return err
}
return expectResultsCount(got, 2)
}

View File

@ -73,10 +73,7 @@ func (rule *Rule) buildQueryInput(input *ExecuteRuleInput, parsed *urlutil.URL,
req.Header.Set("User-Agent", uarand.GetRandom()) req.Header.Set("User-Agent", uarand.GetRandom())
} else { } else {
req = input.BaseRequest.Clone(context.TODO()) req = input.BaseRequest.Clone(context.TODO())
//TODO: abstract below 3 lines with `req.UpdateURL(xx *urlutil.URL)` req.SetURL(parsed)
req.URL = parsed
req.Request.URL = parsed.URL
req.Update()
} }
request := GeneratedRequest{ request := GeneratedRequest{
Request: req, Request: req,

View File

@ -7,6 +7,7 @@ import (
useragent "github.com/projectdiscovery/nuclei/v2/pkg/model/types/userAgent" useragent "github.com/projectdiscovery/nuclei/v2/pkg/model/types/userAgent"
"github.com/projectdiscovery/nuclei/v2/pkg/operators" "github.com/projectdiscovery/nuclei/v2/pkg/operators"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols" "github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/fuzz"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/headless/engine" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/headless/engine"
fileutil "github.com/projectdiscovery/utils/file" fileutil "github.com/projectdiscovery/utils/file"
@ -54,6 +55,9 @@ type Request struct {
// cache any variables that may be needed for operation. // cache any variables that may be needed for operation.
options *protocols.ExecutorOptions options *protocols.ExecutorOptions
generator *generators.PayloadGenerator generator *generators.PayloadGenerator
// Fuzzing describes schema to fuzz headless requests
Fuzzing []*fuzz.Rule `yaml:"fuzzing,omitempty" json:"fuzzing,omitempty" jsonschema:"title=fuzzin rules for http fuzzing,description=Fuzzing describes rule schema to fuzz headless requests"`
} }
// RequestPartDefinitions contains a mapping of request part definitions and their // RequestPartDefinitions contains a mapping of request part definitions and their
@ -129,6 +133,21 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
request.CompiledOperators = compiled request.CompiledOperators = compiled
} }
request.options = options request.options = options
if len(request.Fuzzing) > 0 {
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")
}
}
}
return nil return nil
} }

View File

@ -1,6 +1,7 @@
package headless package headless
import ( import (
"io"
"net/url" "net/url"
"strings" "strings"
"time" "time"
@ -12,6 +13,7 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/output" "github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols" "github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/fuzz"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter"
@ -19,6 +21,7 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump"
protocolutils "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" protocolutils "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils"
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
urlutil "github.com/projectdiscovery/utils/url"
) )
var _ protocols.Request = &Request{} var _ protocols.Request = &Request{}
@ -51,7 +54,10 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
gotmatches = results.OperatorsResult.Matched gotmatches = results.OperatorsResult.Matched
} }
} }
// verify if fuzz elaboration was requested
if len(request.Fuzzing) > 0 {
return request.executeFuzzingRule(inputURL, payloads, previous, wrappedCallback)
}
if request.generator != nil { if request.generator != nil {
iterator := request.generator.NewIterator() iterator := request.generator.NewIterator()
for { for {
@ -166,3 +172,38 @@ func dumpResponse(event *output.InternalWrappedEvent, requestOptions *protocols.
gologger.Debug().Msgf("[%s] Dumped Headless response for %s\n\n%s", requestOptions.TemplateID, input, highlightedResponse) gologger.Debug().Msgf("[%s] Dumped Headless response for %s\n\n%s", requestOptions.TemplateID, input, highlightedResponse)
} }
} }
// executeFuzzingRule executes a fuzzing rule in the template request
func (request *Request) executeFuzzingRule(inputURL string, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
// check for operator matches by wrapping callback
gotmatches := false
fuzzRequestCallback := func(gr fuzz.GeneratedRequest) bool {
if gotmatches && (request.StopAtFirstMatch || request.options.Options.StopAtFirstMatch || request.options.StopAtFirstMatch) {
return true
}
if err := request.executeRequestWithPayloads(gr.Request.URL.String(), gr.DynamicValues, previous, callback); err != nil {
return false
}
return true
}
parsedURL, err := urlutil.Parse(inputURL)
if err != nil {
return errors.Wrap(err, "could not parse url")
}
for _, rule := range request.Fuzzing {
err := rule.Execute(&fuzz.ExecuteRuleInput{
URL: parsedURL,
Callback: fuzzRequestCallback,
Values: payloads,
BaseRequest: nil,
})
if err == io.EOF {
return nil
}
if err != nil {
return errors.Wrap(err, "could not execute rule")
}
}
return nil
}

View File

@ -11,8 +11,8 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/operators" "github.com/projectdiscovery/nuclei/v2/pkg/operators"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols" "github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/fuzz"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/fuzz"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/httpclientpool" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/httpclientpool"
"github.com/projectdiscovery/rawhttp" "github.com/projectdiscovery/rawhttp"
"github.com/projectdiscovery/retryablehttp-go" "github.com/projectdiscovery/retryablehttp-go"

View File

@ -24,12 +24,12 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/protocols" "github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/fuzz"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/tostring" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/tostring"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/fuzz"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/httpclientpool" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/httpclientpool"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/signer" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/signer"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/signerpool" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/signerpool"