diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go index 5aea16337c..2d2be0aa1a 100644 --- a/cmd/nuclei/main.go +++ b/cmd/nuclei/main.go @@ -297,7 +297,6 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.BoolVarP(&options.ShowMatchLine, "show-match-line", "sml", false, "show match lines for file templates, works with extractors only"), flagSet.BoolVar(&options.ZTLS, "ztls", false, "use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default"), //nolint:all flagSet.StringVar(&options.SNI, "sni", "", "tls sni hostname to use (default: input domain name)"), - flagSet.DurationVarP(&options.DialerTimeout, "dialer-timeout", "dt", 0, "timeout for network requests."), flagSet.DurationVarP(&options.DialerKeepAlive, "dialer-keep-alive", "dka", 0, "keep-alive duration for network requests."), flagSet.BoolVarP(&options.AllowLocalFileAccess, "allow-local-file-access", "lfa", false, "allows file (payload) access anywhere on the system"), flagSet.BoolVarP(&options.RestrictLocalNetworkAccess, "restrict-local-network-access", "lna", false, "blocks connections to the local / private network"), @@ -306,7 +305,6 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.StringVarP(&options.SourceIP, "source-ip", "sip", "", "source ip address to use for network scan"), flagSet.IntVarP(&options.ResponseReadSize, "response-size-read", "rsr", 0, "max response size to read in bytes"), flagSet.IntVarP(&options.ResponseSaveSize, "response-size-save", "rss", unitutils.Mega, "max response size to read in bytes"), - flagSet.DurationVarP(&options.ResponseReadTimeout, "response-read-timeout", "rrt", time.Duration(5*time.Second), "response read timeout in seconds"), flagSet.CallbackVar(resetCallback, "reset", "reset removes all nuclei configuration and data files (including nuclei-templates)"), flagSet.BoolVarP(&options.TlsImpersonate, "tls-impersonate", "tlsi", false, "enable experimental client hello (ja3) tls randomization"), flagSet.StringVarP(&options.HttpApiEndpoint, "http-api-endpoint", "hae", "", "experimental http api endpoint"), diff --git a/pkg/js/compiler/compiler.go b/pkg/js/compiler/compiler.go index 367bd013b2..f50e44ff2a 100644 --- a/pkg/js/compiler/compiler.go +++ b/pkg/js/compiler/compiler.go @@ -4,11 +4,11 @@ package compiler import ( "context" "fmt" - "time" "github.com/dop251/goja" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators" + "github.com/projectdiscovery/nuclei/v3/pkg/types" contextutil "github.com/projectdiscovery/utils/context" "github.com/projectdiscovery/utils/errkit" stringsutil "github.com/projectdiscovery/utils/strings" @@ -38,13 +38,13 @@ type ExecuteOptions struct { // Cleanup is extra cleanup function to be called after execution Cleanup func(runtime *goja.Runtime) - /// Timeout for this script execution - Timeout int // Source is original source of the script Source *string Context context.Context + TimeoutVariants *types.Timeouts + // Manually exported objects exports map[string]interface{} } @@ -79,15 +79,6 @@ func (e ExecuteResult) GetSuccess() bool { return val } -// Execute executes a script with the default options. -func (c *Compiler) Execute(code string, args *ExecuteArgs) (ExecuteResult, error) { - p, err := WrapScriptNCompile(code, false) - if err != nil { - return nil, err - } - return c.ExecuteWithOptions(p, args, &ExecuteOptions{Context: context.Background()}) -} - // ExecuteWithOptions executes a script with the provided options. func (c *Compiler) ExecuteWithOptions(program *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (ExecuteResult, error) { if opts == nil { @@ -106,14 +97,9 @@ func (c *Compiler) ExecuteWithOptions(program *goja.Program, args *ExecuteArgs, // merge all args into templatectx args.TemplateCtx = generators.MergeMaps(args.TemplateCtx, args.Args) - if opts.Timeout <= 0 || opts.Timeout > 180 { - // some js scripts can take longer time so allow configuring timeout - // from template but keep it within sane limits (180s) - opts.Timeout = JsProtocolTimeout - } - // execute with context and timeout - ctx, cancel := context.WithTimeoutCause(opts.Context, time.Duration(opts.Timeout)*time.Second, ErrJSExecDeadline) + + ctx, cancel := context.WithTimeoutCause(opts.Context, opts.TimeoutVariants.JsCompilerExecutionTimeout, ErrJSExecDeadline) defer cancel() // execute the script results, err := contextutil.ExecFuncWithTwoReturns(ctx, func() (val goja.Value, err error) { diff --git a/pkg/js/compiler/compiler_test.go b/pkg/js/compiler/compiler_test.go index ca09e7782e..3a43a4016e 100644 --- a/pkg/js/compiler/compiler_test.go +++ b/pkg/js/compiler/compiler_test.go @@ -1,11 +1,14 @@ package compiler import ( + "context" "strings" "testing" + "time" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/gologger/levels" + "github.com/projectdiscovery/nuclei/v3/pkg/types" ) func TestNewCompilerConsoleDebug(t *testing.T) { @@ -18,23 +21,19 @@ func TestNewCompilerConsoleDebug(t *testing.T) { }) compiler := New() - _, err := compiler.Execute("console.log('hello world');", NewExecuteArgs()) + p, err := WrapScriptNCompile("console.log('hello world');", false) if err != nil { t.Fatal(err) } - if !strings.HasSuffix(gotString, "hello world") { - t.Fatalf("console.log not working, got=%v", gotString) - } -} -func TestExecuteResultGetSuccess(t *testing.T) { - compiler := New() - result, err := compiler.Execute("1+1 == 2", NewExecuteArgs()) + _, err = compiler.ExecuteWithOptions(p, NewExecuteArgs(), &ExecuteOptions{Context: context.Background(), + TimeoutVariants: &types.Timeouts{JsCompilerExecutionTimeout: time.Duration(20) * time.Second}}, + ) if err != nil { t.Fatal(err) } - if result.GetSuccess() != true { - t.Fatalf("expected true, got=%v", result.GetSuccess()) + if !strings.HasSuffix(gotString, "hello world") { + t.Fatalf("console.log not working, got=%v", gotString) } } diff --git a/pkg/js/compiler/init.go b/pkg/js/compiler/init.go index 8a171dc3da..92301df5e4 100644 --- a/pkg/js/compiler/init.go +++ b/pkg/js/compiler/init.go @@ -7,26 +7,17 @@ import ( // jsprotocolInit var ( - // Per Execution Javascript timeout in seconds - JsProtocolTimeout = 10 PoolingJsVmConcurrency = 100 NonPoolingVMConcurrency = 20 - JsTimeoutMultiplier = 1.5 ) // Init initializes the javascript protocol func Init(opts *types.Options) error { - if opts.Timeout < 10 { - // keep existing 10s timeout - return nil - } + if opts.JsConcurrency < 100 { // 100 is reasonable default opts.JsConcurrency = 100 } - // we have dialer timeout set to 10s so js needs to be at least - // 15s to return the actual error if not it will be a dialer timeout - JsProtocolTimeout = int(float64(opts.Timeout) * JsTimeoutMultiplier) PoolingJsVmConcurrency = opts.JsConcurrency PoolingJsVmConcurrency -= NonPoolingVMConcurrency return nil diff --git a/pkg/protocols/code/code.go b/pkg/protocols/code/code.go index f103f50d0c..7736113dca 100644 --- a/pkg/protocols/code/code.go +++ b/pkg/protocols/code/code.go @@ -37,8 +37,7 @@ import ( ) const ( - pythonEnvRegex = `os\.getenv\(['"]([^'"]+)['"]\)` - TimeoutMultiplier = 6 // timeout multiplier for code protocol + pythonEnvRegex = `os\.getenv\(['"]([^'"]+)['"]\)` ) var ( @@ -179,9 +178,6 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa metaSrc.AddVariable(gozerotypes.Variable{Name: name, Value: v}) } - // set timeout using multiplier - timeout := TimeoutMultiplier * request.options.Options.Timeout - if request.PreCondition != "" { if request.options.Options.Debug || request.options.Options.DebugRequests { gologger.Debug().Msgf("[%s] Executing Precondition for Code request\n", request.TemplateID) @@ -199,11 +195,11 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa result, err := request.options.JsCompiler.ExecuteWithOptions(request.preConditionCompiled, args, &compiler.ExecuteOptions{ - Timeout: timeout, - Source: &request.PreCondition, - Callback: registerPreConditionFunctions, - Cleanup: cleanUpPreConditionFunctions, - Context: input.Context(), + TimeoutVariants: request.options.Options.GetTimeouts(), + Source: &request.PreCondition, + Callback: registerPreConditionFunctions, + Cleanup: cleanUpPreConditionFunctions, + Context: input.Context(), }) if err != nil { return errorutil.NewWithTag(request.TemplateID, "could not execute pre-condition: %s", err) @@ -218,7 +214,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa } } - ctx, cancel := context.WithTimeoutCause(input.Context(), time.Duration(timeout)*time.Second, ErrCodeExecutionDeadline) + ctx, cancel := context.WithTimeoutCause(input.Context(), request.options.Options.GetTimeouts().CodeExecutionTimeout, ErrCodeExecutionDeadline) defer cancel() // Note: we use contextutil despite the fact that gozero accepts context as argument gOutput, err := contextutil.ExecFuncWithTwoReturns(ctx, func() (*gozerotypes.Result, error) { diff --git a/pkg/protocols/common/protocolstate/state.go b/pkg/protocols/common/protocolstate/state.go index cf6f5234ec..b7c7796fa8 100644 --- a/pkg/protocols/common/protocolstate/state.go +++ b/pkg/protocols/common/protocolstate/state.go @@ -34,9 +34,7 @@ func Init(options *types.Options) error { lfaAllowed = options.AllowLocalFileAccess opts := fastdialer.DefaultOptions - if options.DialerTimeout > 0 { - opts.DialerTimeout = options.DialerTimeout - } + opts.DialerTimeout = options.GetTimeouts().DialTimeout if options.DialerKeepAlive > 0 { opts.DialerKeepAlive = options.DialerKeepAlive } diff --git a/pkg/protocols/http/http.go b/pkg/protocols/http/http.go index c20cbbfa66..fc347ce88d 100644 --- a/pkg/protocols/http/http.go +++ b/pkg/protocols/http/http.go @@ -336,7 +336,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { request.Raw[i] = strings.ReplaceAll(raw, "\n", "\r\n") } } - request.rawhttpClient = httpclientpool.GetRawHTTP(options.Options) + request.rawhttpClient = httpclientpool.GetRawHTTP(options) } if len(request.Matchers) > 0 || len(request.Extractors) > 0 { compiled := &request.Operators diff --git a/pkg/protocols/http/httpclientpool/clientpool.go b/pkg/protocols/http/httpclientpool/clientpool.go index b4ca5d40cf..2c244556ae 100644 --- a/pkg/protocols/http/httpclientpool/clientpool.go +++ b/pkg/protocols/http/httpclientpool/clientpool.go @@ -17,6 +17,7 @@ import ( "golang.org/x/net/publicsuffix" "github.com/projectdiscovery/fastdialer/fastdialer/ja3/impersonate" + "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils" "github.com/projectdiscovery/nuclei/v3/pkg/types" @@ -31,28 +32,14 @@ var ( forceMaxRedirects int normalClient *retryablehttp.Client clientPool *mapsutil.SyncLockMap[string, *retryablehttp.Client] - // MaxResponseHeaderTimeout is the timeout for response headers - // to be read from the server (this prevents infinite hang started by server if any) - // Note: this will be overridden temporarily when using @timeout request annotation - MaxResponseHeaderTimeout = time.Duration(10) * time.Second - // HttpTimeoutMultiplier is the multiplier for the http timeout - HttpTimeoutMultiplier = 3 ) -// GetHttpTimeout returns the http timeout for the client -func GetHttpTimeout(opts *types.Options) time.Duration { - return time.Duration(opts.Timeout*HttpTimeoutMultiplier) * time.Second -} - // Init initializes the clientpool implementation func Init(options *types.Options) error { // Don't create clients if already created in the past. if normalClient != nil { return nil } - if options.Timeout > 10 { - MaxResponseHeaderTimeout = time.Duration(options.Timeout) * time.Second - } if options.ShouldFollowHTTPRedirects() { forceMaxRedirects = options.MaxRedirects } @@ -143,7 +130,7 @@ func (c *Configuration) HasStandardOptions() bool { } // GetRawHTTP returns the rawhttp request client -func GetRawHTTP(options *types.Options) *rawhttp.Client { +func GetRawHTTP(options *protocols.ExecutorOptions) *rawhttp.Client { if rawHttpClient == nil { rawHttpOptions := rawhttp.DefaultOptions if types.ProxyURL != "" { @@ -153,7 +140,7 @@ func GetRawHTTP(options *types.Options) *rawhttp.Client { } else if protocolstate.Dialer != nil { rawHttpOptions.FastDialer = protocolstate.Dialer } - rawHttpOptions.Timeout = GetHttpTimeout(options) + rawHttpOptions.Timeout = options.Options.GetTimeouts().HttpTimeout rawHttpClient = rawhttp.NewClient(rawHttpOptions) } return rawHttpClient @@ -239,7 +226,7 @@ func wrappedGet(options *types.Options, configuration *Configuration) (*retryabl } // responseHeaderTimeout is max timeout for response headers to be read - responseHeaderTimeout := MaxResponseHeaderTimeout + responseHeaderTimeout := options.GetTimeouts().HttpResponseHeaderTimeout if configuration.ResponseHeaderTimeout != 0 { responseHeaderTimeout = configuration.ResponseHeaderTimeout } @@ -308,7 +295,7 @@ func wrappedGet(options *types.Options, configuration *Configuration) (*retryabl CheckRedirect: makeCheckRedirectFunc(redirectFlow, maxRedirects), } if !configuration.NoTimeout { - httpclient.Timeout = GetHttpTimeout(options) + httpclient.Timeout = options.GetTimeouts().HttpTimeout } client := retryablehttp.NewWithHTTPClient(httpclient, retryableHttpOptions) if jar != nil { diff --git a/pkg/protocols/http/request.go b/pkg/protocols/http/request.go index 7e5e069348..c17e746e44 100644 --- a/pkg/protocols/http/request.go +++ b/pkg/protocols/http/request.go @@ -483,7 +483,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa request.options.RateLimitTake() ctx := request.newContext(input) - ctxWithTimeout, cancel := context.WithTimeoutCause(ctx, httpclientpool.GetHttpTimeout(request.options.Options), ErrHttpEngineRequestDeadline) + ctxWithTimeout, cancel := context.WithTimeoutCause(ctx, request.options.Options.GetTimeouts().HttpTimeout, ErrHttpEngineRequestDeadline) defer cancel() generatedHttpRequest, err := generator.Make(ctxWithTimeout, input, data, payloads, dynamicValue) diff --git a/pkg/protocols/http/request_annotations.go b/pkg/protocols/http/request_annotations.go index c0f4b69774..fa2a5eaedd 100644 --- a/pkg/protocols/http/request_annotations.go +++ b/pkg/protocols/http/request_annotations.go @@ -130,6 +130,10 @@ func (r *Request) parseAnnotations(rawRequest string, request *retryablehttp.Req if duration := reTimeoutAnnotation.FindStringSubmatch(rawRequest); len(duration) > 0 { value := strings.TrimSpace(duration[1]) if parsed, err := time.ParseDuration(value); err == nil { + // to avoid dos via timeout request annotation in http template we set it to maximum of 2 minutes + if parsed > 2*time.Minute { + parsed = 2 * time.Minute + } //nolint:govet // cancelled automatically by withTimeout // global timeout is overridden by annotation by replacing context ctx, overrides.cancelFunc = context.WithTimeoutCause(context.TODO(), parsed, ErrTimeoutAnnotationDeadline) @@ -140,7 +144,7 @@ func (r *Request) parseAnnotations(rawRequest string, request *retryablehttp.Req } else { //nolint:govet // cancelled automatically by withTimeout // global timeout is overridden by annotation by replacing context - ctx, overrides.cancelFunc = context.WithTimeoutCause(context.TODO(), httpclientpool.GetHttpTimeout(r.options.Options), ErrRequestTimeoutDeadline) + ctx, overrides.cancelFunc = context.WithTimeoutCause(context.TODO(), r.options.Options.GetTimeouts().HttpTimeout, ErrRequestTimeoutDeadline) request = request.Clone(ctx) } } diff --git a/pkg/protocols/javascript/js.go b/pkg/protocols/javascript/js.go index 71f9f53413..2e43c61cd9 100644 --- a/pkg/protocols/javascript/js.go +++ b/pkg/protocols/javascript/js.go @@ -65,9 +65,6 @@ type Request struct { // Code contains code to execute for the javascript request. Code string `yaml:"code,omitempty" json:"code,omitempty" jsonschema:"title=code to execute in javascript,description=Executes inline javascript code for the request"` // description: | - // Timeout in seconds is optional timeout for each javascript script execution (i.e init, pre-condition, code) - Timeout int `yaml:"timeout,omitempty" json:"timeout,omitempty" jsonschema:"title=timeout for javascript execution,description=Timeout in seconds is optional timeout for entire javascript script execution"` - // description: | // StopAtFirstMatch stops processing the request at first match. StopAtFirstMatch bool `yaml:"stop-at-first-match,omitempty" json:"stop-at-first-match,omitempty" jsonschema:"title=stop at first match,description=Stop the execution after a match is found"` // description: | @@ -153,9 +150,9 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error { } opts := &compiler.ExecuteOptions{ - Timeout: request.Timeout, - Source: &request.Init, - Context: context.Background(), + TimeoutVariants: request.options.Options.GetTimeouts(), + Source: &request.Init, + Context: context.Background(), } // register 'export' function to export variables from init code // these are saved in args and are available in pre-condition and request code @@ -345,7 +342,10 @@ func (request *Request) ExecuteWithResults(target *contextargs.Context, dynamicV argsCopy.TemplateCtx = templateCtx.GetAll() result, err := request.options.JsCompiler.ExecuteWithOptions(request.preConditionCompiled, argsCopy, - &compiler.ExecuteOptions{Timeout: request.Timeout, Source: &request.PreCondition, Context: target.Context()}) + &compiler.ExecuteOptions{ + TimeoutVariants: requestOptions.Options.GetTimeouts(), + Source: &request.PreCondition, Context: target.Context(), + }) if err != nil { return errorutil.NewWithTag(request.TemplateID, "could not execute pre-condition: %s", err) } @@ -500,7 +500,11 @@ func (request *Request) executeRequestWithPayloads(hostPort string, input *conte } results, err := request.options.JsCompiler.ExecuteWithOptions(request.scriptCompiled, argsCopy, - &compiler.ExecuteOptions{Timeout: request.Timeout, Source: &request.Code, Context: input.Context()}) + &compiler.ExecuteOptions{ + TimeoutVariants: requestOptions.Options.GetTimeouts(), + Source: &request.Code, + Context: input.Context(), + }) if err != nil { // shouldn't fail even if it returned error instead create a failure event results = compiler.ExecuteResult{"success": false, "error": err.Error()} diff --git a/pkg/protocols/network/request.go b/pkg/protocols/network/request.go index 62807abf93..90390e53c3 100644 --- a/pkg/protocols/network/request.go +++ b/pkg/protocols/network/request.go @@ -327,7 +327,7 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac } if input.Read > 0 { - buffer, err := ConnReadNWithTimeout(conn, int64(input.Read), request.options.Options.ResponseReadTimeout) + buffer, err := ConnReadNWithTimeout(conn, int64(input.Read), request.options.Options.GetTimeouts().TcpReadTimeout) if err != nil { return errorutil.NewWithErr(err).Msgf("could not read response from connection") } @@ -377,7 +377,7 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac bufferSize = -1 } - final, err := ConnReadNWithTimeout(conn, int64(bufferSize), request.options.Options.ResponseReadTimeout) + final, err := ConnReadNWithTimeout(conn, int64(bufferSize), request.options.Options.GetTimeouts().TcpReadTimeout) if err != nil { request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err) gologger.Verbose().Msgf("could not read more data from %s: %s", actualAddress, err) diff --git a/pkg/templates/compile_test.go b/pkg/templates/compile_test.go index 81c5f8fd51..91a858bd7e 100644 --- a/pkg/templates/compile_test.go +++ b/pkg/templates/compile_test.go @@ -39,15 +39,15 @@ func setup() { progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0) executerOpts = protocols.ExecutorOptions{ - Output: testutils.NewMockOutputWriter(options.OmitTemplate), - Options: options, - Progress: progressImpl, - ProjectFile: nil, - IssuesClient: nil, - Browser: nil, - Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory), - RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), - Parser: templates.NewParser(), + Output: testutils.NewMockOutputWriter(options.OmitTemplate), + Options: options, + Progress: progressImpl, + ProjectFile: nil, + IssuesClient: nil, + Browser: nil, + Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory), + RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), + Parser: templates.NewParser(), } workflowLoader, err := workflow.NewLoader(&executerOpts) if err != nil { diff --git a/pkg/testutils/testutils.go b/pkg/testutils/testutils.go index 6e6f94d9eb..930787aab9 100644 --- a/pkg/testutils/testutils.go +++ b/pkg/testutils/testutils.go @@ -76,7 +76,6 @@ var DefaultOptions = &types.Options{ InteractionsPollDuration: 5, GitHubTemplateRepo: []string{}, GitHubToken: "", - ResponseReadTimeout: time.Second * 5, } // TemplateInfo contains info for a mock executed template. @@ -90,17 +89,17 @@ type TemplateInfo struct { func NewMockExecuterOptions(options *types.Options, info *TemplateInfo) *protocols.ExecutorOptions { progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0) executerOpts := &protocols.ExecutorOptions{ - TemplateID: info.ID, - TemplateInfo: info.Info, - TemplatePath: info.Path, - Output: NewMockOutputWriter(options.OmitTemplate), - Options: options, - Progress: progressImpl, - ProjectFile: nil, - IssuesClient: nil, - Browser: nil, - Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory), - RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), + TemplateID: info.ID, + TemplateInfo: info.Info, + TemplatePath: info.Path, + Output: NewMockOutputWriter(options.OmitTemplate), + Options: options, + Progress: progressImpl, + ProjectFile: nil, + IssuesClient: nil, + Browser: nil, + Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory), + RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), } executerOpts.CreateTemplateCtxStore() return executerOpts diff --git a/pkg/tmplexec/flow/flow_executor_test.go b/pkg/tmplexec/flow/flow_executor_test.go index b47b38a2a0..cf7b1790a6 100644 --- a/pkg/tmplexec/flow/flow_executor_test.go +++ b/pkg/tmplexec/flow/flow_executor_test.go @@ -27,15 +27,15 @@ func setup() { progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0) executerOpts = protocols.ExecutorOptions{ - Output: testutils.NewMockOutputWriter(options.OmitTemplate), - Options: options, - Progress: progressImpl, - ProjectFile: nil, - IssuesClient: nil, - Browser: nil, - Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory), - RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), - Parser: templates.NewParser(), + Output: testutils.NewMockOutputWriter(options.OmitTemplate), + Options: options, + Progress: progressImpl, + ProjectFile: nil, + IssuesClient: nil, + Browser: nil, + Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory), + RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), + Parser: templates.NewParser(), } workflowLoader, err := workflow.NewLoader(&executerOpts) if err != nil { diff --git a/pkg/types/types.go b/pkg/types/types.go index f49da194cc..48e9039d37 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -278,8 +278,6 @@ type Options struct { SNI string // InputFileMode specifies the mode of input file (jsonl, burp, openapi, swagger, etc) InputFileMode string - // DialerTimeout sets the timeout for network requests. - DialerTimeout time.Duration // DialerKeepAlive sets the keep alive duration for network requests. DialerKeepAlive time.Duration // Interface to use for network scan @@ -292,8 +290,6 @@ type Options struct { ResponseReadSize int // ResponseSaveSize is the maximum size of response to save ResponseSaveSize int - // ResponseReadTimeout is response read timeout in seconds - ResponseReadTimeout time.Duration // Health Check HealthCheck bool // Time to wait between each input read operation before closing the stream @@ -406,6 +402,79 @@ type Options struct { HttpApiEndpoint string // ListTemplateProfiles lists all available template profiles ListTemplateProfiles bool + // timeouts contains various types of timeouts used in nuclei + // these timeouts are derived from dial-timeout (-timeout) with known multipliers + // This is internally managed and does not need to be set by user by explicitly setting + // this overrides the default/derived one + timeouts *Timeouts +} + +// SetTimeouts sets the timeout variants to use for the executor +func (opts *Options) SetTimeouts(t *Timeouts) { + opts.timeouts = t +} + +// GetTimeouts returns the timeout variants to use for the executor +func (eo *Options) GetTimeouts() *Timeouts { + if eo.timeouts != nil { + // redundant but apply to avoid any potential issues + eo.timeouts.ApplyDefaults() + return eo.timeouts + } + // set timeout variant value + eo.timeouts = NewTimeoutVariant(eo.Timeout) + eo.timeouts.ApplyDefaults() + return eo.timeouts +} + +// Timeouts is a struct that contains all the timeout variants for nuclei +// dialer timeout is used to derive other timeouts +type Timeouts struct { + // DialTimeout for fastdialer (default 10s) + DialTimeout time.Duration + // Tcp(Network Protocol) Read From Connection Timeout (default 5s) + TcpReadTimeout time.Duration + // Http Response Header Timeout (default 10s) + // this timeout prevents infinite hangs started by server if any + // this is temporarily overridden when using @timeout request annotation + HttpResponseHeaderTimeout time.Duration + // HttpTimeout for http client (default -> 3 x dial-timeout = 30s) + HttpTimeout time.Duration + // JsCompilerExec timeout/deadline (default -> 2 x dial-timeout = 20s) + JsCompilerExecutionTimeout time.Duration + // CodeExecutionTimeout for code execution (default -> 3 x dial-timeout = 30s) + CodeExecutionTimeout time.Duration +} + +// NewTimeoutVariant creates a new timeout variant with the given dial timeout in seconds +func NewTimeoutVariant(dialTimeoutSec int) *Timeouts { + tv := &Timeouts{ + DialTimeout: time.Duration(dialTimeoutSec) * time.Second, + } + tv.ApplyDefaults() + return tv +} + +// ApplyDefaults applies default values to timeout variants when missing +func (tv *Timeouts) ApplyDefaults() { + if tv.DialTimeout == 0 { + tv.DialTimeout = 10 * time.Second + } + if tv.TcpReadTimeout == 0 { + tv.TcpReadTimeout = 5 * time.Second + } + if tv.HttpResponseHeaderTimeout == 0 { + tv.HttpResponseHeaderTimeout = 10 * time.Second + } + if tv.HttpTimeout == 0 { + tv.HttpTimeout = 3 * tv.DialTimeout + } + if tv.JsCompilerExecutionTimeout == 0 { + tv.JsCompilerExecutionTimeout = 2 * tv.DialTimeout + } + if tv.CodeExecutionTimeout == 0 { + tv.CodeExecutionTimeout = 3 * tv.DialTimeout + } } // ShouldLoadResume resume file @@ -444,7 +513,6 @@ func DefaultOptions() *Options { MaxHostError: 30, ResponseReadSize: 10 * unitutils.Mega, ResponseSaveSize: unitutils.Mega, - ResponseReadTimeout: 5 * time.Second, } }