Files
nuclei/pkg/protocols/protocols.go

742 lines
30 KiB
Go

package protocols
import (
"context"
"encoding/base64"
"errors"
"reflect"
"sync/atomic"
"github.com/projectdiscovery/fastdialer/fastdialer"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/ratelimit"
mapsutil "github.com/projectdiscovery/utils/maps"
stringsutil "github.com/projectdiscovery/utils/strings"
"github.com/logrusorgru/aurora/v4"
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/frequency"
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats"
"github.com/projectdiscovery/nuclei/v3/pkg/input"
"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler"
"github.com/projectdiscovery/nuclei/v3/pkg/loader/parser"
"github.com/projectdiscovery/nuclei/v3/pkg/model"
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
llmclient "github.com/projectdiscovery/nuclei/v3/pkg/operators/common/llm"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
"github.com/projectdiscovery/nuclei/v3/pkg/projectfile"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hostbackoff"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hostratelimit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/variables"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/catchall"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting"
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
unitutils "github.com/projectdiscovery/utils/unit"
)
var (
MaxTemplateFileSizeForEncoding = unitutils.Mega
)
// Executer is an interface implemented any protocol based request executer.
type Executer interface {
// Compile compiles the execution generators preparing any requests possible.
Compile() error
// Requests returns the total number of requests the rule will perform
Requests() int
// Execute executes the protocol group and returns true or false if results were found.
Execute(ctx *scan.ScanContext) (bool, error)
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error)
}
// TemplateVerification holds cached verification information for a template.
type TemplateVerification struct {
Verified bool
Verifier string
VerifierFingerprint [32]byte
// ContentDigest binds the cached result to the verified template and
// imported-file contents.
ContentDigest [32]byte
}
// ExecutorOptions contains the configuration options for executer clients
type ExecutorOptions struct {
// TemplateID is the ID of the template for the request
TemplateID string
// TemplatePath is the path of the template for the request
TemplatePath string
// TemplateInfo contains information block of the template request
TemplateInfo model.Info
// TemplateVerifier is the verifier for the template
TemplateVerifier string
// Verified reports whether a trusted verifier verified the template's
// signature. Code and JavaScript protocols check it at execution time.
Verified bool
// LLMClient is the scan's llm client, injected into llm matchers and
// extractors when a request compiles. Nil unless -llm is set, which makes
// those operators fail closed.
LLMClient llmclient.Client
// TemplateVerificationCallback returns cached verification info for a template path.
// If it returns nil, verification should be computed normally.
TemplateVerificationCallback func(templatePath string) *TemplateVerification
// RawTemplate is the raw template for the request
RawTemplate []byte
// Output is a writer interface for writing output events from executer.
Output output.Writer
// Options contains configuration options for the executer.
Options *types.Options
// IssuesClient is a client for nuclei issue tracker reporting
IssuesClient reporting.Client
// Progress is a progress client for scan reporting
Progress progress.Progress
// RateLimiter is a rate-limiter for limiting sent number of requests.
RateLimiter *ratelimit.Limiter
// Catalog is a template catalog implementation for nuclei
Catalog catalog.Catalog
// ProjectFile is the project file for nuclei
ProjectFile *projectfile.ProjectFile
// Browser is a browser engine for running headless templates
Browser *engine.Browser
// Interactsh is a client for interactsh oob polling server
Interactsh interactsh.IClient
// InteractshScope isolates delayed callbacks and cleanup for one execution
// when Interactsh is shared by concurrent engines.
InteractshScope *interactsh.RequestScope
// HostErrorsCache is an optional cache for handling host errors
HostErrorsCache hosterrorscache.CacheInterface
// HostBackoff paces a host that signals it is being overloaded, so a scan
// slows down for it instead of being blocked outright. Nil disables it.
HostBackoff *hostbackoff.Governor
// CatchAll discounts HTTP matches on a host's catch-all page. Nil disables it.
CatchAll *catchall.Detector
// Stop execution once first match is found (Assigned while parsing templates)
// Note: this is different from Options.StopAtFirstMatch (Assigned from CLI option)
StopAtFirstMatch bool
// Variables is a list of variables from template
Variables variables.Variable
// Constants is a list of constants from template
Constants map[string]interface{}
// ExcludeMatchers is the list of matchers to exclude
ExcludeMatchers *excludematchers.ExcludeMatchers
// InputHelper is a helper for input normalization
InputHelper *input.Helper
// FuzzParamsFrequency is a cache for parameter frequency
FuzzParamsFrequency *frequency.Tracker
// FuzzStatsDB is a database for fuzzing stats
FuzzStatsDB *stats.Tracker
Operators []*operators.Operators // only used by offlinehttp module
// DoNotCache bool disables optional caching of the templates structure
DoNotCache bool
Colorizer *aurora.Aurora
WorkflowLoader model.WorkflowLoader
ResumeCfg *types.ResumeCfg
// ProtocolType is the type of the template
ProtocolType templateTypes.ProtocolType
// Flow is execution flow for the template (written in javascript)
Flow string
// IsMultiProtocol is true if template has more than one protocol
IsMultiProtocol bool
// templateStore is a map which contains template context for each scan (i.e input * template-id pair)
templateCtxStore *mapsutil.SyncLockMap[string, *contextargs.Context]
// JsCompiler is abstracted javascript compiler which adds node modules and provides execution
// environment for javascript templates
JsCompiler *compiler.Compiler
// AuthProvider is a provider for auth strategies
AuthProvider authprovider.AuthProvider
//TemporaryDirectory is the directory to store temporary files
TemporaryDirectory string
Parser parser.Parser
// ExportReqURLPattern exports the request URL pattern
// in ResultEvent it contains the exact url pattern (ex: {{BaseURL}}/{{randstr}}/xyz) used in the request
ExportReqURLPattern bool
// GlobalMatchers is the storage for global matchers with http passive templates
GlobalMatchers *globalmatchers.Storage
// Logger is the shared logging instance
Logger *gologger.Logger
// CustomFastdialer is a fastdialer dialer instance
CustomFastdialer *fastdialer.Dialer
// ClusterMappings stores cluster ID to template IDs mapping during execution
ClusterMappings *templateTypes.ClusterMappingsMap
// TargetScope narrows the templates run on each target (per-target profiles)
TargetScope TargetScope
}
// TargetScope resolves the template selection that applies to a target.
type TargetScope interface {
// For returns the selection for input, or nil when every template applies.
For(input *contextargs.MetaInput) TemplateSelection
}
// TemplateSelection decides which templates run on a target. Implementations
// must be comparable, such as pointers, since targets are grouped by selection.
type TemplateSelection interface {
// Allows reports whether the template loaded from templatePath is selected.
Allows(templatePath string) bool
}
// RegisterInteractshRequest attaches execution-local output dependencies before
// registering a delayed OOB callback on a potentially shared Interactsh client.
func (e *ExecutorOptions) RegisterInteractshRequest(urls []string, data *interactsh.RequestData) {
if e == nil || e.Interactsh == nil || data == nil {
return
}
data.Output = e.Output
data.Progress = e.Progress
data.IssuesClient = e.IssuesClient
data.FuzzParamsFrequency = e.FuzzParamsFrequency
data.Scope = e.InteractshScope
e.Interactsh.RequestEvent(urls, data)
}
// todo: centralizing components is not feasible with current clogged architecture
// a possible approach could be an internal event bus with pub-subs? This would be less invasive than
// reworking dep injection from scratch
// RateLimitTake waits for the rate limit of a request to host: that host's own
// limiter when per-host rate limiting is on, since the global limiter is then
// unlimited, and the global limiter otherwise or when host is unknown.
func (e *ExecutorOptions) RateLimitTake(host string) error {
return e.RateLimitTakeContext(context.Background(), host)
}
// RateLimitTakeContext waits for admission or returns when ctx is canceled.
func (e *ExecutorOptions) RateLimitTakeContext(ctx context.Context, host string) error {
if e.Options != nil && e.Options.PerHostRateLimit && host != "" {
limiter, err := hostratelimit.GetPerHostRateLimiter(e.Options, host)
if err != nil {
return err
}
if limiter != nil {
if err := limiter.Wait(ctx); err != nil {
return err
}
hostratelimit.RecordPerHostRateLimitRequest(e.Options, host)
return nil
}
}
// The code below can race and there isn't a great way to fix this without adding an idempotent
// function to the rate limiter implementation. For now, stick with whatever rate is already set.
/*
if e.RateLimiter.GetLimit() != uint(e.Options.RateLimit) {
e.RateLimiter.SetLimit(uint(e.Options.RateLimit))
e.RateLimiter.SetDuration(e.Options.RateLimitDuration)
}
*/
if e.RateLimiter != nil {
return e.RateLimiter.TakeContext(ctx)
}
return nil
}
// HostBackoffWait pauses for the delay address has earned with HostBackoff.
func (e *ExecutorOptions) HostBackoffWait(ctx context.Context, address string) error {
return e.HostBackoff.Wait(ctx, hostratelimit.NormalizeHostPort(address))
}
// HostBackoffObserveDial reports a connection attempt to address to HostBackoff.
func (e *ExecutorOptions) HostBackoffObserveDial(address string, err error) {
// a canceled dial is the scan stopping, not the host asking for less traffic
if errors.Is(err, context.Canceled) {
return
}
e.HostBackoff.ObserveDial(hostratelimit.NormalizeHostPort(address), err)
}
// GetThreadsForNPayloadRequests returns the number of threads to use as default for
// given max-request of payloads
func (e *ExecutorOptions) GetThreadsForNPayloadRequests(totalRequests int, currentThreads int) int {
if currentThreads > 0 {
return currentThreads
}
return e.Options.PayloadConcurrency
}
// CreateTemplateCtxStore creates template context store (which contains templateCtx for every scan)
func (e *ExecutorOptions) CreateTemplateCtxStore() {
e.templateCtxStore = &mapsutil.SyncLockMap[string, *contextargs.Context]{
Map: make(map[string]*contextargs.Context),
ReadOnly: atomic.Bool{},
}
}
// RemoveTemplateCtx removes template context of given scan from store
func (e *ExecutorOptions) RemoveTemplateCtx(input *contextargs.MetaInput) {
scanId := input.GetScanHash(e.TemplateID)
if e.templateCtxStore != nil {
e.templateCtxStore.Delete(scanId)
}
}
// HasTemplateCtx returns true if template context exists for given input
func (e *ExecutorOptions) HasTemplateCtx(input *contextargs.MetaInput) bool {
scanId := input.GetScanHash(e.TemplateID)
if e.templateCtxStore != nil {
return e.templateCtxStore.Has(scanId)
}
return false
}
// GetTemplateCtx returns template context for given input
func (e *ExecutorOptions) GetTemplateCtx(input *contextargs.MetaInput) *contextargs.Context {
scanId := input.GetScanHash(e.TemplateID)
if e.templateCtxStore == nil {
// if template context store is not initialized create it
e.CreateTemplateCtxStore()
}
// get template context from store
templateCtx, ok := e.templateCtxStore.Get(scanId)
if !ok {
// if template context does not exist create new and add it to store and return it
templateCtx = contextargs.New(context.Background())
templateCtx.MetaInput = input
_ = e.templateCtxStore.Set(scanId, templateCtx)
}
return templateCtx
}
// NewVariablesScope creates a variable evaluation scope with terminal data
// layers applied in order.
func (e *ExecutorOptions) NewVariablesScope(values ...map[string]interface{}) *variables.Scope {
return variables.NewScope().WithOptions(e.GetOptions()).AddData(values...)
}
// AddTemplateCtxToVariablesScope adds template context values to a variable
// scope while preserving which keys came from prior variable evaluation.
func (e *ExecutorOptions) AddTemplateCtxToVariablesScope(input *contextargs.MetaInput, scope *variables.Scope) {
if input == nil || scope == nil || !e.HasTemplateCtx(input) {
return
}
templateCtx := e.GetTemplateCtx(input)
scope.AddData(templateCtx.GetAll())
scope.AddTemplate(templateCtx.GetTemplateVariables())
}
// AddTemplateVars adds vars to template context with given template type as prefix
// this method is no-op if template is not multi protocol
func (e *ExecutorOptions) AddTemplateVars(input *contextargs.MetaInput, reqType templateTypes.ProtocolType, reqID string, vars map[string]interface{}) {
// if we want to disable adding response variables and other variables to template context
// this is the statement that does it . template context is currently only enabled for
// multiprotocol and flow templates
if !e.IsMultiProtocol && e.Flow == "" {
// no-op if not multi protocol template or flow template
return
}
templateCtx := e.GetTemplateCtx(input)
for k, v := range vars {
if stringsutil.HasPrefixAny(k, templateTypes.SupportedProtocolsStrings()...) {
// this was inherited from previous protocols no need to modify it we can directly set it or omit
templateCtx.Set(k, v)
continue
}
if !stringsutil.EqualFoldAny(k, "template-id", "template-info", "template-path") {
if reqID != "" {
k = reqID + "_" + k
} else if reqType < templateTypes.InvalidProtocol {
k = reqType.String() + "_" + k
}
templateCtx.Set(k, v)
}
}
}
// AddTemplateVar adds given var to template context with given template type as prefix
// this method is no-op if template is not multi protocol
func (e *ExecutorOptions) AddTemplateVar(input *contextargs.MetaInput, templateType templateTypes.ProtocolType, reqID string, key string, value interface{}) {
if !e.IsMultiProtocol && e.Flow == "" {
// no-op if not multi protocol template or flow template
return
}
templateCtx := e.GetTemplateCtx(input)
if stringsutil.HasPrefixAny(key, templateTypes.SupportedProtocolsStrings()...) {
// this was inherited from previous protocols no need to modify it we can directly set it or omit
templateCtx.Set(key, value)
return
}
if reqID != "" {
key = reqID + "_" + key
} else if templateType < templateTypes.InvalidProtocol {
key = templateType.String() + "_" + key
}
templateCtx.Set(key, value)
}
// Copy returns a copy of the executeroptions structure
func (e *ExecutorOptions) Copy() *ExecutorOptions {
copy := &ExecutorOptions{
TemplateID: e.TemplateID,
TemplatePath: e.TemplatePath,
TemplateInfo: e.TemplateInfo,
TemplateVerifier: e.TemplateVerifier,
Verified: e.Verified,
LLMClient: e.LLMClient,
TemplateVerificationCallback: e.TemplateVerificationCallback,
RawTemplate: e.RawTemplate,
Output: e.Output,
Options: e.Options,
IssuesClient: e.IssuesClient,
Progress: e.Progress,
RateLimiter: e.RateLimiter,
Catalog: e.Catalog,
ProjectFile: e.ProjectFile,
Browser: e.Browser,
Interactsh: e.Interactsh,
HostErrorsCache: e.HostErrorsCache,
InteractshScope: e.InteractshScope,
HostBackoff: e.HostBackoff,
CatchAll: e.CatchAll,
StopAtFirstMatch: e.StopAtFirstMatch,
Variables: e.Variables,
Constants: e.Constants,
ExcludeMatchers: e.ExcludeMatchers,
InputHelper: e.InputHelper,
FuzzParamsFrequency: e.FuzzParamsFrequency,
FuzzStatsDB: e.FuzzStatsDB,
Operators: e.Operators,
DoNotCache: e.DoNotCache,
Colorizer: e.Colorizer,
WorkflowLoader: e.WorkflowLoader,
ResumeCfg: e.ResumeCfg,
ProtocolType: e.ProtocolType,
Flow: e.Flow,
IsMultiProtocol: e.IsMultiProtocol,
JsCompiler: e.JsCompiler,
AuthProvider: e.AuthProvider,
TemporaryDirectory: e.TemporaryDirectory,
Parser: e.Parser,
ExportReqURLPattern: e.ExportReqURLPattern,
GlobalMatchers: e.GlobalMatchers,
Logger: e.Logger,
TargetScope: e.TargetScope,
}
copy.ClusterMappings = e.ClusterMappings.Copy()
copy.CreateTemplateCtxStore()
return copy
}
// CloneOperators returns a copy of operators with only template-authored fields.
// Compiled matcher/extractor state is rebuilt per execution by Request.Compile.
func CloneOperators(src operators.Operators) operators.Operators {
value := cloneExportedValue(reflect.ValueOf(src))
cloned := value.Interface().(operators.Operators)
cloned.TemplateID = ""
cloned.ExcludeMatchers = nil
return cloned
}
// CloneMatchers returns a copy of matcher definitions without compiled matcher
// state so callers can safely compile them for a single execution.
func CloneMatchers(src []*matchers.Matcher) []*matchers.Matcher {
if src == nil {
return nil
}
value := cloneExportedValue(reflect.ValueOf(src))
return value.Interface().([]*matchers.Matcher)
}
func cloneExportedValue(value reflect.Value) reflect.Value {
if !value.IsValid() {
return value
}
switch value.Kind() {
case reflect.Interface:
if value.IsNil() {
return reflect.Zero(value.Type())
}
cloned := cloneExportedValue(value.Elem())
result := reflect.New(value.Type()).Elem()
result.Set(cloned)
return result
case reflect.Pointer:
if value.IsNil() {
return reflect.Zero(value.Type())
}
cloned := reflect.New(value.Type().Elem())
cloned.Elem().Set(cloneExportedValue(value.Elem()))
return cloned
case reflect.Struct:
cloned := reflect.New(value.Type()).Elem()
for i := 0; i < value.NumField(); i++ {
if value.Type().Field(i).IsExported() {
cloned.Field(i).Set(cloneExportedValue(value.Field(i)))
}
}
return cloned
case reflect.Slice:
if value.IsNil() {
return reflect.Zero(value.Type())
}
cloned := reflect.MakeSlice(value.Type(), value.Len(), value.Len())
for i := 0; i < value.Len(); i++ {
cloned.Index(i).Set(cloneExportedValue(value.Index(i)))
}
return cloned
case reflect.Map:
if value.IsNil() {
return reflect.Zero(value.Type())
}
cloned := reflect.MakeMapWithSize(value.Type(), value.Len())
iterator := value.MapRange()
for iterator.Next() {
cloned.SetMapIndex(cloneExportedValue(iterator.Key()), cloneExportedValue(iterator.Value()))
}
return cloned
case reflect.Array:
cloned := reflect.New(value.Type()).Elem()
for i := 0; i < value.Len(); i++ {
cloned.Index(i).Set(cloneExportedValue(value.Index(i)))
}
return cloned
default:
return value
}
}
// Request is an interface implemented any protocol based request generator.
type Request interface {
// Compile compiles the request generators preparing any requests possible.
Compile(options *ExecutorOptions) error
// Requests returns the total number of requests the rule will perform
Requests() int
// GetID returns the ID for the request if any. IDs are used for multi-request
// condition matching. So, two requests can be sent and their match can
// be evaluated from the third request by using the IDs for both requests.
GetID() string
// Match performs matching operation for a matcher on model and returns:
// true and a list of matched snippets if the matcher type is supports it
// otherwise false and an empty string slice
Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string)
// Extract performs extracting operation for an extractor on model and returns true or false.
Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{}
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback OutputEventCallback) error
// MakeResultEventItem creates a result event from internal wrapped event. Intended to be used by MakeResultEventItem internally
MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent
// MakeResultEvent creates a flat list of result events from an internal wrapped event, based on successful matchers and extracted data
MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent
// GetCompiledOperators returns a list of the compiled operators
GetCompiledOperators() []*operators.Operators
// Type returns the type of the protocol request
Type() templateTypes.ProtocolType
}
// OutputEventCallback is a callback event for any results found during scanning.
type OutputEventCallback func(result *output.InternalWrappedEvent)
func MakeDefaultResultEvent(request Request, wrapped *output.InternalWrappedEvent) []*output.ResultEvent {
// Note: operator result is generated if something was successful match/extract/dynamic-extract
// but results should not be generated if
// 1. no match was found and some dynamic values were extracted
// 2. if something was extracted (matchers exist but no match was found)
if len(wrapped.OperatorsResult.DynamicValues) > 0 && !wrapped.OperatorsResult.Matched {
return nil
}
// check if something was extracted (except dynamic values)
extracted := len(wrapped.OperatorsResult.Extracts) > 0 || len(wrapped.OperatorsResult.OutputExtracts) > 0
if extracted && len(wrapped.OperatorsResult.Operators.Matchers) > 0 && !wrapped.OperatorsResult.Matched {
// if extracted and matchers exist but no match was found then don't generate result
return nil
}
results := make([]*output.ResultEvent, 0, len(wrapped.OperatorsResult.Matches)+1)
// If we have multiple matchers with names, write each of them separately.
if len(wrapped.OperatorsResult.Matches) > 0 {
for matcherNames := range wrapped.OperatorsResult.Matches {
data := request.MakeResultEventItem(wrapped)
data.MatcherName = matcherNames
results = append(results, data)
}
} else if len(wrapped.OperatorsResult.Extracts) > 0 {
for k, v := range wrapped.OperatorsResult.Extracts {
data := request.MakeResultEventItem(wrapped)
data.ExtractorName = k
data.ExtractedResults = v
results = append(results, data)
}
} else {
data := request.MakeResultEventItem(wrapped)
results = append(results, data)
}
// Every protocol builds its results here, so attaching the llm audit and
// rendering the info at this one point covers findings from all of them
// rather than only the protocol that remembers to ask. Each is a no-op when
// it has nothing to do: no llm operator ran, or the info has no placeholders.
results = AttachLLMAudits(results, wrapped.InternalEvent)
return renderInfo(results, wrapped.InternalEvent)
}
// MakeDefaultExtractFunc performs extracting operation for an extractor on model and returns true or false.
func MakeDefaultExtractFunc(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {
return MakeDefaultExtractFuncWithOptions(data, extractor, nil)
}
// MakeDefaultExtractFuncWithOptions extracts using the current scan's DSL policy.
func MakeDefaultExtractFuncWithOptions(data map[string]interface{}, extractor *extractors.Extractor, options *types.Options) map[string]struct{} {
part := extractor.Part
if part == "" {
part = "response"
}
item, ok := data[part]
if !ok && !extractors.SupportsMap(extractor) {
return nil
}
itemStr := types.ToString(item)
switch extractor.GetType() {
case extractors.RegexExtractor:
return extractor.ExtractRegex(itemStr)
case extractors.KValExtractor:
return extractor.ExtractKval(data)
case extractors.JSONExtractor:
return extractor.ExtractJSON(itemStr)
case extractors.XPathExtractor:
return extractor.ExtractXPath(itemStr)
case extractors.DSLExtractor:
return extractor.ExtractDSLWithOptions(data, options)
}
return nil
}
// MakeDefaultMatchFunc performs matching operation for a matcher on model and returns true or false.
func MakeDefaultMatchFunc(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {
return MakeDefaultMatchFuncWithOptions(data, matcher, nil)
}
// MakeDefaultMatchFuncWithOptions matches using the current scan's DSL policy.
func MakeDefaultMatchFuncWithOptions(data map[string]interface{}, matcher *matchers.Matcher, options *types.Options) (bool, []string) {
part := matcher.Part
if part == "" {
part = "response"
}
partItem, ok := data[part]
if !ok && matcher.Type.MatcherType != matchers.DSLMatcher {
return false, nil
}
item := types.ToString(partItem)
switch matcher.GetType() {
case matchers.SizeMatcher:
result := matcher.Result(matcher.MatchSize(len(item)))
return result, nil
case matchers.WordsMatcher:
return matcher.ResultWithMatchedSnippet(matcher.MatchWordsWithOptions(item, nil, options))
case matchers.RegexMatcher:
return matcher.ResultWithMatchedSnippet(matcher.MatchRegex(item))
case matchers.BinaryMatcher:
return matcher.ResultWithMatchedSnippet(matcher.MatchBinary(item))
case matchers.DSLMatcher:
return matcher.Result(matcher.MatchDSLWithOptions(data, options)), nil
case matchers.XPathMatcher:
return matcher.Result(matcher.MatchXPath(item)), []string{}
}
return false, nil
}
// MakeDefaultMatchFuncWithExecutorOptions matches using the current scan's DSL
// policy. It is the entry point a protocol uses when it may carry llm
// operators, which need the declared values that *types.Options alone does not
// hold.
func MakeDefaultMatchFuncWithExecutorOptions(data map[string]interface{}, matcher *matchers.Matcher, e *ExecutorOptions) (bool, []string) {
if matcher.GetType() == matchers.LLMMatcher {
return MatchLLM(data, matcher, defaultOperatorPart(data, matcher.Part), e)
}
return MakeDefaultMatchFuncWithOptions(data, matcher, e.GetOptions())
}
// MakeDefaultExtractFuncWithExecutorOptions is the extractor counterpart of
// MakeDefaultMatchFuncWithExecutorOptions.
func MakeDefaultExtractFuncWithExecutorOptions(data map[string]interface{}, extractor *extractors.Extractor, e *ExecutorOptions) map[string]struct{} {
if extractor.GetType() == extractors.LLMExtractor {
return ExtractLLM(data, extractor, defaultOperatorPart(data, extractor.Part), e)
}
return MakeDefaultExtractFuncWithOptions(data, extractor, e.GetOptions())
}
// defaultOperatorPart resolves an operator part against a protocol event whose
// payload is named "response", matching the default builders above. An llm
// operator is not rejected when the part is missing: with inputs it compares
// whole responses instead, and without them an empty corpus is the honest
// thing to ask about.
func defaultOperatorPart(data map[string]interface{}, part string) string {
if part == "" {
part = "response"
}
return types.ToString(data[part])
}
func (e *ExecutorOptions) EncodeTemplate() string {
if !e.Options.OmitTemplate && len(e.RawTemplate) <= MaxTemplateFileSizeForEncoding {
return base64.StdEncoding.EncodeToString(e.RawTemplate)
}
return ""
}
// ApplyNewEngineOptions updates an existing ExecutorOptions with options from a new engine. This
// handles things like the ExecutionID that need to be updated.
func (e *ExecutorOptions) ApplyNewEngineOptions(n *ExecutorOptions) {
// TODO: cached code|headless templates have nil ExecuterOptions if -code or -headless are not enabled
if e == nil || n == nil || n.Options == nil {
return
}
e.Options = n.Options.Copy()
e.Output = n.Output
e.IssuesClient = n.IssuesClient
e.Progress = n.Progress
e.RateLimiter = n.RateLimiter
e.Catalog = n.Catalog
e.ProjectFile = n.ProjectFile
e.Browser = n.Browser
e.Interactsh = n.Interactsh
e.InteractshScope = n.InteractshScope
e.HostErrorsCache = n.HostErrorsCache
e.HostBackoff = n.HostBackoff
e.CatchAll = n.CatchAll
e.InputHelper = n.InputHelper
e.FuzzParamsFrequency = n.FuzzParamsFrequency
e.FuzzStatsDB = n.FuzzStatsDB
e.DoNotCache = n.DoNotCache
e.Colorizer = n.Colorizer
e.WorkflowLoader = n.WorkflowLoader
e.ResumeCfg = n.ResumeCfg
e.JsCompiler = n.JsCompiler
e.AuthProvider = n.AuthProvider
e.TemporaryDirectory = n.TemporaryDirectory
e.Parser = n.Parser
e.ExportReqURLPattern = n.ExportReqURLPattern
e.GlobalMatchers = n.GlobalMatchers
e.Logger = n.Logger
e.CustomFastdialer = n.CustomFastdialer
e.ClusterMappings = n.ClusterMappings.Copy()
}
// GetOptions returns scan options, or nil for an unbound executor.
func (e *ExecutorOptions) GetOptions() *types.Options {
if e == nil {
return nil
}
return e.Options
}