Path: blob/dev/pkg/protocols/offlinehttp/operators.go
2070 views
package offlinehttp12import (3"maps"4"net/http"5"strings"6"time"78"github.com/projectdiscovery/nuclei/v3/pkg/model"9"github.com/projectdiscovery/nuclei/v3/pkg/operators"10"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors"11"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"12"github.com/projectdiscovery/nuclei/v3/pkg/output"13"github.com/projectdiscovery/nuclei/v3/pkg/protocols"14"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter"15"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"16"github.com/projectdiscovery/nuclei/v3/pkg/types"17)1819// Match matches a generic data response again a given matcher20func (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {21item, ok := getMatchPart(matcher.Part, data)22if !ok && matcher.Type.MatcherType != matchers.DSLMatcher {23return false, []string{}24}2526switch matcher.GetType() {27case matchers.StatusMatcher:28statusCode, ok := getStatusCode(data)29if !ok {30return false, []string{}31}32return matcher.Result(matcher.MatchStatusCode(statusCode)), []string{responsehighlighter.CreateStatusCodeSnippet(data["response"].(string), statusCode)}33case matchers.SizeMatcher:34return matcher.Result(matcher.MatchSize(len(item))), []string{}35case matchers.WordsMatcher:36return matcher.ResultWithMatchedSnippet(matcher.MatchWords(item, nil))37case matchers.RegexMatcher:38return matcher.ResultWithMatchedSnippet(matcher.MatchRegex(item))39case matchers.BinaryMatcher:40return matcher.ResultWithMatchedSnippet(matcher.MatchBinary(item))41case matchers.DSLMatcher:42return matcher.Result(matcher.MatchDSL(data)), []string{}43case matchers.XPathMatcher:44return matcher.Result(matcher.MatchXPath(item)), []string{}45}46return false, []string{}47}4849func getStatusCode(data map[string]interface{}) (int, bool) {50statusCodeValue, ok := data["status_code"]51if !ok {52return 0, false53}54statusCode, ok := statusCodeValue.(int)55if !ok {56return 0, false57}58return statusCode, true59}6061// Extract performs extracting operation for an extractor on model and returns true or false.62func (request *Request) Extract(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {63item, ok := getMatchPart(extractor.Part, data)64if !ok && !extractors.SupportsMap(extractor) {65return nil66}67switch extractor.GetType() {68case extractors.RegexExtractor:69return extractor.ExtractRegex(item)70case extractors.KValExtractor:71return extractor.ExtractKval(data)72case extractors.DSLExtractor:73return extractor.ExtractDSL(data)74}75return nil76}7778// getMatchPart returns the match part honoring "all" matchers + others.79func getMatchPart(part string, data output.InternalEvent) (string, bool) {80if part == "" {81part = "body"82}83if part == "header" {84part = "all_headers"85}86var itemStr string8788if part == "all" {89builder := &strings.Builder{}90builder.WriteString(types.ToString(data["body"]))91builder.WriteString(types.ToString(data["all_headers"]))92itemStr = builder.String()93} else {94item, ok := data[part]95if !ok {96return "", false97}98itemStr = types.ToString(item)99}100return itemStr, true101}102103// responseToDSLMap converts an HTTP response to a map for use in DSL matching104func (request *Request) responseToDSLMap(resp *http.Response, host, matched, rawReq, rawResp, body, headers string, duration time.Duration, extra map[string]interface{}) output.InternalEvent {105data := make(output.InternalEvent, 12+len(extra)+len(resp.Header)+len(resp.Cookies()))106maps.Copy(data, extra)107for _, cookie := range resp.Cookies() {108data[strings.ToLower(cookie.Name)] = cookie.Value109}110for k, v := range resp.Header {111k = strings.ToLower(strings.ReplaceAll(strings.TrimSpace(k), "-", "_"))112data[k] = strings.Join(v, " ")113}114115data["path"] = host116data["matched"] = matched117data["request"] = rawReq118data["response"] = rawResp119data["status_code"] = resp.StatusCode120data["body"] = body121data["type"] = request.Type().String()122data["all_headers"] = headers123data["duration"] = duration.Seconds()124data["template-id"] = request.options.TemplateID125data["template-info"] = request.options.TemplateInfo126data["template-path"] = request.options.TemplatePath127data["content_length"] = utils.CalculateContentLength(resp.ContentLength, int64(len(body)))128129return data130}131132// MakeResultEvent creates a result event from internal wrapped event133func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {134return protocols.MakeDefaultResultEvent(request, wrapped)135}136137func (request *Request) GetCompiledOperators() []*operators.Operators {138return request.compiledOperators139}140141func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {142data := &output.ResultEvent{143TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),144TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),145Info: wrapped.InternalEvent["template-info"].(model.Info),146TemplateVerifier: request.options.TemplateVerifier,147Type: types.ToString(wrapped.InternalEvent["type"]),148Path: types.ToString(wrapped.InternalEvent["path"]),149Matched: types.ToString(wrapped.InternalEvent["matched"]),150Metadata: wrapped.OperatorsResult.PayloadValues,151ExtractedResults: wrapped.OperatorsResult.OutputExtracts,152MatcherStatus: true,153IP: types.ToString(wrapped.InternalEvent["ip"]),154Request: types.ToString(wrapped.InternalEvent["request"]),155Response: types.ToString(wrapped.InternalEvent["raw"]),156TemplateEncoded: request.options.EncodeTemplate(),157Error: types.ToString(wrapped.InternalEvent["error"]),158}159return data160}161162163