nuclei/v2/pkg/protocols/multi/request.go
Tarun Koyalwar e1d3f474a4
support for dynamic variables in template context (multi protocol execution) (#3672)
* multi proto request genesis

* adds template context dynamic vars

* feat: proto level resp variables

* remove proto prefix hacky logic

* implement template ctx args

* remove old var name logic

* improve AddTemplateVars func

* add multi proto comments+docs

* vardump with sorted keys

* fix race condition in ctx args

* default initialize ctx args

* use generic map

* index variables with multiple values

* fix nil cookies

* use synclock map

* fix build failure

* fix lint error

* resolve merge conflicts

* multi proto: add unit+ integration tests

* fix unit tests

* Issue 3339 headless fuzz (#3790)

* Basic headless fuzzing

* Remove debug statements

* Add integration tests

* Update template

* Fix recognize payload value in matcher

* Update tempalte

* use req.SetURL()

---------

Co-authored-by: Tarun Koyalwar <tarun@projectdiscovery.io>

* Auto Generate Syntax Docs + JSONSchema [Fri Jun  9 00:23:32 UTC 2023] 🤖

* Add headless header and status matchers (#3794)

* add headless header and status matchers

* rename headers as header

* add integration test for header+status

* fix typo

---------

Co-authored-by: Shubham Rasal <shubham@projectdiscovery.io>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Dogan Can Bakir <65292895+dogancanbakir@users.noreply.github.com>
2023-06-09 19:52:56 +05:30

173 lines
7.0 KiB
Go

package multi
import (
"strconv"
"github.com/projectdiscovery/nuclei/v2/pkg/model"
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
errorutil "github.com/projectdiscovery/utils/errors"
)
var _ protocols.Request = &Request{}
// refer doc.go for package description , limitations etc
// Request contains a multi protocol request
type Request struct {
// description: |
// ID is the unique id for the template.
//
// #### Good IDs
//
// A good ID uniquely identifies what the requests in the template
// are doing. Let's say you have a template that identifies a git-config
// file on the webservers, a good name would be `git-config-exposure`. Another
// example name is `azure-apps-nxdomain-takeover`.
// examples:
// - name: ID Example
// value: "\"CVE-2021-19520\""
ID string `yaml:"id" json:"id" jsonschema:"title=id of the template,description=The Unique ID for the template,example=cve-2021-19520,pattern=^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$"`
// description: |
// Info contains metadata information about the template.
// examples:
// - value: exampleInfoStructure
Info model.Info `yaml:"info" json:"info" jsonschema:"title=info for the template,description=Info contains metadata for the template"`
// Queue is queue of all protocols present in the template
Queue []protocols.Request `yaml:"-" json:"-"`
// request executor options
options *protocols.ExecutorOptions `yaml:"-" json:"-"`
}
// getLastRequest returns the last request in the queue
func (r *Request) getLastRequest() protocols.Request {
if len(r.Queue) == 0 {
return nil
}
return r.Queue[len(r.Queue)-1]
}
// Requests returns the total number of requests template will send
func (r *Request) Requests() int {
var count int
for _, protocol := range r.Queue {
count += protocol.Requests()
}
return count
}
// Compile compiles the protocol request for further execution.
func (r *Request) Compile(executerOptions *protocols.ExecutorOptions) error {
r.options = executerOptions
r.options.TemplateCtx = contextargs.New()
r.options.ProtocolType = types.MultiProtocol
for _, protocol := range r.Queue {
if err := protocol.Compile(r.options); err != nil {
return errorutil.NewWithErr(err).Msgf("failed to compile protocol %s", protocol.Type())
}
}
return nil
}
// GetID returns the unique template ID
func (r *Request) GetID() string {
return r.ID
}
// Match executes matcher on model and returns true or false (used for clustering if request supports clustering)
func (r *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {
return protocols.MakeDefaultMatchFunc(data, matcher)
}
// Extract performs extracting operation for an extractor on model and returns true or false (used for clustering if request supports clustering)
func (r *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} {
return protocols.MakeDefaultExtractFunc(data, matcher)
}
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
func (r *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
var finalProtoEvent *output.InternalWrappedEvent
// callback to process results from all protocols
multiProtoCallback := func(event *output.InternalWrappedEvent) {
finalProtoEvent = 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 {
r.options.TemplateCtx.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 {
r.options.TemplateCtx.Set(k, val)
} else {
r.options.TemplateCtx.Set(k+strconv.Itoa(i), val)
}
}
}
}
}
}
// 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 r.Queue {
err := req.ExecuteWithResults(input, dynamicValues, previous, multiProtoCallback)
// if error skip execution of next protocols
if err != nil {
return err
}
}
// Review: how to handle events of multiple protocols in a single template
// currently the outer callback is only executed once (for the last protocol in queue)
// due to workflow logic at https://github.com/projectdiscovery/nuclei/blob/main/v2/pkg/protocols/common/executer/executer.go#L150
// this causes addition of duplicated / unncessary variables with prefix template_id_all_variables
callback(finalProtoEvent)
return nil
}
// MakeResultEventItem creates a result event from internal wrapped event. Intended to be used by MakeResultEventItem internally
func (r *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {
if r.getLastRequest() == nil {
return nil
}
return r.getLastRequest().MakeResultEventItem(wrapped)
}
// MakeResultEvent creates a flat list of result events from an internal wrapped event, based on successful matchers and extracted data
func (r *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {
return protocols.MakeDefaultResultEvent(r.getLastRequest(), wrapped)
}
// GetCompiledOperators returns a list of the compiled operators
func (r *Request) GetCompiledOperators() []*operators.Operators {
last := r.getLastRequest()
if last == nil {
return nil
}
return last.GetCompiledOperators()
}
// Type returns the type of the protocol request
func (r *Request) Type() types.ProtocolType {
return types.MultiProtocol
}