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