Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/network/request.go
2070 views
1
package network
2
3
import (
4
"encoding/hex"
5
"fmt"
6
maps0 "maps"
7
"net"
8
"net/url"
9
"os"
10
"strings"
11
"sync"
12
"sync/atomic"
13
"time"
14
15
"github.com/pkg/errors"
16
"go.uber.org/multierr"
17
"golang.org/x/exp/maps"
18
19
"github.com/projectdiscovery/gologger"
20
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
21
"github.com/projectdiscovery/nuclei/v3/pkg/output"
22
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
23
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
24
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
25
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
26
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator"
27
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter"
28
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
29
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer"
30
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
31
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network/networkclientpool"
32
protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
33
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
34
"github.com/projectdiscovery/utils/errkit"
35
mapsutil "github.com/projectdiscovery/utils/maps"
36
"github.com/projectdiscovery/utils/reader"
37
syncutil "github.com/projectdiscovery/utils/sync"
38
)
39
40
var _ protocols.Request = &Request{}
41
42
// Type returns the type of the protocol request
43
func (request *Request) Type() templateTypes.ProtocolType {
44
return templateTypes.NetworkProtocol
45
}
46
47
// getOpenPorts returns all open ports from list of ports provided in template
48
// if only 1 port is provided, no need to check if port is open or not
49
func (request *Request) getOpenPorts(target *contextargs.Context) ([]string, error) {
50
if len(request.ports) == 1 {
51
// no need to check if port is open or not
52
return request.ports, nil
53
}
54
errs := []error{}
55
// if more than 1 port is provided, check if port is open or not
56
openPorts := make([]string, 0)
57
for _, port := range request.ports {
58
cloned := target.Clone()
59
if err := cloned.UseNetworkPort(port, request.ExcludePorts); err != nil {
60
errs = append(errs, err)
61
continue
62
}
63
addr, err := getAddress(cloned.MetaInput.Input)
64
if err != nil {
65
errs = append(errs, err)
66
continue
67
}
68
if request.dialer == nil {
69
request.dialer, _ = networkclientpool.Get(request.options.Options, &networkclientpool.Configuration{})
70
}
71
72
conn, err := request.dialer.Dial(target.Context(), "tcp", addr)
73
if err != nil {
74
errs = append(errs, err)
75
continue
76
}
77
_ = conn.Close()
78
openPorts = append(openPorts, port)
79
}
80
if len(openPorts) == 0 {
81
return nil, multierr.Combine(errs...)
82
}
83
return openPorts, nil
84
}
85
86
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
87
func (request *Request) ExecuteWithResults(target *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
88
visitedAddresses := make(mapsutil.Map[string, struct{}])
89
90
if request.Port == "" {
91
// backwords compatibility or for other use cases
92
// where port is not provided in template
93
if err := request.executeOnTarget(target, visitedAddresses, metadata, previous, callback); err != nil {
94
return err
95
}
96
}
97
98
// get open ports from list of ports provided in template
99
ports, err := request.getOpenPorts(target)
100
if len(ports) == 0 {
101
return err
102
}
103
if err != nil {
104
// TODO: replace this after scan context is implemented
105
gologger.Verbose().Msgf("[%v] got errors while checking open ports: %s\n", request.options.TemplateID, err)
106
}
107
108
// stop at first match if requested
109
atomicBool := &atomic.Bool{}
110
shouldStopAtFirstMatch := request.StopAtFirstMatch || request.options.StopAtFirstMatch || request.options.Options.StopAtFirstMatch
111
wrappedCallback := func(event *output.InternalWrappedEvent) {
112
if event != nil && event.HasOperatorResult() {
113
atomicBool.Store(true)
114
}
115
callback(event)
116
}
117
118
for _, port := range ports {
119
input := target.Clone()
120
// use network port updates input with new port requested in template file
121
// and it is ignored if input port is not standard http(s) ports like 80,8080,8081 etc
122
// idea is to reduce redundant dials to http ports
123
if err := input.UseNetworkPort(port, request.ExcludePorts); err != nil {
124
gologger.Debug().Msgf("Could not network port from constants: %s\n", err)
125
}
126
if err := request.executeOnTarget(input, visitedAddresses, metadata, previous, wrappedCallback); err != nil {
127
return err
128
}
129
if shouldStopAtFirstMatch && atomicBool.Load() {
130
break
131
}
132
}
133
134
return nil
135
}
136
137
func (request *Request) executeOnTarget(input *contextargs.Context, visited mapsutil.Map[string, struct{}], metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
138
var address string
139
var err error
140
if request.isUnresponsiveAddress(input) {
141
// skip on unresponsive address no need to continue
142
return nil
143
}
144
145
if request.SelfContained {
146
address = ""
147
} else {
148
address, err = getAddress(input.MetaInput.Input)
149
}
150
if err != nil {
151
request.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, request.Type().String(), err)
152
request.options.Progress.IncrementFailedRequestsBy(1)
153
return errors.Wrap(err, "could not get address from url")
154
}
155
variables := protocolutils.GenerateVariables(address, false, nil)
156
// add template ctx variables to varMap
157
if request.options.HasTemplateCtx(input.MetaInput) {
158
variables = generators.MergeMaps(variables, request.options.GetTemplateCtx(input.MetaInput).GetAll())
159
}
160
variablesMap := request.options.Variables.Evaluate(variables)
161
variables = generators.MergeMaps(variablesMap, variables, request.options.Constants)
162
163
// stop at first match if requested
164
atomicBool := &atomic.Bool{}
165
shouldStopAtFirstMatch := request.StopAtFirstMatch || request.options.StopAtFirstMatch || request.options.Options.StopAtFirstMatch
166
wrappedCallback := func(event *output.InternalWrappedEvent) {
167
if event != nil && event.HasOperatorResult() {
168
atomicBool.Store(true)
169
}
170
callback(event)
171
}
172
173
for _, kv := range request.addresses {
174
select {
175
case <-input.Context().Done():
176
return input.Context().Err()
177
default:
178
}
179
180
actualAddress := replacer.Replace(kv.address, variables)
181
182
if visited.Has(actualAddress) && !request.options.Options.DisableClustering {
183
continue
184
}
185
visited.Set(actualAddress, struct{}{})
186
if err = request.executeAddress(variables, actualAddress, address, input, kv.tls, previous, wrappedCallback); err != nil {
187
outputEvent := request.responseToDSLMap("", "", "", address, "")
188
callback(&output.InternalWrappedEvent{InternalEvent: outputEvent})
189
gologger.Warning().Msgf("[%v] Could not make network request for (%s) : %s\n", request.options.TemplateID, actualAddress, err)
190
}
191
if shouldStopAtFirstMatch && atomicBool.Load() {
192
break
193
}
194
}
195
return err
196
}
197
198
// executeAddress executes the request for an address
199
func (request *Request) executeAddress(variables map[string]interface{}, actualAddress, address string, input *contextargs.Context, shouldUseTLS bool, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
200
variables = generators.MergeMaps(variables, map[string]interface{}{"Hostname": address})
201
payloads := generators.BuildPayloadFromOptions(request.options.Options)
202
203
if !strings.Contains(actualAddress, ":") {
204
err := errors.New("no port provided in network protocol request")
205
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
206
request.options.Progress.IncrementFailedRequestsBy(1)
207
return err
208
}
209
updatedTarget := input.Clone()
210
updatedTarget.MetaInput.Input = actualAddress
211
212
// if request threads matches global payload concurrency we follow it
213
shouldFollowGlobal := request.Threads == request.options.Options.PayloadConcurrency
214
215
if request.generator != nil {
216
iterator := request.generator.NewIterator()
217
var multiErr error
218
m := &sync.Mutex{}
219
swg, err := syncutil.New(syncutil.WithSize(request.Threads))
220
if err != nil {
221
return err
222
}
223
224
for {
225
value, ok := iterator.Value()
226
if !ok {
227
break
228
}
229
230
select {
231
case <-input.Context().Done():
232
return input.Context().Err()
233
default:
234
}
235
236
// resize check point - nop if there are no changes
237
if shouldFollowGlobal && swg.Size != request.options.Options.PayloadConcurrency {
238
if err := swg.Resize(input.Context(), request.options.Options.PayloadConcurrency); err != nil {
239
m.Lock()
240
multiErr = multierr.Append(multiErr, err)
241
m.Unlock()
242
}
243
}
244
if request.isUnresponsiveAddress(updatedTarget) {
245
// skip on unresponsive address no need to continue
246
return nil
247
}
248
249
value = generators.MergeMaps(value, payloads)
250
swg.Add()
251
go func(vars map[string]interface{}) {
252
defer swg.Done()
253
if request.isUnresponsiveAddress(updatedTarget) {
254
// skip on unresponsive address no need to continue
255
return
256
}
257
if err := request.executeRequestWithPayloads(variables, actualAddress, address, input, shouldUseTLS, vars, previous, callback); err != nil {
258
m.Lock()
259
multiErr = multierr.Append(multiErr, err)
260
m.Unlock()
261
}
262
}(value)
263
}
264
swg.Wait()
265
if multiErr != nil {
266
return multiErr
267
}
268
} else {
269
value := maps.Clone(payloads)
270
if err := request.executeRequestWithPayloads(variables, actualAddress, address, input, shouldUseTLS, value, previous, callback); err != nil {
271
return err
272
}
273
}
274
return nil
275
}
276
277
func (request *Request) executeRequestWithPayloads(variables map[string]interface{}, actualAddress, address string, input *contextargs.Context, shouldUseTLS bool, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
278
var (
279
hostname string
280
conn net.Conn
281
err error
282
)
283
if host, _, err := net.SplitHostPort(actualAddress); err == nil {
284
hostname = host
285
}
286
updatedTarget := input.Clone()
287
updatedTarget.MetaInput.Input = actualAddress
288
289
if request.isUnresponsiveAddress(updatedTarget) {
290
// skip on unresponsive address no need to continue
291
return nil
292
}
293
294
if shouldUseTLS {
295
conn, err = request.dialer.DialTLS(input.Context(), "tcp", actualAddress)
296
} else {
297
conn, err = request.dialer.Dial(input.Context(), "tcp", actualAddress)
298
}
299
// adds it to unresponsive address list if applicable
300
request.markHostError(updatedTarget, err)
301
if err != nil {
302
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
303
request.options.Progress.IncrementFailedRequestsBy(1)
304
return errors.Wrap(err, "could not connect to server")
305
}
306
defer func() {
307
_ = conn.Close()
308
}()
309
_ = conn.SetDeadline(time.Now().Add(time.Duration(request.options.Options.Timeout) * time.Second))
310
311
var interactshURLs []string
312
313
var responseBuilder, reqBuilder strings.Builder
314
315
interimValues := generators.MergeMaps(variables, payloads)
316
317
if vardump.EnableVarDump {
318
gologger.Debug().Msgf("Network Protocol request variables: %s\n", vardump.DumpVariables(interimValues))
319
}
320
321
inputEvents := make(map[string]interface{})
322
323
for _, input := range request.Inputs {
324
dataInBytes := []byte(input.Data)
325
var err error
326
327
dataInBytes, err = expressions.EvaluateByte(dataInBytes, interimValues)
328
if err != nil {
329
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
330
request.options.Progress.IncrementFailedRequestsBy(1)
331
return errors.Wrap(err, "could not evaluate template expressions")
332
}
333
334
data := string(dataInBytes)
335
if request.options.Interactsh != nil {
336
data, interactshURLs = request.options.Interactsh.Replace(data, []string{})
337
dataInBytes = []byte(data)
338
}
339
340
reqBuilder.Write(dataInBytes)
341
342
if err := expressions.ContainsUnresolvedVariables(data); err != nil {
343
gologger.Warning().Msgf("[%s] Could not make network request for %s: %v\n", request.options.TemplateID, actualAddress, err)
344
return nil
345
}
346
347
if input.Type.GetType() == hexType {
348
dataInBytes, err = hex.DecodeString(data)
349
if err != nil {
350
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
351
request.options.Progress.IncrementFailedRequestsBy(1)
352
return errors.Wrap(err, "could not write request to server")
353
}
354
}
355
356
if _, err := conn.Write(dataInBytes); err != nil {
357
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
358
request.options.Progress.IncrementFailedRequestsBy(1)
359
return errors.Wrap(err, "could not write request to server")
360
}
361
362
if input.Read > 0 {
363
buffer, err := ConnReadNWithTimeout(conn, int64(input.Read), request.options.Options.GetTimeouts().TcpReadTimeout)
364
if err != nil {
365
return errkit.Wrap(err, "could not read response from connection")
366
}
367
368
responseBuilder.Write(buffer)
369
370
bufferStr := string(buffer)
371
if input.Name != "" {
372
inputEvents[input.Name] = bufferStr
373
interimValues[input.Name] = bufferStr
374
}
375
376
// Run any internal extractors for the request here and add found values to map.
377
if request.CompiledOperators != nil {
378
values := request.CompiledOperators.ExecuteInternalExtractors(map[string]interface{}{input.Name: bufferStr}, request.Extract)
379
maps0.Copy(payloads, values)
380
}
381
}
382
}
383
384
request.options.Progress.IncrementRequests()
385
386
if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {
387
requestBytes := []byte(reqBuilder.String())
388
msg := fmt.Sprintf("[%s] Dumped Network request for %s\n%s", request.options.TemplateID, actualAddress, hex.Dump(requestBytes))
389
if request.options.Options.Debug || request.options.Options.DebugRequests {
390
gologger.Info().Str("address", actualAddress).Msg(msg)
391
}
392
if request.options.Options.StoreResponse {
393
request.options.Output.WriteStoreDebugData(address, request.options.TemplateID, request.Type().String(), msg)
394
}
395
if request.options.Options.VerboseVerbose {
396
gologger.Print().Msgf("\nCompact HEX view:\n%s", hex.EncodeToString(requestBytes))
397
}
398
}
399
400
request.options.Output.Request(request.options.TemplatePath, actualAddress, request.Type().String(), err)
401
gologger.Verbose().Msgf("Sent TCP request to %s", actualAddress)
402
403
bufferSize := 1024
404
if request.ReadSize != 0 {
405
bufferSize = request.ReadSize
406
}
407
if request.ReadAll {
408
bufferSize = -1
409
}
410
411
final, err := ConnReadNWithTimeout(conn, int64(bufferSize), request.options.Options.GetTimeouts().TcpReadTimeout)
412
if err != nil {
413
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
414
gologger.Verbose().Msgf("could not read more data from %s: %s", actualAddress, err)
415
}
416
responseBuilder.Write(final)
417
418
response := responseBuilder.String()
419
outputEvent := request.responseToDSLMap(reqBuilder.String(), string(final), response, input.MetaInput.Input, actualAddress)
420
// add response fields to template context and merge templatectx variables to output event
421
request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent)
422
if request.options.HasTemplateCtx(input.MetaInput) {
423
outputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll())
424
}
425
outputEvent["ip"] = request.dialer.GetDialedIP(hostname)
426
if request.options.StopAtFirstMatch {
427
outputEvent["stop-at-first-match"] = true
428
}
429
maps0.Copy(outputEvent, previous)
430
maps0.Copy(outputEvent, interimValues)
431
maps0.Copy(outputEvent, inputEvents)
432
if request.options.Interactsh != nil {
433
request.options.Interactsh.MakePlaceholders(interactshURLs, outputEvent)
434
}
435
436
var event *output.InternalWrappedEvent
437
if len(interactshURLs) == 0 {
438
event = eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(payloads, outputEvent), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {
439
wrappedEvent.OperatorsResult.PayloadValues = payloads
440
})
441
callback(event)
442
} else if request.options.Interactsh != nil {
443
event = &output.InternalWrappedEvent{InternalEvent: outputEvent}
444
request.options.Interactsh.RequestEvent(interactshURLs, &interactsh.RequestData{
445
MakeResultFunc: request.MakeResultEvent,
446
Event: event,
447
Operators: request.CompiledOperators,
448
MatchFunc: request.Match,
449
ExtractFunc: request.Extract,
450
})
451
}
452
if len(interactshURLs) > 0 {
453
event.UsesInteractsh = true
454
}
455
456
dumpResponse(event, request, response, actualAddress, address)
457
458
return nil
459
}
460
461
func dumpResponse(event *output.InternalWrappedEvent, request *Request, response string, actualAddress, address string) {
462
cliOptions := request.options.Options
463
if cliOptions.Debug || cliOptions.DebugResponse || cliOptions.StoreResponse {
464
requestBytes := []byte(response)
465
highlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, hex.Dump(requestBytes), cliOptions.NoColor, true)
466
msg := fmt.Sprintf("[%s] Dumped Network response for %s\n\n", request.options.TemplateID, actualAddress)
467
if cliOptions.Debug || cliOptions.DebugResponse {
468
gologger.Debug().Msg(fmt.Sprintf("%s%s", msg, highlightedResponse))
469
}
470
if cliOptions.StoreResponse {
471
request.options.Output.WriteStoreDebugData(address, request.options.TemplateID, request.Type().String(), fmt.Sprintf("%s%s", msg, hex.Dump(requestBytes)))
472
}
473
if cliOptions.VerboseVerbose {
474
displayCompactHexView(event, response, cliOptions.NoColor)
475
}
476
}
477
}
478
479
func displayCompactHexView(event *output.InternalWrappedEvent, response string, noColor bool) {
480
operatorsResult := event.OperatorsResult
481
if operatorsResult != nil {
482
var allMatches []string
483
for _, namedMatch := range operatorsResult.Matches {
484
for _, matchElement := range namedMatch {
485
allMatches = append(allMatches, hex.EncodeToString([]byte(matchElement)))
486
}
487
}
488
tempOperatorResult := &operators.Result{Matches: map[string][]string{"matchesInHex": allMatches}}
489
gologger.Print().Msgf("\nCompact HEX view:\n%s", responsehighlighter.Highlight(tempOperatorResult, hex.EncodeToString([]byte(response)), noColor, false))
490
}
491
}
492
493
// getAddress returns the address of the host to make request to
494
func getAddress(toTest string) (string, error) {
495
if strings.Contains(toTest, "://") {
496
parsed, err := url.Parse(toTest)
497
if err != nil {
498
return "", err
499
}
500
toTest = parsed.Host
501
}
502
return toTest, nil
503
}
504
505
func ConnReadNWithTimeout(conn net.Conn, n int64, timeout time.Duration) ([]byte, error) {
506
switch n {
507
case -1:
508
// if n is -1 then read all available data from connection
509
return reader.ConnReadNWithTimeout(conn, -1, timeout)
510
case 0:
511
n = 4096 // default buffer size
512
}
513
b := make([]byte, n)
514
_ = conn.SetDeadline(time.Now().Add(timeout))
515
count, err := conn.Read(b)
516
_ = conn.SetDeadline(time.Time{})
517
if err != nil && os.IsTimeout(err) && count > 0 {
518
// in case of timeout with some value read, return the value
519
return b[:count], nil
520
}
521
if err != nil {
522
return nil, err
523
}
524
return b[:count], nil
525
}
526
527
// markHostError checks if the error is a unreponsive host error and marks it
528
func (request *Request) markHostError(input *contextargs.Context, err error) {
529
if request.options.HostErrorsCache != nil {
530
request.options.HostErrorsCache.MarkFailedOrRemove(request.options.ProtocolType.String(), input, err)
531
}
532
}
533
534
// isUnresponsiveAddress checks if the error is a unreponsive based on its execution history
535
func (request *Request) isUnresponsiveAddress(input *contextargs.Context) bool {
536
if request.options.HostErrorsCache != nil {
537
return request.options.HostErrorsCache.Check(request.options.ProtocolType.String(), input)
538
}
539
return false
540
}
541
542