mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-18 20:05:24 +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>
145 lines
5.9 KiB
Go
145 lines
5.9 KiB
Go
package multiproto
|
|
|
|
import (
|
|
"strconv"
|
|
"sync/atomic"
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/utils"
|
|
mapsutil "github.com/projectdiscovery/utils/maps"
|
|
stringsutil "github.com/projectdiscovery/utils/strings"
|
|
)
|
|
|
|
// Mutliprotocol is a template executer engine that executes multiple protocols
|
|
// with logic in between
|
|
type MultiProtocol struct {
|
|
requests []protocols.Request
|
|
options *protocols.ExecutorOptions
|
|
results *atomic.Bool
|
|
readOnlyArgs map[string]interface{} // readOnlyArgs are readonly args that are available after compilation
|
|
}
|
|
|
|
// NewMultiProtocol creates a new multiprotocol template engine from a list of requests
|
|
func NewMultiProtocol(requests []protocols.Request, options *protocols.ExecutorOptions, results *atomic.Bool) *MultiProtocol {
|
|
if results == nil {
|
|
results = &atomic.Bool{}
|
|
}
|
|
return &MultiProtocol{requests: requests, options: options, results: results}
|
|
}
|
|
|
|
// Compile engine specific compilation
|
|
func (m *MultiProtocol) Compile() error {
|
|
// load all variables and evaluate with existing data
|
|
variableMap := m.options.Variables.GetAll()
|
|
// cli options
|
|
optionVars := generators.BuildPayloadFromOptions(m.options.Options)
|
|
// constants
|
|
constants := m.options.Constants
|
|
allVars := generators.MergeMaps(variableMap, constants, optionVars)
|
|
allVars = m.options.Variables.Evaluate(allVars)
|
|
m.readOnlyArgs = allVars
|
|
// no need to load files since they are done at template level
|
|
return nil
|
|
}
|
|
|
|
// ExecuteWithResults executes the template and returns results
|
|
func (m *MultiProtocol) ExecuteWithResults(ctx *scan.ScanContext) error {
|
|
select {
|
|
case <-ctx.Context().Done():
|
|
return ctx.Context().Err()
|
|
default:
|
|
}
|
|
|
|
// put all readonly args into template context
|
|
m.options.GetTemplateCtx(ctx.Input.MetaInput).Merge(m.readOnlyArgs)
|
|
|
|
// add all input args to template context
|
|
ctx.Input.ForEach(func(key string, value interface{}) {
|
|
m.options.GetTemplateCtx(ctx.Input.MetaInput).Set(key, value)
|
|
})
|
|
|
|
previous := mapsutil.NewSyncLockMap[string, any]()
|
|
|
|
// template context: contains values extracted using `internal` extractor from previous protocols
|
|
// these values are extracted from each protocol in queue and are passed to next protocol in queue
|
|
// instead of adding seperator field to handle such cases these values are appended to `dynamicValues` (which are meant to be used in workflows)
|
|
// this makes it possible to use multi protocol templates in workflows
|
|
// Note: internal extractor values take precedence over dynamicValues from workflows (i.e other templates in workflow)
|
|
|
|
// execute all protocols in the queue
|
|
for _, req := range m.requests {
|
|
select {
|
|
case <-ctx.Context().Done():
|
|
return ctx.Context().Err()
|
|
default:
|
|
}
|
|
inputItem := ctx.Input.Clone()
|
|
if m.options.InputHelper != nil && ctx.Input.MetaInput.Input != "" {
|
|
if inputItem.MetaInput.Input = m.options.InputHelper.Transform(inputItem.MetaInput.Input, req.Type()); inputItem.MetaInput.Input == "" {
|
|
return nil
|
|
}
|
|
}
|
|
// FIXME: this hack of using hash to get templateCtx has known issues scan context based approach should be adopted ASAP
|
|
values := m.options.GetTemplateCtx(inputItem.MetaInput).GetAll()
|
|
err := req.ExecuteWithResults(inputItem, output.InternalEvent(values), output.InternalEvent(previous.GetAll()), func(event *output.InternalWrappedEvent) {
|
|
if event == nil {
|
|
return
|
|
}
|
|
|
|
utils.FillPreviousEvent(req.GetID(), event, previous)
|
|
|
|
// log event and generate result for the event
|
|
ctx.LogEvent(event)
|
|
// export dynamic values from operators (i.e internal:true)
|
|
if event.OperatorsResult != nil && len(event.OperatorsResult.DynamicValues) > 0 {
|
|
for k, v := range event.OperatorsResult.DynamicValues {
|
|
// TBD: iterate-all is only supported in `http` protocol
|
|
// we either need to add support for iterate-all in other protocols or implement a different logic (specific to template context)
|
|
// currently if dynamic value array only contains one value we replace it with the value
|
|
if len(v) == 1 {
|
|
m.options.GetTemplateCtx(ctx.Input.MetaInput).Set(k, v[0])
|
|
} else {
|
|
// Note: if extracted value contains multiple values then they can be accessed by indexing
|
|
// ex: if values are dynamic = []string{"a","b","c"} then they are available as
|
|
// dynamic = "a" , dynamic1 = "b" , dynamic2 = "c"
|
|
// we intentionally omit first index for unknown situations (where no of extracted values are not known)
|
|
for i, val := range v {
|
|
if i == 0 {
|
|
m.options.GetTemplateCtx(ctx.Input.MetaInput).Set(k, val)
|
|
} else {
|
|
m.options.GetTemplateCtx(ctx.Input.MetaInput).Set(k+strconv.Itoa(i), val)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// evaluate all variables after execution of each protocol
|
|
variableMap := m.options.Variables.Evaluate(m.options.GetTemplateCtx(ctx.Input.MetaInput).GetAll())
|
|
m.options.GetTemplateCtx(ctx.Input.MetaInput).Merge(variableMap) // merge all variables into template context
|
|
})
|
|
// in case of fatal error skip execution of next protocols
|
|
if err != nil {
|
|
// always log errors
|
|
ctx.LogError(err)
|
|
// for some classes of protocols (i.e ssl) errors like tls handshake are a legitimate behavior so we don't stop execution
|
|
// connection failures are already tracked by the internal host error cache
|
|
// we use strings comparison as the error is not formalized into instance within the standard library
|
|
// within a flow instead we consider ssl errors as fatal, since a specific logic was requested
|
|
if req.Type() == types.SSLProtocol && stringsutil.ContainsAnyI(err.Error(), "protocol version not supported", "could not do tls handshake") {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Name of the template engine
|
|
func (m *MultiProtocol) Name() string {
|
|
return "multiproto"
|
|
}
|