Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/http/operators.go
2070 views
1
package http
2
3
import (
4
"maps"
5
"net/http"
6
"strings"
7
"time"
8
9
"github.com/projectdiscovery/nuclei/v3/pkg/model"
10
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
11
"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors"
12
"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
13
"github.com/projectdiscovery/nuclei/v3/pkg/output"
14
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
15
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter"
16
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
17
"github.com/projectdiscovery/nuclei/v3/pkg/types"
18
)
19
20
// Match matches a generic data response again a given matcher
21
// TODO: Try to consolidate this in protocols.MakeDefaultMatchFunc to avoid any inconsistencies
22
func (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {
23
item, ok := request.getMatchPart(matcher.Part, data)
24
if !ok && matcher.Type.MatcherType != matchers.DSLMatcher {
25
return false, []string{}
26
}
27
28
switch matcher.GetType() {
29
case matchers.StatusMatcher:
30
statusCode, ok := getStatusCode(data)
31
if !ok {
32
return false, []string{}
33
}
34
return matcher.Result(matcher.MatchStatusCode(statusCode)), []string{responsehighlighter.CreateStatusCodeSnippet(data["response"].(string), statusCode)}
35
case matchers.SizeMatcher:
36
return matcher.Result(matcher.MatchSize(len(item))), []string{}
37
case matchers.WordsMatcher:
38
return matcher.ResultWithMatchedSnippet(matcher.MatchWords(item, data))
39
case matchers.RegexMatcher:
40
return matcher.ResultWithMatchedSnippet(matcher.MatchRegex(item))
41
case matchers.BinaryMatcher:
42
return matcher.ResultWithMatchedSnippet(matcher.MatchBinary(item))
43
case matchers.DSLMatcher:
44
return matcher.Result(matcher.MatchDSL(data)), []string{}
45
case matchers.XPathMatcher:
46
return matcher.Result(matcher.MatchXPath(item)), []string{}
47
}
48
return false, []string{}
49
}
50
51
func getStatusCode(data map[string]interface{}) (int, bool) {
52
statusCodeValue, ok := data["status_code"]
53
if !ok {
54
return 0, false
55
}
56
statusCode, ok := statusCodeValue.(int)
57
if !ok {
58
return 0, false
59
}
60
return statusCode, true
61
}
62
63
// Extract performs extracting operation for an extractor on model and returns true or false.
64
func (request *Request) Extract(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {
65
item, ok := request.getMatchPart(extractor.Part, data)
66
if !ok && !extractors.SupportsMap(extractor) {
67
return nil
68
}
69
switch extractor.GetType() {
70
case extractors.RegexExtractor:
71
return extractor.ExtractRegex(item)
72
case extractors.KValExtractor:
73
return extractor.ExtractKval(data)
74
case extractors.XPathExtractor:
75
return extractor.ExtractXPath(item)
76
case extractors.JSONExtractor:
77
return extractor.ExtractJSON(item)
78
case extractors.DSLExtractor:
79
return extractor.ExtractDSL(data)
80
}
81
return nil
82
}
83
84
// getMatchPart returns the match part honoring "all" matchers + others.
85
func (request *Request) getMatchPart(part string, data output.InternalEvent) (string, bool) {
86
if part == "" {
87
part = "body"
88
}
89
if part == "header" {
90
part = "all_headers"
91
}
92
var itemStr string
93
94
if part == "all" {
95
builder := &strings.Builder{}
96
builder.WriteString(types.ToString(data["body"]))
97
builder.WriteString(types.ToString(data["all_headers"]))
98
itemStr = builder.String()
99
} else {
100
item, ok := data[part]
101
if !ok {
102
return "", false
103
}
104
itemStr = types.ToString(item)
105
}
106
return itemStr, true
107
}
108
109
// responseToDSLMap converts an HTTP response to a map for use in DSL matching
110
func (request *Request) responseToDSLMap(resp *http.Response, host, matched, rawReq, rawResp, body, headers string, duration time.Duration, extra map[string]interface{}) output.InternalEvent {
111
data := make(output.InternalEvent, 12+len(extra)+len(resp.Header)+len(resp.Cookies()))
112
maps.Copy(data, extra)
113
for _, cookie := range resp.Cookies() {
114
request.setHashOrDefault(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
request.setHashOrDefault(data, k, strings.Join(v, " "))
119
}
120
data["host"] = host
121
data["type"] = request.Type().String()
122
data["matched"] = matched
123
request.setHashOrDefault(data, "request", rawReq)
124
request.setHashOrDefault(data, "response", rawResp)
125
data["status_code"] = resp.StatusCode
126
request.setHashOrDefault(data, "body", body)
127
request.setHashOrDefault(data, "all_headers", headers)
128
request.setHashOrDefault(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
// TODO: disabling hdd storage while testing backpressure mechanism
143
func (request *Request) setHashOrDefault(data output.InternalEvent, k string, v string) {
144
// if hash, err := request.options.Storage.SetString(v); err == nil {
145
// data[k] = hash
146
// } else {
147
data[k] = v
148
//}
149
}
150
151
// MakeResultEvent creates a result event from internal wrapped event
152
func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {
153
return protocols.MakeDefaultResultEvent(request, wrapped)
154
}
155
156
func (request *Request) GetCompiledOperators() []*operators.Operators {
157
return []*operators.Operators{request.CompiledOperators}
158
}
159
160
func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {
161
fields := utils.GetJsonFieldsFromURL(types.ToString(wrapped.InternalEvent["host"]))
162
if types.ToString(wrapped.InternalEvent["ip"]) != "" {
163
fields.Ip = types.ToString(wrapped.InternalEvent["ip"])
164
}
165
if types.ToString(wrapped.InternalEvent["path"]) != "" {
166
fields.Path = types.ToString(wrapped.InternalEvent["path"])
167
}
168
var isGlobalMatchers bool
169
if value, ok := wrapped.InternalEvent["global-matchers"]; ok {
170
isGlobalMatchers = value.(bool)
171
}
172
var analyzerDetails string
173
if value, ok := wrapped.InternalEvent["analyzer_details"]; ok {
174
analyzerDetails = value.(string)
175
}
176
data := &output.ResultEvent{
177
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
178
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
179
Info: wrapped.InternalEvent["template-info"].(model.Info),
180
TemplateVerifier: request.options.TemplateVerifier,
181
Type: types.ToString(wrapped.InternalEvent["type"]),
182
Host: fields.Host,
183
Port: fields.Port,
184
Scheme: fields.Scheme,
185
URL: fields.URL,
186
Path: fields.Path,
187
Matched: types.ToString(wrapped.InternalEvent["matched"]),
188
Metadata: wrapped.OperatorsResult.PayloadValues,
189
ExtractedResults: wrapped.OperatorsResult.OutputExtracts,
190
Timestamp: time.Now(),
191
MatcherStatus: true,
192
IP: fields.Ip,
193
GlobalMatchers: isGlobalMatchers,
194
Request: types.ToString(wrapped.InternalEvent["request"]),
195
Response: request.truncateResponse(wrapped.InternalEvent["response"]),
196
CURLCommand: types.ToString(wrapped.InternalEvent["curl-command"]),
197
TemplateEncoded: request.options.EncodeTemplate(),
198
Error: types.ToString(wrapped.InternalEvent["error"]),
199
AnalyzerDetails: analyzerDetails,
200
}
201
return data
202
}
203
204
func (request *Request) truncateResponse(response interface{}) string {
205
responseString := types.ToString(response)
206
if len(responseString) > request.options.Options.ResponseSaveSize {
207
return responseString[:request.options.Options.ResponseSaveSize]
208
}
209
return responseString
210
}
211
212