mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-17 18:15:28 +00:00
* bugfix: fix memory blowup using previousEvent for multi-proto execution * refactor(tmplexec): uses supported protocol types Signed-off-by: Dwi Siswanto <git@dw1.io> * add co-author Co-authored-by: Nakul Bharti <knakul853@users.noreply.github.com> Signed-off-by: Dwi Siswanto <git@dw1.io> * refactor(tmplexec): mv builder inside loop scope Signed-off-by: Dwi Siswanto <git@dw1.io> * refactor(tmplexec): skip existing keys in `FillPreviousEvent` The `FillPreviousEvent` func was modified to prevent overwriting/duplicating entries in the previous map. It now checks if a key `k` from `event.InternalEvent` already exists in the previous map. If it does, the key is skipped. This ensures that if `k` was already set (potentially w/o a prefix), it's not re-added with an `ID_` prefix. Additionally, keys in `event.InternalEvent` that already start with the current `ID_` prefix are also skipped to avoid redundant prefixing. This change simplifies the logic by removing the `reqTypeWithIndexRegex` and directly addresses the potential for duplicate / incorrectly prefixed keys when `event.InternalEvent` grows during protocol request execution. Signed-off-by: Dwi Siswanto <git@dw1.io> * chore(tmplexec): naming convention, `ID` => `protoID` Signed-off-by: Dwi Siswanto <git@dw1.io> * chore(tmplexec): it's request ID lol sorry Signed-off-by: Dwi Siswanto <git@dw1.io> --------- Signed-off-by: Dwi Siswanto <git@dw1.io> Co-authored-by: Ice3man <nizamulrana@gmail.com> Co-authored-by: Nakul Bharti <knakul853@users.noreply.github.com>
96 lines
3.1 KiB
Go
96 lines
3.1 KiB
Go
package generic
|
|
|
|
import (
|
|
"sync/atomic"
|
|
|
|
"github.com/projectdiscovery/gologger"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/utils"
|
|
mapsutil "github.com/projectdiscovery/utils/maps"
|
|
)
|
|
|
|
// generic engine as name suggests is a generic template
|
|
// execution engine and executes all requests one after another
|
|
// without any logic in between
|
|
type Generic struct {
|
|
requests []protocols.Request
|
|
options *protocols.ExecutorOptions
|
|
results *atomic.Bool
|
|
}
|
|
|
|
// NewGenericEngine creates a new generic engine from a list of requests
|
|
func NewGenericEngine(requests []protocols.Request, options *protocols.ExecutorOptions, results *atomic.Bool) *Generic {
|
|
if results == nil {
|
|
results = &atomic.Bool{}
|
|
}
|
|
return &Generic{requests: requests, options: options, results: results}
|
|
}
|
|
|
|
// Compile engine specific compilation
|
|
func (g *Generic) Compile() error {
|
|
// protocol/ request is already handled by template executer
|
|
return nil
|
|
}
|
|
|
|
// ExecuteWithResults executes the template and returns results
|
|
func (g *Generic) ExecuteWithResults(ctx *scan.ScanContext) error {
|
|
dynamicValues := make(map[string]interface{})
|
|
if ctx.Input.HasArgs() {
|
|
ctx.Input.ForEach(func(key string, value interface{}) {
|
|
dynamicValues[key] = value
|
|
})
|
|
}
|
|
previous := mapsutil.NewSyncLockMap[string, any]()
|
|
|
|
for _, req := range g.requests {
|
|
select {
|
|
case <-ctx.Context().Done():
|
|
return ctx.Context().Err()
|
|
default:
|
|
}
|
|
|
|
inputItem := ctx.Input.Clone()
|
|
if g.options.InputHelper != nil && ctx.Input.MetaInput.Input != "" {
|
|
if inputItem.MetaInput.Input = g.options.InputHelper.Transform(inputItem.MetaInput.Input, req.Type()); inputItem.MetaInput.Input == "" {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
err := req.ExecuteWithResults(inputItem, dynamicValues, output.InternalEvent(previous.GetAll()), func(event *output.InternalWrappedEvent) {
|
|
// this callback is not concurrent safe so mutex should be used to synchronize
|
|
if event == nil {
|
|
// ideally this should never happen since protocol exits on error and callback is not called
|
|
return
|
|
}
|
|
|
|
utils.FillPreviousEvent(req.GetID(), event, previous)
|
|
|
|
if event.HasOperatorResult() {
|
|
g.results.CompareAndSwap(false, true)
|
|
}
|
|
// for ExecuteWithResults : this callback will execute user defined callback and some error handling
|
|
// for Execute : this callback will print the result to output
|
|
ctx.LogEvent(event)
|
|
})
|
|
if err != nil {
|
|
ctx.LogError(err)
|
|
gologger.Warning().Msgf("[%s] Could not execute request for %s: %s\n", g.options.TemplateID, ctx.Input.MetaInput.PrettyPrint(), err)
|
|
}
|
|
if g.options.HostErrorsCache != nil {
|
|
g.options.HostErrorsCache.MarkFailedOrRemove(g.options.ProtocolType.String(), ctx.Input, err)
|
|
}
|
|
// If a match was found and stop at first match is set, break out of the loop and return
|
|
if g.results.Load() && (g.options.StopAtFirstMatch || g.options.Options.StopAtFirstMatch) {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Type returns the type of engine
|
|
func (g *Generic) Name() string {
|
|
return "generic"
|
|
}
|