package protocols
import (
"context"
"encoding/base64"
"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"
"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"
"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/hosterrorscache"
"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/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
)
type Executer interface {
Compile() error
Requests() int
Execute(ctx *scan.ScanContext) (bool, error)
ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error)
}
type ExecutorOptions struct {
TemplateID string
TemplatePath string
TemplateInfo model.Info
TemplateVerifier string
RawTemplate []byte
Output output.Writer
Options *types.Options
IssuesClient reporting.Client
Progress progress.Progress
RateLimiter *ratelimit.Limiter
Catalog catalog.Catalog
ProjectFile *projectfile.ProjectFile
Browser *engine.Browser
Interactsh *interactsh.Client
HostErrorsCache hosterrorscache.CacheInterface
StopAtFirstMatch bool
Variables variables.Variable
Constants map[string]interface{}
ExcludeMatchers *excludematchers.ExcludeMatchers
InputHelper *input.Helper
FuzzParamsFrequency *frequency.Tracker
FuzzStatsDB *stats.Tracker
Operators []*operators.Operators
DoNotCache bool
Colorizer aurora.Aurora
WorkflowLoader model.WorkflowLoader
ResumeCfg *types.ResumeCfg
ProtocolType templateTypes.ProtocolType
Flow string
IsMultiProtocol bool
templateCtxStore *mapsutil.SyncLockMap[string, *contextargs.Context]
JsCompiler *compiler.Compiler
AuthProvider authprovider.AuthProvider
TemporaryDirectory string
Parser parser.Parser
ExportReqURLPattern bool
GlobalMatchers *globalmatchers.Storage
Logger *gologger.Logger
CustomFastdialer *fastdialer.Dialer
}
func (e *ExecutorOptions) RateLimitTake() {
if e.RateLimiter != nil {
e.RateLimiter.Take()
}
}
func (e *ExecutorOptions) GetThreadsForNPayloadRequests(totalRequests int, currentThreads int) int {
if currentThreads > 0 {
return currentThreads
}
return e.Options.PayloadConcurrency
}
func (e *ExecutorOptions) CreateTemplateCtxStore() {
e.templateCtxStore = &mapsutil.SyncLockMap[string, *contextargs.Context]{
Map: make(map[string]*contextargs.Context),
ReadOnly: atomic.Bool{},
}
}
func (e *ExecutorOptions) RemoveTemplateCtx(input *contextargs.MetaInput) {
scanId := input.GetScanHash(e.TemplateID)
if e.templateCtxStore != nil {
e.templateCtxStore.Delete(scanId)
}
}
func (e *ExecutorOptions) HasTemplateCtx(input *contextargs.MetaInput) bool {
scanId := input.GetScanHash(e.TemplateID)
if e.templateCtxStore != nil {
return e.templateCtxStore.Has(scanId)
}
return false
}
func (e *ExecutorOptions) GetTemplateCtx(input *contextargs.MetaInput) *contextargs.Context {
scanId := input.GetScanHash(e.TemplateID)
if e.templateCtxStore == nil {
e.CreateTemplateCtxStore()
}
templateCtx, ok := e.templateCtxStore.Get(scanId)
if !ok {
templateCtx = contextargs.New(context.Background())
templateCtx.MetaInput = input
_ = e.templateCtxStore.Set(scanId, templateCtx)
}
return templateCtx
}
func (e *ExecutorOptions) AddTemplateVars(input *contextargs.MetaInput, reqType templateTypes.ProtocolType, reqID string, vars map[string]interface{}) {
if !e.IsMultiProtocol && e.Flow == "" {
return
}
templateCtx := e.GetTemplateCtx(input)
for k, v := range vars {
if stringsutil.HasPrefixAny(k, templateTypes.SupportedProtocolsStrings()...) {
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)
}
}
}
func (e *ExecutorOptions) AddTemplateVar(input *contextargs.MetaInput, templateType templateTypes.ProtocolType, reqID string, key string, value interface{}) {
if !e.IsMultiProtocol && e.Flow == "" {
return
}
templateCtx := e.GetTemplateCtx(input)
if stringsutil.HasPrefixAny(key, templateTypes.SupportedProtocolsStrings()...) {
templateCtx.Set(key, value)
return
}
if reqID != "" {
key = reqID + "_" + key
} else if templateType < templateTypes.InvalidProtocol {
key = templateType.String() + "_" + key
}
templateCtx.Set(key, value)
}
func (e *ExecutorOptions) Copy() *ExecutorOptions {
copy := &ExecutorOptions{
TemplateID: e.TemplateID,
TemplatePath: e.TemplatePath,
TemplateInfo: e.TemplateInfo,
TemplateVerifier: e.TemplateVerifier,
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,
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,
}
copy.CreateTemplateCtxStore()
return copy
}
type Request interface {
Compile(options *ExecutorOptions) error
Requests() int
GetID() string
Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string)
Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{}
ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback OutputEventCallback) error
MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent
MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent
GetCompiledOperators() []*operators.Operators
Type() templateTypes.ProtocolType
}
type OutputEventCallback func(result *output.InternalWrappedEvent)
func MakeDefaultResultEvent(request Request, wrapped *output.InternalWrappedEvent) []*output.ResultEvent {
if len(wrapped.OperatorsResult.DynamicValues) > 0 && !wrapped.OperatorsResult.Matched {
return nil
}
extracted := len(wrapped.OperatorsResult.Extracts) > 0 || len(wrapped.OperatorsResult.OutputExtracts) > 0
if extracted && len(wrapped.OperatorsResult.Operators.Matchers) > 0 && !wrapped.OperatorsResult.Matched {
return nil
}
results := make([]*output.ResultEvent, 0, len(wrapped.OperatorsResult.Matches)+1)
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)
}
return results
}
func MakeDefaultExtractFunc(data map[string]interface{}, extractor *extractors.Extractor) 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.ExtractDSL(data)
}
return nil
}
func MakeDefaultMatchFunc(data map[string]interface{}, matcher *matchers.Matcher) (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.MatchWords(item, nil))
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.MatchDSL(data)), nil
case matchers.XPathMatcher:
return matcher.Result(matcher.MatchXPath(item)), []string{}
}
return false, nil
}
func (e *ExecutorOptions) EncodeTemplate() string {
if !e.Options.OmitTemplate && len(e.RawTemplate) <= MaxTemplateFileSizeForEncoding {
return base64.StdEncoding.EncodeToString(e.RawTemplate)
}
return ""
}
func (e *ExecutorOptions) ApplyNewEngineOptions(n *ExecutorOptions) {
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.HostErrorsCache = n.HostErrorsCache
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
}