Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/javascript/js.go
2070 views
1
package javascript
2
3
import (
4
"bytes"
5
"context"
6
"fmt"
7
"maps"
8
"net"
9
"strings"
10
"sync/atomic"
11
"time"
12
13
"github.com/Mzack9999/goja"
14
"github.com/alecthomas/chroma/quick"
15
"github.com/ditashi/jsbeautifier-go/jsbeautifier"
16
"github.com/pkg/errors"
17
"github.com/projectdiscovery/gologger"
18
"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler"
19
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
20
"github.com/projectdiscovery/nuclei/v3/pkg/model"
21
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
22
"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors"
23
"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
24
"github.com/projectdiscovery/nuclei/v3/pkg/output"
25
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
26
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
27
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
28
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
29
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator"
30
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
31
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
32
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
33
protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
34
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
35
"github.com/projectdiscovery/nuclei/v3/pkg/types"
36
"github.com/projectdiscovery/utils/errkit"
37
iputil "github.com/projectdiscovery/utils/ip"
38
mapsutil "github.com/projectdiscovery/utils/maps"
39
syncutil "github.com/projectdiscovery/utils/sync"
40
urlutil "github.com/projectdiscovery/utils/url"
41
)
42
43
// Request is a request for the javascript protocol
44
type Request struct {
45
// Operators for the current request go here.
46
operators.Operators `yaml:",inline,omitempty" json:",inline,omitempty"`
47
CompiledOperators *operators.Operators `yaml:"-" json:"-"`
48
49
// description: |
50
// ID is request id in that protocol
51
ID string `yaml:"id,omitempty" json:"id,omitempty" jsonschema:"title=id of the request,description=ID is the optional ID of the Request"`
52
53
// description: |
54
// Init is javascript code to execute after compiling template and before executing it on any target
55
// This is helpful for preparing payloads or other setup that maybe required for exploits
56
Init string `yaml:"init,omitempty" json:"init,omitempty" jsonschema:"title=init javascript code,description=Init is the javascript code to execute after compiling template"`
57
58
// description: |
59
// PreCondition is a condition which is evaluated before sending the request.
60
PreCondition string `yaml:"pre-condition,omitempty" json:"pre-condition,omitempty" jsonschema:"title=pre-condition for the request,description=PreCondition is a condition which is evaluated before sending the request"`
61
62
// description: |
63
// Args contains the arguments to pass to the javascript code.
64
Args map[string]interface{} `yaml:"args,omitempty" json:"args,omitempty"`
65
// description: |
66
// Code contains code to execute for the javascript request.
67
Code string `yaml:"code,omitempty" json:"code,omitempty" jsonschema:"title=code to execute in javascript,description=Executes inline javascript code for the request"`
68
// description: |
69
// StopAtFirstMatch stops processing the request at first match.
70
StopAtFirstMatch bool `yaml:"stop-at-first-match,omitempty" json:"stop-at-first-match,omitempty" jsonschema:"title=stop at first match,description=Stop the execution after a match is found"`
71
// description: |
72
// Attack is the type of payload combinations to perform.
73
//
74
// Sniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates
75
// permutations and combinations for all payloads.
76
AttackType generators.AttackTypeHolder `yaml:"attack,omitempty" json:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=sniper,enum=pitchfork,enum=clusterbomb"`
77
// description: |
78
// Payload concurreny i.e threads for sending requests.
79
// examples:
80
// - name: Send requests using 10 concurrent threads
81
// value: 10
82
Threads int `yaml:"threads,omitempty" json:"threads,omitempty" jsonschema:"title=threads for sending requests,description=Threads specifies number of threads to use sending requests. This enables Connection Pooling"`
83
// description: |
84
// Payloads contains any payloads for the current request.
85
//
86
// Payloads support both key-values combinations where a list
87
// of payloads is provided, or optionally a single file can also
88
// be provided as payload which will be read on run-time.
89
Payloads map[string]interface{} `yaml:"payloads,omitempty" json:"payloads,omitempty" jsonschema:"title=payloads for the webosocket request,description=Payloads contains any payloads for the current request"`
90
91
generator *generators.PayloadGenerator
92
93
// cache any variables that may be needed for operation.
94
options *protocols.ExecutorOptions `yaml:"-" json:"-"`
95
96
preConditionCompiled *goja.Program `yaml:"-" json:"-"`
97
98
scriptCompiled *goja.Program `yaml:"-" json:"-"`
99
}
100
101
// Compile compiles the request generators preparing any requests possible.
102
func (request *Request) Compile(options *protocols.ExecutorOptions) error {
103
request.options = options
104
105
var err error
106
if len(request.Payloads) > 0 {
107
request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, options.Catalog, options.Options.AttackType, options.Options)
108
if err != nil {
109
return errors.Wrap(err, "could not parse payloads")
110
}
111
// default to 20 threads for payload requests
112
request.Threads = options.GetThreadsForNPayloadRequests(request.Requests(), request.Threads)
113
}
114
115
if len(request.Matchers) > 0 || len(request.Extractors) > 0 {
116
compiled := &request.Operators
117
compiled.ExcludeMatchers = options.ExcludeMatchers
118
compiled.TemplateID = options.TemplateID
119
for _, matcher := range compiled.Matchers {
120
if matcher.Part == "" && matcher.Type.MatcherType != matchers.DSLMatcher {
121
matcher.Part = "response"
122
}
123
}
124
for _, extractor := range compiled.Extractors {
125
if extractor.Part == "" {
126
extractor.Part = "response"
127
}
128
}
129
if err := compiled.Compile(); err != nil {
130
return errkit.Newf("could not compile operators got %v", err)
131
}
132
request.CompiledOperators = compiled
133
}
134
135
// "Port" is a special variable and it should not contains any dsl expressions
136
if strings.Contains(request.getPort(), "{{") {
137
return errkit.New("'Port' variable cannot contain any dsl expressions")
138
}
139
140
if request.Init != "" {
141
// execute init code if any
142
if request.options.Options.Debug || request.options.Options.DebugRequests {
143
gologger.Debug().Msgf("[%s] Executing Template Init\n", request.TemplateID)
144
var highlightFormatter = "terminal256"
145
if request.options.Options.NoColor {
146
highlightFormatter = "text"
147
}
148
var buff bytes.Buffer
149
_ = quick.Highlight(&buff, beautifyJavascript(request.Init), "javascript", highlightFormatter, "monokai")
150
prettyPrint(request.TemplateID, buff.String())
151
}
152
153
opts := &compiler.ExecuteOptions{
154
ExecutionId: request.options.Options.ExecutionId,
155
TimeoutVariants: request.options.Options.GetTimeouts(),
156
Source: &request.Init,
157
Context: context.Background(),
158
}
159
// register 'export' function to export variables from init code
160
// these are saved in args and are available in pre-condition and request code
161
opts.Callback = func(runtime *goja.Runtime) error {
162
err := gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
163
Name: "set",
164
Signatures: []string{
165
"set(string, interface{})",
166
},
167
Description: "set variable from init code. this function is available in init code block only",
168
FuncDecl: func(varname string, value any) error {
169
if varname == "" {
170
return fmt.Errorf("variable name cannot be empty")
171
}
172
if value == nil {
173
return fmt.Errorf("variable value cannot be empty")
174
}
175
if request.Args == nil {
176
request.Args = make(map[string]interface{})
177
}
178
request.Args[varname] = value
179
return nil
180
},
181
})
182
if err != nil {
183
return err
184
}
185
186
return gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
187
Name: "updatePayload",
188
Signatures: []string{
189
"updatePayload(string, interface{})",
190
},
191
Description: "update/override any payload from init code. this function is available in init code block only",
192
FuncDecl: func(varname string, Value any) error {
193
if request.Payloads == nil {
194
request.Payloads = make(map[string]interface{})
195
}
196
if request.generator != nil {
197
request.Payloads[varname] = Value
198
request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, options.Catalog, options.Options.AttackType, options.Options)
199
if err != nil {
200
return err
201
}
202
} else {
203
return fmt.Errorf("payloads not defined and cannot be updated")
204
}
205
return nil
206
},
207
})
208
}
209
opts.Cleanup = func(runtime *goja.Runtime) {
210
_ = runtime.GlobalObject().Delete("set")
211
_ = runtime.GlobalObject().Delete("updatePayload")
212
}
213
214
args := compiler.NewExecuteArgs()
215
allVars := generators.MergeMaps(options.Variables.GetAll(), options.Options.Vars.AsMap(), request.options.Constants)
216
// proceed with whatever args we have
217
args.Args, _ = request.evaluateArgs(allVars, options, true)
218
219
initCompiled, err := compiler.SourceAutoMode(request.Init, false)
220
if err != nil {
221
return errkit.Newf("could not compile init code: %s", err)
222
}
223
result, err := request.options.JsCompiler.ExecuteWithOptions(initCompiled, args, opts)
224
if err != nil {
225
return errkit.Newf("could not execute pre-condition: %s", err)
226
}
227
if types.ToString(result["error"]) != "" {
228
gologger.Warning().Msgf("[%s] Init failed with error %v\n", request.TemplateID, result["error"])
229
return nil
230
} else {
231
if request.options.Options.Debug || request.options.Options.DebugResponse {
232
gologger.Debug().Msgf("[%s] Init executed successfully\n", request.TemplateID)
233
gologger.Debug().Msgf("[%s] Init result: %v\n", request.TemplateID, result["response"])
234
}
235
}
236
}
237
238
// compile pre-condition if any
239
if request.PreCondition != "" {
240
preConditionCompiled, err := compiler.SourceAutoMode(request.PreCondition, false)
241
if err != nil {
242
return errkit.Newf("could not compile pre-condition: %s", err)
243
}
244
request.preConditionCompiled = preConditionCompiled
245
}
246
247
// compile actual source code
248
if request.Code != "" {
249
scriptCompiled, err := compiler.SourceAutoMode(request.Code, false)
250
if err != nil {
251
return errkit.Newf("could not compile javascript code: %s", err)
252
}
253
request.scriptCompiled = scriptCompiled
254
}
255
256
return nil
257
}
258
259
// Options returns executer options for http request
260
func (r *Request) Options() *protocols.ExecutorOptions {
261
return r.options
262
}
263
264
// Requests returns the total number of requests the rule will perform
265
func (request *Request) Requests() int {
266
pre_conditions := 0
267
if request.PreCondition != "" {
268
pre_conditions = 1
269
}
270
if request.generator != nil {
271
payloadRequests := request.generator.NewIterator().Total()
272
return payloadRequests + pre_conditions
273
}
274
return 1 + pre_conditions
275
}
276
277
// GetID returns the ID for the request if any.
278
func (request *Request) GetID() string {
279
return request.ID
280
}
281
282
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
283
func (request *Request) ExecuteWithResults(target *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
284
285
input := target.Clone()
286
// use network port updates input with new port requested in template file
287
// and it is ignored if input port is not standard http(s) ports like 80,8080,8081 etc
288
// idea is to reduce redundant dials to http ports
289
if err := input.UseNetworkPort(request.getPort(), request.getExcludePorts()); err != nil {
290
gologger.Debug().Msgf("Could not network port from constants: %s\n", err)
291
}
292
293
hostPort, err := getAddress(input.MetaInput.Input)
294
if err != nil {
295
request.options.Progress.IncrementFailedRequestsBy(1)
296
return err
297
}
298
hostname, port, _ := net.SplitHostPort(hostPort)
299
if hostname == "" {
300
hostname = hostPort
301
}
302
303
requestOptions := request.options
304
templateCtx := request.options.GetTemplateCtx(input.MetaInput)
305
306
payloadValues := generators.BuildPayloadFromOptions(request.options.Options)
307
maps.Copy(payloadValues, dynamicValues)
308
309
payloadValues["Hostname"] = hostPort
310
payloadValues["Host"] = hostname
311
payloadValues["Port"] = port
312
313
hostnameVariables := protocolutils.GenerateDNSVariables(hostname)
314
values := generators.MergeMaps(payloadValues, hostnameVariables, request.options.Constants, templateCtx.GetAll())
315
variablesMap := request.options.Variables.Evaluate(values)
316
payloadValues = generators.MergeMaps(variablesMap, payloadValues, request.options.Constants, hostnameVariables)
317
318
var interactshURLs []string
319
if request.options.Interactsh != nil {
320
for payloadName, payloadValue := range payloadValues {
321
var urls []string
322
payloadValue, urls = request.options.Interactsh.Replace(types.ToString(payloadValue), interactshURLs)
323
if len(urls) > 0 {
324
interactshURLs = append(interactshURLs, urls...)
325
payloadValues[payloadName] = payloadValue
326
}
327
}
328
}
329
330
// export all variables to template context
331
templateCtx.Merge(payloadValues)
332
333
if vardump.EnableVarDump {
334
gologger.Debug().Msgf("JavaScript Protocol request variables: %s\n", vardump.DumpVariables(payloadValues))
335
}
336
337
if request.PreCondition != "" {
338
payloads := generators.MergeMaps(payloadValues, previous)
339
340
if request.options.Options.Debug || request.options.Options.DebugRequests {
341
gologger.Debug().Msgf("[%s] Executing Precondition for request\n", request.TemplateID)
342
var highlightFormatter = "terminal256"
343
if requestOptions.Options.NoColor {
344
highlightFormatter = "text"
345
}
346
var buff bytes.Buffer
347
_ = quick.Highlight(&buff, beautifyJavascript(request.PreCondition), "javascript", highlightFormatter, "monokai")
348
prettyPrint(request.TemplateID, buff.String())
349
}
350
351
argsCopy, err := request.getArgsCopy(input, payloads, requestOptions, true)
352
if err != nil {
353
return err
354
}
355
argsCopy.TemplateCtx = templateCtx.GetAll()
356
357
result, err := request.options.JsCompiler.ExecuteWithOptions(request.preConditionCompiled, argsCopy,
358
&compiler.ExecuteOptions{
359
ExecutionId: requestOptions.Options.ExecutionId,
360
TimeoutVariants: requestOptions.Options.GetTimeouts(),
361
Source: &request.PreCondition, Context: target.Context(),
362
})
363
// if precondition was successful
364
if err == nil && result.GetSuccess() {
365
if request.options.Options.Debug || request.options.Options.DebugRequests {
366
request.options.Progress.IncrementRequests()
367
gologger.Debug().Msgf("[%s] Precondition for request was satisfied\n", request.TemplateID)
368
}
369
} else {
370
var outError error
371
// if js code failed to execute
372
if err != nil {
373
outError = errkit.Append(errkit.New("pre-condition not satisfied skipping template execution"), err)
374
} else {
375
// execution successful but pre-condition returned false
376
outError = errkit.New("pre-condition not satisfied skipping template execution")
377
}
378
results := map[string]interface{}(result)
379
results["error"] = outError.Error()
380
// generate and return failed event
381
data := request.generateEventData(input, results, hostPort)
382
data = generators.MergeMaps(data, payloadValues)
383
event := eventcreator.CreateEventWithAdditionalOptions(request, data, request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {
384
allVars := argsCopy.Map()
385
allVars = generators.MergeMaps(allVars, data)
386
wrappedEvent.OperatorsResult.PayloadValues = allVars
387
})
388
callback(event)
389
return err
390
}
391
}
392
393
if request.generator != nil && request.Threads > 1 {
394
request.executeRequestParallel(target.Context(), hostPort, hostname, input, payloadValues, callback)
395
return nil
396
}
397
398
var gotMatches bool
399
if request.generator != nil {
400
iterator := request.generator.NewIterator()
401
402
for {
403
value, ok := iterator.Value()
404
if !ok {
405
return nil
406
}
407
408
select {
409
case <-input.Context().Done():
410
return input.Context().Err()
411
default:
412
}
413
414
if err := request.executeRequestWithPayloads(hostPort, input, hostname, value, payloadValues, func(result *output.InternalWrappedEvent) {
415
if result.OperatorsResult != nil && result.OperatorsResult.Matched {
416
gotMatches = true
417
request.options.Progress.IncrementMatched()
418
}
419
callback(result)
420
}, requestOptions, interactshURLs); err != nil {
421
if errkit.IsNetworkPermanentErr(err) {
422
// gologger.Verbose().Msgf("Could not execute request: %s\n", err)
423
return err
424
}
425
}
426
// If this was a match, and we want to stop at first match, skip all further requests.
427
shouldStopAtFirstMatch := request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch
428
if shouldStopAtFirstMatch && gotMatches {
429
return nil
430
}
431
}
432
}
433
return request.executeRequestWithPayloads(hostPort, input, hostname, nil, payloadValues, callback, requestOptions, interactshURLs)
434
}
435
436
func (request *Request) executeRequestParallel(ctxParent context.Context, hostPort, hostname string, input *contextargs.Context, payloadValues map[string]interface{}, callback protocols.OutputEventCallback) {
437
threads := request.Threads
438
if threads == 0 {
439
threads = 1
440
}
441
ctx, cancel := context.WithCancelCause(ctxParent)
442
defer cancel(nil)
443
requestOptions := request.options
444
gotmatches := &atomic.Bool{}
445
446
// if request threads matches global payload concurrency we follow it
447
shouldFollowGlobal := threads == request.options.Options.PayloadConcurrency
448
449
sg, _ := syncutil.New(syncutil.WithSize(threads))
450
451
if request.generator != nil {
452
iterator := request.generator.NewIterator()
453
for {
454
value, ok := iterator.Value()
455
if !ok {
456
break
457
}
458
459
select {
460
case <-input.Context().Done():
461
return
462
default:
463
}
464
465
// resize check point - nop if there are no changes
466
if shouldFollowGlobal && sg.Size != request.options.Options.PayloadConcurrency {
467
if err := sg.Resize(ctxParent, request.options.Options.PayloadConcurrency); err != nil {
468
gologger.Warning().Msgf("Could not resize workpool: %s\n", err)
469
}
470
}
471
472
sg.Add()
473
go func() {
474
defer sg.Done()
475
if ctx.Err() != nil {
476
// work already done exit
477
return
478
}
479
shouldStopAtFirstMatch := request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch
480
if err := request.executeRequestWithPayloads(hostPort, input, hostname, value, payloadValues, func(result *output.InternalWrappedEvent) {
481
if result.OperatorsResult != nil && result.OperatorsResult.Matched {
482
gotmatches.Store(true)
483
}
484
callback(result)
485
}, requestOptions, []string{}); err != nil {
486
if errkit.IsNetworkPermanentErr(err) {
487
cancel(err)
488
return
489
}
490
}
491
// If this was a match, and we want to stop at first match, skip all further requests.
492
493
if shouldStopAtFirstMatch && gotmatches.Load() {
494
cancel(nil)
495
return
496
}
497
}()
498
}
499
}
500
sg.Wait()
501
if gotmatches.Load() {
502
request.options.Progress.IncrementMatched()
503
}
504
}
505
506
func (request *Request) executeRequestWithPayloads(hostPort string, input *contextargs.Context, _ string, payload map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback, requestOptions *protocols.ExecutorOptions, interactshURLs []string) error {
507
payloadValues := generators.MergeMaps(payload, previous)
508
argsCopy, err := request.getArgsCopy(input, payloadValues, requestOptions, false)
509
if err != nil {
510
return err
511
}
512
if request.options.HasTemplateCtx(input.MetaInput) {
513
argsCopy.TemplateCtx = request.options.GetTemplateCtx(input.MetaInput).GetAll()
514
} else {
515
argsCopy.TemplateCtx = map[string]interface{}{}
516
}
517
518
if request.options.Interactsh != nil {
519
if argsCopy.Args != nil {
520
for k, v := range argsCopy.Args {
521
var urls []string
522
v, urls = request.options.Interactsh.Replace(fmt.Sprint(v), []string{})
523
if len(urls) > 0 {
524
interactshURLs = append(interactshURLs, urls...)
525
argsCopy.Args[k] = v
526
}
527
}
528
}
529
}
530
531
results, err := request.options.JsCompiler.ExecuteWithOptions(request.scriptCompiled, argsCopy,
532
&compiler.ExecuteOptions{
533
ExecutionId: requestOptions.Options.ExecutionId,
534
TimeoutVariants: requestOptions.Options.GetTimeouts(),
535
Source: &request.Code,
536
Context: input.Context(),
537
})
538
if err != nil {
539
// shouldn't fail even if it returned error instead create a failure event
540
results = compiler.ExecuteResult{"success": false, "error": err.Error()}
541
}
542
request.options.Progress.IncrementRequests()
543
requestOptions.Output.Request(requestOptions.TemplateID, hostPort, request.Type().String(), err)
544
gologger.Verbose().Msgf("[%s] Sent Javascript request to %s", request.options.TemplateID, hostPort)
545
546
if requestOptions.Options.Debug || requestOptions.Options.DebugRequests || requestOptions.Options.StoreResponse {
547
msg := fmt.Sprintf("[%s] Dumped Javascript request for %s:\nVariables:\n %v", requestOptions.TemplateID, input.MetaInput.Input, vardump.DumpVariables(argsCopy.Args))
548
549
if requestOptions.Options.Debug || requestOptions.Options.DebugRequests {
550
gologger.Debug().Str("address", input.MetaInput.Input).Msg(msg)
551
var highlightFormatter = "terminal256"
552
if requestOptions.Options.NoColor {
553
highlightFormatter = "text"
554
}
555
var buff bytes.Buffer
556
_ = quick.Highlight(&buff, beautifyJavascript(request.Code), "javascript", highlightFormatter, "monokai")
557
prettyPrint(request.TemplateID, buff.String())
558
}
559
if requestOptions.Options.StoreResponse {
560
request.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), msg)
561
}
562
}
563
564
values := mapsutil.Merge(payloadValues, results)
565
// generate event data
566
data := request.generateEventData(input, values, hostPort)
567
568
// add and get values from templatectx
569
request.options.AddTemplateVars(input.MetaInput, request.Type(), request.GetID(), data)
570
data = generators.MergeMaps(data, request.options.GetTemplateCtx(input.MetaInput).GetAll())
571
572
if requestOptions.Options.Debug || requestOptions.Options.DebugRequests || requestOptions.Options.StoreResponse {
573
msg := fmt.Sprintf("[%s] Dumped Javascript response for %s:\n%v", requestOptions.TemplateID, input.MetaInput.Input, vardump.DumpVariables(results))
574
if requestOptions.Options.Debug || requestOptions.Options.DebugRequests {
575
gologger.Debug().Str("address", input.MetaInput.Input).Msg(msg)
576
}
577
if requestOptions.Options.StoreResponse {
578
request.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), msg)
579
}
580
}
581
582
if _, ok := data["error"]; ok {
583
event := eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(data, payloadValues), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {
584
wrappedEvent.OperatorsResult.PayloadValues = payload
585
})
586
callback(event)
587
return err
588
}
589
590
if request.options.Interactsh != nil {
591
request.options.Interactsh.MakePlaceholders(interactshURLs, data)
592
}
593
594
var event *output.InternalWrappedEvent
595
if len(interactshURLs) == 0 {
596
event = eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(data, payloadValues), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {
597
wrappedEvent.OperatorsResult.PayloadValues = payload
598
})
599
callback(event)
600
} else if request.options.Interactsh != nil {
601
event = &output.InternalWrappedEvent{InternalEvent: data, UsesInteractsh: true}
602
request.options.Interactsh.RequestEvent(interactshURLs, &interactsh.RequestData{
603
MakeResultFunc: request.MakeResultEvent,
604
Event: event,
605
Operators: request.CompiledOperators,
606
MatchFunc: request.Match,
607
ExtractFunc: request.Extract,
608
})
609
}
610
return nil
611
}
612
613
// generateEventData generates event data for the request
614
func (request *Request) generateEventData(input *contextargs.Context, values map[string]interface{}, matched string) map[string]interface{} {
615
dialers := protocolstate.GetDialersWithId(request.options.Options.ExecutionId)
616
if dialers == nil {
617
panic(fmt.Sprintf("dialers not initialized for %s", request.options.Options.ExecutionId))
618
}
619
620
data := make(map[string]interface{})
621
maps.Copy(data, values)
622
data["type"] = request.Type().String()
623
data["request-pre-condition"] = beautifyJavascript(request.PreCondition)
624
data["request"] = beautifyJavascript(request.Code)
625
data["host"] = input.MetaInput.Input
626
data["matched"] = matched
627
data["template-path"] = request.options.TemplatePath
628
data["template-id"] = request.options.TemplateID
629
data["template-info"] = request.options.TemplateInfo
630
if request.StopAtFirstMatch || request.options.StopAtFirstMatch {
631
data["stop-at-first-match"] = true
632
}
633
// add ip address to data
634
if input.MetaInput.CustomIP != "" {
635
data["ip"] = input.MetaInput.CustomIP
636
} else {
637
// context: https://github.com/projectdiscovery/nuclei/issues/5021
638
hostname := input.MetaInput.Input
639
if strings.Contains(hostname, ":") {
640
host, _, err := net.SplitHostPort(hostname)
641
if err == nil {
642
hostname = host
643
} else {
644
// naive way
645
if !strings.Contains(hostname, "]") {
646
hostname = hostname[:strings.LastIndex(hostname, ":")]
647
}
648
}
649
}
650
data["ip"] = dialers.Fastdialer.GetDialedIP(hostname)
651
// if input itself was an ip, use it
652
if iputil.IsIP(hostname) {
653
data["ip"] = hostname
654
}
655
656
// if ip is not found,this is because ssh and other protocols do not use fastdialer
657
// although its not perfect due to its use case dial and get ip
658
dnsData, err := dialers.Fastdialer.GetDNSData(hostname)
659
if err == nil {
660
for _, v := range dnsData.A {
661
data["ip"] = v
662
break
663
}
664
if data["ip"] == "" {
665
for _, v := range dnsData.AAAA {
666
data["ip"] = v
667
break
668
}
669
}
670
}
671
}
672
return data
673
}
674
675
func (request *Request) getArgsCopy(input *contextargs.Context, payloadValues map[string]interface{}, requestOptions *protocols.ExecutorOptions, ignoreErrors bool) (*compiler.ExecuteArgs, error) {
676
// Template args from payloads
677
argsCopy, err := request.evaluateArgs(payloadValues, requestOptions, ignoreErrors)
678
if err != nil {
679
requestOptions.Output.Request(requestOptions.TemplateID, input.MetaInput.Input, request.Type().String(), err)
680
requestOptions.Progress.IncrementFailedRequestsBy(1)
681
}
682
// "Port" is a special variable that is considered as network port
683
// and is conditional based on input port and default port specified in input
684
argsCopy["Port"] = input.Port()
685
686
return &compiler.ExecuteArgs{Args: argsCopy}, nil
687
}
688
689
// evaluateArgs evaluates arguments using available payload values and returns a copy of args
690
func (request *Request) evaluateArgs(payloadValues map[string]interface{}, _ *protocols.ExecutorOptions, ignoreErrors bool) (map[string]interface{}, error) {
691
argsCopy := make(map[string]interface{})
692
mainLoop:
693
for k, v := range request.Args {
694
if vVal, ok := v.(string); ok && strings.Contains(vVal, "{") {
695
finalAddress, dataErr := expressions.Evaluate(vVal, payloadValues)
696
if dataErr != nil {
697
return nil, errors.Wrap(dataErr, "could not evaluate template expressions")
698
}
699
if finalAddress == vVal && ignoreErrors {
700
argsCopy[k] = ""
701
continue mainLoop
702
}
703
argsCopy[k] = finalAddress
704
} else {
705
argsCopy[k] = v
706
}
707
}
708
return argsCopy, nil
709
}
710
711
// RequestPartDefinitions contains a mapping of request part definitions and their
712
// description. Multiple definitions are separated by commas.
713
// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.
714
var RequestPartDefinitions = map[string]string{
715
"type": "Type is the type of request made",
716
"response": "Javascript protocol result response",
717
"host": "Host is the input to the template",
718
"matched": "Matched is the input which was matched upon",
719
}
720
721
// getAddress returns the address of the host to make request to
722
func getAddress(toTest string) (string, error) {
723
urlx, err := urlutil.Parse(toTest)
724
if err != nil {
725
// use given input instead of url parsing failure
726
return toTest, nil
727
}
728
return urlx.Host, nil
729
}
730
731
// Match performs matching operation for a matcher on model and returns:
732
// true and a list of matched snippets if the matcher type is supports it
733
// otherwise false and an empty string slice
734
func (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {
735
return protocols.MakeDefaultMatchFunc(data, matcher)
736
}
737
738
// Extract performs extracting operation for an extractor on model and returns true or false.
739
func (request *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} {
740
return protocols.MakeDefaultExtractFunc(data, matcher)
741
}
742
743
// MakeResultEvent creates a result event from internal wrapped event
744
func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {
745
return protocols.MakeDefaultResultEvent(request, wrapped)
746
}
747
748
// GetCompiledOperators returns a list of the compiled operators
749
func (request *Request) GetCompiledOperators() []*operators.Operators {
750
return []*operators.Operators{request.CompiledOperators}
751
}
752
753
// Type returns the type of the protocol request
754
func (request *Request) Type() templateTypes.ProtocolType {
755
return templateTypes.JavascriptProtocol
756
}
757
758
func (request *Request) getPort() string {
759
for k, v := range request.Args {
760
if strings.EqualFold(k, "Port") {
761
return types.ToString(v)
762
}
763
}
764
return ""
765
}
766
767
func (request *Request) getExcludePorts() string {
768
for k, v := range request.Args {
769
if strings.EqualFold(k, "exclude-ports") {
770
return types.ToString(v)
771
}
772
}
773
return ""
774
}
775
776
func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {
777
fields := protocolutils.GetJsonFieldsFromURL(types.ToString(wrapped.InternalEvent["host"]))
778
if types.ToString(wrapped.InternalEvent["ip"]) != "" {
779
fields.Ip = types.ToString(wrapped.InternalEvent["ip"])
780
}
781
data := &output.ResultEvent{
782
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
783
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
784
Info: wrapped.InternalEvent["template-info"].(model.Info),
785
TemplateVerifier: request.options.TemplateVerifier,
786
Type: types.ToString(wrapped.InternalEvent["type"]),
787
Host: fields.Host,
788
Port: fields.Port,
789
URL: fields.URL,
790
Matched: types.ToString(wrapped.InternalEvent["matched"]),
791
Metadata: wrapped.OperatorsResult.PayloadValues,
792
ExtractedResults: wrapped.OperatorsResult.OutputExtracts,
793
Timestamp: time.Now(),
794
MatcherStatus: true,
795
Request: types.ToString(wrapped.InternalEvent["request"]),
796
Response: types.ToString(wrapped.InternalEvent["response"]),
797
IP: fields.Ip,
798
TemplateEncoded: request.options.EncodeTemplate(),
799
Error: types.ToString(wrapped.InternalEvent["error"]),
800
}
801
return data
802
}
803
804
func beautifyJavascript(code string) string {
805
opts := jsbeautifier.DefaultOptions()
806
beautified, err := jsbeautifier.Beautify(&code, opts)
807
if err != nil {
808
return code
809
}
810
return beautified
811
}
812
813
func prettyPrint(templateId string, buff string) {
814
lines := strings.Split(buff, "\n")
815
final := []string{}
816
for _, v := range lines {
817
if v != "" {
818
final = append(final, "\t"+v)
819
}
820
}
821
gologger.Debug().Msgf(" [%v] Javascript Code:\n\n%v\n\n", templateId, strings.Join(final, "\n"))
822
}
823
824
// UpdateOptions replaces this request's options with a new copy
825
func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {
826
r.options.ApplyNewEngineOptions(opts)
827
}
828
829