mirror of https://github.com/daffainfo/nuclei.git
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
parent
b4e4715d36
commit
a34b94e62f
|
@ -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}}"
|
|
@ -16,6 +16,7 @@ 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)
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue