github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/http/operators.go (about)

     1  package http
     2  
     3  import (
     4  	"net/http"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/projectdiscovery/nuclei/v2/pkg/model"
     9  	"github.com/projectdiscovery/nuclei/v2/pkg/operators"
    10  	"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
    11  	"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
    12  	"github.com/projectdiscovery/nuclei/v2/pkg/output"
    13  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
    14  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter"
    15  	"github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils"
    16  	"github.com/projectdiscovery/nuclei/v2/pkg/types"
    17  )
    18  
    19  // Match matches a generic data response again a given matcher
    20  func (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {
    21  	item, ok := request.getMatchPart(matcher.Part, data)
    22  	if !ok && matcher.Type.MatcherType != matchers.DSLMatcher {
    23  		return false, []string{}
    24  	}
    25  
    26  	switch matcher.GetType() {
    27  	case matchers.StatusMatcher:
    28  		statusCode, ok := getStatusCode(data)
    29  		if !ok {
    30  			return false, []string{}
    31  		}
    32  		return matcher.Result(matcher.MatchStatusCode(statusCode)), []string{responsehighlighter.CreateStatusCodeSnippet(data["response"].(string), statusCode)}
    33  	case matchers.SizeMatcher:
    34  		return matcher.Result(matcher.MatchSize(len(item))), []string{}
    35  	case matchers.WordsMatcher:
    36  		return matcher.ResultWithMatchedSnippet(matcher.MatchWords(item, data))
    37  	case matchers.RegexMatcher:
    38  		return matcher.ResultWithMatchedSnippet(matcher.MatchRegex(item))
    39  	case matchers.BinaryMatcher:
    40  		return matcher.ResultWithMatchedSnippet(matcher.MatchBinary(item))
    41  	case matchers.DSLMatcher:
    42  		return matcher.Result(matcher.MatchDSL(data)), []string{}
    43  	case matchers.XPathMatcher:
    44  		return matcher.Result(matcher.MatchXPath(item)), []string{}
    45  	}
    46  	return false, []string{}
    47  }
    48  
    49  func getStatusCode(data map[string]interface{}) (int, bool) {
    50  	statusCodeValue, ok := data["status_code"]
    51  	if !ok {
    52  		return 0, false
    53  	}
    54  	statusCode, ok := statusCodeValue.(int)
    55  	if !ok {
    56  		return 0, false
    57  	}
    58  	return statusCode, true
    59  }
    60  
    61  // Extract performs extracting operation for an extractor on model and returns true or false.
    62  func (request *Request) Extract(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {
    63  	item, ok := request.getMatchPart(extractor.Part, data)
    64  	if !ok && !extractors.SupportsMap(extractor) {
    65  		return nil
    66  	}
    67  	switch extractor.GetType() {
    68  	case extractors.RegexExtractor:
    69  		return extractor.ExtractRegex(item)
    70  	case extractors.KValExtractor:
    71  		return extractor.ExtractKval(data)
    72  	case extractors.XPathExtractor:
    73  		return extractor.ExtractXPath(item)
    74  	case extractors.JSONExtractor:
    75  		return extractor.ExtractJSON(item)
    76  	case extractors.DSLExtractor:
    77  		return extractor.ExtractDSL(data)
    78  	}
    79  	return nil
    80  }
    81  
    82  // getMatchPart returns the match part honoring "all" matchers + others.
    83  func (request *Request) getMatchPart(part string, data output.InternalEvent) (string, bool) {
    84  	if part == "" {
    85  		part = "body"
    86  	}
    87  	if part == "header" {
    88  		part = "all_headers"
    89  	}
    90  	var itemStr string
    91  
    92  	if part == "all" {
    93  		builder := &strings.Builder{}
    94  		builder.WriteString(types.ToString(data["body"]))
    95  		builder.WriteString(types.ToString(data["all_headers"]))
    96  		itemStr = builder.String()
    97  	} else {
    98  		item, ok := data[part]
    99  		if !ok {
   100  			return "", false
   101  		}
   102  		itemStr = types.ToString(item)
   103  	}
   104  	return itemStr, true
   105  }
   106  
   107  // responseToDSLMap converts an HTTP response to a map for use in DSL matching
   108  func (request *Request) responseToDSLMap(resp *http.Response, host, matched, rawReq, rawResp, body, headers string, duration time.Duration, extra map[string]interface{}) output.InternalEvent {
   109  	data := make(output.InternalEvent, 12+len(extra)+len(resp.Header)+len(resp.Cookies()))
   110  	for k, v := range extra {
   111  		data[k] = v
   112  	}
   113  	for _, cookie := range resp.Cookies() {
   114  		data[strings.ToLower(cookie.Name)] = cookie.Value
   115  	}
   116  	for k, v := range resp.Header {
   117  		k = strings.ToLower(strings.ReplaceAll(strings.TrimSpace(k), "-", "_"))
   118  		data[k] = strings.Join(v, " ")
   119  	}
   120  	data["host"] = host
   121  	data["type"] = request.Type().String()
   122  	data["matched"] = matched
   123  	data["request"] = rawReq
   124  	data["response"] = rawResp
   125  	data["status_code"] = resp.StatusCode
   126  	data["body"] = body
   127  	data["all_headers"] = headers
   128  	data["header"] = headers
   129  	data["duration"] = duration.Seconds()
   130  	data["template-id"] = request.options.TemplateID
   131  	data["template-info"] = request.options.TemplateInfo
   132  	data["template-path"] = request.options.TemplatePath
   133  
   134  	data["content_length"] = utils.CalculateContentLength(resp.ContentLength, int64(len(body)))
   135  
   136  	if request.StopAtFirstMatch || request.options.StopAtFirstMatch {
   137  		data["stop-at-first-match"] = true
   138  	}
   139  	return data
   140  }
   141  
   142  // MakeResultEvent creates a result event from internal wrapped event
   143  func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {
   144  	return protocols.MakeDefaultResultEvent(request, wrapped)
   145  }
   146  
   147  func (request *Request) GetCompiledOperators() []*operators.Operators {
   148  	return []*operators.Operators{request.CompiledOperators}
   149  }
   150  
   151  func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {
   152  	data := &output.ResultEvent{
   153  		TemplateID:       types.ToString(wrapped.InternalEvent["template-id"]),
   154  		TemplatePath:     types.ToString(wrapped.InternalEvent["template-path"]),
   155  		Info:             wrapped.InternalEvent["template-info"].(model.Info),
   156  		Type:             types.ToString(wrapped.InternalEvent["type"]),
   157  		Host:             types.ToString(wrapped.InternalEvent["host"]),
   158  		Matched:          types.ToString(wrapped.InternalEvent["matched"]),
   159  		Metadata:         wrapped.OperatorsResult.PayloadValues,
   160  		ExtractedResults: wrapped.OperatorsResult.OutputExtracts,
   161  		Timestamp:        time.Now(),
   162  		MatcherStatus:    true,
   163  		IP:               types.ToString(wrapped.InternalEvent["ip"]),
   164  		Request:          types.ToString(wrapped.InternalEvent["request"]),
   165  		Response:         request.truncateResponse(wrapped.InternalEvent["response"]),
   166  		CURLCommand:      types.ToString(wrapped.InternalEvent["curl-command"]),
   167  	}
   168  	return data
   169  }
   170  
   171  func (request *Request) truncateResponse(response interface{}) string {
   172  	responseString := types.ToString(response)
   173  	if len(responseString) > request.options.Options.ResponseSaveSize {
   174  		return responseString[:request.options.Options.ResponseSaveSize]
   175  	}
   176  	return responseString
   177  }