From 79aed22d462a9623041d25f96470c5888eb94c8a Mon Sep 17 00:00:00 2001 From: forgedhallpass <13679401+forgedhallpass@users.noreply.github.com> Date: Tue, 7 Dec 2021 17:34:36 +0200 Subject: [PATCH] feat: Improve DSL function UX #1295 Added support for letting people know if: * the DSL expression does not return a boolean value * an invalid custom function signature was provided and then display all available function signatures * an invalid function was provided and then display the correct signature Unified the DSL function names to use snake case. The old signatures are also kept for backward compatibility. --- v2/pkg/operators/common/dsl/dsl.go | 153 +++++++++++++------ v2/pkg/operators/common/dsl/dsl_test.go | 93 ++++++++++- v2/pkg/operators/matchers/compile.go | 21 ++- v2/pkg/operators/matchers/match.go | 40 ++--- v2/pkg/protocols/common/executer/executer.go | 17 +++ v2/pkg/reporting/exporters/sarif/sarif.go | 2 +- 6 files changed, 253 insertions(+), 73 deletions(-) diff --git a/v2/pkg/operators/common/dsl/dsl.go b/v2/pkg/operators/common/dsl/dsl.go index cd8a9c12..5b443102 100644 --- a/v2/pkg/operators/common/dsl/dsl.go +++ b/v2/pkg/operators/common/dsl/dsl.go @@ -20,10 +20,12 @@ import ( "time" "github.com/Knetic/govaluate" + "github.com/logrusorgru/aurora" + "github.com/spaolacci/murmur3" + "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/deserialization" "github.com/projectdiscovery/nuclei/v2/pkg/types" - "github.com/spaolacci/murmur3" ) const ( @@ -32,7 +34,7 @@ const ( ) var invalidDslFunctionError = errors.New("invalid DSL function signature") -var invalidDslFunctionMessageTemplate = "correct method signature '%s'. %w" +var invalidDslFunctionMessageTemplate = "%w. correct method signature %q" var dslFunctions map[string]dslFunction @@ -47,10 +49,10 @@ func init() { length := len(types.ToString(args[0])) return float64(length), nil }), - "toupper": makeDslFunction(1, func(args ...interface{}) (interface{}, error) { + "to_upper": makeDslFunction(1, func(args ...interface{}) (interface{}, error) { return strings.ToUpper(types.ToString(args[0])), nil }), - "tolower": makeDslFunction(1, func(args ...interface{}) (interface{}, error) { + "to_lower": makeDslFunction(1, func(args ...interface{}) (interface{}, error) { return strings.ToLower(types.ToString(args[0])), nil }), "replace": makeDslFunction(3, func(args ...interface{}) (interface{}, error) { @@ -66,19 +68,19 @@ func init() { "trim": makeDslFunction(2, func(args ...interface{}) (interface{}, error) { return strings.Trim(types.ToString(args[0]), types.ToString(args[1])), nil }), - "trimleft": makeDslFunction(2, func(args ...interface{}) (interface{}, error) { + "trim_left": makeDslFunction(2, func(args ...interface{}) (interface{}, error) { return strings.TrimLeft(types.ToString(args[0]), types.ToString(args[1])), nil }), - "trimright": makeDslFunction(2, func(args ...interface{}) (interface{}, error) { + "trim_right": makeDslFunction(2, func(args ...interface{}) (interface{}, error) { return strings.TrimRight(types.ToString(args[0]), types.ToString(args[1])), nil }), - "trimspace": makeDslFunction(1, func(args ...interface{}) (interface{}, error) { + "trim_space": makeDslFunction(1, func(args ...interface{}) (interface{}, error) { return strings.TrimSpace(types.ToString(args[0])), nil }), - "trimprefix": makeDslFunction(2, func(args ...interface{}) (interface{}, error) { + "trim_prefix": makeDslFunction(2, func(args ...interface{}) (interface{}, error) { return strings.TrimPrefix(types.ToString(args[0]), types.ToString(args[1])), nil }), - "trimsuffix": makeDslFunction(2, func(args ...interface{}) (interface{}, error) { + "trim_suffix": makeDslFunction(2, func(args ...interface{}) (interface{}, error) { return strings.TrimSuffix(types.ToString(args[0]), types.ToString(args[1])), nil }), "reverse": makeDslFunction(1, func(args ...interface{}) (interface{}, error) { @@ -98,6 +100,7 @@ func init() { return buffer.String(), nil }), "base64_py": makeDslFunction(1, func(args ...interface{}) (interface{}, error) { + // python encodes to base64 with lines of 76 bytes terminated by new line "\n" stdBase64 := base64.StdEncoding.EncodeToString([]byte(types.ToString(args[0]))) return deserialization.InsertInto(stdBase64, 76, '\n'), nil }), @@ -154,33 +157,32 @@ func init() { } return compiled.MatchString(types.ToString(args[1])), nil }), + "remove_bad_chars": makeDslFunction(2, func(args ...interface{}) (interface{}, error) { + input := types.ToString(args[0]) + badChars := types.ToString(args[1]) + return trimAll(input, badChars), nil + }), "rand_char": makeDslWithOptionalArgsFunction( - "(optionalCharSet, optionalBachChars) string", + "(optionalCharSet string) string", func(args ...interface{}) (interface{}, error) { charSet := letters + numbers - badChars := "" argSize := len(args) - if argSize != 1 && argSize != 2 { + if argSize != 0 && argSize != 1 { return nil, invalidDslFunctionError } if argSize >= 1 { charSet = types.ToString(args[0]) } - if argSize == 2 { - badChars = types.ToString(args[1]) - } - charSet = trimAll(charSet, badChars) return charSet[rand.Intn(len(charSet))], nil }, ), "rand_base": makeDslWithOptionalArgsFunction( - "(length, optionalCharSet, optionalBadChars) string", + "(length uint, optionalCharSet string) string", func(args ...interface{}) (interface{}, error) { var length int - badChars := "" charSet := letters + numbers argSize := len(args) @@ -190,18 +192,14 @@ func init() { length = int(args[0].(float64)) - if argSize >= 2 { - badChars = types.ToString(args[1]) + if argSize == 2 { + charSet = types.ToString(args[1]) } - if argSize == 3 { - charSet = types.ToString(args[2]) - } - charSet = trimAll(charSet, badChars) return randSeq(charSet, length), nil }, ), "rand_text_alphanumeric": makeDslWithOptionalArgsFunction( - "(length, optionalBadChars) string", + "(length uint, optionalBadChars string) string", func(args ...interface{}) (interface{}, error) { length := 0 badChars := "" @@ -221,7 +219,7 @@ func init() { }, ), "rand_text_alpha": makeDslWithOptionalArgsFunction( - "(length, optionalBadChars) string", + "(length uint, optionalBadChars string) string", func(args ...interface{}) (interface{}, error) { var length int badChars := "" @@ -241,7 +239,7 @@ func init() { }, ), "rand_text_numeric": makeDslWithOptionalArgsFunction( - "(size int, optionalBadNumbers string) string", + "(length uint, optionalBadNumbers string) string", func(args ...interface{}) (interface{}, error) { argSize := len(args) if argSize != 1 && argSize != 2 { @@ -249,7 +247,7 @@ func init() { } length := args[0].(int) - var badNumbers = "" + badNumbers := "" if argSize == 2 { badNumbers = types.ToString(args[1]) @@ -260,7 +258,7 @@ func init() { }, ), "rand_int": makeDslWithOptionalArgsFunction( - "(optionalMin, optionalMax int) int", + "(optionalMin, optionalMax uint) int", func(args ...interface{}) (interface{}, error) { argSize := len(args) if argSize >= 2 { @@ -286,7 +284,7 @@ func init() { data := deserialization.GenerateJavaGadget(gadget, cmd, encoding) return data, nil }), - "unixtime": makeDslWithOptionalArgsFunction( + "unix_time": makeDslWithOptionalArgsFunction( "(optionalSeconds uint) float64", func(args ...interface{}) (interface{}, error) { seconds := 0 @@ -302,7 +300,7 @@ func init() { return float64(offset.Unix()), nil }, ), - "waitfor": makeDslWithOptionalArgsFunction( + "wait_for": makeDslWithOptionalArgsFunction( "(seconds uint)", func(args ...interface{}) (interface{}, error) { if len(args) != 1 { @@ -323,15 +321,6 @@ func init() { return true, nil }, ), - "time_now": makeDslWithOptionalArgsFunction( - "() float64", - func(args ...interface{}) (interface{}, error) { - if len(args) == 0 { - return nil, invalidDslFunctionError - } - return float64(time.Now().Unix()), nil - }, - ), } dslFunctions = make(map[string]dslFunction, len(tempDslFunctions)) @@ -364,7 +353,7 @@ func makeDslFunction(numberOfParameters int, dslFunctionLogic govaluate.Expressi signature, func(args ...interface{}) (interface{}, error) { if len(args) != numberOfParameters { - return nil, fmt.Errorf(invalidDslFunctionMessageTemplate, signature, invalidDslFunctionError) + return nil, fmt.Errorf(invalidDslFunctionMessageTemplate, invalidDslFunctionError, signature) } return dslFunctionLogic(args...) }, @@ -378,12 +367,42 @@ func HelperFunctions() map[string]govaluate.ExpressionFunction { for functionName, dslFunction := range dslFunctions { helperFunctions[functionName] = dslFunction.expressFunc + helperFunctions[strings.ReplaceAll(functionName, "_", "")] = dslFunction.expressFunc // for backwards compatibility } return helperFunctions } -func GetDslFunctionSignatures() []string { +// AddHelperFunction allows creation of additional helper functions to be supported with templates +//goland:noinspection GoUnusedExportedFunction +func AddHelperFunction(key string, value func(args ...interface{}) (interface{}, error)) error { + if _, ok := dslFunctions[key]; !ok { + dslFunction := dslFunctions[key] + dslFunction.signature = "(args ...interface{}) interface{}" + dslFunction.expressFunc = value + return nil + } + return errors.New("duplicate helper function key defined") +} + +func GetPrintableDslFunctionSignatures(noColor bool) string { + aggregateSignatures := func(values []string) string { + builder := &strings.Builder{} + for _, value := range values { + builder.WriteRune('\t') + builder.WriteString(value) + builder.WriteRune('\n') + } + return builder.String() + } + + if noColor { + return aggregateSignatures(getDslFunctionSignatures()) + } + return aggregateSignatures(colorizeDslFunctionSignatures()) +} + +func getDslFunctionSignatures() []string { result := make([]string, 0, len(dslFunctions)) for _, dslFunction := range dslFunctions { @@ -393,15 +412,49 @@ func GetDslFunctionSignatures() []string { return result } -// AddHelperFunction allows creation of additional helper functions to be supported with templates -func AddHelperFunction(key string, value func(args ...interface{}) (interface{}, error)) error { - if _, ok := dslFunctions[key]; !ok { - dslFunction := dslFunctions[key] - dslFunction.signature = "(args ...interface{}) interface{}" - dslFunction.expressFunc = value - return nil +var functionSignaturePattern = regexp.MustCompile(`(\w+)\s*\((?:([\w\d,\s]+)\s+([.\w\d{}&*]+))?\)([\s.\w\d{}&*]+)?`) + +func colorizeDslFunctionSignatures() []string { + signatures := getDslFunctionSignatures() + + colorToOrange := func(value string) string { + return aurora.Index(208, value).String() } - return errors.New("duplicate helper function key defined") + + result := make([]string, 0, len(signatures)) + + for _, signature := range signatures { + subMatchSlices := functionSignaturePattern.FindAllStringSubmatch(signature, -1) + if len(subMatchSlices) != 1 { + // TODO log when #1166 is implemented + return signatures + } + matches := subMatchSlices[0] + if len(matches) != 5 { + // TODO log when #1166 is implemented + return signatures + } + + functionParameters := strings.Split(matches[2], ",") + + var coloredParameterAndTypes []string + for _, functionParameter := range functionParameters { + functionParameter = strings.TrimSpace(functionParameter) + paramAndType := strings.Split(functionParameter, " ") + if len(paramAndType) == 1 { + coloredParameterAndTypes = append(coloredParameterAndTypes, paramAndType[0]) + } else if len(paramAndType) == 2 { + coloredParameterAndTypes = append(coloredParameterAndTypes, fmt.Sprintf("%s %s", paramAndType[0], colorToOrange(paramAndType[1]))) + } + } + + highlightedParams := strings.TrimSpace(fmt.Sprintf("%s %s", strings.Join(coloredParameterAndTypes, ", "), colorToOrange(matches[3]))) + colorizedDslSignature := fmt.Sprintf("%s(%s)%s", aurora.BrightYellow(matches[1]).String(), highlightedParams, colorToOrange(matches[4])) + + result = append(result, colorizedDslSignature) + } + + return result } func reverseString(s string) string { diff --git a/v2/pkg/operators/common/dsl/dsl_test.go b/v2/pkg/operators/common/dsl/dsl_test.go index f75baf08..f50447d6 100644 --- a/v2/pkg/operators/common/dsl/dsl_test.go +++ b/v2/pkg/operators/common/dsl/dsl_test.go @@ -2,14 +2,17 @@ package dsl import ( "compress/gzip" + "fmt" "io/ioutil" "strings" "testing" "time" "github.com/Knetic/govaluate" - "github.com/projectdiscovery/nuclei/v2/pkg/types" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/projectdiscovery/nuclei/v2/pkg/types" ) func TestDSLURLEncodeDecode(t *testing.T) { @@ -45,3 +48,91 @@ func TestDSLGzipSerialize(t *testing.T) { require.Equal(t, "hello world", string(data), "could not get gzip encoded data") } + +func Test1(t *testing.T) { + type testCase struct { + methodName string + arguments []interface{} + expected interface{} + err string + } + + toUpperSignatureError := createSignatureError("to_upper(arg1 interface{}) interface{}") + removeBadCharsSignatureError := createSignatureError("remove_bad_chars(arg1, arg2 interface{}) interface{}") + + testCases := []testCase{ + {"to_upper", []interface{}{}, nil, toUpperSignatureError}, + {"to_upper", []interface{}{"a"}, "A", ""}, + {"toupper", []interface{}{"a"}, "A", ""}, + {"to_upper", []interface{}{"a", "b", "c"}, nil, toUpperSignatureError}, + + {"remove_bad_chars", []interface{}{}, nil, removeBadCharsSignatureError}, + {"remove_bad_chars", []interface{}{"a"}, nil, removeBadCharsSignatureError}, + {"remove_bad_chars", []interface{}{"abba baab", "b"}, "aa aa", ""}, + {"remove_bad_chars", []interface{}{"a", "b", "c"}, nil, removeBadCharsSignatureError}, + } + + helperFunctions := HelperFunctions() + for _, currentTestCase := range testCases { + methodName := currentTestCase.methodName + t.Run(methodName, func(t *testing.T) { + actualResult, err := helperFunctions[methodName](currentTestCase.arguments...) + + if currentTestCase.err == "" { + assert.Nil(t, err) + } else { + assert.Equal(t, err.Error(), currentTestCase.err) + } + assert.Equal(t, currentTestCase.expected, actualResult) + }) + } +} + +func createSignatureError(signature string) string { + return fmt.Errorf(invalidDslFunctionMessageTemplate, invalidDslFunctionError, signature).Error() +} + +func Test(t *testing.T) { + expectedColorizedSignatures := []string{ + "\x1b[93mbase64_py\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mprint_debug\x1b[0m(args \x1b[38;5;208m...interface{}\x1b[0m)\x1b[38;5;208m\x1b[0m", + "\x1b[93mregex\x1b[0m(arg1, arg2 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mmmh3\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mto_lower\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mmd5\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mreplace_regex\x1b[0m(arg1, arg2, arg3 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mhtml_unescape\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mhex_encode\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mrand_base\x1b[0m(length \x1b[38;5;208muint\x1b[0m, optionalCharSet \x1b[38;5;208mstring\x1b[0m)\x1b[38;5;208m string\x1b[0m", + "\x1b[93msha1\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mtrim_right\x1b[0m(arg1, arg2 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mwait_for\x1b[0m(seconds \x1b[38;5;208muint\x1b[0m)\x1b[38;5;208m\x1b[0m", + "\x1b[93mtrim\x1b[0m(arg1, arg2 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93murl_encode\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mto_upper\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mrand_text_alpha\x1b[0m(length \x1b[38;5;208muint\x1b[0m, optionalBadChars \x1b[38;5;208mstring\x1b[0m)\x1b[38;5;208m string\x1b[0m", + "\x1b[93msha256\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mgzip\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mlen\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mtrim_space\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mrand_int\x1b[0m(optionalMin, optionalMax \x1b[38;5;208muint\x1b[0m)\x1b[38;5;208m int\x1b[0m", + "\x1b[93mremove_bad_chars\x1b[0m(arg1, arg2 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mrand_char\x1b[0m(optionalCharSet \x1b[38;5;208mstring\x1b[0m)\x1b[38;5;208m string\x1b[0m", + "\x1b[93mreverse\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mhtml_escape\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mbase64\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mbase64_decode\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mhex_decode\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mtrim_prefix\x1b[0m(arg1, arg2 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93murl_decode\x1b[0m(arg1 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mreplace\x1b[0m(arg1, arg2, arg3 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mtrim_suffix\x1b[0m(arg1, arg2 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mrand_text_numeric\x1b[0m(length \x1b[38;5;208muint\x1b[0m, optionalBadNumbers \x1b[38;5;208mstring\x1b[0m)\x1b[38;5;208m string\x1b[0m", + "\x1b[93mcontains\x1b[0m(arg1, arg2 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mgenerate_java_gadget\x1b[0m(arg1, arg2, arg3 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93munix_time\x1b[0m(optionalSeconds \x1b[38;5;208muint\x1b[0m)\x1b[38;5;208m float64\x1b[0m", + "\x1b[93mtrim_left\x1b[0m(arg1, arg2 \x1b[38;5;208minterface{}\x1b[0m)\x1b[38;5;208m interface{}\x1b[0m", + "\x1b[93mrand_text_alphanumeric\x1b[0m(length \x1b[38;5;208muint\x1b[0m, optionalBadChars \x1b[38;5;208mstring\x1b[0m)\x1b[38;5;208m string\x1b[0m", + } + assert.ElementsMatch(t, expectedColorizedSignatures, colorizeDslFunctionSignatures()) +} diff --git a/v2/pkg/operators/matchers/compile.go b/v2/pkg/operators/matchers/compile.go index c50d1b63..4d0a05d7 100644 --- a/v2/pkg/operators/matchers/compile.go +++ b/v2/pkg/operators/matchers/compile.go @@ -55,12 +55,12 @@ func (m *Matcher) CompileMatchers() error { } // Compile the dsl expressions - for _, expr := range m.DSL { - compiled, err := govaluate.NewEvaluableExpressionWithFunctions(expr, dsl.HelperFunctions()) + for _, dslExpression := range m.DSL { + compiledExpression, err := govaluate.NewEvaluableExpressionWithFunctions(dslExpression, dsl.HelperFunctions()) if err != nil { - return fmt.Errorf("could not compile dsl: %s. %w", expr, err) + return &DslCompilationError{DslSignature: dslExpression, WrappedError: err} } - m.dslCompiled = append(m.dslCompiled, compiled) + m.dslCompiled = append(m.dslCompiled, compiledExpression) } // Set up the condition type, if any. @@ -83,3 +83,16 @@ func (m *Matcher) CompileMatchers() error { } return nil } + +type DslCompilationError struct { + DslSignature string + WrappedError error +} + +func (e *DslCompilationError) Error() string { + return fmt.Sprintf("could not compile DSL expression: %s. %v", e.DslSignature, e.WrappedError) +} + +func (e *DslCompilationError) Unwrap() error { + return e.WrappedError +} diff --git a/v2/pkg/operators/matchers/match.go b/v2/pkg/operators/matchers/match.go index 84601be6..8245ab33 100644 --- a/v2/pkg/operators/matchers/match.go +++ b/v2/pkg/operators/matchers/match.go @@ -3,6 +3,7 @@ package matchers import ( "strings" + "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions" ) @@ -54,17 +55,19 @@ func (m *Matcher) MatchWords(corpus string, dynamicValues map[string]interface{} var err error word, err = expressions.Evaluate(word, dynamicValues) if err != nil { + gologger.Warning().Msgf("Error while evaluating word matcher: %q", word) continue } // Continue if the word doesn't match if !strings.Contains(corpus, word) { // If we are in an AND request and a match failed, // return false as the AND condition fails on any single mismatch. - if m.condition == ANDCondition { + switch m.condition { + case ANDCondition: return false, []string{} + case ORCondition: + continue } - // Continue with the flow since it's an OR Condition. - continue } // If the condition was an OR, return on the first match. @@ -91,11 +94,12 @@ func (m *Matcher) MatchRegex(corpus string) (bool, []string) { if !regex.MatchString(corpus) { // If we are in an AND request and a match failed, // return false as the AND condition fails on any single mismatch. - if m.condition == ANDCondition { + switch m.condition { + case ANDCondition: return false, []string{} + case ORCondition: + continue } - // Continue with the flow since it's an OR Condition. - continue } currentMatches := regex.FindAllString(corpus, -1) @@ -122,11 +126,12 @@ func (m *Matcher) MatchBinary(corpus string) (bool, []string) { if !strings.Contains(corpus, binary) { // If we are in an AND request and a match failed, // return false as the AND condition fails on any single mismatch. - if m.condition == ANDCondition { + switch m.condition { + case ANDCondition: return false, []string{} + case ORCondition: + continue } - // Continue with the flow since it's an OR Condition. - continue } // If the condition was an OR, return on the first match. @@ -150,21 +155,22 @@ func (m *Matcher) MatchDSL(data map[string]interface{}) bool { for i, expression := range m.dslCompiled { result, err := expression.Evaluate(data) if err != nil { + gologger.Warning().Msgf(err.Error()) continue } - var bResult bool - bResult, ok := result.(bool) - - // Continue if the regex doesn't match - if !ok || !bResult { + if boolResult, ok := result.(bool); !ok { + gologger.Warning().Msgf("The return value of a DSL statement must return a boolean value.") + continue + } else if !boolResult { // If we are in an AND request and a match failed, // return false as the AND condition fails on any single mismatch. - if m.condition == ANDCondition { + switch m.condition { + case ANDCondition: return false + case ORCondition: + continue } - // Continue with the flow since it's an OR Condition. - continue } // If the condition was an OR, return on the first match. diff --git a/v2/pkg/protocols/common/executer/executer.go b/v2/pkg/protocols/common/executer/executer.go index fe650e94..de5cfc9c 100644 --- a/v2/pkg/protocols/common/executer/executer.go +++ b/v2/pkg/protocols/common/executer/executer.go @@ -1,9 +1,14 @@ package executer import ( + "fmt" "strings" + "github.com/pkg/errors" + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/operators/common/dsl" + "github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers" "github.com/projectdiscovery/nuclei/v2/pkg/output" "github.com/projectdiscovery/nuclei/v2/pkg/protocols" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/writer" @@ -24,8 +29,20 @@ func NewExecuter(requests []protocols.Request, options *protocols.ExecuterOption // Compile compiles the execution generators preparing any requests possible. func (e *Executer) Compile() error { + cliOptions := e.options.Options + for _, request := range e.requests { if err := request.Compile(e.options); err != nil { + var dslCompilationError *matchers.DslCompilationError + if errors.As(err, &dslCompilationError) { + if cliOptions.Verbose { + rawErrorMessage := dslCompilationError.Error() + formattedErrorMessage := strings.ToUpper(rawErrorMessage[:1]) + rawErrorMessage[1:] + "." + gologger.Warning().Msgf(formattedErrorMessage) + gologger.Info().Msgf("The available custom DSL functions are:") + fmt.Println(dsl.GetPrintableDslFunctionSignatures(cliOptions.NoColor)) + } + } return err } } diff --git a/v2/pkg/reporting/exporters/sarif/sarif.go b/v2/pkg/reporting/exporters/sarif/sarif.go index 6395ee31..4869374c 100644 --- a/v2/pkg/reporting/exporters/sarif/sarif.go +++ b/v2/pkg/reporting/exporters/sarif/sarif.go @@ -90,7 +90,7 @@ func (i *Exporter) Export(event *output.ResultEvent) error { WithMessage(sarif.NewMessage().WithText(event.Host)). WithLevel(sarifSeverity) - // Also write file match metadata to file + // Also write file match metadata to file if event.Type == "file" && (event.FileToIndexPosition != nil && len(event.FileToIndexPosition) > 0) { for file, line := range event.FileToIndexPosition { result.WithLocation(sarif.NewLocation().WithMessage(sarif.NewMessage().WithText(ruleName)).WithPhysicalLocation(