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 }