per-target rate limit

This commit is contained in:
Mzack9999 2025-08-19 20:39:16 +02:00
parent 44eeb5a60b
commit d075a4e217
9 changed files with 34 additions and 14 deletions

2
go.mod
View File

@ -99,7 +99,7 @@ require (
github.com/projectdiscovery/mapcidr v1.1.34 github.com/projectdiscovery/mapcidr v1.1.34
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
github.com/projectdiscovery/networkpolicy v0.1.18 github.com/projectdiscovery/networkpolicy v0.1.18
github.com/projectdiscovery/ratelimit v0.0.81 github.com/projectdiscovery/ratelimit v0.0.82-0.20250819165347-d56e782c656d
github.com/projectdiscovery/rdap v0.9.0 github.com/projectdiscovery/rdap v0.9.0
github.com/projectdiscovery/sarif v0.0.1 github.com/projectdiscovery/sarif v0.0.1
github.com/projectdiscovery/tlsx v1.1.9 github.com/projectdiscovery/tlsx v1.1.9

6
go.sum
View File

@ -805,6 +805,12 @@ github.com/projectdiscovery/networkpolicy v0.1.18 h1:DAeP73SvcuT4evaohNS7BPELw+V
github.com/projectdiscovery/networkpolicy v0.1.18/go.mod h1:2yWanKsU2oBZ75ch94IsEQy6hByFp+3oTiSyC6ew3TE= github.com/projectdiscovery/networkpolicy v0.1.18/go.mod h1:2yWanKsU2oBZ75ch94IsEQy6hByFp+3oTiSyC6ew3TE=
github.com/projectdiscovery/ratelimit v0.0.81 h1:u6lW+rAhS/UO0amHTYmYLipPK8NEotA9521hdojBtgI= github.com/projectdiscovery/ratelimit v0.0.81 h1:u6lW+rAhS/UO0amHTYmYLipPK8NEotA9521hdojBtgI=
github.com/projectdiscovery/ratelimit v0.0.81/go.mod h1:tK04WXHuC4i6AsFkByInODSNf45gd9sfaMHzmy2bAsA= github.com/projectdiscovery/ratelimit v0.0.81/go.mod h1:tK04WXHuC4i6AsFkByInODSNf45gd9sfaMHzmy2bAsA=
github.com/projectdiscovery/ratelimit v0.0.82-0.20250819014026-b6ac32486d12 h1:oS/0Oe6tv7SG2zoFBofwvoB3mfDb5Yk76np4zU0Mcg8=
github.com/projectdiscovery/ratelimit v0.0.82-0.20250819014026-b6ac32486d12/go.mod h1:z076BrLkBb5yS7uhHNoCTf8X/BvFSGRxwQ8EzEL9afM=
github.com/projectdiscovery/ratelimit v0.0.82-0.20250819164203-5e0837ee74a5 h1:tulP7aVzZsWlXbWo6W78nVFqJ05c6zSS8dsos/cBaSY=
github.com/projectdiscovery/ratelimit v0.0.82-0.20250819164203-5e0837ee74a5/go.mod h1:z076BrLkBb5yS7uhHNoCTf8X/BvFSGRxwQ8EzEL9afM=
github.com/projectdiscovery/ratelimit v0.0.82-0.20250819165347-d56e782c656d h1:zUptoE5gh4gyJsbcHfNUEKgdoieyAHW2hCfWXZ/Zye4=
github.com/projectdiscovery/ratelimit v0.0.82-0.20250819165347-d56e782c656d/go.mod h1:z076BrLkBb5yS7uhHNoCTf8X/BvFSGRxwQ8EzEL9afM=
github.com/projectdiscovery/rawhttp v0.1.90 h1:LOSZ6PUH08tnKmWsIwvwv1Z/4zkiYKYOSZ6n+8RFKtw= github.com/projectdiscovery/rawhttp v0.1.90 h1:LOSZ6PUH08tnKmWsIwvwv1Z/4zkiYKYOSZ6n+8RFKtw=
github.com/projectdiscovery/rawhttp v0.1.90/go.mod h1:VZYAM25UI/wVB3URZ95ZaftgOnsbphxyAw/XnQRRz4Y= github.com/projectdiscovery/rawhttp v0.1.90/go.mod h1:VZYAM25UI/wVB3URZ95ZaftgOnsbphxyAw/XnQRRz4Y=
github.com/projectdiscovery/rdap v0.9.0 h1:wPhHx5pQ2QI+WGhyNb2PjhTl0NtB39Nk7YFZ9cp8ZGA= github.com/projectdiscovery/rdap v0.9.0 h1:wPhHx5pQ2QI+WGhyNb2PjhTl0NtB39Nk7YFZ9cp8ZGA=

View File

@ -87,7 +87,7 @@ type Runner struct {
colorizer aurora.Aurora colorizer aurora.Aurora
issuesClient reporting.Client issuesClient reporting.Client
browser *engine.Browser browser *engine.Browser
rateLimiter *ratelimit.Limiter rateLimiter *ratelimit.AutoLimiter
hostErrors hosterrorscache.CacheInterface hostErrors hosterrorscache.CacheInterface
resumeCfg *types.ResumeCfg resumeCfg *types.ResumeCfg
pprofServer *pprofutil.PprofServer pprofServer *pprofutil.PprofServer
@ -384,11 +384,17 @@ func New(options *types.Options) (*Runner, error) {
if options.RateLimit > 0 && options.RateLimitDuration == 0 { if options.RateLimit > 0 && options.RateLimitDuration == 0 {
options.RateLimitDuration = time.Second options.RateLimitDuration = time.Second
} }
var ratelimiter *ratelimit.AutoLimiter
if options.RateLimit == 0 && options.RateLimitDuration == 0 { if options.RateLimit == 0 && options.RateLimitDuration == 0 {
runner.rateLimiter = ratelimit.NewUnlimited(context.Background()) ratelimiter = ratelimit.NewAutoLimiter(context.Background(), ratelimit.WithUnlimited())
} else { } else {
runner.rateLimiter = ratelimit.New(context.Background(), uint(options.RateLimit), options.RateLimitDuration) ratelimiter = ratelimit.NewAutoLimiter(
context.Background(),
ratelimit.WithMaxCount(uint(options.RateLimit)),
ratelimit.WithDuration(options.RateLimitDuration),
)
} }
runner.rateLimiter = ratelimiter
if tmpDir, err := os.MkdirTemp("", "nuclei-tmp-*"); err == nil { if tmpDir, err := os.MkdirTemp("", "nuclei-tmp-*"); err == nil {
runner.tmpDir = tmpDir runner.tmpDir = tmpDir

View File

@ -50,7 +50,7 @@ type NucleiExecutorOptions struct {
Progress progress.Progress Progress progress.Progress
Catalog catalog.Catalog Catalog catalog.Catalog
IssuesClient reporting.Client IssuesClient reporting.Client
RateLimiter *ratelimit.Limiter RateLimiter *ratelimit.AutoLimiter
Interactsh *interactsh.Client Interactsh *interactsh.Client
ProjectFile *projectfile.ProjectFile ProjectFile *projectfile.ProjectFile
Browser *browserEngine.Browser Browser *browserEngine.Browser

View File

@ -151,7 +151,7 @@ func (request *Request) execute(input *contextargs.Context, domain string, metad
} }
} }
request.options.RateLimitTake() request.options.RateLimitTake(input)
// Send the request to the target servers // Send the request to the target servers
response, err := dnsClient.Do(compiledRequest) response, err := dnsClient.Do(compiledRequest)

View File

@ -291,7 +291,7 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
return return
} }
// putting ratelimiter here prevents any unnecessary waiting if any // putting ratelimiter here prevents any unnecessary waiting if any
request.options.RateLimitTake() request.options.RateLimitTake(input)
// after ratelimit take, check if we need to stop // after ratelimit take, check if we need to stop
if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(updatedInput) || spmHandler.Cancelled() { if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(updatedInput) || spmHandler.Cancelled() {
@ -470,7 +470,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
executeFunc := func(data string, payloads, dynamicValue map[string]interface{}) (bool, error) { executeFunc := func(data string, payloads, dynamicValue map[string]interface{}) (bool, error) {
hasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators) hasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators)
request.options.RateLimitTake() request.options.RateLimitTake(input)
ctx := request.newContext(input) ctx := request.newContext(input)
ctxWithTimeout, cancel := context.WithTimeoutCause(ctx, request.options.Options.GetTimeouts().HttpTimeout, ErrHttpEngineRequestDeadline) ctxWithTimeout, cancel := context.WithTimeoutCause(ctx, request.options.Options.GetTimeouts().HttpTimeout, ErrHttpEngineRequestDeadline)

View File

@ -181,7 +181,7 @@ func (request *Request) executeGeneratedFuzzingRequest(gr fuzz.GeneratedRequest,
if request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(request.options.ProtocolType.String(), input) { if request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(request.options.ProtocolType.String(), input) {
return false return false
} }
request.options.RateLimitTake() request.options.RateLimitTake(input)
req := &generatedRequest{ req := &generatedRequest{
request: gr.Request, request: gr.Request,
dynamicValues: gr.DynamicValues, dynamicValues: gr.DynamicValues,

View File

@ -78,7 +78,7 @@ type ExecutorOptions struct {
// Progress is a progress client for scan reporting // Progress is a progress client for scan reporting
Progress progress.Progress Progress progress.Progress
// RateLimiter is a rate-limiter for limiting sent number of requests. // RateLimiter is a rate-limiter for limiting sent number of requests.
RateLimiter *ratelimit.Limiter RateLimiter *ratelimit.AutoLimiter
// Catalog is a template catalog implementation for nuclei // Catalog is a template catalog implementation for nuclei
Catalog catalog.Catalog Catalog catalog.Catalog
// ProjectFile is the project file for nuclei // ProjectFile is the project file for nuclei
@ -143,7 +143,7 @@ type ExecutorOptions struct {
// todo: centralizing components is not feasible with current clogged architecture // 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 // a possible approach could be an internal event bus with pub-subs? This would be less invasive than
// reworking dep injection from scratch // reworking dep injection from scratch
func (e *ExecutorOptions) RateLimitTake() { func (e *ExecutorOptions) RateLimitTake(ctx *contextargs.Context) {
// The code below can race and there isn't a great way to fix this without adding an idempotent // 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. // function to the rate limiter implementation. For now, stick with whatever rate is already set.
/* /*
@ -152,8 +152,11 @@ func (e *ExecutorOptions) RateLimitTake() {
e.RateLimiter.SetDuration(e.Options.RateLimitDuration) e.RateLimiter.SetDuration(e.Options.RateLimitDuration)
} }
*/ */
if hostErrorCache, ok := e.HostErrorsCache.(*hosterrorscache.Cache); ok {
key := hostErrorCache.GetKeyFromContext(ctx, nil)
if e.RateLimiter != nil { if e.RateLimiter != nil {
e.RateLimiter.Take() e.RateLimiter.Take(key)
}
} }
} }

View File

@ -88,6 +88,11 @@ type TemplateInfo struct {
// NewMockExecuterOptions creates a new mock executeroptions struct // NewMockExecuterOptions creates a new mock executeroptions struct
func NewMockExecuterOptions(options *types.Options, info *TemplateInfo) *protocols.ExecutorOptions { func NewMockExecuterOptions(options *types.Options, info *TemplateInfo) *protocols.ExecutorOptions {
progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0) progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0)
rateLimiter := ratelimit.NewAutoLimiter(
context.Background(),
ratelimit.WithMaxCount(uint(options.RateLimit)),
ratelimit.WithDuration(time.Second),
)
executerOpts := &protocols.ExecutorOptions{ executerOpts := &protocols.ExecutorOptions{
TemplateID: info.ID, TemplateID: info.ID,
TemplateInfo: info.Info, TemplateInfo: info.Info,
@ -99,7 +104,7 @@ func NewMockExecuterOptions(options *types.Options, info *TemplateInfo) *protoco
IssuesClient: nil, IssuesClient: nil,
Browser: nil, Browser: nil,
Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory), Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory),
RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second), RateLimiter: rateLimiter,
} }
executerOpts.CreateTemplateCtxStore() executerOpts.CreateTemplateCtxStore()
return executerOpts return executerOpts