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/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
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/sarif v0.0.1
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/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.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/go.mod h1:VZYAM25UI/wVB3URZ95ZaftgOnsbphxyAw/XnQRRz4Y=
github.com/projectdiscovery/rdap v0.9.0 h1:wPhHx5pQ2QI+WGhyNb2PjhTl0NtB39Nk7YFZ9cp8ZGA=

View File

@ -87,7 +87,7 @@ type Runner struct {
colorizer aurora.Aurora
issuesClient reporting.Client
browser *engine.Browser
rateLimiter *ratelimit.Limiter
rateLimiter *ratelimit.AutoLimiter
hostErrors hosterrorscache.CacheInterface
resumeCfg *types.ResumeCfg
pprofServer *pprofutil.PprofServer
@ -384,11 +384,17 @@ func New(options *types.Options) (*Runner, error) {
if options.RateLimit > 0 && options.RateLimitDuration == 0 {
options.RateLimitDuration = time.Second
}
var ratelimiter *ratelimit.AutoLimiter
if options.RateLimit == 0 && options.RateLimitDuration == 0 {
runner.rateLimiter = ratelimit.NewUnlimited(context.Background())
ratelimiter = ratelimit.NewAutoLimiter(context.Background(), ratelimit.WithUnlimited())
} 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 {
runner.tmpDir = tmpDir

View File

@ -50,7 +50,7 @@ type NucleiExecutorOptions struct {
Progress progress.Progress
Catalog catalog.Catalog
IssuesClient reporting.Client
RateLimiter *ratelimit.Limiter
RateLimiter *ratelimit.AutoLimiter
Interactsh *interactsh.Client
ProjectFile *projectfile.ProjectFile
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
response, err := dnsClient.Do(compiledRequest)

View File

@ -291,7 +291,7 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
return
}
// 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
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) {
hasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators)
request.options.RateLimitTake()
request.options.RateLimitTake(input)
ctx := request.newContext(input)
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) {
return false
}
request.options.RateLimitTake()
request.options.RateLimitTake(input)
req := &generatedRequest{
request: gr.Request,
dynamicValues: gr.DynamicValues,

View File

@ -78,7 +78,7 @@ type ExecutorOptions struct {
// 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
RateLimiter *ratelimit.AutoLimiter
// Catalog is a template catalog implementation for nuclei
Catalog catalog.Catalog
// ProjectFile is the project file for nuclei
@ -143,7 +143,7 @@ type ExecutorOptions struct {
// 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
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
// 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)
}
*/
if e.RateLimiter != nil {
e.RateLimiter.Take()
if hostErrorCache, ok := e.HostErrorsCache.(*hosterrorscache.Cache); ok {
key := hostErrorCache.GetKeyFromContext(ctx, nil)
if e.RateLimiter != nil {
e.RateLimiter.Take(key)
}
}
}

View File

@ -88,6 +88,11 @@ type TemplateInfo struct {
// NewMockExecuterOptions creates a new mock executeroptions struct
func NewMockExecuterOptions(options *types.Options, info *TemplateInfo) *protocols.ExecutorOptions {
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{
TemplateID: info.ID,
TemplateInfo: info.Info,
@ -99,7 +104,7 @@ func NewMockExecuterOptions(options *types.Options, info *TemplateInfo) *protoco
IssuesClient: nil,
Browser: nil,
Catalog: disk.NewCatalog(config.DefaultConfig.TemplatesDirectory),
RateLimiter: ratelimit.New(context.Background(), uint(options.RateLimit), time.Second),
RateLimiter: rateLimiter,
}
executerOpts.CreateTemplateCtxStore()
return executerOpts