Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/code/code.go
2070 views
1
package code
2
3
import (
4
"bytes"
5
"context"
6
"fmt"
7
"regexp"
8
"strings"
9
"time"
10
11
"github.com/Mzack9999/goja"
12
"github.com/alecthomas/chroma/quick"
13
"github.com/ditashi/jsbeautifier-go/jsbeautifier"
14
15
"github.com/projectdiscovery/gologger"
16
"github.com/projectdiscovery/gozero"
17
gozerotypes "github.com/projectdiscovery/gozero/types"
18
"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler"
19
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
20
"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors"
21
"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
22
"github.com/projectdiscovery/nuclei/v3/pkg/output"
23
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
24
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
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/utils/vardump"
30
protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
31
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
32
"github.com/projectdiscovery/nuclei/v3/pkg/types"
33
contextutil "github.com/projectdiscovery/utils/context"
34
"github.com/projectdiscovery/utils/errkit"
35
)
36
37
const (
38
pythonEnvRegex = `os\.getenv\(['"]([^'"]+)['"]\)`
39
)
40
41
var (
42
// pythonEnvRegexCompiled is the compiled regex for python environment variables
43
pythonEnvRegexCompiled = regexp.MustCompile(pythonEnvRegex)
44
// ErrCodeExecutionDeadline is the error returned when alloted time for script execution exceeds
45
ErrCodeExecutionDeadline = errkit.New("code execution deadline exceeded").SetKind(errkit.ErrKindDeadline).Build()
46
)
47
48
// Request is a request for the SSL protocol
49
type Request struct {
50
// Operators for the current request go here.
51
operators.Operators `yaml:",inline,omitempty"`
52
CompiledOperators *operators.Operators `yaml:"-" json:"-"`
53
54
// ID is the optional id of the request
55
ID string `yaml:"id,omitempty" json:"id,omitempty" jsonschema:"title=id of the request,description=ID is the optional ID of the Request"`
56
// description: |
57
// Engine type
58
Engine []string `yaml:"engine,omitempty" json:"engine,omitempty" jsonschema:"title=engine,description=Engine"`
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
// description: |
63
// Engine Arguments
64
Args []string `yaml:"args,omitempty" json:"args,omitempty" jsonschema:"title=args,description=Args"`
65
// description: |
66
// Pattern preferred for file name
67
Pattern string `yaml:"pattern,omitempty" json:"pattern,omitempty" jsonschema:"title=pattern,description=Pattern"`
68
// description: |
69
// Source File/Snippet
70
Source string `yaml:"source,omitempty" json:"source,omitempty" jsonschema:"title=source file/snippet,description=Source snippet"`
71
72
options *protocols.ExecutorOptions `yaml:"-" json:"-"`
73
preConditionCompiled *goja.Program `yaml:"-" json:"-"`
74
gozero *gozero.Gozero `yaml:"-" json:"-"`
75
src *gozero.Source `yaml:"-" json:"-"`
76
}
77
78
// Compile compiles the request generators preparing any requests possible.
79
func (request *Request) Compile(options *protocols.ExecutorOptions) error {
80
request.options = options
81
82
gozeroOptions := &gozero.Options{
83
Engines: request.Engine,
84
Args: request.Args,
85
EarlyCloseFileDescriptor: true,
86
}
87
88
if options.Options.Debug || options.Options.DebugResponse {
89
// enable debug mode for gozero
90
gozeroOptions.DebugMode = true
91
}
92
93
engine, err := gozero.New(gozeroOptions)
94
if err != nil {
95
errMsg := fmt.Sprintf("[%s] engines '%s' not available on host", options.TemplateID, strings.Join(request.Engine, ","))
96
97
// NOTE(dwisiswant0): In validation mode, skip engine avail check to
98
// allow template validation w/o requiring all engines to be installed
99
// on the host.
100
//
101
// TODO: Ideally, error checking should be done at the highest level
102
// (e.g. runner, main function). For example, we can reuse errors[1][2]
103
// from the `projectdiscovery/gozero` package and wrap (yes, not string
104
// format[3][4]) em inside `projectdiscovery/utils/errors` package to
105
// preserve error semantics and enable runtime type assertion via
106
// builtin `errors.Is` func for granular err handling in the call stack.
107
//
108
// [1]: https://github.com/projectdiscovery/gozero/blob/v0.0.3/gozero.go#L20
109
// [2]: https://github.com/projectdiscovery/gozero/blob/v0.0.3/gozero.go#L35
110
// [3]: https://github.com/projectdiscovery/utils/blob/v0.4.21/errors/enriched.go#L85
111
// [4]: https://github.com/projectdiscovery/utils/blob/v0.4.21/errors/enriched.go#L137
112
if options.Options.Validate {
113
options.Logger.Error().Msgf("%s <- %s", errMsg, err)
114
} else {
115
return errkit.Wrap(err, errMsg)
116
}
117
} else {
118
request.gozero = engine
119
}
120
121
var src *gozero.Source
122
123
src, err = gozero.NewSourceWithString(request.Source, request.Pattern, request.options.TemporaryDirectory)
124
if err != nil {
125
return err
126
}
127
request.src = src
128
129
if len(request.Matchers) > 0 || len(request.Extractors) > 0 {
130
compiled := &request.Operators
131
compiled.ExcludeMatchers = options.ExcludeMatchers
132
compiled.TemplateID = options.TemplateID
133
if err := compiled.Compile(); err != nil {
134
return errkit.Wrap(err, "could not compile operators")
135
}
136
for _, matcher := range compiled.Matchers {
137
// default matcher part for code protocol is response
138
if matcher.Part == "" || matcher.Part == "body" {
139
matcher.Part = "response"
140
}
141
}
142
for _, extractor := range compiled.Extractors {
143
// default extractor part for code protocol is response
144
if extractor.Part == "" || extractor.Part == "body" {
145
extractor.Part = "response"
146
}
147
}
148
request.CompiledOperators = compiled
149
}
150
151
// compile pre-condition if any
152
if request.PreCondition != "" {
153
preConditionCompiled, err := compiler.SourceAutoMode(request.PreCondition, false)
154
if err != nil {
155
return errkit.Newf("could not compile pre-condition: %s", err)
156
}
157
request.preConditionCompiled = preConditionCompiled
158
}
159
return nil
160
}
161
162
// Requests returns the total number of requests the rule will perform
163
func (request *Request) Requests() int {
164
return 1
165
}
166
167
// GetID returns the ID for the request if any.
168
func (request *Request) GetID() string {
169
return request.ID
170
}
171
172
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
173
func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) (err error) {
174
metaSrc, err := gozero.NewSourceWithString(input.MetaInput.Input, "", request.options.TemporaryDirectory)
175
if err != nil {
176
return err
177
}
178
defer func() {
179
if err := metaSrc.Cleanup(); err != nil {
180
gologger.Warning().Msgf("%s\n", err)
181
}
182
}()
183
184
var interactshURLs []string
185
186
// inject all template context values as gozero env allvars
187
allvars := protocolutils.GenerateVariables(input.MetaInput.Input, false, nil)
188
// add template context values if available
189
if request.options.HasTemplateCtx(input.MetaInput) {
190
allvars = generators.MergeMaps(allvars, request.options.GetTemplateCtx(input.MetaInput).GetAll())
191
}
192
// add dynamic and previous variables
193
allvars = generators.MergeMaps(allvars, dynamicValues, previous)
194
// optionvars are vars passed from CLI or env variables
195
optionVars := generators.BuildPayloadFromOptions(request.options.Options)
196
variablesMap := request.options.Variables.Evaluate(allvars)
197
// since we evaluate variables using allvars, give precedence to variablesMap
198
allvars = generators.MergeMaps(allvars, variablesMap, optionVars, request.options.Constants)
199
for name, value := range allvars {
200
v := fmt.Sprint(value)
201
v, interactshURLs = request.options.Interactsh.Replace(v, interactshURLs)
202
// if value is updated by interactsh, update allvars to reflect the change downstream
203
allvars[name] = v
204
metaSrc.AddVariable(gozerotypes.Variable{Name: name, Value: v})
205
}
206
207
if request.PreCondition != "" {
208
if request.options.Options.Debug || request.options.Options.DebugRequests {
209
gologger.Debug().Msgf("[%s] Executing Precondition for Code request\n", request.TemplateID)
210
var highlightFormatter = "terminal256"
211
if request.options.Options.NoColor {
212
highlightFormatter = "text"
213
}
214
var buff bytes.Buffer
215
_ = quick.Highlight(&buff, beautifyJavascript(request.PreCondition), "javascript", highlightFormatter, "monokai")
216
prettyPrint(request.TemplateID, buff.String())
217
}
218
219
args := compiler.NewExecuteArgs()
220
args.TemplateCtx = allvars
221
222
result, err := request.options.JsCompiler.ExecuteWithOptions(request.preConditionCompiled, args,
223
&compiler.ExecuteOptions{
224
ExecutionId: request.options.Options.ExecutionId,
225
TimeoutVariants: request.options.Options.GetTimeouts(),
226
Source: &request.PreCondition,
227
Callback: registerPreConditionFunctions,
228
Cleanup: cleanUpPreConditionFunctions,
229
Context: input.Context(),
230
})
231
if err != nil {
232
return errkit.Newf("could not execute pre-condition: %s", err)
233
}
234
if !result.GetSuccess() || types.ToString(result["error"]) != "" {
235
gologger.Warning().Msgf("[%s] Precondition for request %s was not satisfied\n", request.TemplateID, request.PreCondition)
236
request.options.Progress.IncrementFailedRequestsBy(1)
237
return nil
238
}
239
if request.options.Options.Debug || request.options.Options.DebugRequests {
240
gologger.Debug().Msgf("[%s] Precondition for request was satisfied\n", request.TemplateID)
241
}
242
}
243
244
ctx, cancel := context.WithTimeoutCause(input.Context(), request.options.Options.GetTimeouts().CodeExecutionTimeout, ErrCodeExecutionDeadline)
245
defer cancel()
246
// Note: we use contextutil despite the fact that gozero accepts context as argument
247
gOutput, err := contextutil.ExecFuncWithTwoReturns(ctx, func() (*gozerotypes.Result, error) {
248
return request.gozero.Eval(ctx, request.src, metaSrc)
249
})
250
if gOutput == nil {
251
// write error to stderr buff
252
var buff bytes.Buffer
253
if err != nil {
254
buff.WriteString(err.Error())
255
} else {
256
buff.WriteString("no output something went wrong")
257
}
258
gOutput = &gozerotypes.Result{
259
Stderr: buff,
260
}
261
}
262
gologger.Verbose().Msgf("[%s] Executed code on local machine %v", request.options.TemplateID, input.MetaInput.Input)
263
264
if vardump.EnableVarDump {
265
gologger.Debug().Msgf("Code Protocol request variables: %s\n", vardump.DumpVariables(allvars))
266
}
267
268
if request.options.Options.Debug || request.options.Options.DebugRequests {
269
gologger.Debug().MsgFunc(func() string {
270
dashes := strings.Repeat("-", 15)
271
sb := &strings.Builder{}
272
fmt.Fprintf(sb, "[%s] Dumped Executed Source Code for input/stdin: '%v'", request.options.TemplateID, input.MetaInput.Input)
273
fmt.Fprintf(sb, "\n%v\n%v\n%v\n", dashes, "Source Code:", dashes)
274
sb.WriteString(interpretEnvVars(request.Source, allvars))
275
sb.WriteString("\n")
276
fmt.Fprintf(sb, "\n%v\n%v\n%v\n", dashes, "Command Executed:", dashes)
277
sb.WriteString(interpretEnvVars(gOutput.Command, allvars))
278
sb.WriteString("\n")
279
fmt.Fprintf(sb, "\n%v\n%v\n%v\n", dashes, "Command Output:", dashes)
280
sb.WriteString(gOutput.DebugData.String())
281
sb.WriteString("\n")
282
sb.WriteString("[WRN] Command Output here is stdout+sterr, in response variables they are seperate (use -v -svd flags for more details)")
283
return sb.String()
284
})
285
}
286
287
dataOutputString := fmtStdout(gOutput.Stdout.String())
288
289
data := make(output.InternalEvent)
290
// also include all request variables in result event
291
for _, value := range metaSrc.Variables {
292
data[value.Name] = value.Value
293
}
294
295
data["type"] = request.Type().String()
296
data["response"] = dataOutputString // response contains filtered output (eg without trailing \n)
297
data["input"] = input.MetaInput.Input
298
data["template-path"] = request.options.TemplatePath
299
data["template-id"] = request.options.TemplateID
300
data["template-info"] = request.options.TemplateInfo
301
if gOutput.Stderr.Len() > 0 {
302
data["stderr"] = fmtStdout(gOutput.Stderr.String())
303
}
304
305
// expose response variables in proto_var format
306
// this is no-op if the template is not a multi protocol template
307
request.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, data)
308
309
// add variables from template context before matching/extraction
310
if request.options.HasTemplateCtx(input.MetaInput) {
311
data = generators.MergeMaps(data, request.options.GetTemplateCtx(input.MetaInput).GetAll())
312
}
313
314
if request.options.Interactsh != nil {
315
request.options.Interactsh.MakePlaceholders(interactshURLs, data)
316
}
317
318
// todo #1: interactsh async callback should be eliminated as it lead to ton of code duplication
319
// todo #2: various structs InternalWrappedEvent, InternalEvent should be unwrapped and merged into minimal callbacks and a unique struct (eg. event?)
320
event := eventcreator.CreateEvent(request, data, request.options.Options.Debug || request.options.Options.DebugResponse)
321
if request.options.Interactsh != nil {
322
event.UsesInteractsh = true
323
request.options.Interactsh.RequestEvent(interactshURLs, &interactsh.RequestData{
324
MakeResultFunc: request.MakeResultEvent,
325
Event: event,
326
Operators: request.CompiledOperators,
327
MatchFunc: request.Match,
328
ExtractFunc: request.Extract,
329
})
330
}
331
332
if request.options.Options.Debug || request.options.Options.DebugResponse || request.options.Options.StoreResponse {
333
msg := fmt.Sprintf("[%s] Dumped Code Execution for %s\n\n", request.options.TemplateID, input.MetaInput.Input)
334
if request.options.Options.Debug || request.options.Options.DebugResponse {
335
gologger.Debug().Msg(msg)
336
gologger.Print().Msgf("%s\n\n", responsehighlighter.Highlight(event.OperatorsResult, dataOutputString, request.options.Options.NoColor, false))
337
}
338
if request.options.Options.StoreResponse {
339
request.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), fmt.Sprintf("%s\n%s", msg, dataOutputString))
340
}
341
}
342
343
callback(event)
344
345
return nil
346
}
347
348
// RequestPartDefinitions contains a mapping of request part definitions and their
349
// description. Multiple definitions are separated by commas.
350
// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.
351
var RequestPartDefinitions = map[string]string{
352
"type": "Type is the type of request made",
353
"host": "Host is the input to the template",
354
"matched": "Matched is the input which was matched upon",
355
}
356
357
// Match performs matching operation for a matcher on model and returns:
358
// true and a list of matched snippets if the matcher type is supports it
359
// otherwise false and an empty string slice
360
func (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {
361
return protocols.MakeDefaultMatchFunc(data, matcher)
362
}
363
364
// Extract performs extracting operation for an extractor on model and returns true or false.
365
func (request *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} {
366
return protocols.MakeDefaultExtractFunc(data, matcher)
367
}
368
369
// MakeResultEvent creates a result event from internal wrapped event
370
func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {
371
return protocols.MakeDefaultResultEvent(request, wrapped)
372
}
373
374
// GetCompiledOperators returns a list of the compiled operators
375
func (request *Request) GetCompiledOperators() []*operators.Operators {
376
return []*operators.Operators{request.CompiledOperators}
377
}
378
379
// Type returns the type of the protocol request
380
func (request *Request) Type() templateTypes.ProtocolType {
381
return templateTypes.CodeProtocol
382
}
383
384
func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {
385
fields := protocolutils.GetJsonFieldsFromURL(types.ToString(wrapped.InternalEvent["input"]))
386
if types.ToString(wrapped.InternalEvent["ip"]) != "" {
387
fields.Ip = types.ToString(wrapped.InternalEvent["ip"])
388
}
389
data := &output.ResultEvent{
390
TemplateID: types.ToString(request.options.TemplateID),
391
TemplatePath: types.ToString(request.options.TemplatePath),
392
Info: request.options.TemplateInfo,
393
TemplateVerifier: request.options.TemplateVerifier,
394
Type: types.ToString(wrapped.InternalEvent["type"]),
395
Matched: types.ToString(wrapped.InternalEvent["input"]),
396
Host: fields.Host,
397
Port: fields.Port,
398
Scheme: fields.Scheme,
399
URL: fields.URL,
400
IP: fields.Ip,
401
Metadata: wrapped.OperatorsResult.PayloadValues,
402
ExtractedResults: wrapped.OperatorsResult.OutputExtracts,
403
Timestamp: time.Now(),
404
MatcherStatus: true,
405
TemplateEncoded: request.options.EncodeTemplate(),
406
Error: types.ToString(wrapped.InternalEvent["error"]),
407
}
408
return data
409
}
410
411
func fmtStdout(data string) string {
412
return strings.Trim(data, " \n\r\t")
413
}
414
415
// interpretEnvVars replaces environment variables in the input string
416
func interpretEnvVars(source string, vars map[string]interface{}) string {
417
// bash mode
418
if strings.Contains(source, "$") {
419
for k, v := range vars {
420
source = strings.ReplaceAll(source, "$"+k, fmt.Sprintf("%s", v))
421
}
422
}
423
// python mode
424
if strings.Contains(source, "os.getenv") {
425
matches := pythonEnvRegexCompiled.FindAllStringSubmatch(source, -1)
426
for _, match := range matches {
427
if len(match) == 0 {
428
continue
429
}
430
source = strings.ReplaceAll(source, fmt.Sprintf("os.getenv('%s')", match), fmt.Sprintf("'%s'", vars[match[0]]))
431
}
432
}
433
return source
434
}
435
436
func beautifyJavascript(code string) string {
437
opts := jsbeautifier.DefaultOptions()
438
beautified, err := jsbeautifier.Beautify(&code, opts)
439
if err != nil {
440
return code
441
}
442
return beautified
443
}
444
445
func prettyPrint(templateId string, buff string) {
446
lines := strings.Split(buff, "\n")
447
final := []string{}
448
for _, v := range lines {
449
if v != "" {
450
final = append(final, "\t"+v)
451
}
452
}
453
gologger.Debug().Msgf(" [%v] Pre-condition Code:\n\n%v\n\n", templateId, strings.Join(final, "\n"))
454
}
455
456
// UpdateOptions replaces this request's options with a new copy
457
func (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {
458
r.options.ApplyNewEngineOptions(opts)
459
}
460
461