Skip to content

Commit 2418319

Browse files
js: generate matcher-status event (projectdiscovery#5450)
* js: generate matcher-status event * isPortOpen: use fastdialer instance * update sdk unit test * add docs :)
1 parent 6d325a4 commit 2418319

File tree

4 files changed

+137
-73
lines changed

4 files changed

+137
-73
lines changed

examples/advanced/advanced.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
package main
22

33
import (
4+
"context"
5+
46
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
7+
"github.com/projectdiscovery/nuclei/v3/pkg/installer"
58
syncutil "github.com/projectdiscovery/utils/sync"
69
)
710

811
func main() {
12+
ctx := context.Background()
13+
// when running nuclei in parallel for first time it is a good practice to make sure
14+
// templates exists first
15+
tm := installer.TemplateManager{}
16+
if err := tm.FreshInstallIfNotExists(); err != nil {
17+
panic(err)
18+
}
19+
920
// create nuclei engine with options
10-
ne, err := nuclei.NewThreadSafeNucleiEngine()
21+
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(ctx)
1122
if err != nil {
1223
panic(err)
1324
}

pkg/js/compiler/compiler.go

+20-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ type ExecuteArgs struct {
5555
TemplateCtx map[string]interface{} // templateCtx contains template scoped variables
5656
}
5757

58+
// Map returns a merged map of the TemplateCtx and Args fields.
59+
func (e *ExecuteArgs) Map() map[string]interface{} {
60+
return generators.MergeMaps(e.TemplateCtx, e.Args)
61+
}
62+
5863
// NewExecuteArgs returns a new execute arguments.
5964
func NewExecuteArgs() *ExecuteArgs {
6065
return &ExecuteArgs{
@@ -66,12 +71,24 @@ func NewExecuteArgs() *ExecuteArgs {
6671
// ExecuteResult is the result of executing a script.
6772
type ExecuteResult map[string]interface{}
6873

74+
// Map returns the map representation of the ExecuteResult
75+
func (e ExecuteResult) Map() map[string]interface{} {
76+
if e == nil {
77+
return make(map[string]interface{})
78+
}
79+
return e
80+
}
81+
82+
// NewExecuteResult returns a new execute result instance
6983
func NewExecuteResult() ExecuteResult {
7084
return make(map[string]interface{})
7185
}
7286

7387
// GetSuccess returns whether the script was successful or not.
7488
func (e ExecuteResult) GetSuccess() bool {
89+
if e == nil {
90+
return false
91+
}
7592
val, ok := e["success"].(bool)
7693
if !ok {
7794
return false
@@ -114,7 +131,9 @@ func (c *Compiler) ExecuteWithOptions(program *goja.Program, args *ExecuteArgs,
114131
if val, ok := err.(*goja.Exception); ok {
115132
err = val.Unwrap()
116133
}
117-
return nil, err
134+
e := NewExecuteResult()
135+
e["error"] = err.Error()
136+
return e, err
118137
}
119138
var res ExecuteResult
120139
if opts.exports != nil {

pkg/js/global/scripts.go

+19-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package global
22

33
import (
44
"bytes"
5+
"context"
56
"embed"
67
"math/rand"
78
"net"
@@ -12,8 +13,10 @@ import (
1213
"github.com/logrusorgru/aurora"
1314
"github.com/projectdiscovery/gologger"
1415
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
16+
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
1517
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
1618
"github.com/projectdiscovery/nuclei/v3/pkg/types"
19+
"github.com/projectdiscovery/utils/errkit"
1720
errorutil "github.com/projectdiscovery/utils/errors"
1821
stringsutil "github.com/projectdiscovery/utils/strings"
1922
)
@@ -111,11 +114,16 @@ func initBuiltInFunc(runtime *goja.Runtime) {
111114
},
112115
Description: "isPortOpen checks if given TCP port is open on host. timeout is optional and defaults to 5 seconds",
113116
FuncDecl: func(host string, port string, timeout ...int) (bool, error) {
114-
timeoutInSec := 5
117+
ctx := context.Background()
115118
if len(timeout) > 0 {
116-
timeoutInSec = timeout[0]
119+
var cancel context.CancelFunc
120+
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeout[0])*time.Second)
121+
defer cancel()
117122
}
118-
conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, port), time.Duration(timeoutInSec)*time.Second)
123+
if host == "" || port == "" {
124+
return false, errkit.New("isPortOpen: host or port is empty")
125+
}
126+
conn, err := protocolstate.Dialer.Dial(ctx, "tcp", net.JoinHostPort(host, port))
119127
if err != nil {
120128
return false, err
121129
}
@@ -131,16 +139,20 @@ func initBuiltInFunc(runtime *goja.Runtime) {
131139
},
132140
Description: "isUDPPortOpen checks if the given UDP port is open on the host. Timeout is optional and defaults to 5 seconds.",
133141
FuncDecl: func(host string, port string, timeout ...int) (bool, error) {
134-
timeoutInSec := 5
142+
ctx := context.Background()
135143
if len(timeout) > 0 {
136-
timeoutInSec = timeout[0]
144+
var cancel context.CancelFunc
145+
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeout[0])*time.Second)
146+
defer cancel()
147+
}
148+
if host == "" || port == "" {
149+
return false, errkit.New("isPortOpen: host or port is empty")
137150
}
138-
conn, err := net.DialTimeout("udp", net.JoinHostPort(host, port), time.Duration(timeoutInSec)*time.Second)
151+
conn, err := protocolstate.Dialer.Dial(ctx, "udp", net.JoinHostPort(host, port))
139152
if err != nil {
140153
return false, err
141154
}
142155
_ = conn.Close()
143-
144156
return true, nil
145157
},
146158
})

pkg/protocols/javascript/js.go

+86-64
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"github.com/projectdiscovery/utils/errkit"
3636
errorutil "github.com/projectdiscovery/utils/errors"
3737
iputil "github.com/projectdiscovery/utils/ip"
38+
mapsutil "github.com/projectdiscovery/utils/maps"
3839
syncutil "github.com/projectdiscovery/utils/sync"
3940
urlutil "github.com/projectdiscovery/utils/url"
4041
)
@@ -346,17 +347,33 @@ func (request *Request) ExecuteWithResults(target *contextargs.Context, dynamicV
346347
TimeoutVariants: requestOptions.Options.GetTimeouts(),
347348
Source: &request.PreCondition, Context: target.Context(),
348349
})
349-
if err != nil {
350-
return errorutil.NewWithTag(request.TemplateID, "could not execute pre-condition: %s", err)
351-
}
352-
if !result.GetSuccess() || types.ToString(result["error"]) != "" {
353-
gologger.Warning().Msgf("[%s] Precondition for request %s was not satisfied\n", request.TemplateID, request.PreCondition)
354-
request.options.Progress.IncrementFailedRequestsBy(1)
355-
return nil
356-
}
357-
if request.options.Options.Debug || request.options.Options.DebugRequests {
358-
request.options.Progress.IncrementRequests()
359-
gologger.Debug().Msgf("[%s] Precondition for request was satisfied\n", request.TemplateID)
350+
// if precondition was successful
351+
if err == nil && result.GetSuccess() {
352+
if request.options.Options.Debug || request.options.Options.DebugRequests {
353+
request.options.Progress.IncrementRequests()
354+
gologger.Debug().Msgf("[%s] Precondition for request was satisfied\n", request.TemplateID)
355+
}
356+
} else {
357+
var outError error
358+
// if js code failed to execute
359+
if err != nil {
360+
outError = errkit.Append(errkit.New("pre-condition not satisfied skipping template execution"), err)
361+
} else {
362+
// execution successful but pre-condition returned false
363+
outError = errkit.New("pre-condition not satisfied skipping template execution")
364+
}
365+
results := map[string]interface{}(result)
366+
results["error"] = outError.Error()
367+
// generate and return failed event
368+
data := request.generateEventData(input, results, hostPort)
369+
data = generators.MergeMaps(data, payloadValues)
370+
event := eventcreator.CreateEventWithAdditionalOptions(request, data, request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {
371+
allVars := argsCopy.Map()
372+
allVars = generators.MergeMaps(allVars, data)
373+
wrappedEvent.OperatorsResult.PayloadValues = allVars
374+
})
375+
callback(event)
376+
return err
360377
}
361378
}
362379

@@ -531,24 +548,72 @@ func (request *Request) executeRequestWithPayloads(hostPort string, input *conte
531548
}
532549
}
533550

551+
values := mapsutil.Merge(payloadValues, results)
552+
// generate event data
553+
data := request.generateEventData(input, values, hostPort)
554+
555+
// add and get values from templatectx
556+
request.options.AddTemplateVars(input.MetaInput, request.Type(), request.GetID(), data)
557+
data = generators.MergeMaps(data, request.options.GetTemplateCtx(input.MetaInput).GetAll())
558+
559+
if requestOptions.Options.Debug || requestOptions.Options.DebugRequests || requestOptions.Options.StoreResponse {
560+
msg := fmt.Sprintf("[%s] Dumped Javascript response for %s:\n%v", requestOptions.TemplateID, input.MetaInput.Input, vardump.DumpVariables(results))
561+
if requestOptions.Options.Debug || requestOptions.Options.DebugRequests {
562+
gologger.Debug().Str("address", input.MetaInput.Input).Msg(msg)
563+
}
564+
if requestOptions.Options.StoreResponse {
565+
request.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), msg)
566+
}
567+
}
568+
569+
if _, ok := data["error"]; ok {
570+
event := eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(data, payloadValues), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {
571+
wrappedEvent.OperatorsResult.PayloadValues = payload
572+
})
573+
callback(event)
574+
return err
575+
}
576+
577+
if request.options.Interactsh != nil {
578+
request.options.Interactsh.MakePlaceholders(interactshURLs, data)
579+
}
580+
581+
var event *output.InternalWrappedEvent
582+
if len(interactshURLs) == 0 {
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+
} else if request.options.Interactsh != nil {
588+
event = &output.InternalWrappedEvent{InternalEvent: data, UsesInteractsh: true}
589+
request.options.Interactsh.RequestEvent(interactshURLs, &interactsh.RequestData{
590+
MakeResultFunc: request.MakeResultEvent,
591+
Event: event,
592+
Operators: request.CompiledOperators,
593+
MatchFunc: request.Match,
594+
ExtractFunc: request.Extract,
595+
})
596+
}
597+
return nil
598+
}
599+
600+
// generateEventData generates event data for the request
601+
func (request *Request) generateEventData(input *contextargs.Context, values map[string]interface{}, matched string) map[string]interface{} {
534602
data := make(map[string]interface{})
535-
for k, v := range payloadValues {
603+
for k, v := range values {
536604
data[k] = v
537605
}
538606
data["type"] = request.Type().String()
539-
for k, v := range results {
540-
data[k] = v
541-
}
607+
data["request-pre-condition"] = beautifyJavascript(request.PreCondition)
542608
data["request"] = beautifyJavascript(request.Code)
543609
data["host"] = input.MetaInput.Input
544-
data["matched"] = hostPort
545-
data["template-path"] = requestOptions.TemplatePath
546-
data["template-id"] = requestOptions.TemplateID
547-
data["template-info"] = requestOptions.TemplateInfo
610+
data["matched"] = matched
611+
data["template-path"] = request.options.TemplatePath
612+
data["template-id"] = request.options.TemplateID
613+
data["template-info"] = request.options.TemplateInfo
548614
if request.StopAtFirstMatch || request.options.StopAtFirstMatch {
549615
data["stop-at-first-match"] = true
550616
}
551-
552617
// add ip address to data
553618
if input.MetaInput.CustomIP != "" {
554619
data["ip"] = input.MetaInput.CustomIP
@@ -588,50 +653,7 @@ func (request *Request) executeRequestWithPayloads(hostPort string, input *conte
588653
}
589654
}
590655
}
591-
592-
// add and get values from templatectx
593-
request.options.AddTemplateVars(input.MetaInput, request.Type(), request.GetID(), data)
594-
data = generators.MergeMaps(data, request.options.GetTemplateCtx(input.MetaInput).GetAll())
595-
596-
if requestOptions.Options.Debug || requestOptions.Options.DebugRequests || requestOptions.Options.StoreResponse {
597-
msg := fmt.Sprintf("[%s] Dumped Javascript response for %s:\n%v", requestOptions.TemplateID, input.MetaInput.Input, vardump.DumpVariables(results))
598-
if requestOptions.Options.Debug || requestOptions.Options.DebugRequests {
599-
gologger.Debug().Str("address", input.MetaInput.Input).Msg(msg)
600-
}
601-
if requestOptions.Options.StoreResponse {
602-
request.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), msg)
603-
}
604-
}
605-
606-
if _, ok := data["error"]; ok {
607-
event := eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(data, payloadValues), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {
608-
wrappedEvent.OperatorsResult.PayloadValues = payload
609-
})
610-
callback(event)
611-
return err
612-
}
613-
614-
if request.options.Interactsh != nil {
615-
request.options.Interactsh.MakePlaceholders(interactshURLs, data)
616-
}
617-
618-
var event *output.InternalWrappedEvent
619-
if len(interactshURLs) == 0 {
620-
event = eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(data, payloadValues), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {
621-
wrappedEvent.OperatorsResult.PayloadValues = payload
622-
})
623-
callback(event)
624-
} else if request.options.Interactsh != nil {
625-
event = &output.InternalWrappedEvent{InternalEvent: data, UsesInteractsh: true}
626-
request.options.Interactsh.RequestEvent(interactshURLs, &interactsh.RequestData{
627-
MakeResultFunc: request.MakeResultEvent,
628-
Event: event,
629-
Operators: request.CompiledOperators,
630-
MatchFunc: request.Match,
631-
ExtractFunc: request.Extract,
632-
})
633-
}
634-
return nil
656+
return data
635657
}
636658

637659
func (request *Request) getArgsCopy(input *contextargs.Context, payloadValues map[string]interface{}, requestOptions *protocols.ExecutorOptions, ignoreErrors bool) (*compiler.ExecuteArgs, error) {

0 commit comments

Comments
 (0)