323 lines
8.6 KiB
Go
Raw Normal View History

2020-04-04 00:16:27 +05:30
package matchers
import (
"os"
2020-04-04 00:16:27 +05:30
"strings"
"github.com/Knetic/govaluate"
"github.com/antchfx/htmlquery"
"github.com/antchfx/xmlquery"
2021-12-07 18:17:34 +02:00
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
stringsutil "github.com/projectdiscovery/utils/strings"
)
var (
// showDSLErr controls whether to show hidden DSL errors or not
showDSLErr = strings.EqualFold(os.Getenv("SHOW_DSL_ERRORS"), "true")
2020-04-04 00:16:27 +05:30
)
2020-12-24 20:47:41 +05:30
// MatchStatusCode matches a status code check against a corpus
2021-11-25 16:57:43 +02:00
func (matcher *Matcher) MatchStatusCode(statusCode int) bool {
2020-04-04 00:16:27 +05:30
// Iterate over all the status codes accepted as valid
2020-04-04 00:32:03 +05:30
//
// Status codes don't support AND conditions.
2021-11-25 16:57:43 +02:00
for _, status := range matcher.Status {
2020-04-04 00:16:27 +05:30
// Continue if the status codes don't match
if statusCode != status {
continue
}
2020-04-04 00:32:03 +05:30
// Return on the first match.
return true
2020-04-04 00:16:27 +05:30
}
return false
}
2020-12-24 20:47:41 +05:30
// MatchSize matches a size check against a corpus
2021-11-25 16:57:43 +02:00
func (matcher *Matcher) MatchSize(length int) bool {
2020-04-04 00:16:27 +05:30
// Iterate over all the sizes accepted as valid
2020-04-04 00:32:03 +05:30
//
// Sizes codes don't support AND conditions.
2021-11-25 16:57:43 +02:00
for _, size := range matcher.Size {
2020-04-04 00:16:27 +05:30
// Continue if the size doesn't match
if length != size {
continue
}
2020-04-04 00:32:03 +05:30
// Return on the first match.
return true
2020-04-04 00:16:27 +05:30
}
return false
}
2020-12-24 20:47:41 +05:30
// MatchWords matches a word check against a corpus.
func (matcher *Matcher) MatchWords(corpus string, data map[string]interface{}) (bool, []string) {
2021-11-25 16:57:43 +02:00
if matcher.CaseInsensitive {
corpus = strings.ToLower(corpus)
}
var matchedWords []string
2020-04-04 00:16:27 +05:30
// Iterate over all the words accepted as valid
2021-11-25 16:57:43 +02:00
for i, word := range matcher.Words {
if data == nil {
data = make(map[string]interface{})
}
var err error
word, err = expressions.Evaluate(word, data)
if err != nil {
gologger.Warning().Msgf("Error while evaluating word matcher: %q", word)
if matcher.condition == ANDCondition {
return false, []string{}
}
}
2020-04-04 00:16:27 +05:30
// Continue if the word doesn't match
if !strings.Contains(corpus, word) {
2020-04-04 00:16:27 +05:30
// If we are in an AND request and a match failed,
// return false as the AND condition fails on any single mismatch.
2021-12-07 18:17:34 +02:00
switch matcher.condition {
case ANDCondition:
return false, []string{}
case ORCondition:
continue
2020-04-04 00:16:27 +05:30
}
}
// If the condition was an OR, return on the first match.
if matcher.condition == ORCondition && !matcher.MatchAll {
return true, []string{word}
2020-04-04 00:16:27 +05:30
}
matchedWords = append(matchedWords, word)
2020-04-04 00:32:03 +05:30
// If we are at the end of the words, return with true
if len(matcher.Words)-1 == i && !matcher.MatchAll {
return true, matchedWords
2020-04-04 00:32:03 +05:30
}
2020-04-04 00:16:27 +05:30
}
if len(matchedWords) > 0 && matcher.MatchAll {
return true, matchedWords
}
return false, []string{}
2020-04-04 00:16:27 +05:30
}
2020-12-24 20:47:41 +05:30
// MatchRegex matches a regex check against a corpus
2021-11-25 16:57:43 +02:00
func (matcher *Matcher) MatchRegex(corpus string) (bool, []string) {
var matchedRegexes []string
2020-04-04 00:16:27 +05:30
// Iterate over all the regexes accepted as valid
2021-11-25 16:57:43 +02:00
for i, regex := range matcher.regexCompiled {
2020-04-04 00:16:27 +05:30
// Continue if the regex doesn't match
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.
2021-12-07 18:17:34 +02:00
switch matcher.condition {
case ANDCondition:
return false, []string{}
case ORCondition:
continue
2020-04-04 00:16:27 +05:30
}
}
currentMatches := regex.FindAllString(corpus, -1)
2020-04-04 00:16:27 +05:30
// If the condition was an OR, return on the first match.
if matcher.condition == ORCondition && !matcher.MatchAll {
return true, currentMatches
2020-04-04 00:16:27 +05:30
}
2020-04-04 00:32:03 +05:30
matchedRegexes = append(matchedRegexes, currentMatches...)
2020-04-04 00:32:03 +05:30
// If we are at the end of the regex, return with true
if len(matcher.regexCompiled)-1 == i && !matcher.MatchAll {
return true, matchedRegexes
2020-04-04 00:32:03 +05:30
}
2020-04-04 00:16:27 +05:30
}
if len(matchedRegexes) > 0 && matcher.MatchAll {
return true, matchedRegexes
}
return false, []string{}
2020-04-04 00:16:27 +05:30
}
2020-12-24 20:47:41 +05:30
// MatchBinary matches a binary check against a corpus
2021-11-25 16:57:43 +02:00
func (matcher *Matcher) MatchBinary(corpus string) (bool, []string) {
var matchedBinary []string
// Iterate over all the words accepted as valid
2021-11-25 16:57:43 +02:00
for i, binary := range matcher.binaryDecoded {
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.
2021-12-07 18:17:34 +02:00
switch matcher.condition {
case ANDCondition:
return false, []string{}
case ORCondition:
continue
}
}
// If the condition was an OR, return on the first match.
2021-11-25 16:57:43 +02:00
if matcher.condition == ORCondition {
return true, []string{binary}
}
matchedBinary = append(matchedBinary, binary)
// If we are at the end of the words, return with true
2021-11-25 16:57:43 +02:00
if len(matcher.Binary)-1 == i {
return true, matchedBinary
}
}
return false, []string{}
}
2020-04-26 23:32:58 +02:00
2020-12-24 20:47:41 +05:30
// MatchDSL matches on a generic map result
2021-11-25 16:57:43 +02:00
func (matcher *Matcher) MatchDSL(data map[string]interface{}) bool {
logExpressionEvaluationFailure := func(matcherName string, err error) {
2021-12-07 18:17:34 +02:00
gologger.Warning().Msgf("Could not evaluate expression: %s, error: %s", matcherName, err.Error())
}
2020-11-25 18:27:14 +01:00
// Iterate over all the expressions accepted as valid
2021-11-25 16:57:43 +02:00
for i, expression := range matcher.dslCompiled {
if varErr := expressions.ContainsUnresolvedVariables(expression.String()); varErr != nil {
resolvedExpression, err := expressions.Evaluate(expression.String(), data)
if err != nil {
2021-12-07 18:17:34 +02:00
logExpressionEvaluationFailure(matcher.Name, err)
return false
}
expression, err = govaluate.NewEvaluableExpressionWithFunctions(resolvedExpression, dsl.HelperFunctions)
if err != nil {
2021-12-07 18:17:34 +02:00
logExpressionEvaluationFailure(matcher.Name, err)
return false
}
}
2021-12-07 18:17:34 +02:00
2020-12-24 20:47:41 +05:30
result, err := expression.Evaluate(data)
2020-04-26 23:32:58 +02:00
if err != nil {
if matcher.condition == ANDCondition {
return false
}
if !matcher.ignoreErr(err) {
gologger.Warning().Msgf("[%s] %s", data["template-id"], err.Error())
}
2020-04-26 23:32:58 +02:00
continue
}
if boolResult, ok := result.(bool); !ok {
gologger.Error().Label("WRN").Msgf("[%s] The return value of a DSL statement must return a boolean value.", data["template-id"])
continue
} else if !boolResult {
2020-04-26 23:32:58 +02:00
// If we are in an AND request and a match failed,
// return false as the AND condition fails on any single mismatch.
2021-12-07 18:17:34 +02:00
switch matcher.condition {
case ANDCondition:
2020-04-26 23:32:58 +02:00
return false
case ORCondition:
continue
2020-04-26 23:32:58 +02:00
}
}
// If the condition was an OR, return on the first match.
2021-11-25 16:57:43 +02:00
if matcher.condition == ORCondition {
2020-04-26 23:32:58 +02:00
return true
}
// If we are at the end of the dsl, return with true
2021-11-25 16:57:43 +02:00
if len(matcher.dslCompiled)-1 == i {
2020-04-26 23:32:58 +02:00
return true
}
}
return false
}
// MatchXPath matches on a generic map result
func (matcher *Matcher) MatchXPath(corpus string) bool {
if strings.HasPrefix(corpus, "<?xml") {
return matcher.MatchXML(corpus)
}
return matcher.MatchHTML(corpus)
}
// MatchHTML matches items from HTML using XPath selectors
func (matcher *Matcher) MatchHTML(corpus string) bool {
doc, err := htmlquery.Parse(strings.NewReader(corpus))
if err != nil {
return false
}
matches := 0
for _, k := range matcher.XPath {
nodes, err := htmlquery.QueryAll(doc, k)
if err != nil {
continue
}
// Continue if the xpath doesn't return any nodes
if len(nodes) == 0 {
// If we are in an AND request and a match failed,
// return false as the AND condition fails on any single mismatch.
switch matcher.condition {
case ANDCondition:
return false
case ORCondition:
continue
}
}
// If the condition was an OR, return on the first match.
if matcher.condition == ORCondition && !matcher.MatchAll {
return true
}
matches = matches + len(nodes)
}
return matches > 0
}
// MatchXML matches items from XML using XPath selectors
func (matcher *Matcher) MatchXML(corpus string) bool {
doc, err := xmlquery.Parse(strings.NewReader(corpus))
if err != nil {
return false
}
matches := 0
for _, k := range matcher.XPath {
nodes, err := xmlquery.QueryAll(doc, k)
if err != nil {
continue
}
// Continue if the xpath doesn't return any nodes
if len(nodes) == 0 {
// If we are in an AND request and a match failed,
// return false as the AND condition fails on any single mismatch.
switch matcher.condition {
case ANDCondition:
return false
case ORCondition:
continue
}
}
// If the condition was an OR, return on the first match.
if matcher.condition == ORCondition && !matcher.MatchAll {
return true
}
matches = matches + len(nodes)
}
return matches > 0
}
// ignoreErr checks if the error is to be ignored or not
// Reference: https://github.com/projectdiscovery/nuclei/issues/3950
func (m *Matcher) ignoreErr(err error) bool {
if showDSLErr {
return false
}
if stringsutil.ContainsAny(err.Error(), "No parameter", "error parsing argument value") {
return true
}
return false
}