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

Dsl variable support for matchers #1290

Merged
merged 6 commits into from
Dec 2, 2021
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
23 changes: 23 additions & 0 deletions integration_tests/http/dsl-matcher-variable.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
id: dsl-matcher-variable

info:
name: dsl-matcher-variable
author: pd-team
severity: info

requests:
-
path:
- "{{BaseURL}}"
payloads:
VALUES:
- This
- is
- test
- matcher
- text
matchers:
-
dsl:
- 'contains(body,"{{VALUES}}")'
type: dsl
22 changes: 22 additions & 0 deletions v2/cmd/integration-test/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var httpTestcases = map[string]testutils.TestCase{
"http/get-case-insensitive.yaml": &httpGetCaseInsensitive{},
"http/get.yaml,http/get-case-insensitive.yaml": &httpGetCaseInsensitiveCluster{},
"http/get-redirects-chain-headers.yaml": &httpGetRedirectsChainHeaders{},
"http/dsl-matcher-variable.yaml": &httpDSLVariable{},
}

type httpInteractshRequest struct{}
Expand Down Expand Up @@ -155,6 +156,27 @@ func (h *httpGet) Execute(filePath string) error {
return nil
}

type httpDSLVariable struct{}

// Execute executes a test case and returns an error if occurred
func (h *httpDSLVariable) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprintf(w, "This is test matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()

results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
if len(results) != 5 {
return errIncorrectResultsCount(results)
}
return nil
}

type httpPostBody struct{}

// Execute executes a test case and returns an error if occurred
Expand Down
23 changes: 19 additions & 4 deletions v2/pkg/operators/matchers/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package matchers
import (
"strings"

"github.com/Knetic/govaluate"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v2/pkg/operators/common/dsl"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
)

Expand Down Expand Up @@ -39,20 +42,20 @@ func (matcher *Matcher) MatchSize(length int) bool {
}

// MatchWords matches a word check against a corpus.
func (matcher *Matcher) MatchWords(corpus string, dynamicValues map[string]interface{}) (bool, []string) {
func (matcher *Matcher) MatchWords(corpus string, data map[string]interface{}) (bool, []string) {
if matcher.CaseInsensitive {
corpus = strings.ToLower(corpus)
}

var matchedWords []string
// Iterate over all the words accepted as valid
for i, word := range matcher.Words {
if dynamicValues == nil {
dynamicValues = make(map[string]interface{})
if data == nil {
data = make(map[string]interface{})
}

var err error
word, err = expressions.Evaluate(word, dynamicValues)
word, err = expressions.Evaluate(word, data)
if err != nil {
continue
}
Expand Down Expand Up @@ -148,6 +151,18 @@ func (matcher *Matcher) MatchBinary(corpus string) (bool, []string) {
func (matcher *Matcher) MatchDSL(data map[string]interface{}) bool {
// Iterate over all the expressions accepted as valid
for i, expression := range matcher.dslCompiled {
if varErr := expressions.ContainsUnresolvedVariables(expression.String()); varErr != nil {
resolvedExpression, err := expressions.Evaluate(expression.String(), data)
if err != nil {
gologger.Warning().Msgf("Could not evaluate expression: %s, error: %s", matcher.Name, err.Error())
return false
}
expression, err = govaluate.NewEvaluableExpressionWithFunctions(resolvedExpression, dsl.HelperFunctions())
if err != nil {
gologger.Warning().Msgf("Could not evaluate expression: %s, error: %s", matcher.Name, err.Error())
return false
}
}
result, err := expression.Evaluate(data)
if err != nil {
continue
Expand Down
18 changes: 18 additions & 0 deletions v2/pkg/operators/matchers/match_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package matchers
import (
"testing"

"github.com/Knetic/govaluate"
"github.com/projectdiscovery/nuclei/v2/pkg/operators/common/dsl"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -71,3 +73,19 @@ func TestHexEncoding(t *testing.T) {
require.True(t, isMatched, "Could not match valid Hex condition")
require.Equal(t, m.Words, matched)
}

func TestMatcher_MatchDSL(t *testing.T) {
compiled, err := govaluate.NewEvaluableExpressionWithFunctions("contains(body, \"{{VARIABLE}}\")", dsl.HelperFunctions())
require.Nil(t, err, "couldn't compile expression")

m := &Matcher{Type: MatcherTypeHolder{MatcherType: DSLMatcher}, dslCompiled: []*govaluate.EvaluableExpression{compiled}}
err = m.CompileMatchers()
require.Nil(t, err, "could not compile matcher")

values := []string{"PING", "pong"}

for value := range values {
isMatched := m.MatchDSL(map[string]interface{}{"body": value, "VARIABLE": value})
require.True(t, isMatched)
}
}
1 change: 0 additions & 1 deletion v2/pkg/protocols/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ type Request struct {
generator *generators.PayloadGenerator // optional, only enabled when using payloads
httpClient *retryablehttp.Client
rawhttpClient *rawhttp.Client
dynamicValues map[string]interface{}

// description: |
// SelfContained specifies if the request is self-contained.
Expand Down
2 changes: 1 addition & 1 deletion v2/pkg/protocols/http/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (request *Request) Match(data map[string]interface{}, matcher *matchers.Mat
case matchers.SizeMatcher:
return matcher.Result(matcher.MatchSize(len(item))), []string{}
case matchers.WordsMatcher:
return matcher.ResultWithMatchedSnippet(matcher.MatchWords(item, request.dynamicValues))
return matcher.ResultWithMatchedSnippet(matcher.MatchWords(item, data))
case matchers.RegexMatcher:
return matcher.ResultWithMatchedSnippet(matcher.MatchRegex(item))
case matchers.BinaryMatcher:
Expand Down
4 changes: 1 addition & 3 deletions v2/pkg/protocols/http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,6 @@ func (request *Request) ExecuteWithResults(reqURL string, dynamicValues, previou
if reqURL == "" {
reqURL = generatedHttpRequest.URL()
}
request.dynamicValues = generatedHttpRequest.dynamicValues

// Check if hosts keep erroring
if request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(reqURL) {
return true, nil
Expand Down Expand Up @@ -520,7 +518,7 @@ func (request *Request) executeRequest(reqURL string, generatedRequest *generate
}
}

event := eventcreator.CreateEventWithAdditionalOptions(request, finalEvent, request.options.Options.Debug || request.options.Options.DebugResponse, func(internalWrappedEvent *output.InternalWrappedEvent) {
event := eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(generatedRequest.dynamicValues, finalEvent), request.options.Options.Debug || request.options.Options.DebugResponse, func(internalWrappedEvent *output.InternalWrappedEvent) {
internalWrappedEvent.OperatorsResult.PayloadValues = generatedRequest.meta
})
if hasInteractMarkers {
Expand Down
2 changes: 1 addition & 1 deletion v2/pkg/protocols/http/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestHTTPExtractMultipleReuse(t *testing.T) {
Extractors: []*extractors.Extractor{{
Part: "body",
Name: "endpoint",
Type: extractors.TypeHolder{ExtractorType: extractors.RegexExtractor},
Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},
Regex: []string{"(?m)/([a-zA-Z0-9-_/\\\\]+)"},
Internal: true,
}},
Expand Down
5 changes: 2 additions & 3 deletions v2/pkg/protocols/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,8 @@ type Request struct {

generator *generators.PayloadGenerator
// cache any variables that may be needed for operation.
dialer *fastdialer.Dialer
options *protocols.ExecuterOptions
dynamicValues map[string]interface{}
dialer *fastdialer.Dialer
options *protocols.ExecuterOptions
}

// RequestPartDefinitions contains a mapping of request part definitions and their
Expand Down
2 changes: 1 addition & 1 deletion v2/pkg/protocols/network/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (request *Request) Match(data map[string]interface{}, matcher *matchers.Mat
case matchers.SizeMatcher:
return matcher.Result(matcher.MatchSize(len(itemStr))), []string{}
case matchers.WordsMatcher:
return matcher.ResultWithMatchedSnippet(matcher.MatchWords(itemStr, request.dynamicValues))
return matcher.ResultWithMatchedSnippet(matcher.MatchWords(itemStr, data))
case matchers.RegexMatcher:
return matcher.ResultWithMatchedSnippet(matcher.MatchRegex(itemStr))
case matchers.BinaryMatcher:
Expand Down
6 changes: 3 additions & 3 deletions v2/pkg/protocols/network/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ func (request *Request) executeAddress(variables map[string]interface{}, actualA
}

payloads := generators.BuildPayloadFromOptions(request.options.Options)
// add Hostname variable to the payload
payloads = generators.MergeMaps(payloads, map[string]interface{}{"Hostname": address})

if request.generator != nil {
iterator := request.generator.NewIterator()
Expand Down Expand Up @@ -100,8 +102,6 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac
err error
)

request.dynamicValues = generators.MergeMaps(payloads, variables)

if host, _, splitErr := net.SplitHostPort(actualAddress); splitErr == nil {
hostname = host
}
Expand Down Expand Up @@ -259,7 +259,7 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac

var event *output.InternalWrappedEvent
if len(interactshURLs) == 0 {
event = eventcreator.CreateEventWithAdditionalOptions(request, outputEvent, request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {
event = eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(payloads, outputEvent), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {
wrappedEvent.OperatorsResult.PayloadValues = payloads
})
callback(event)
Expand Down