Path: blob/dev/pkg/tmplexec/multiproto/multi.go
2070 views
package multiproto12import (3"strconv"4"sync/atomic"56"github.com/projectdiscovery/nuclei/v3/pkg/output"7"github.com/projectdiscovery/nuclei/v3/pkg/protocols"8"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"9"github.com/projectdiscovery/nuclei/v3/pkg/scan"10"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"11"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/utils"12mapsutil "github.com/projectdiscovery/utils/maps"13stringsutil "github.com/projectdiscovery/utils/strings"14)1516// Mutliprotocol is a template executer engine that executes multiple protocols17// with logic in between18type MultiProtocol struct {19requests []protocols.Request20options *protocols.ExecutorOptions21results *atomic.Bool22readOnlyArgs map[string]interface{} // readOnlyArgs are readonly args that are available after compilation23}2425// NewMultiProtocol creates a new multiprotocol template engine from a list of requests26func NewMultiProtocol(requests []protocols.Request, options *protocols.ExecutorOptions, results *atomic.Bool) *MultiProtocol {27if results == nil {28results = &atomic.Bool{}29}30return &MultiProtocol{requests: requests, options: options, results: results}31}3233// Compile engine specific compilation34func (m *MultiProtocol) Compile() error {35// load all variables and evaluate with existing data36variableMap := m.options.Variables.GetAll()37// cli options38optionVars := generators.BuildPayloadFromOptions(m.options.Options)39// constants40constants := m.options.Constants41allVars := generators.MergeMaps(variableMap, constants, optionVars)42allVars = m.options.Variables.Evaluate(allVars)43m.readOnlyArgs = allVars44// no need to load files since they are done at template level45return nil46}4748// ExecuteWithResults executes the template and returns results49func (m *MultiProtocol) ExecuteWithResults(ctx *scan.ScanContext) error {50select {51case <-ctx.Context().Done():52return ctx.Context().Err()53default:54}5556// put all readonly args into template context57m.options.GetTemplateCtx(ctx.Input.MetaInput).Merge(m.readOnlyArgs)5859// add all input args to template context60ctx.Input.ForEach(func(key string, value interface{}) {61m.options.GetTemplateCtx(ctx.Input.MetaInput).Set(key, value)62})6364previous := mapsutil.NewSyncLockMap[string, any]()6566// template context: contains values extracted using `internal` extractor from previous protocols67// these values are extracted from each protocol in queue and are passed to next protocol in queue68// instead of adding seperator field to handle such cases these values are appended to `dynamicValues` (which are meant to be used in workflows)69// this makes it possible to use multi protocol templates in workflows70// Note: internal extractor values take precedence over dynamicValues from workflows (i.e other templates in workflow)7172// execute all protocols in the queue73for _, req := range m.requests {74select {75case <-ctx.Context().Done():76return ctx.Context().Err()77default:78}79inputItem := ctx.Input.Clone()80if m.options.InputHelper != nil && ctx.Input.MetaInput.Input != "" {81if inputItem.MetaInput.Input = m.options.InputHelper.Transform(inputItem.MetaInput.Input, req.Type()); inputItem.MetaInput.Input == "" {82return nil83}84}85// FIXME: this hack of using hash to get templateCtx has known issues scan context based approach should be adopted ASAP86values := m.options.GetTemplateCtx(inputItem.MetaInput).GetAll()87err := req.ExecuteWithResults(inputItem, output.InternalEvent(values), output.InternalEvent(previous.GetAll()), func(event *output.InternalWrappedEvent) {88if event == nil {89return90}9192utils.FillPreviousEvent(req.GetID(), event, previous)9394// log event and generate result for the event95ctx.LogEvent(event)96// export dynamic values from operators (i.e internal:true)97if event.OperatorsResult != nil && len(event.OperatorsResult.DynamicValues) > 0 {98for k, v := range event.OperatorsResult.DynamicValues {99// TBD: iterate-all is only supported in `http` protocol100// we either need to add support for iterate-all in other protocols or implement a different logic (specific to template context)101// currently if dynamic value array only contains one value we replace it with the value102if len(v) == 1 {103m.options.GetTemplateCtx(ctx.Input.MetaInput).Set(k, v[0])104} else {105// Note: if extracted value contains multiple values then they can be accessed by indexing106// ex: if values are dynamic = []string{"a","b","c"} then they are available as107// dynamic = "a" , dynamic1 = "b" , dynamic2 = "c"108// we intentionally omit first index for unknown situations (where no of extracted values are not known)109for i, val := range v {110if i == 0 {111m.options.GetTemplateCtx(ctx.Input.MetaInput).Set(k, val)112} else {113m.options.GetTemplateCtx(ctx.Input.MetaInput).Set(k+strconv.Itoa(i), val)114}115}116}117}118}119120// evaluate all variables after execution of each protocol121variableMap := m.options.Variables.Evaluate(m.options.GetTemplateCtx(ctx.Input.MetaInput).GetAll())122m.options.GetTemplateCtx(ctx.Input.MetaInput).Merge(variableMap) // merge all variables into template context123})124// in case of fatal error skip execution of next protocols125if err != nil {126// always log errors127ctx.LogError(err)128// for some classes of protocols (i.e ssl) errors like tls handshake are a legitimate behavior so we don't stop execution129// connection failures are already tracked by the internal host error cache130// we use strings comparison as the error is not formalized into instance within the standard library131// within a flow instead we consider ssl errors as fatal, since a specific logic was requested132if req.Type() == types.SSLProtocol && stringsutil.ContainsAnyI(err.Error(), "protocol version not supported", "could not do tls handshake") {133continue134}135}136}137return nil138}139140// Name of the template engine141func (m *MultiProtocol) Name() string {142return "multiproto"143}144145146