[go: up one dir, main page]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

introduce timeouts config in types.Options #5228

Merged
merged 8 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions cmd/nuclei/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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"),
Expand Down
15 changes: 5 additions & 10 deletions pkg/js/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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{}
}
Expand Down Expand Up @@ -106,14 +106,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) {
Expand Down
12 changes: 11 additions & 1 deletion pkg/js/compiler/compiler_test.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -18,7 +21,14 @@ 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)
}

_, err = compiler.ExecuteWithOptions(p, NewExecuteArgs(), &ExecuteOptions{Context: context.Background(),
TimeoutVariants: &types.Timeouts{JsCompilerExecutionTimeout: time.Duration(20) * time.Second}},
)
if err != nil {
t.Fatal(err)
}
Expand Down
11 changes: 1 addition & 10 deletions pkg/js/compiler/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 7 additions & 11 deletions pkg/protocols/code/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ import (
)

const (
pythonEnvRegex = `os\.getenv\(['"]([^'"]+)['"]\)`
TimeoutMultiplier = 6 // timeout multiplier for code protocol
pythonEnvRegex = `os\.getenv\(['"]([^'"]+)['"]\)`
)

var (
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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) {
Expand Down
4 changes: 1 addition & 3 deletions pkg/protocols/common/protocolstate/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/protocols/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 5 additions & 18 deletions pkg/protocols/http/httpclientpool/clientpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}
Expand Down Expand Up @@ -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 != "" {
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion pkg/protocols/http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion pkg/protocols/http/request_annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
}
Expand Down
20 changes: 12 additions & 8 deletions pkg/protocols/javascript/js.go
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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()}
Expand Down
4 changes: 2 additions & 2 deletions pkg/protocols/network/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 9 additions & 9 deletions pkg/templates/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading
Loading