Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/http/request.go
2070 views
1
package http
2
3
import (
4
"bytes"
5
"context"
6
"encoding/hex"
7
"fmt"
8
"io"
9
"maps"
10
"net/http"
11
"strconv"
12
"strings"
13
"sync"
14
"sync/atomic"
15
"time"
16
17
"github.com/pkg/errors"
18
"go.uber.org/multierr"
19
"moul.io/http2curl"
20
21
"github.com/projectdiscovery/fastdialer/fastdialer"
22
"github.com/projectdiscovery/gologger"
23
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers"
24
fuzzStats "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats"
25
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
26
"github.com/projectdiscovery/nuclei/v3/pkg/output"
27
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
28
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
29
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
30
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
31
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator"
32
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter"
33
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
34
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
35
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool"
36
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httputils"
37
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/signer"
38
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/signerpool"
39
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
40
"github.com/projectdiscovery/nuclei/v3/pkg/types"
41
"github.com/projectdiscovery/nuclei/v3/pkg/types/nucleierr"
42
"github.com/projectdiscovery/rawhttp"
43
convUtil "github.com/projectdiscovery/utils/conversion"
44
"github.com/projectdiscovery/utils/errkit"
45
httpUtils "github.com/projectdiscovery/utils/http"
46
"github.com/projectdiscovery/utils/reader"
47
sliceutil "github.com/projectdiscovery/utils/slice"
48
stringsutil "github.com/projectdiscovery/utils/strings"
49
unitutils "github.com/projectdiscovery/utils/unit"
50
urlutil "github.com/projectdiscovery/utils/url"
51
)
52
53
const (
54
defaultMaxWorkers = 150
55
// max unique errors to store & combine
56
// when executing requests in parallel
57
maxErrorsWhenParallel = 3
58
)
59
60
var (
61
MaxBodyRead = 10 * unitutils.Mega
62
// ErrMissingVars is error occured when variables are missing
63
ErrMissingVars = errkit.New("stop execution due to unresolved variables").SetKind(nucleierr.ErrTemplateLogic).Build()
64
// ErrHttpEngineRequestDeadline is error occured when request deadline set by http request engine is exceeded
65
ErrHttpEngineRequestDeadline = errkit.New("http request engine deadline exceeded").SetKind(errkit.ErrKindDeadline).Build()
66
)
67
68
// Type returns the type of the protocol request
69
func (request *Request) Type() templateTypes.ProtocolType {
70
return templateTypes.HTTPProtocol
71
}
72
73
// executeRaceRequest executes race condition request for a URL
74
func (request *Request) executeRaceRequest(input *contextargs.Context, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
75
reqURL := input.MetaInput.Input
76
var generatedRequests []*generatedRequest
77
78
// Requests within race condition should be dumped once and the output prefilled to allow DSL language to work
79
// This will introduce a delay and will populate in hacky way the field "request" of outputEvent
80
generator := request.newGenerator(false)
81
82
inputData, payloads, ok := generator.nextValue()
83
if !ok {
84
return nil
85
}
86
ctx := request.newContext(input)
87
requestForDump, err := generator.Make(ctx, input, inputData, payloads, nil)
88
if err != nil {
89
return err
90
}
91
request.setCustomHeaders(requestForDump)
92
dumpedRequest, err := dump(requestForDump, reqURL)
93
if err != nil {
94
return err
95
}
96
if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {
97
msg := fmt.Sprintf("[%s] Dumped HTTP request for %s\n\n", request.options.TemplateID, reqURL)
98
if request.options.Options.Debug || request.options.Options.DebugRequests {
99
gologger.Info().Msg(msg)
100
gologger.Print().Msgf("%s", string(dumpedRequest))
101
}
102
if request.options.Options.StoreResponse {
103
request.options.Output.WriteStoreDebugData(reqURL, request.options.TemplateID, request.Type().String(), fmt.Sprintf("%s\n%s", msg, dumpedRequest))
104
}
105
}
106
previous["request"] = string(dumpedRequest)
107
108
// Pre-Generate requests
109
for i := 0; i < request.RaceNumberRequests; i++ {
110
generator := request.newGenerator(false)
111
inputData, payloads, ok := generator.nextValue()
112
if !ok {
113
break
114
}
115
ctx := request.newContext(input)
116
generatedRequest, err := generator.Make(ctx, input, inputData, payloads, nil)
117
if err != nil {
118
return err
119
}
120
generatedRequests = append(generatedRequests, generatedRequest)
121
}
122
123
shouldStop := (request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch || request.options.StopAtFirstMatch)
124
125
childCtx, cancel := context.WithCancel(context.Background())
126
defer cancel()
127
128
spmHandler := httputils.NewNonBlockingSPMHandler[error](childCtx, maxErrorsWhenParallel, shouldStop)
129
defer spmHandler.Cancel()
130
131
gotMatches := &atomic.Bool{}
132
// wrappedCallback is a callback that wraps the original callback
133
// to implement stop at first match logic
134
wrappedCallback := func(event *output.InternalWrappedEvent) {
135
if !event.HasOperatorResult() {
136
callback(event) // not required but we can allow it
137
return
138
}
139
// this will execute match condition such that if stop at first match is enabled
140
// this will be only executed once
141
spmHandler.MatchCallback(func() {
142
gotMatches.Store(true)
143
callback(event)
144
})
145
if shouldStop {
146
// stop all running requests and exit
147
spmHandler.Trigger()
148
}
149
}
150
151
// look for unresponsive hosts and cancel inflight requests as well
152
spmHandler.SetOnResultCallback(func(err error) {
153
// marks thsi host as unresponsive if applicable
154
request.markHostError(input, err)
155
if request.isUnresponsiveAddress(input) {
156
// stop all inflight requests
157
spmHandler.Cancel()
158
}
159
})
160
161
for i := 0; i < request.RaceNumberRequests; i++ {
162
if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(input) {
163
// stop sending more requests condition is met
164
break
165
}
166
spmHandler.Acquire()
167
// execute http request
168
go func(httpRequest *generatedRequest) {
169
defer spmHandler.Release()
170
if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(input) {
171
// stop sending more requests condition is met
172
return
173
}
174
175
select {
176
case <-spmHandler.Done():
177
return
178
case spmHandler.ResultChan <- request.executeRequest(input, httpRequest, previous, false, wrappedCallback, 0):
179
return
180
}
181
}(generatedRequests[i])
182
request.options.Progress.IncrementRequests()
183
}
184
spmHandler.Wait()
185
186
if spmHandler.FoundFirstMatch() {
187
// ignore any context cancellation and in-transit execution errors
188
return nil
189
}
190
return multierr.Combine(spmHandler.CombinedResults()...)
191
}
192
193
// executeRaceRequest executes parallel requests for a template
194
func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicValues output.InternalEvent, callback protocols.OutputEventCallback) error {
195
// Workers that keeps enqueuing new requests
196
maxWorkers := request.Threads
197
198
// if request threads matches global payload concurrency we follow it
199
shouldFollowGlobal := maxWorkers == request.options.Options.PayloadConcurrency
200
201
if protocolstate.IsLowOnMemory() {
202
maxWorkers = protocolstate.GuardThreadsOrDefault(request.Threads)
203
}
204
205
// Stop-at-first-match logic while executing requests
206
// parallely using threads
207
shouldStop := (request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch || request.options.StopAtFirstMatch)
208
209
ctx, cancel := context.WithCancel(context.Background())
210
defer cancel()
211
212
spmHandler := httputils.NewBlockingSPMHandler[error](ctx, maxWorkers, maxErrorsWhenParallel, shouldStop)
213
defer spmHandler.Cancel()
214
215
// wrappedCallback is a callback that wraps the original callback
216
// to implement stop at first match logic
217
wrappedCallback := func(event *output.InternalWrappedEvent) {
218
if !event.HasOperatorResult() {
219
callback(event) // not required but we can allow it
220
return
221
}
222
// this will execute match condition such that if stop at first match is enabled
223
// this will be only executed once
224
spmHandler.MatchCallback(func() {
225
callback(event)
226
})
227
if shouldStop {
228
// stop all running requests and exit
229
spmHandler.Trigger()
230
}
231
}
232
233
// look for unresponsive hosts and cancel inflight requests as well
234
spmHandler.SetOnResultCallback(func(err error) {
235
// marks thsi host as unresponsive if applicable
236
request.markHostError(input, err)
237
if request.isUnresponsiveAddress(input) {
238
// stop all inflight requests
239
spmHandler.Cancel()
240
}
241
})
242
243
// iterate payloads and make requests
244
generator := request.newGenerator(false)
245
for {
246
inputData, payloads, ok := generator.nextValue()
247
if !ok {
248
break
249
}
250
251
select {
252
case <-input.Context().Done():
253
return input.Context().Err()
254
default:
255
}
256
257
// resize check point - nop if there are no changes
258
if shouldFollowGlobal && spmHandler.Size() != request.options.Options.PayloadConcurrency {
259
if err := spmHandler.Resize(input.Context(), request.options.Options.PayloadConcurrency); err != nil {
260
return err
261
}
262
}
263
264
// break if stop at first match is found or host is unresponsive
265
if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(input) {
266
break
267
}
268
269
ctx := request.newContext(input)
270
generatedHttpRequest, err := generator.Make(ctx, input, inputData, payloads, dynamicValues)
271
if err != nil {
272
if err == types.ErrNoMoreRequests {
273
break
274
}
275
request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))
276
return err
277
}
278
if input.MetaInput.Input == "" {
279
input.MetaInput.Input = generatedHttpRequest.URL()
280
}
281
updatedInput := contextargs.GetCopyIfHostOutdated(input, generatedHttpRequest.URL())
282
if request.isUnresponsiveAddress(updatedInput) {
283
// skip on unresponsive host no need to continue
284
spmHandler.Cancel()
285
return nil
286
}
287
spmHandler.Acquire()
288
go func(httpRequest *generatedRequest) {
289
defer spmHandler.Release()
290
if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(updatedInput) || spmHandler.Cancelled() {
291
return
292
}
293
// putting ratelimiter here prevents any unnecessary waiting if any
294
request.options.RateLimitTake()
295
296
// after ratelimit take, check if we need to stop
297
if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(updatedInput) || spmHandler.Cancelled() {
298
return
299
}
300
301
select {
302
case <-spmHandler.Done():
303
return
304
case spmHandler.ResultChan <- request.executeRequest(input, httpRequest, make(map[string]interface{}), false, wrappedCallback, 0):
305
return
306
}
307
}(generatedHttpRequest)
308
request.options.Progress.IncrementRequests()
309
}
310
spmHandler.Wait()
311
if spmHandler.FoundFirstMatch() {
312
// ignore any context cancellation and in-transit execution errors
313
return nil
314
}
315
return multierr.Combine(spmHandler.CombinedResults()...)
316
}
317
318
// executeTurboHTTP executes turbo http request for a URL
319
func (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
320
generator := request.newGenerator(false)
321
322
// need to extract the target from the url
323
URL, err := urlutil.Parse(input.MetaInput.Input)
324
if err != nil {
325
return err
326
}
327
328
pipeOptions := rawhttp.DefaultPipelineOptions
329
pipeOptions.Host = URL.Host
330
pipeOptions.MaxConnections = 1
331
if request.PipelineConcurrentConnections > 0 {
332
pipeOptions.MaxConnections = request.PipelineConcurrentConnections
333
}
334
if request.PipelineRequestsPerConnection > 0 {
335
pipeOptions.MaxPendingRequests = request.PipelineRequestsPerConnection
336
}
337
pipeClient := rawhttp.NewPipelineClient(pipeOptions)
338
339
// defaultMaxWorkers should be a sufficient value to keep queues always full
340
// in case the queue is bigger increase the workers
341
maxWorkers := max(pipeOptions.MaxPendingRequests, defaultMaxWorkers)
342
343
// Stop-at-first-match logic while executing requests
344
// parallely using threads
345
shouldStop := (request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch || request.options.StopAtFirstMatch)
346
347
ctx, cancel := context.WithCancel(context.Background())
348
defer cancel()
349
350
spmHandler := httputils.NewBlockingSPMHandler[error](ctx, maxWorkers, maxErrorsWhenParallel, shouldStop)
351
defer spmHandler.Cancel()
352
353
// wrappedCallback is a callback that wraps the original callback
354
// to implement stop at first match logic
355
wrappedCallback := func(event *output.InternalWrappedEvent) {
356
if !event.HasOperatorResult() {
357
callback(event) // not required but we can allow it
358
return
359
}
360
// this will execute match condition such that if stop at first match is enabled
361
// this will be only executed once
362
spmHandler.MatchCallback(func() {
363
callback(event)
364
})
365
if shouldStop {
366
// stop all running requests and exit
367
spmHandler.Trigger()
368
}
369
}
370
371
// look for unresponsive hosts and cancel inflight requests as well
372
spmHandler.SetOnResultCallback(func(err error) {
373
// marks thsi host as unresponsive if applicable
374
request.markHostError(input, err)
375
if request.isUnresponsiveAddress(input) {
376
// stop all inflight requests
377
spmHandler.Cancel()
378
}
379
})
380
381
for {
382
inputData, payloads, ok := generator.nextValue()
383
if !ok {
384
break
385
}
386
387
select {
388
case <-input.Context().Done():
389
return input.Context().Err()
390
default:
391
}
392
393
if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(input) || spmHandler.Cancelled() {
394
// skip if first match is found
395
break
396
}
397
398
ctx := request.newContext(input)
399
generatedHttpRequest, err := generator.Make(ctx, input, inputData, payloads, dynamicValues)
400
if err != nil {
401
request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))
402
return err
403
}
404
if input.MetaInput.Input == "" {
405
input.MetaInput.Input = generatedHttpRequest.URL()
406
}
407
updatedInput := contextargs.GetCopyIfHostOutdated(input, generatedHttpRequest.URL())
408
if request.isUnresponsiveAddress(updatedInput) {
409
// skip on unresponsive host no need to continue
410
spmHandler.Cancel()
411
return nil
412
}
413
generatedHttpRequest.pipelinedClient = pipeClient
414
spmHandler.Acquire()
415
go func(httpRequest *generatedRequest) {
416
defer spmHandler.Release()
417
if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(updatedInput) {
418
// skip if first match is found
419
return
420
}
421
select {
422
case <-spmHandler.Done():
423
return
424
case spmHandler.ResultChan <- request.executeRequest(input, httpRequest, previous, false, wrappedCallback, 0):
425
return
426
}
427
}(generatedHttpRequest)
428
request.options.Progress.IncrementRequests()
429
}
430
spmHandler.Wait()
431
if spmHandler.FoundFirstMatch() {
432
// ignore any context cancellation and in-transit execution errors
433
return nil
434
}
435
return multierr.Combine(spmHandler.CombinedResults()...)
436
}
437
438
// ExecuteWithResults executes the final request on a URL
439
func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
440
if request.Pipeline || request.Race && request.RaceNumberRequests > 0 || request.Threads > 0 {
441
variablesMap := request.options.Variables.Evaluate(generators.MergeMaps(dynamicValues, previous))
442
dynamicValues = generators.MergeMaps(variablesMap, dynamicValues, request.options.Constants)
443
}
444
// verify if pipeline was requested
445
if request.Pipeline {
446
return request.executeTurboHTTP(input, dynamicValues, previous, callback)
447
}
448
// verify if a basic race condition was requested
449
if request.Race && request.RaceNumberRequests > 0 {
450
return request.executeRaceRequest(input, dynamicValues, callback)
451
}
452
453
// verify if fuzz elaboration was requested
454
if len(request.Fuzzing) > 0 {
455
return request.executeFuzzingRule(input, dynamicValues, callback)
456
}
457
458
// verify if parallel elaboration was requested
459
if request.Threads > 0 && len(request.Payloads) > 0 {
460
return request.executeParallelHTTP(input, dynamicValues, callback)
461
}
462
463
generator := request.newGenerator(false)
464
465
var gotDynamicValues map[string][]string
466
var requestErr error
467
468
for {
469
// returns two values, error and skip, which skips the execution for the request instance.
470
executeFunc := func(data string, payloads, dynamicValue map[string]interface{}) (bool, error) {
471
hasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators)
472
473
request.options.RateLimitTake()
474
475
ctx := request.newContext(input)
476
ctxWithTimeout, cancel := context.WithTimeoutCause(ctx, request.options.Options.GetTimeouts().HttpTimeout, ErrHttpEngineRequestDeadline)
477
defer cancel()
478
479
generatedHttpRequest, err := generator.Make(ctxWithTimeout, input, data, payloads, dynamicValue)
480
if err != nil {
481
if err == types.ErrNoMoreRequests {
482
return true, nil
483
}
484
return true, err
485
}
486
// ideally if http template used a custom port or hostname
487
// we would want to update it in input but currently templateCtx logic
488
// is closely tied to contextargs.Context so we are temporarily creating
489
// a copy and using it to check for host errors etc
490
// but this should be replaced once templateCtx is refactored properly
491
updatedInput := contextargs.GetCopyIfHostOutdated(input, generatedHttpRequest.URL())
492
493
if generatedHttpRequest.customCancelFunction != nil {
494
defer generatedHttpRequest.customCancelFunction()
495
}
496
497
hasInteractMarkers := interactsh.HasMarkers(data) || len(generatedHttpRequest.interactshURLs) > 0
498
if input.MetaInput.Input == "" {
499
input.MetaInput.Input = generatedHttpRequest.URL()
500
}
501
// Check if hosts keep erroring
502
if request.isUnresponsiveAddress(updatedInput) {
503
return true, nil
504
}
505
var gotMatches bool
506
execReqErr := request.executeRequest(input, generatedHttpRequest, previous, hasInteractMatchers, func(event *output.InternalWrappedEvent) {
507
// a special case where operators has interactsh matchers and multiple request are made
508
// ex: status_code_2 , interactsh_protocol (from 1st request) etc
509
needsRequestEvent := interactsh.HasMatchers(request.CompiledOperators) && request.NeedsRequestCondition()
510
if (hasInteractMarkers || needsRequestEvent) && request.options.Interactsh != nil {
511
requestData := &interactsh.RequestData{
512
MakeResultFunc: request.MakeResultEvent,
513
Event: event,
514
Operators: request.CompiledOperators,
515
MatchFunc: request.Match,
516
ExtractFunc: request.Extract,
517
}
518
allOASTUrls := httputils.GetInteractshURLSFromEvent(event.InternalEvent)
519
allOASTUrls = append(allOASTUrls, generatedHttpRequest.interactshURLs...)
520
request.options.Interactsh.RequestEvent(sliceutil.Dedupe(allOASTUrls), requestData)
521
gotMatches = request.options.Interactsh.AlreadyMatched(requestData)
522
}
523
// Add the extracts to the dynamic values if any.
524
if event.OperatorsResult != nil {
525
gotMatches = event.OperatorsResult.Matched
526
gotDynamicValues = generators.MergeMapsMany(event.OperatorsResult.DynamicValues, dynamicValues, gotDynamicValues)
527
}
528
// Note: This is a race condition prone zone i.e when request has interactsh_matchers
529
// Interactsh.RequestEvent tries to access/update output.InternalWrappedEvent depending on logic
530
// to avoid conflicts with `callback` mutex is used here and in Interactsh.RequestEvent
531
// Note: this only happens if requests > 1 and interactsh matcher is used
532
// TODO: interactsh logic in nuclei needs to be refactored to avoid such situations
533
callback(event)
534
}, generator.currentIndex)
535
536
// If a variable is unresolved, skip all further requests
537
if errors.Is(execReqErr, ErrMissingVars) {
538
return true, nil
539
}
540
541
if execReqErr != nil {
542
request.markHostError(updatedInput, execReqErr)
543
544
// if applicable mark the host as unresponsive
545
reqKitErr := errkit.FromError(execReqErr)
546
reqKitErr.Msgf("got err while executing %v", generatedHttpRequest.URL())
547
548
requestErr = reqKitErr
549
request.options.Progress.IncrementFailedRequestsBy(1)
550
} else {
551
request.options.Progress.IncrementRequests()
552
}
553
554
// If this was a match, and we want to stop at first match, skip all further requests.
555
shouldStopAtFirstMatch := generatedHttpRequest.original.options.Options.StopAtFirstMatch || generatedHttpRequest.original.options.StopAtFirstMatch || request.StopAtFirstMatch
556
if shouldStopAtFirstMatch && gotMatches {
557
return true, nil
558
}
559
return false, nil
560
}
561
562
inputData, payloads, ok := generator.nextValue()
563
if !ok {
564
break
565
}
566
567
select {
568
case <-input.Context().Done():
569
return input.Context().Err()
570
default:
571
}
572
573
var gotErr error
574
var skip bool
575
if len(gotDynamicValues) > 0 {
576
operators.MakeDynamicValuesCallback(gotDynamicValues, request.IterateAll, func(data map[string]interface{}) bool {
577
if skip, gotErr = executeFunc(inputData, payloads, data); skip || gotErr != nil {
578
return true
579
}
580
return false
581
})
582
} else {
583
skip, gotErr = executeFunc(inputData, payloads, dynamicValues)
584
}
585
if gotErr != nil && requestErr == nil {
586
requestErr = gotErr
587
}
588
if skip || gotErr != nil {
589
request.options.Progress.SetRequests(uint64(generator.Remaining() + 1))
590
break
591
}
592
}
593
return requestErr
594
}
595
596
const drainReqSize = int64(8 * unitutils.Kilo)
597
598
// executeRequest executes the actual generated request and returns error if occurred
599
func (request *Request) executeRequest(input *contextargs.Context, generatedRequest *generatedRequest, previousEvent output.InternalEvent, hasInteractMatchers bool, processEvent protocols.OutputEventCallback, requestCount int) (err error) {
600
// Check if hosts keep erroring
601
if request.isUnresponsiveAddress(input) {
602
return fmt.Errorf("hostErrorsCache : host %s is unresponsive", input.MetaInput.Input)
603
}
604
605
// wrap one more callback for validation and fixing event
606
callback := func(event *output.InternalWrappedEvent) {
607
// validateNFixEvent performs necessary validation on generated event
608
// and attempts to fix it , this includes things like making sure
609
// `template-id` is set , `request-url-pattern` is set etc
610
request.validateNFixEvent(input, generatedRequest, err, event)
611
processEvent(event)
612
}
613
614
request.setCustomHeaders(generatedRequest)
615
616
// Try to evaluate any payloads before replacement
617
finalMap := generators.MergeMaps(generatedRequest.dynamicValues, generatedRequest.meta)
618
619
// add known variables from metainput
620
if _, ok := finalMap["ip"]; !ok && input.MetaInput.CustomIP != "" {
621
finalMap["ip"] = input.MetaInput.CustomIP
622
}
623
624
for payloadName, payloadValue := range generatedRequest.meta {
625
if data, err := expressions.Evaluate(types.ToString(payloadValue), finalMap); err == nil {
626
generatedRequest.meta[payloadName] = data
627
}
628
}
629
630
var (
631
resp *http.Response
632
fromCache bool
633
dumpedRequest []byte
634
)
635
636
// Dump request for variables checks
637
// For race conditions we can't dump the request body at this point as it's already waiting the open-gate event, already handled with a similar code within the race function
638
if !generatedRequest.original.Race {
639
640
// change encoding type to content-length unless transfer-encoding header is manually set
641
if generatedRequest.request != nil && !stringsutil.EqualFoldAny(generatedRequest.request.Method, http.MethodGet, http.MethodHead) && generatedRequest.request.Body != nil && generatedRequest.request.Header.Get("Transfer-Encoding") != "chunked" {
642
var newReqBody *reader.ReusableReadCloser
643
newReqBody, ok := generatedRequest.request.Body.(*reader.ReusableReadCloser)
644
if !ok {
645
newReqBody, err = reader.NewReusableReadCloser(generatedRequest.request.Body)
646
}
647
if err == nil {
648
// update the request body with the reusable reader
649
generatedRequest.request.Body = newReqBody
650
// get content length
651
length, _ := io.Copy(io.Discard, newReqBody)
652
generatedRequest.request.ContentLength = length
653
} else {
654
// log error and continue
655
gologger.Verbose().Msgf("[%v] Could not read request body while forcing transfer encoding: %s\n", request.options.TemplateID, err)
656
err = nil
657
}
658
}
659
660
// do the same for unsafe requests
661
if generatedRequest.rawRequest != nil && !stringsutil.EqualFoldAny(generatedRequest.rawRequest.Method, http.MethodGet, http.MethodHead) && generatedRequest.rawRequest.Data != "" && generatedRequest.rawRequest.Headers["Transfer-Encoding"] != "chunked" {
662
generatedRequest.rawRequest.Headers["Content-Length"] = strconv.Itoa(len(generatedRequest.rawRequest.Data))
663
}
664
665
var dumpError error
666
// TODO: dump is currently not working with post-processors - somehow it alters the signature
667
dumpedRequest, dumpError = dump(generatedRequest, input.MetaInput.Input)
668
if dumpError != nil {
669
return dumpError
670
}
671
dumpedRequestString := string(dumpedRequest)
672
673
if ignoreList := GetVariablesNamesSkipList(generatedRequest.original.Signature.Value); ignoreList != nil {
674
if varErr := expressions.ContainsVariablesWithIgnoreList(ignoreList, dumpedRequestString); varErr != nil && !request.SkipVariablesCheck {
675
gologger.Warning().Msgf("[%s] Could not make http request for %s: %v\n", request.options.TemplateID, input.MetaInput.Input, varErr)
676
return ErrMissingVars
677
}
678
} else { // Check if are there any unresolved variables. If yes, skip unless overridden by user.
679
if varErr := expressions.ContainsUnresolvedVariables(dumpedRequestString); varErr != nil && !request.SkipVariablesCheck {
680
gologger.Warning().Msgf("[%s] Could not make http request for %s: %v\n", request.options.TemplateID, input.MetaInput.Input, varErr)
681
return ErrMissingVars
682
}
683
}
684
}
685
686
// === apply auth strategies ===
687
if generatedRequest.request != nil && !request.SkipSecretFile {
688
generatedRequest.ApplyAuth(request.options.AuthProvider)
689
}
690
691
var formedURL string
692
var hostname string
693
timeStart := time.Now()
694
if generatedRequest.original.Pipeline {
695
// if request is a pipeline request, use the pipelined client
696
if generatedRequest.rawRequest != nil {
697
formedURL = generatedRequest.rawRequest.FullURL
698
if parsed, parseErr := urlutil.ParseURL(formedURL, true); parseErr == nil {
699
hostname = parsed.Host
700
}
701
resp, err = generatedRequest.pipelinedClient.DoRaw(generatedRequest.rawRequest.Method, input.MetaInput.Input, generatedRequest.rawRequest.Path, generators.ExpandMapValues(generatedRequest.rawRequest.Headers), io.NopCloser(strings.NewReader(generatedRequest.rawRequest.Data)))
702
} else if generatedRequest.request != nil {
703
resp, err = generatedRequest.pipelinedClient.Dor(generatedRequest.request)
704
}
705
} else if generatedRequest.original.Unsafe && generatedRequest.rawRequest != nil {
706
// if request is a unsafe request, use the rawhttp client
707
formedURL = generatedRequest.rawRequest.FullURL
708
// use request url as matched url if empty
709
if formedURL == "" {
710
urlx, err := urlutil.Parse(input.MetaInput.Input)
711
if err != nil {
712
formedURL = fmt.Sprintf("%s%s", input.MetaInput.Input, generatedRequest.rawRequest.Path)
713
} else {
714
_ = urlx.MergePath(generatedRequest.rawRequest.Path, true)
715
formedURL = urlx.String()
716
}
717
}
718
if parsed, parseErr := urlutil.ParseURL(formedURL, true); parseErr == nil {
719
hostname = parsed.Host
720
}
721
options := *generatedRequest.original.rawhttpClient.Options
722
options.FollowRedirects = request.Redirects
723
options.CustomRawBytes = generatedRequest.rawRequest.UnsafeRawBytes
724
options.ForceReadAllBody = request.ForceReadAllBody
725
options.SNI = request.options.Options.SNI
726
inputUrl := input.MetaInput.Input
727
if url, err := urlutil.ParseURL(inputUrl, false); err == nil {
728
url.Path = ""
729
url.Params = urlutil.NewOrderedParams() // donot include query params
730
// inputUrl should only contain scheme://host:port
731
inputUrl = url.String()
732
}
733
formedURL = fmt.Sprintf("%s%s", inputUrl, generatedRequest.rawRequest.Path)
734
735
// send rawhttp request and get response
736
resp, err = httpclientpool.SendRawRequest(generatedRequest.original.rawhttpClient, &httpclientpool.RawHttpRequestOpts{
737
Method: generatedRequest.rawRequest.Method,
738
URL: inputUrl,
739
Path: generatedRequest.rawRequest.Path,
740
Headers: generators.ExpandMapValues(generatedRequest.rawRequest.Headers),
741
Body: io.NopCloser(strings.NewReader(generatedRequest.rawRequest.Data)),
742
Options: &options,
743
})
744
} else {
745
//** For Normal requests **//
746
hostname = generatedRequest.request.Host
747
formedURL = generatedRequest.request.String()
748
// if nuclei-project is available check if the request was already sent previously
749
if request.options.ProjectFile != nil {
750
// if unavailable fail silently
751
fromCache = true
752
resp, err = request.options.ProjectFile.Get(dumpedRequest)
753
if err != nil {
754
fromCache = false
755
}
756
}
757
if resp == nil {
758
if errSignature := request.handleSignature(generatedRequest); errSignature != nil {
759
return errSignature
760
}
761
httpclient := request.httpClient
762
763
// this will be assigned/updated if this specific request has a custom configuration
764
var modifiedConfig *httpclientpool.Configuration
765
766
// check for cookie related configuration
767
if input.CookieJar != nil {
768
connConfiguration := request.connConfiguration.Clone()
769
connConfiguration.Connection.SetCookieJar(input.CookieJar)
770
modifiedConfig = connConfiguration
771
}
772
// check for request updatedTimeout annotation
773
updatedTimeout, ok := generatedRequest.request.Context().Value(httpclientpool.WithCustomTimeout{}).(httpclientpool.WithCustomTimeout)
774
if ok {
775
if modifiedConfig == nil {
776
connConfiguration := request.connConfiguration.Clone()
777
modifiedConfig = connConfiguration
778
}
779
modifiedConfig.ResponseHeaderTimeout = updatedTimeout.Timeout
780
}
781
782
if modifiedConfig != nil {
783
client, err := httpclientpool.Get(request.options.Options, modifiedConfig)
784
if err != nil {
785
return errors.Wrap(err, "could not get http client")
786
}
787
httpclient = client
788
}
789
790
resp, err = httpclient.Do(generatedRequest.request)
791
}
792
}
793
// use request url as matched url if empty
794
if formedURL == "" {
795
formedURL = input.MetaInput.Input
796
}
797
798
// converts whitespace and other chars that cannot be printed to url encoded values
799
formedURL = urlutil.URLEncodeWithEscapes(formedURL)
800
801
// Dump the requests containing all headers
802
if !generatedRequest.original.Race {
803
var dumpError error
804
dumpedRequest, dumpError = dump(generatedRequest, input.MetaInput.Input)
805
if dumpError != nil {
806
return dumpError
807
}
808
dumpedRequestString := string(dumpedRequest)
809
if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {
810
msg := fmt.Sprintf("[%s] Dumped HTTP request for %s\n\n", request.options.TemplateID, formedURL)
811
812
if request.options.Options.Debug || request.options.Options.DebugRequests {
813
gologger.Info().Msg(msg)
814
gologger.Print().Msgf("%s", dumpedRequestString)
815
}
816
if request.options.Options.StoreResponse {
817
request.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), fmt.Sprintf("%s\n%s", msg, dumpedRequestString))
818
}
819
}
820
}
821
822
dialers := protocolstate.GetDialersWithId(request.options.Options.ExecutionId)
823
if dialers == nil {
824
return fmt.Errorf("dialers not found for execution id %s", request.options.Options.ExecutionId)
825
}
826
827
if err != nil {
828
// rawhttp doesn't support draining response bodies.
829
if resp != nil && resp.Body != nil && generatedRequest.rawRequest == nil && !generatedRequest.original.Pipeline {
830
_, _ = io.CopyN(io.Discard, resp.Body, drainReqSize)
831
_ = resp.Body.Close()
832
}
833
request.options.Output.Request(request.options.TemplatePath, formedURL, request.Type().String(), err)
834
request.options.Progress.IncrementErrorsBy(1)
835
836
// In case of interactsh markers and request times out, still send
837
// a callback event so in case we receive an interaction, correlation is possible.
838
// Also, to log failed use-cases.
839
outputEvent := request.responseToDSLMap(&http.Response{}, input.MetaInput.Input, formedURL, convUtil.String(dumpedRequest), "", "", "", 0, generatedRequest.meta)
840
if i := strings.LastIndex(hostname, ":"); i != -1 {
841
hostname = hostname[:i]
842
}
843
844
if input.MetaInput.CustomIP != "" {
845
outputEvent["ip"] = input.MetaInput.CustomIP
846
} else {
847
outputEvent["ip"] = dialers.Fastdialer.GetDialedIP(hostname)
848
// try getting cname
849
request.addCNameIfAvailable(hostname, outputEvent)
850
}
851
852
if len(generatedRequest.interactshURLs) > 0 {
853
// according to logic we only need to trigger a callback if interactsh was used
854
// and request failed in hope that later on oast interaction will be received
855
event := &output.InternalWrappedEvent{}
856
if request.CompiledOperators != nil && request.CompiledOperators.HasDSL() {
857
event.InternalEvent = outputEvent
858
}
859
callback(event)
860
}
861
return err
862
}
863
864
var curlCommand string
865
if !request.Unsafe && resp != nil && generatedRequest.request != nil && resp.Request != nil && !request.Race {
866
bodyBytes, _ := generatedRequest.request.BodyBytes()
867
// Use a clone to avoid a race condition with the http transport
868
req := resp.Request.Clone(resp.Request.Context())
869
req.Body = io.NopCloser(bytes.NewReader(bodyBytes))
870
command, err := http2curl.GetCurlCommand(req)
871
if err == nil && command != nil {
872
curlCommand = command.String()
873
}
874
}
875
876
gologger.Verbose().Msgf("[%s] Sent HTTP request to %s", request.options.TemplateID, formedURL)
877
request.options.Output.Request(request.options.TemplatePath, formedURL, request.Type().String(), err)
878
879
duration := time.Since(timeStart)
880
881
// define max body read limit
882
maxBodylimit := MaxBodyRead // 10MB
883
if request.MaxSize > 0 {
884
maxBodylimit = request.MaxSize
885
}
886
if request.options.Options.ResponseReadSize != 0 {
887
maxBodylimit = request.options.Options.ResponseReadSize
888
}
889
890
// respChain is http response chain that reads response body
891
// efficiently by reusing buffers and does all decoding and optimizations
892
respChain := httpUtils.NewResponseChain(resp, int64(maxBodylimit))
893
defer respChain.Close() // reuse buffers
894
895
// we only intend to log/save the final redirected response
896
// i.e why we have to use sync.Once to ensure it's only done once
897
var errx error
898
onceFunc := sync.OnceFunc(func() {
899
// if nuclei-project is enabled store the response if not previously done
900
if request.options.ProjectFile != nil && !fromCache {
901
if err := request.options.ProjectFile.Set(dumpedRequest, resp, respChain.Body().Bytes()); err != nil {
902
errx = errors.Wrap(err, "could not store in project file")
903
}
904
}
905
})
906
907
// evaluate responses continiously until first redirect request in reverse order
908
for respChain.Has() {
909
// fill buffers, read response body and reuse connection
910
if err := respChain.Fill(); err != nil {
911
return errors.Wrap(err, "could not generate response chain")
912
}
913
914
// log request stats
915
request.options.Output.RequestStatsLog(strconv.Itoa(respChain.Response().StatusCode), respChain.FullResponse().String())
916
917
// save response to projectfile
918
onceFunc()
919
matchedURL := input.MetaInput.Input
920
if generatedRequest.rawRequest != nil {
921
if generatedRequest.rawRequest.FullURL != "" {
922
matchedURL = generatedRequest.rawRequest.FullURL
923
} else {
924
matchedURL = formedURL
925
}
926
}
927
if generatedRequest.request != nil {
928
matchedURL = generatedRequest.request.String()
929
}
930
// Give precedence to the final URL from response
931
if respChain.Request() != nil {
932
if responseURL := respChain.Request().URL.String(); responseURL != "" {
933
matchedURL = responseURL
934
}
935
}
936
937
finalEvent := make(output.InternalEvent)
938
939
if request.Analyzer != nil {
940
analyzer := analyzers.GetAnalyzer(request.Analyzer.Name)
941
analysisMatched, analysisDetails, err := analyzer.Analyze(&analyzers.Options{
942
FuzzGenerated: generatedRequest.fuzzGeneratedRequest,
943
HttpClient: request.httpClient,
944
ResponseTimeDelay: duration,
945
AnalyzerParameters: request.Analyzer.Parameters,
946
})
947
if err != nil {
948
gologger.Warning().Msgf("Could not analyze response: %v\n", err)
949
}
950
if analysisMatched {
951
finalEvent["analyzer_details"] = analysisDetails
952
finalEvent["analyzer"] = true
953
}
954
}
955
956
outputEvent := request.responseToDSLMap(respChain.Response(), input.MetaInput.Input, matchedURL, convUtil.String(dumpedRequest), respChain.FullResponse().String(), respChain.Body().String(), respChain.Headers().String(), duration, generatedRequest.meta)
957
// add response fields to template context and merge templatectx variables to output event
958
request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent)
959
if request.options.HasTemplateCtx(input.MetaInput) {
960
outputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll())
961
}
962
if i := strings.LastIndex(hostname, ":"); i != -1 {
963
hostname = hostname[:i]
964
}
965
outputEvent["curl-command"] = curlCommand
966
if input.MetaInput.CustomIP != "" {
967
outputEvent["ip"] = input.MetaInput.CustomIP
968
} else {
969
dialer := dialers.Fastdialer
970
if dialer != nil {
971
outputEvent["ip"] = dialer.GetDialedIP(hostname)
972
}
973
974
// try getting cname
975
request.addCNameIfAvailable(hostname, outputEvent)
976
}
977
if request.options.Interactsh != nil {
978
request.options.Interactsh.MakePlaceholders(generatedRequest.interactshURLs, outputEvent)
979
}
980
maps.Copy(finalEvent, previousEvent)
981
maps.Copy(finalEvent, outputEvent)
982
983
// Add to history the current request number metadata if asked by the user.
984
if request.NeedsRequestCondition() {
985
for k, v := range outputEvent {
986
key := fmt.Sprintf("%s_%d", k, requestCount)
987
if previousEvent != nil {
988
previousEvent[key] = v
989
}
990
finalEvent[key] = v
991
}
992
}
993
// prune signature internal values if any
994
request.pruneSignatureInternalValues(generatedRequest.meta)
995
996
interimEvent := generators.MergeMaps(generatedRequest.dynamicValues, finalEvent)
997
isDebug := request.options.Options.Debug || request.options.Options.DebugResponse
998
event := eventcreator.CreateEventWithAdditionalOptions(request, interimEvent, isDebug, func(internalWrappedEvent *output.InternalWrappedEvent) {
999
internalWrappedEvent.OperatorsResult.PayloadValues = generatedRequest.meta
1000
})
1001
1002
if hasInteractMatchers {
1003
event.UsesInteractsh = true
1004
}
1005
1006
if request.options.GlobalMatchers.HasMatchers() {
1007
request.options.GlobalMatchers.Match(interimEvent, request.Match, request.Extract, isDebug, func(event output.InternalEvent, result *operators.Result) {
1008
callback(eventcreator.CreateEventWithOperatorResults(request, event, result))
1009
})
1010
}
1011
1012
// if requrlpattern is enabled, only then it is reflected in result event else it is empty string
1013
// consult @Ice3man543 before changing this logic (context: vuln_hash)
1014
if request.options.ExportReqURLPattern {
1015
for _, v := range event.Results {
1016
v.ReqURLPattern = generatedRequest.requestURLPattern
1017
}
1018
}
1019
1020
responseContentType := respChain.Response().Header.Get("Content-Type")
1021
isResponseTruncated := request.MaxSize > 0 && respChain.Body().Len() >= request.MaxSize
1022
dumpResponse(event, request, respChain.FullResponse().Bytes(), formedURL, responseContentType, isResponseTruncated, input.MetaInput.Input)
1023
1024
callback(event)
1025
1026
if request.options.FuzzStatsDB != nil && generatedRequest.fuzzGeneratedRequest.Request != nil {
1027
request.options.FuzzStatsDB.RecordResultEvent(fuzzStats.FuzzingEvent{
1028
URL: input.MetaInput.Target(),
1029
TemplateID: request.options.TemplateID,
1030
ComponentType: generatedRequest.fuzzGeneratedRequest.Component.Name(),
1031
ComponentName: generatedRequest.fuzzGeneratedRequest.Parameter,
1032
PayloadSent: generatedRequest.fuzzGeneratedRequest.Value,
1033
StatusCode: respChain.Response().StatusCode,
1034
Matched: event.HasResults(),
1035
RawRequest: string(dumpedRequest),
1036
RawResponse: respChain.FullResponse().String(),
1037
Severity: request.options.TemplateInfo.SeverityHolder.Severity.String(),
1038
})
1039
}
1040
1041
// Skip further responses if we have stop-at-first-match and a match
1042
if (request.options.Options.StopAtFirstMatch || request.options.StopAtFirstMatch || request.StopAtFirstMatch) && event.HasResults() {
1043
return nil
1044
}
1045
// proceed with previous response
1046
// we evaluate operators recursively for each response
1047
// until we reach the first redirect response
1048
if !respChain.Previous() {
1049
break
1050
}
1051
}
1052
// return project file save error if any
1053
return errx
1054
}
1055
1056
// validateNFixEvent validates and fixes the event
1057
// it adds any missing template-id and request-url-pattern
1058
func (request *Request) validateNFixEvent(input *contextargs.Context, gr *generatedRequest, err error, event *output.InternalWrappedEvent) {
1059
if event != nil {
1060
if event.InternalEvent == nil {
1061
event.InternalEvent = make(map[string]interface{})
1062
event.InternalEvent["template-id"] = request.options.TemplateID
1063
}
1064
// add the request URL pattern to the event
1065
event.InternalEvent[ReqURLPatternKey] = gr.requestURLPattern
1066
if event.InternalEvent["host"] == nil {
1067
event.InternalEvent["host"] = input.MetaInput.Input
1068
}
1069
if event.InternalEvent["template-id"] == nil {
1070
event.InternalEvent["template-id"] = request.options.TemplateID
1071
}
1072
if event.InternalEvent["type"] == nil {
1073
event.InternalEvent["type"] = request.Type().String()
1074
}
1075
if event.InternalEvent["template-path"] == nil {
1076
event.InternalEvent["template-path"] = request.options.TemplatePath
1077
}
1078
if event.InternalEvent["template-info"] == nil {
1079
event.InternalEvent["template-info"] = request.options.TemplateInfo
1080
}
1081
if err != nil {
1082
event.InternalEvent["error"] = err.Error()
1083
}
1084
}
1085
}
1086
1087
// addCNameIfAvailable adds the cname to the event if available
1088
func (request *Request) addCNameIfAvailable(hostname string, outputEvent map[string]interface{}) {
1089
if request.dialer == nil {
1090
return
1091
}
1092
1093
data, err := request.dialer.GetDNSData(hostname)
1094
if err == nil {
1095
switch len(data.CNAME) {
1096
case 0:
1097
return
1098
case 1:
1099
outputEvent["cname"] = data.CNAME[0]
1100
default:
1101
// add 1st and put others in cname_all
1102
outputEvent["cname"] = data.CNAME[0]
1103
outputEvent["cname_all"] = data.CNAME
1104
}
1105
}
1106
}
1107
1108
// handleSignature of the http request
1109
func (request *Request) handleSignature(generatedRequest *generatedRequest) error {
1110
switch request.Signature.Value {
1111
case AWSSignature:
1112
var awsSigner signer.Signer
1113
allvars := generators.MergeMaps(request.options.Options.Vars.AsMap(), generatedRequest.dynamicValues)
1114
awsopts := signer.AWSOptions{
1115
AwsID: types.ToString(allvars["aws-id"]),
1116
AwsSecretToken: types.ToString(allvars["aws-secret"]),
1117
}
1118
awsSigner, err := signerpool.Get(request.options.Options, &signerpool.Configuration{SignerArgs: &awsopts})
1119
if err != nil {
1120
return err
1121
}
1122
ctx := signer.GetCtxWithArgs(allvars, signer.AwsDefaultVars)
1123
err = awsSigner.SignHTTP(ctx, generatedRequest.request.Request)
1124
if err != nil {
1125
return err
1126
}
1127
}
1128
1129
return nil
1130
}
1131
1132
// setCustomHeaders sets the custom headers for generated request
1133
func (request *Request) setCustomHeaders(req *generatedRequest) {
1134
for k, v := range request.customHeaders {
1135
if req.rawRequest != nil {
1136
req.rawRequest.Headers[k] = v
1137
} else {
1138
kk, vv := strings.TrimSpace(k), strings.TrimSpace(v)
1139
// NOTE(dwisiswant0): Do we really not need to convert it first into
1140
// lowercase?
1141
if kk == "Host" {
1142
req.request.Host = vv
1143
1144
continue
1145
}
1146
1147
req.request.Header[kk] = []string{vv}
1148
}
1149
}
1150
}
1151
1152
const CRLF = "\r\n"
1153
1154
func dumpResponse(event *output.InternalWrappedEvent, request *Request, redirectedResponse []byte, formedURL string, responseContentType string, isResponseTruncated bool, reqURL string) {
1155
cliOptions := request.options.Options
1156
if cliOptions.Debug || cliOptions.DebugResponse || cliOptions.StoreResponse {
1157
response := string(redirectedResponse)
1158
1159
var highlightedResult string
1160
if (responseContentType == "application/octet-stream" || responseContentType == "application/x-www-form-urlencoded") && responsehighlighter.HasBinaryContent(response) {
1161
highlightedResult = createResponseHexDump(event, response, cliOptions.NoColor)
1162
} else {
1163
highlightedResult = responsehighlighter.Highlight(event.OperatorsResult, response, cliOptions.NoColor, false)
1164
}
1165
1166
msg := "[%s] Dumped HTTP response %s\n\n%s"
1167
if isResponseTruncated {
1168
msg = "[%s] Dumped HTTP response (Truncated) %s\n\n%s"
1169
}
1170
fMsg := fmt.Sprintf(msg, request.options.TemplateID, formedURL, highlightedResult)
1171
if cliOptions.Debug || cliOptions.DebugResponse {
1172
gologger.Debug().Msg(fMsg)
1173
}
1174
if cliOptions.StoreResponse {
1175
request.options.Output.WriteStoreDebugData(reqURL, request.options.TemplateID, request.Type().String(), fMsg)
1176
}
1177
}
1178
}
1179
1180
func createResponseHexDump(event *output.InternalWrappedEvent, response string, noColor bool) string {
1181
CRLFs := CRLF + CRLF
1182
headerEndIndex := strings.Index(response, CRLFs) + len(CRLFs)
1183
if headerEndIndex > 0 {
1184
headers := response[0:headerEndIndex]
1185
responseBodyHexDump := hex.Dump([]byte(response[headerEndIndex:]))
1186
1187
highlightedHeaders := responsehighlighter.Highlight(event.OperatorsResult, headers, noColor, false)
1188
highlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, responseBodyHexDump, noColor, true)
1189
return fmt.Sprintf("%s\n%s", highlightedHeaders, highlightedResponse)
1190
} else {
1191
return responsehighlighter.Highlight(event.OperatorsResult, hex.Dump([]byte(response)), noColor, true)
1192
}
1193
}
1194
1195
func (request *Request) pruneSignatureInternalValues(maps ...map[string]interface{}) {
1196
var signatureFieldsToSkip map[string]interface{}
1197
switch request.Signature.Value {
1198
case AWSSignature:
1199
signatureFieldsToSkip = signer.AwsInternalOnlyVars
1200
default:
1201
return
1202
}
1203
1204
for _, m := range maps {
1205
for fieldName := range signatureFieldsToSkip {
1206
delete(m, fieldName)
1207
}
1208
}
1209
}
1210
1211
func (request *Request) newContext(input *contextargs.Context) context.Context {
1212
if input.MetaInput.CustomIP != "" {
1213
return context.WithValue(input.Context(), fastdialer.IP, input.MetaInput.CustomIP)
1214
}
1215
return input.Context()
1216
}
1217
1218
// markHostError checks if the error is a unreponsive host error and marks it
1219
func (request *Request) markHostError(input *contextargs.Context, err error) {
1220
if request.options.HostErrorsCache != nil && err != nil {
1221
request.options.HostErrorsCache.MarkFailedOrRemove(request.options.ProtocolType.String(), input, err)
1222
}
1223
}
1224
1225
// isUnresponsiveAddress checks if the error is a unreponsive based on its execution history
1226
func (request *Request) isUnresponsiveAddress(input *contextargs.Context) bool {
1227
if request.options.HostErrorsCache != nil {
1228
return request.options.HostErrorsCache.Check(request.options.ProtocolType.String(), input)
1229
}
1230
return false
1231
}
1232
1233