Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/http/request_fuzz.go
2070 views
1
package http
2
3
// === Fuzzing Documentation (Scoped to this File) =====
4
// -> request.executeFuzzingRule [iterates over payloads(+requests) and executes]
5
// -> request.executePayloadUsingRules [executes single payload on all rules (if more than 1)]
6
// -> request.executeGeneratedFuzzingRequest [execute final generated fuzzing request and get result]
7
8
import (
9
"context"
10
"fmt"
11
"net/http"
12
"strings"
13
14
"github.com/pkg/errors"
15
"github.com/projectdiscovery/gologger"
16
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz"
17
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers"
18
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
19
"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
20
"github.com/projectdiscovery/nuclei/v3/pkg/output"
21
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
22
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
23
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
24
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
25
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
26
protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
27
"github.com/projectdiscovery/nuclei/v3/pkg/types"
28
"github.com/projectdiscovery/retryablehttp-go"
29
"github.com/projectdiscovery/useragent"
30
urlutil "github.com/projectdiscovery/utils/url"
31
)
32
33
// executeFuzzingRule executes fuzzing request for a URL
34
// TODO:
35
// 1. use SPMHandler and rewrite stop at first match logic here
36
// 2. use scanContext instead of contextargs.Context
37
func (request *Request) executeFuzzingRule(input *contextargs.Context, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
38
// methdology:
39
// to check applicablity of rule, we first try to execute it with one value
40
// if it is applicable, we execute all requests
41
// if it is not applicable, we log and fail silently
42
43
// check if target should be fuzzed or not
44
if !request.ShouldFuzzTarget(input) {
45
urlx, _ := input.MetaInput.URL()
46
if urlx != nil {
47
gologger.Verbose().Msgf("[%s] fuzz: target(%s) not applicable for fuzzing\n", request.options.TemplateID, urlx.String())
48
} else {
49
gologger.Verbose().Msgf("[%s] fuzz: target(%s) not applicable for fuzzing\n", request.options.TemplateID, input.MetaInput.Input)
50
}
51
return nil
52
}
53
54
if input.MetaInput.Input == "" && input.MetaInput.ReqResp == nil {
55
return errors.New("empty input provided for fuzzing")
56
}
57
58
// ==== fuzzing when full HTTP request is provided =====
59
60
if input.MetaInput.ReqResp != nil {
61
baseRequest, err := input.MetaInput.ReqResp.BuildRequest()
62
if err != nil {
63
return errors.Wrap(err, "fuzz: could not build request obtained from target file")
64
}
65
request.addHeadersToRequest(baseRequest)
66
input.MetaInput.Input = baseRequest.String()
67
// execute with one value first to checks its applicability
68
err = request.executeAllFuzzingRules(input, previous, baseRequest, callback)
69
if err != nil {
70
// in case of any error, return it
71
if fuzz.IsErrRuleNotApplicable(err) {
72
// log and fail silently
73
gologger.Verbose().Msgf("[%s] fuzz: %s\n", request.options.TemplateID, err)
74
return nil
75
}
76
if errors.Is(err, ErrMissingVars) {
77
return err
78
}
79
gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed: %s\n", request.options.TemplateID, err)
80
}
81
return nil
82
}
83
84
// ==== fuzzing when only URL is provided =====
85
86
// we need to use this url instead of input
87
inputx := input.Clone()
88
parsed, err := urlutil.ParseAbsoluteURL(input.MetaInput.Input, true)
89
if err != nil {
90
return errors.Wrap(err, "fuzz: could not parse input url")
91
}
92
baseRequest, err := retryablehttp.NewRequestFromURL(http.MethodGet, parsed, nil)
93
if err != nil {
94
return errors.Wrap(err, "fuzz: could not build request from url")
95
}
96
userAgent := useragent.PickRandom()
97
baseRequest.Header.Set("User-Agent", userAgent.Raw)
98
request.addHeadersToRequest(baseRequest)
99
100
// execute with one value first to checks its applicability
101
err = request.executeAllFuzzingRules(inputx, previous, baseRequest, callback)
102
if err != nil {
103
// in case of any error, return it
104
if fuzz.IsErrRuleNotApplicable(err) {
105
// log and fail silently
106
gologger.Verbose().Msgf("[%s] fuzz: %s\n", request.options.TemplateID, err)
107
return nil
108
}
109
if errors.Is(err, ErrMissingVars) {
110
return err
111
}
112
gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed: %s\n", request.options.TemplateID, err)
113
}
114
return nil
115
}
116
117
func (request *Request) addHeadersToRequest(baseRequest *retryablehttp.Request) {
118
for k, v := range request.Headers {
119
baseRequest.Header.Set(k, v)
120
}
121
}
122
123
// executeAllFuzzingRules executes all fuzzing rules defined in template for a given base request
124
func (request *Request) executeAllFuzzingRules(input *contextargs.Context, values map[string]interface{}, baseRequest *retryablehttp.Request, callback protocols.OutputEventCallback) error {
125
applicable := false
126
values = generators.MergeMaps(request.filterDataMap(input), values)
127
for _, rule := range request.Fuzzing {
128
select {
129
case <-input.Context().Done():
130
return input.Context().Err()
131
default:
132
}
133
134
input := &fuzz.ExecuteRuleInput{
135
Input: input,
136
DisplayFuzzPoints: request.options.Options.DisplayFuzzPoints,
137
Callback: func(gr fuzz.GeneratedRequest) bool {
138
select {
139
case <-input.Context().Done():
140
return false
141
default:
142
}
143
144
// TODO: replace this after scanContext Refactor
145
return request.executeGeneratedFuzzingRequest(gr, input, callback)
146
},
147
Values: values,
148
BaseRequest: baseRequest.Clone(context.TODO()),
149
}
150
if request.Analyzer != nil {
151
analyzer := analyzers.GetAnalyzer(request.Analyzer.Name)
152
input.ApplyPayloadInitialTransformation = analyzer.ApplyInitialTransformation
153
input.AnalyzerParams = request.Analyzer.Parameters
154
}
155
err := rule.Execute(input)
156
if err == nil {
157
applicable = true
158
continue
159
}
160
if fuzz.IsErrRuleNotApplicable(err) {
161
gologger.Verbose().Msgf("[%s] fuzz: %s\n", request.options.TemplateID, err)
162
continue
163
}
164
if err == types.ErrNoMoreRequests {
165
return nil
166
}
167
return errors.Wrap(err, "could not execute rule")
168
}
169
170
if !applicable {
171
return fmt.Errorf("no rule was applicable for this request: %v", input.MetaInput.Input)
172
}
173
174
return nil
175
}
176
177
// executeGeneratedFuzzingRequest executes a generated fuzzing request after building it using rules and payloads
178
func (request *Request) executeGeneratedFuzzingRequest(gr fuzz.GeneratedRequest, input *contextargs.Context, callback protocols.OutputEventCallback) bool {
179
hasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators)
180
hasInteractMarkers := len(gr.InteractURLs) > 0
181
if request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(request.options.ProtocolType.String(), input) {
182
return false
183
}
184
request.options.RateLimitTake()
185
req := &generatedRequest{
186
request: gr.Request,
187
dynamicValues: gr.DynamicValues,
188
interactshURLs: gr.InteractURLs,
189
original: request,
190
fuzzGeneratedRequest: gr,
191
}
192
var gotMatches bool
193
requestErr := request.executeRequest(input, req, gr.DynamicValues, hasInteractMatchers, func(event *output.InternalWrappedEvent) {
194
for _, result := range event.Results {
195
result.IsFuzzingResult = true
196
result.FuzzingMethod = gr.Request.Method
197
result.FuzzingParameter = gr.Parameter
198
result.FuzzingPosition = gr.Component.Name()
199
}
200
201
setInteractshCallback := false
202
if hasInteractMarkers && hasInteractMatchers && request.options.Interactsh != nil {
203
requestData := &interactsh.RequestData{
204
MakeResultFunc: request.MakeResultEvent,
205
Event: event,
206
Operators: request.CompiledOperators,
207
MatchFunc: request.Match,
208
ExtractFunc: request.Extract,
209
Parameter: gr.Parameter,
210
Request: gr.Request,
211
}
212
setInteractshCallback = true
213
request.options.Interactsh.RequestEvent(gr.InteractURLs, requestData)
214
gotMatches = request.options.Interactsh.AlreadyMatched(requestData)
215
} else {
216
callback(event)
217
}
218
// Add the extracts to the dynamic values if any.
219
if event.OperatorsResult != nil {
220
gotMatches = event.OperatorsResult.Matched
221
}
222
if request.options.FuzzParamsFrequency != nil && !setInteractshCallback {
223
if !gotMatches {
224
request.options.FuzzParamsFrequency.MarkParameter(gr.Parameter, gr.Request.String(), request.options.TemplateID)
225
} else {
226
request.options.FuzzParamsFrequency.UnmarkParameter(gr.Parameter, gr.Request.String(), request.options.TemplateID)
227
}
228
}
229
}, 0)
230
// If a variable is unresolved, skip all further requests
231
if errors.Is(requestErr, ErrMissingVars) {
232
return false
233
}
234
if requestErr != nil {
235
gologger.Verbose().Msgf("[%s] Error occurred in request: %s\n", request.options.TemplateID, requestErr)
236
}
237
if request.options.HostErrorsCache != nil {
238
request.options.HostErrorsCache.MarkFailedOrRemove(request.options.ProtocolType.String(), input, requestErr)
239
}
240
request.options.Progress.IncrementRequests()
241
242
// If this was a match, and we want to stop at first match, skip all further requests.
243
shouldStopAtFirstMatch := request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch
244
if shouldStopAtFirstMatch && gotMatches {
245
return false
246
}
247
return true
248
}
249
250
// ShouldFuzzTarget checks if given target should be fuzzed or not using `filter` field in template
251
func (request *Request) ShouldFuzzTarget(input *contextargs.Context) bool {
252
if len(request.FuzzPreCondition) == 0 {
253
return true
254
}
255
status := []bool{}
256
for index, filter := range request.FuzzPreCondition {
257
dataMap := request.filterDataMap(input)
258
// dump if svd is enabled
259
if request.options.Options.ShowVarDump {
260
gologger.Debug().Msgf("Fuzz Filter Variables: \n%s\n", vardump.DumpVariables(dataMap))
261
}
262
isMatch, _ := request.Match(dataMap, filter)
263
status = append(status, isMatch)
264
if request.options.Options.MatcherStatus {
265
gologger.Debug().Msgf("[%s] [%s] Filter => %s : %v", input.MetaInput.Target(), request.options.TemplateID, operators.GetMatcherName(filter, index), isMatch)
266
}
267
}
268
if len(status) == 0 {
269
return true
270
}
271
var matched bool
272
if request.fuzzPreConditionOperator == matchers.ANDCondition {
273
matched = operators.EvalBoolSlice(status, true)
274
} else {
275
matched = operators.EvalBoolSlice(status, false)
276
}
277
if request.options.Options.MatcherStatus {
278
gologger.Debug().Msgf("[%s] [%s] Final Filter Status => %v", input.MetaInput.Target(), request.options.TemplateID, matched)
279
}
280
return matched
281
}
282
283
// input data map returns map[string]interface{} from input
284
func (request *Request) filterDataMap(input *contextargs.Context) map[string]interface{} {
285
m := make(map[string]interface{})
286
parsed, err := input.MetaInput.URL()
287
if err != nil {
288
m["host"] = input.MetaInput.Input
289
return m
290
}
291
m = protocolutils.GenerateVariables(parsed, true, m)
292
for k, v := range m {
293
m[strings.ToLower(k)] = v
294
}
295
m["path"] = parsed.Path // override existing
296
m["query"] = parsed.RawQuery
297
// add request data like headers, body etc
298
if input.MetaInput.ReqResp != nil && input.MetaInput.ReqResp.Request != nil {
299
req := input.MetaInput.ReqResp.Request
300
m["method"] = req.Method
301
m["body"] = req.Body
302
303
sb := &strings.Builder{}
304
req.Headers.Iterate(func(k, v string) bool {
305
k = strings.ToLower(strings.ReplaceAll(strings.TrimSpace(k), "-", "_"))
306
if strings.EqualFold(k, "Cookie") {
307
m["cookie"] = v
308
}
309
if strings.EqualFold(k, "User_Agent") {
310
m["user_agent"] = v
311
}
312
if strings.EqualFold(k, "content_type") {
313
m["content_type"] = v
314
}
315
_, _ = fmt.Fprintf(sb, "%s: %s\n", k, v)
316
return true
317
})
318
m["header"] = sb.String()
319
} else {
320
// add default method value
321
m["method"] = http.MethodGet
322
}
323
return m
324
}
325
326