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