From a8d1393e961d5db72c45efa840a841854e96a2c1 Mon Sep 17 00:00:00 2001
From: Mzack9999
Date: Wed, 3 Apr 2024 17:50:57 +0200
Subject: [PATCH 01/58] init- using resizable components
---
examples/advanced/advanced.go | 7 +++-
go.mod | 2 +
go.sum | 3 ++
internal/runner/inputs.go | 13 +++---
pkg/core/execute_options.go | 7 ++--
pkg/core/executors.go | 14 +++----
pkg/core/workflow_execute.go | 9 ++--
pkg/core/workpool.go | 26 +++++-------
pkg/js/compiler/non-pool.go | 6 +--
pkg/js/compiler/pool.go | 6 +--
.../common/automaticscan/automaticscan.go | 9 ++--
pkg/protocols/dns/request.go | 7 +++-
pkg/protocols/file/request.go | 9 ++--
pkg/protocols/headless/engine/page_actions.go | 28 ++++++-------
pkg/protocols/http/httputils/spm.go | 9 ++--
pkg/protocols/javascript/js.go | 5 ++-
.../network/networkclientpool/clientpool.go | 2 +-
pkg/protocols/network/request.go | 7 +++-
pkg/protocols/offlinehttp/request.go | 11 +++--
pkg/tmplexec/flow/vm.go | 41 +++++++++----------
20 files changed, 121 insertions(+), 100 deletions(-)
diff --git a/examples/advanced/advanced.go b/examples/advanced/advanced.go
index 5ce579b3d6..110160f9a1 100644
--- a/examples/advanced/advanced.go
+++ b/examples/advanced/advanced.go
@@ -2,7 +2,7 @@ package main
import (
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
- "github.com/remeh/sizedwaitgroup"
+ syncutil "github.com/projectdiscovery/utils/sync"
)
func main() {
@@ -12,7 +12,10 @@ func main() {
panic(err)
}
// setup sizedWaitgroup to handle concurrency
- sg := sizedwaitgroup.New(10)
+ sg, err := syncutil.New(syncutil.WithSize(10))
+ if err != nil {
+ panic(err)
+ }
// scan 1 = run dns templates on scanme.sh
sg.Add()
diff --git a/go.mod b/go.mod
index 7eeaf57386..6a0e8bfccf 100644
--- a/go.mod
+++ b/go.mod
@@ -142,6 +142,8 @@ require (
github.com/docker/cli v24.0.5+incompatible // indirect
github.com/docker/docker v24.0.9+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
+ github.com/eapache/channels v1.1.0 // indirect
+ github.com/eapache/queue v1.1.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
diff --git a/go.sum b/go.sum
index cefd9ae445..83551eb1c2 100644
--- a/go.sum
+++ b/go.sum
@@ -296,8 +296,11 @@ github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj6
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k=
+github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
diff --git a/internal/runner/inputs.go b/internal/runner/inputs.go
index 8dc27a7a9e..75a869912b 100644
--- a/internal/runner/inputs.go
+++ b/internal/runner/inputs.go
@@ -12,7 +12,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
stringsutil "github.com/projectdiscovery/utils/strings"
- "github.com/remeh/sizedwaitgroup"
+ syncutil "github.com/projectdiscovery/utils/sync"
)
const probeBulkSize = 50
@@ -45,8 +45,11 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
}
// Probe the non-standard URLs and store them in cache
- swg := sizedwaitgroup.New(bulkSize)
- count := int32(0)
+ swg, err := syncutil.New(syncutil.WithSize(bulkSize))
+ if err != nil {
+ return nil, errors.Wrap(err, "could not create adaptive group")
+ }
+ var count atomic.Int32
r.inputProvider.Iterate(func(value *contextargs.MetaInput) bool {
if stringsutil.HasPrefixAny(value.Input, "http://", "https://") {
return true
@@ -57,7 +60,7 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
defer swg.Done()
if result := utils.ProbeURL(input.Input, httpxClient); result != "" {
- atomic.AddInt32(&count, 1)
+ count.Add(1)
_ = hm.Set(input.Input, []byte(result))
}
}(value)
@@ -65,6 +68,6 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
})
swg.Wait()
- gologger.Info().Msgf("Found %d URL from httpx", atomic.LoadInt32(&count))
+ gologger.Info().Msgf("Found %d URL from httpx", count.Load())
return hm, nil
}
diff --git a/pkg/core/execute_options.go b/pkg/core/execute_options.go
index fd1fadae3d..580b8b0a9d 100644
--- a/pkg/core/execute_options.go
+++ b/pkg/core/execute_options.go
@@ -4,8 +4,6 @@ import (
"sync"
"sync/atomic"
- "github.com/remeh/sizedwaitgroup"
-
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
@@ -14,6 +12,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
"github.com/projectdiscovery/nuclei/v3/pkg/types/scanstrategy"
stringsutil "github.com/projectdiscovery/utils/strings"
+ syncutil "github.com/projectdiscovery/utils/sync"
)
// Execute takes a list of templates/workflows that have been compiled
@@ -111,7 +110,7 @@ func (e *Engine) executeTemplateSpray(templatesList []*templates.Template, targe
for _, template := range templatesList {
templateType := template.Type()
- var wg *sizedwaitgroup.SizedWaitGroup
+ var wg *syncutil.AdaptiveWaitGroup
if templateType == types.HeadlessProtocol {
wg = wp.Headless
} else {
@@ -134,7 +133,7 @@ func (e *Engine) executeTemplateSpray(templatesList []*templates.Template, targe
// executeHostSpray executes scan using host spray strategy where templates are iterated over each target
func (e *Engine) executeHostSpray(templatesList []*templates.Template, target provider.InputProvider) *atomic.Bool {
results := &atomic.Bool{}
- wp := sizedwaitgroup.New(e.options.BulkSize + e.options.HeadlessBulkSize)
+ wp, _ := syncutil.New(syncutil.WithSize(e.options.BulkSize + e.options.HeadlessBulkSize))
target.Iterate(func(value *contextargs.MetaInput) bool {
wp.Add()
diff --git a/pkg/core/executors.go b/pkg/core/executors.go
index b491bd8e0b..ace7acb208 100644
--- a/pkg/core/executors.go
+++ b/pkg/core/executors.go
@@ -11,7 +11,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
generalTypes "github.com/projectdiscovery/nuclei/v3/pkg/types"
- "github.com/remeh/sizedwaitgroup"
+ syncutil "github.com/projectdiscovery/utils/sync"
)
// Executors are low level executors that deals with template execution on a target
@@ -104,9 +104,9 @@ func (e *Engine) executeTemplateWithTargets(template *templates.Template, target
return true
}
- wg.WaitGroup.Add()
+ wg.Add()
go func(index uint32, skip bool, value *contextargs.MetaInput) {
- defer wg.WaitGroup.Done()
+ defer wg.Done()
defer cleanupInFlight(index)
if skip {
return
@@ -140,7 +140,7 @@ func (e *Engine) executeTemplateWithTargets(template *templates.Template, target
index++
return true
})
- wg.WaitGroup.Wait()
+ wg.Wait()
// on completion marks the template as completed
currentInfo.Lock()
@@ -158,14 +158,14 @@ func (e *Engine) executeTemplatesOnTarget(alltemplates []*templates.Template, ta
wp := e.GetWorkPool()
for _, tpl := range alltemplates {
- var sg *sizedwaitgroup.SizedWaitGroup
+ var sg *syncutil.AdaptiveWaitGroup
if tpl.Type() == types.HeadlessProtocol {
sg = wp.Headless
} else {
sg = wp.Default
}
sg.Add()
- go func(template *templates.Template, value *contextargs.MetaInput, wg *sizedwaitgroup.SizedWaitGroup) {
+ go func(template *templates.Template, value *contextargs.MetaInput, wg *syncutil.AdaptiveWaitGroup) {
defer wg.Done()
var match bool
@@ -213,7 +213,7 @@ func (e *ChildExecuter) Close() *atomic.Bool {
func (e *ChildExecuter) Execute(template *templates.Template, value *contextargs.MetaInput) {
templateType := template.Type()
- var wg *sizedwaitgroup.SizedWaitGroup
+ var wg *syncutil.AdaptiveWaitGroup
if templateType == types.HeadlessProtocol {
wg = e.e.workPool.Headless
} else {
diff --git a/pkg/core/workflow_execute.go b/pkg/core/workflow_execute.go
index cb877cc60e..19d6f0d69d 100644
--- a/pkg/core/workflow_execute.go
+++ b/pkg/core/workflow_execute.go
@@ -5,13 +5,12 @@ import (
"net/http/cookiejar"
"sync/atomic"
- "github.com/remeh/sizedwaitgroup"
-
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
"github.com/projectdiscovery/nuclei/v3/pkg/workflows"
+ syncutil "github.com/projectdiscovery/utils/sync"
)
const workflowStepExecutionError = "[%s] Could not execute workflow step: %s\n"
@@ -32,7 +31,7 @@ func (e *Engine) executeWorkflow(ctx *scan.ScanContext, w *workflows.Workflow) b
if templateThreads == 1 {
templateThreads++
}
- swg := sizedwaitgroup.New(templateThreads)
+ swg, _ := syncutil.New(syncutil.WithSize(templateThreads))
for _, template := range w.Workflows {
swg.Add()
@@ -40,7 +39,7 @@ func (e *Engine) executeWorkflow(ctx *scan.ScanContext, w *workflows.Workflow) b
func(template *workflows.WorkflowTemplate) {
defer swg.Done()
- if err := e.runWorkflowStep(template, ctx, results, &swg, w); err != nil {
+ if err := e.runWorkflowStep(template, ctx, results, swg, w); err != nil {
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
}
}(template)
@@ -51,7 +50,7 @@ func (e *Engine) executeWorkflow(ctx *scan.ScanContext, w *workflows.Workflow) b
// runWorkflowStep runs a workflow step for the workflow. It executes the workflow
// in a recursive manner running all subtemplates and matchers.
-func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan.ScanContext, results *atomic.Bool, swg *sizedwaitgroup.SizedWaitGroup, w *workflows.Workflow) error {
+func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan.ScanContext, results *atomic.Bool, swg *syncutil.AdaptiveWaitGroup, w *workflows.Workflow) error {
var firstMatched bool
var err error
var mainErr error
diff --git a/pkg/core/workpool.go b/pkg/core/workpool.go
index 0711759582..cd17a0b367 100644
--- a/pkg/core/workpool.go
+++ b/pkg/core/workpool.go
@@ -1,9 +1,8 @@
package core
import (
- "github.com/remeh/sizedwaitgroup"
-
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
+ syncutil "github.com/projectdiscovery/utils/sync"
)
// WorkPool implements an execution pool for executing different
@@ -12,8 +11,8 @@ import (
// It also allows Configuration of such requirements. This is used
// for per-module like separate headless concurrency etc.
type WorkPool struct {
- Headless *sizedwaitgroup.SizedWaitGroup
- Default *sizedwaitgroup.SizedWaitGroup
+ Headless *syncutil.AdaptiveWaitGroup
+ Default *syncutil.AdaptiveWaitGroup
config WorkPoolConfig
}
@@ -31,13 +30,13 @@ type WorkPoolConfig struct {
// NewWorkPool returns a new WorkPool instance
func NewWorkPool(config WorkPoolConfig) *WorkPool {
- headlessWg := sizedwaitgroup.New(config.HeadlessTypeConcurrency)
- defaultWg := sizedwaitgroup.New(config.TypeConcurrency)
+ headlessWg, _ := syncutil.New(syncutil.WithSize(config.HeadlessTypeConcurrency))
+ defaultWg, _ := syncutil.New(syncutil.WithSize(config.TypeConcurrency))
return &WorkPool{
config: config,
- Headless: &headlessWg,
- Default: &defaultWg,
+ Headless: headlessWg,
+ Default: defaultWg,
}
}
@@ -47,19 +46,14 @@ func (w *WorkPool) Wait() {
w.Headless.Wait()
}
-// InputWorkPool is a work pool per-input
-type InputWorkPool struct {
- WaitGroup *sizedwaitgroup.SizedWaitGroup
-}
-
// InputPool returns a work pool for an input type
-func (w *WorkPool) InputPool(templateType types.ProtocolType) *InputWorkPool {
+func (w *WorkPool) InputPool(templateType types.ProtocolType) *syncutil.AdaptiveWaitGroup {
var count int
if templateType == types.HeadlessProtocol {
count = w.config.HeadlessInputConcurrency
} else {
count = w.config.InputConcurrency
}
- swg := sizedwaitgroup.New(count)
- return &InputWorkPool{WaitGroup: &swg}
+ swg, _ := syncutil.New(syncutil.WithSize(count))
+ return swg
}
diff --git a/pkg/js/compiler/non-pool.go b/pkg/js/compiler/non-pool.go
index 8057c49608..218b89b82a 100644
--- a/pkg/js/compiler/non-pool.go
+++ b/pkg/js/compiler/non-pool.go
@@ -4,13 +4,13 @@ import (
"sync"
"github.com/dop251/goja"
- "github.com/remeh/sizedwaitgroup"
+ syncutil "github.com/projectdiscovery/utils/sync"
)
var (
- ephemeraljsc = sizedwaitgroup.New(NonPoolingVMConcurrency)
+ ephemeraljsc, _ = syncutil.New(syncutil.WithSize(NonPoolingVMConcurrency))
lazyFixedSgInit = sync.OnceFunc(func() {
- ephemeraljsc = sizedwaitgroup.New(NonPoolingVMConcurrency)
+ ephemeraljsc, _ = syncutil.New(syncutil.WithSize(NonPoolingVMConcurrency))
})
)
diff --git a/pkg/js/compiler/pool.go b/pkg/js/compiler/pool.go
index 6dba600f70..fc0b61639b 100644
--- a/pkg/js/compiler/pool.go
+++ b/pkg/js/compiler/pool.go
@@ -36,7 +36,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/goconsole"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
stringsutil "github.com/projectdiscovery/utils/strings"
- "github.com/remeh/sizedwaitgroup"
+ syncutil "github.com/projectdiscovery/utils/sync"
)
const (
@@ -51,9 +51,9 @@ var (
// autoregister console node module with default printer it uses gologger backend
require.RegisterNativeModule(console.ModuleName, console.RequireWithPrinter(goconsole.NewGoConsolePrinter()))
})
- pooljsc sizedwaitgroup.SizedWaitGroup
+ pooljsc *syncutil.AdaptiveWaitGroup
lazySgInit = sync.OnceFunc(func() {
- pooljsc = sizedwaitgroup.New(PoolingJsVmConcurrency)
+ pooljsc, _ = syncutil.New(syncutil.WithSize(PoolingJsVmConcurrency))
})
)
diff --git a/pkg/protocols/common/automaticscan/automaticscan.go b/pkg/protocols/common/automaticscan/automaticscan.go
index 8193947190..7119377b01 100644
--- a/pkg/protocols/common/automaticscan/automaticscan.go
+++ b/pkg/protocols/common/automaticscan/automaticscan.go
@@ -30,8 +30,8 @@ import (
mapsutil "github.com/projectdiscovery/utils/maps"
sliceutil "github.com/projectdiscovery/utils/slice"
stringsutil "github.com/projectdiscovery/utils/strings"
+ syncutil "github.com/projectdiscovery/utils/sync"
wappalyzer "github.com/projectdiscovery/wappalyzergo"
- "github.com/remeh/sizedwaitgroup"
"gopkg.in/yaml.v2"
)
@@ -128,7 +128,10 @@ func (s *Service) Close() bool {
func (s *Service) Execute() error {
gologger.Info().Msgf("Executing Automatic scan on %d target[s]", s.target.Count())
// setup host concurrency
- sg := sizedwaitgroup.New(s.opts.Options.BulkSize)
+ sg, err := syncutil.New(syncutil.WithSize(s.opts.Options.BulkSize))
+ if err != nil {
+ return err
+ }
s.target.Iterate(func(value *contextargs.MetaInput) bool {
sg.Add()
go func(input *contextargs.MetaInput) {
@@ -246,7 +249,7 @@ func (s *Service) getTagsUsingDetectionTemplates(input *contextargs.MetaInput) (
// execute tech detection templates on target
tags := map[string]struct{}{}
m := &sync.Mutex{}
- sg := sizedwaitgroup.New(s.opts.Options.TemplateThreads)
+ sg, _ := syncutil.New(syncutil.WithSize(s.opts.Options.TemplateThreads))
counter := atomic.Uint32{}
for _, t := range s.techTemplates {
diff --git a/pkg/protocols/dns/request.go b/pkg/protocols/dns/request.go
index 280e8161fe..ba9dd66621 100644
--- a/pkg/protocols/dns/request.go
+++ b/pkg/protocols/dns/request.go
@@ -9,7 +9,6 @@ import (
"github.com/miekg/dns"
"github.com/pkg/errors"
- "github.com/remeh/sizedwaitgroup"
"go.uber.org/multierr"
"golang.org/x/exp/maps"
@@ -27,6 +26,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/retryabledns"
iputil "github.com/projectdiscovery/utils/ip"
+ syncutil "github.com/projectdiscovery/utils/sync"
)
var _ protocols.Request = &Request{}
@@ -64,7 +64,10 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
if request.generator != nil {
iterator := request.generator.NewIterator()
- swg := sizedwaitgroup.New(request.Threads)
+ swg, err := syncutil.New(syncutil.WithSize(request.Threads))
+ if err != nil {
+ return err
+ }
var multiErr error
m := &sync.Mutex{}
diff --git a/pkg/protocols/file/request.go b/pkg/protocols/file/request.go
index eb73544f9a..f13f08d1ab 100644
--- a/pkg/protocols/file/request.go
+++ b/pkg/protocols/file/request.go
@@ -11,7 +11,6 @@ import (
"github.com/docker/go-units"
"github.com/mholt/archiver"
"github.com/pkg/errors"
- "github.com/remeh/sizedwaitgroup"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
@@ -24,6 +23,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter"
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
sliceutil "github.com/projectdiscovery/utils/slice"
+ syncutil "github.com/projectdiscovery/utils/sync"
)
var _ protocols.Request = &Request{}
@@ -47,8 +47,11 @@ var errEmptyResult = errors.New("Empty result")
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
- wg := sizedwaitgroup.New(request.options.Options.BulkSize)
- err := request.getInputPaths(input.MetaInput.Input, func(filePath string) {
+ wg, err := syncutil.New(syncutil.WithSize(request.options.Options.BulkSize))
+ if err != nil {
+ return err
+ }
+ err = request.getInputPaths(input.MetaInput.Input, func(filePath string) {
wg.Add()
func(filePath string) {
defer wg.Done()
diff --git a/pkg/protocols/headless/engine/page_actions.go b/pkg/protocols/headless/engine/page_actions.go
index 7338db44ab..348cab0aec 100644
--- a/pkg/protocols/headless/engine/page_actions.go
+++ b/pkg/protocols/headless/engine/page_actions.go
@@ -214,7 +214,7 @@ func geTimeParameter(p *Page, act *Action, parameterName string, defaultValue ti
}
// ActionAddHeader executes a AddHeader action.
-func (p *Page) ActionAddHeader(act *Action, out map[string]string /*TODO review unused parameter*/) error {
+func (p *Page) ActionAddHeader(act *Action, out map[string]string) error {
in := p.getActionArgWithDefaultValues(act, "part")
args := make(map[string]string)
@@ -225,7 +225,7 @@ func (p *Page) ActionAddHeader(act *Action, out map[string]string /*TODO review
}
// ActionSetHeader executes a SetHeader action.
-func (p *Page) ActionSetHeader(act *Action, out map[string]string /*TODO review unused parameter*/) error {
+func (p *Page) ActionSetHeader(act *Action, out map[string]string) error {
in := p.getActionArgWithDefaultValues(act, "part")
args := make(map[string]string)
@@ -236,7 +236,7 @@ func (p *Page) ActionSetHeader(act *Action, out map[string]string /*TODO review
}
// ActionDeleteHeader executes a DeleteHeader action.
-func (p *Page) ActionDeleteHeader(act *Action, out map[string]string /*TODO review unused parameter*/) error {
+func (p *Page) ActionDeleteHeader(act *Action, out map[string]string) error {
in := p.getActionArgWithDefaultValues(act, "part")
args := make(map[string]string)
@@ -343,7 +343,7 @@ func (p *Page) RunScript(action *Action, out map[string]string) error {
}
// ClickElement executes click actions for an element.
-func (p *Page) ClickElement(act *Action, out map[string]string /*TODO review unused parameter*/) error {
+func (p *Page) ClickElement(act *Action, out map[string]string) error {
element, err := p.pageElementBy(act.Data)
if err != nil {
return errors.Wrap(err, errCouldNotGetElement)
@@ -358,12 +358,12 @@ func (p *Page) ClickElement(act *Action, out map[string]string /*TODO review unu
}
// KeyboardAction executes a keyboard action on the page.
-func (p *Page) KeyboardAction(act *Action, out map[string]string /*TODO review unused parameter*/) error {
+func (p *Page) KeyboardAction(act *Action, out map[string]string) error {
return p.page.Keyboard.Type([]input.Key(p.getActionArgWithDefaultValues(act, "keys"))...)
}
// RightClickElement executes right click actions for an element.
-func (p *Page) RightClickElement(act *Action, out map[string]string /*TODO review unused parameter*/) error {
+func (p *Page) RightClickElement(act *Action, out map[string]string) error {
element, err := p.pageElementBy(act.Data)
if err != nil {
return errors.Wrap(err, errCouldNotGetElement)
@@ -441,7 +441,7 @@ func (p *Page) Screenshot(act *Action, out map[string]string) error {
}
// InputElement executes input element actions for an element.
-func (p *Page) InputElement(act *Action, out map[string]string /*TODO review unused parameter*/) error {
+func (p *Page) InputElement(act *Action, out map[string]string) error {
value := p.getActionArgWithDefaultValues(act, "value")
if value == "" {
return errinvalidArguments
@@ -460,7 +460,7 @@ func (p *Page) InputElement(act *Action, out map[string]string /*TODO review unu
}
// TimeInputElement executes time input on an element
-func (p *Page) TimeInputElement(act *Action, out map[string]string /*TODO review unused parameter*/) error {
+func (p *Page) TimeInputElement(act *Action, out map[string]string) error {
value := p.getActionArgWithDefaultValues(act, "value")
if value == "" {
return errinvalidArguments
@@ -483,7 +483,7 @@ func (p *Page) TimeInputElement(act *Action, out map[string]string /*TODO review
}
// SelectInputElement executes select input statement action on a element
-func (p *Page) SelectInputElement(act *Action, out map[string]string /*TODO review unused parameter*/) error {
+func (p *Page) SelectInputElement(act *Action, out map[string]string) error {
value := p.getActionArgWithDefaultValues(act, "value")
if value == "" {
return errinvalidArguments
@@ -508,7 +508,7 @@ func (p *Page) SelectInputElement(act *Action, out map[string]string /*TODO revi
}
// WaitLoad waits for the page to load
-func (p *Page) WaitLoad(act *Action, out map[string]string /*TODO review unused parameter*/) error {
+func (p *Page) WaitLoad(act *Action, out map[string]string) error {
p.page.Timeout(2 * time.Second).WaitNavigation(proto.PageLifecycleEventNameFirstMeaningfulPaint)()
// Wait for the window.onload event and also wait for the network requests
@@ -538,7 +538,7 @@ func (p *Page) GetResource(act *Action, out map[string]string) error {
}
// FilesInput acts with a file input element on page
-func (p *Page) FilesInput(act *Action, out map[string]string /*TODO review unused parameter*/) error {
+func (p *Page) FilesInput(act *Action, out map[string]string) error {
element, err := p.pageElementBy(act.Data)
if err != nil {
return errors.Wrap(err, errCouldNotGetElement)
@@ -589,7 +589,7 @@ func (p *Page) ExtractElement(act *Action, out map[string]string) error {
}
// WaitEvent waits for an event to happen on the page.
-func (p *Page) WaitEvent(act *Action, out map[string]string /*TODO review unused parameter*/) (func() error, error) {
+func (p *Page) WaitEvent(act *Action, out map[string]string) (func() error, error) {
event := p.getActionArgWithDefaultValues(act, "event")
if event == "" {
return nil, errors.New("event not recognized")
@@ -661,14 +661,14 @@ func (p *Page) pageElementBy(data map[string]string) (*rod.Element, error) {
}
// DebugAction enables debug action on a page.
-func (p *Page) DebugAction(act *Action, out map[string]string /*TODO review unused parameter*/) error {
+func (p *Page) DebugAction(act *Action, out map[string]string) error {
p.instance.browser.engine.SlowMotion(5 * time.Second)
p.instance.browser.engine.Trace(true)
return nil
}
// SleepAction sleeps on the page for a specified duration
-func (p *Page) SleepAction(act *Action, out map[string]string /*TODO review unused parameter*/) error {
+func (p *Page) SleepAction(act *Action, out map[string]string) error {
seconds := act.Data["duration"]
if seconds == "" {
seconds = "5"
diff --git a/pkg/protocols/http/httputils/spm.go b/pkg/protocols/http/httputils/spm.go
index ccaa9a85c9..bca6c2ee59 100644
--- a/pkg/protocols/http/httputils/spm.go
+++ b/pkg/protocols/http/httputils/spm.go
@@ -4,7 +4,7 @@ import (
"context"
"sync"
- "github.com/remeh/sizedwaitgroup"
+ syncutil "github.com/projectdiscovery/utils/sync"
)
// WorkPoolType is the type of work pool to use
@@ -26,7 +26,7 @@ type StopAtFirstMatchHandler[T any] struct {
// work pool and its type
poolType WorkPoolType
- sgPool sizedwaitgroup.SizedWaitGroup
+ sgPool *syncutil.AdaptiveWaitGroup
wgPool *sync.WaitGroup
// internal / unexported
@@ -40,10 +40,13 @@ type StopAtFirstMatchHandler[T any] struct {
// NewBlockingSPMHandler creates a new stop at first match handler
func NewBlockingSPMHandler[T any](ctx context.Context, size int, spm bool) *StopAtFirstMatchHandler[T] {
ctx1, cancel := context.WithCancel(ctx)
+
+ awg, _ := syncutil.New(syncutil.WithSize(size))
+
s := &StopAtFirstMatchHandler[T]{
ResultChan: make(chan T, 1),
poolType: Blocking,
- sgPool: sizedwaitgroup.New(size),
+ sgPool: awg,
internalWg: &sync.WaitGroup{},
ctx: ctx1,
cancel: cancel,
diff --git a/pkg/protocols/javascript/js.go b/pkg/protocols/javascript/js.go
index fc32486937..48a1be485d 100644
--- a/pkg/protocols/javascript/js.go
+++ b/pkg/protocols/javascript/js.go
@@ -32,8 +32,8 @@ import (
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
errorutil "github.com/projectdiscovery/utils/errors"
+ syncutil "github.com/projectdiscovery/utils/sync"
urlutil "github.com/projectdiscovery/utils/url"
- "github.com/remeh/sizedwaitgroup"
)
// Request is a request for the javascript protocol
@@ -404,7 +404,8 @@ func (request *Request) executeRequestParallel(ctxParent context.Context, hostPo
requestOptions := request.options
gotmatches := &atomic.Bool{}
- sg := sizedwaitgroup.New(threads)
+ sg, _ := syncutil.New(syncutil.WithSize(threads))
+
if request.generator != nil {
iterator := request.generator.NewIterator()
for {
diff --git a/pkg/protocols/network/networkclientpool/clientpool.go b/pkg/protocols/network/networkclientpool/clientpool.go
index 1a933413e2..a67cee2967 100644
--- a/pkg/protocols/network/networkclientpool/clientpool.go
+++ b/pkg/protocols/network/networkclientpool/clientpool.go
@@ -11,7 +11,7 @@ var (
)
// Init initializes the clientpool implementation
-func Init(options *types.Options /*TODO review unused parameter*/) error {
+func Init(options *types.Options) error {
// Don't create clients if already created in the past.
if normalClient != nil {
return nil
diff --git a/pkg/protocols/network/request.go b/pkg/protocols/network/request.go
index 01991c0459..ef0ff01c7f 100644
--- a/pkg/protocols/network/request.go
+++ b/pkg/protocols/network/request.go
@@ -12,7 +12,6 @@ import (
"time"
"github.com/pkg/errors"
- "github.com/remeh/sizedwaitgroup"
"go.uber.org/multierr"
"golang.org/x/exp/maps"
@@ -34,6 +33,7 @@ import (
errorutil "github.com/projectdiscovery/utils/errors"
mapsutil "github.com/projectdiscovery/utils/maps"
"github.com/projectdiscovery/utils/reader"
+ syncutil "github.com/projectdiscovery/utils/sync"
)
var (
@@ -178,7 +178,10 @@ func (request *Request) executeAddress(variables map[string]interface{}, actualA
iterator := request.generator.NewIterator()
var multiErr error
m := &sync.Mutex{}
- swg := sizedwaitgroup.New(request.Threads)
+ swg, err := syncutil.New(syncutil.WithSize(request.Threads))
+ if err != nil {
+ return err
+ }
for {
value, ok := iterator.Value()
diff --git a/pkg/protocols/offlinehttp/request.go b/pkg/protocols/offlinehttp/request.go
index 7c64859e96..4a440c167f 100644
--- a/pkg/protocols/offlinehttp/request.go
+++ b/pkg/protocols/offlinehttp/request.go
@@ -6,7 +6,6 @@ import (
"os"
"github.com/pkg/errors"
- "github.com/remeh/sizedwaitgroup"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
@@ -17,6 +16,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
"github.com/projectdiscovery/utils/conversion"
+ syncutil "github.com/projectdiscovery/utils/sync"
)
var _ protocols.Request = &Request{}
@@ -29,10 +29,13 @@ func (request *Request) Type() templateTypes.ProtocolType {
}
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
-func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata /*TODO review unused parameter*/, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
- wg := sizedwaitgroup.New(request.options.Options.BulkSize)
+func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
+ wg, err := syncutil.New(syncutil.WithSize(request.options.Options.BulkSize))
+ if err != nil {
+ return err
+ }
- err := request.getInputPaths(input.MetaInput.Input, func(data string) {
+ err = request.getInputPaths(input.MetaInput.Input, func(data string) {
wg.Add()
go func(data string) {
diff --git a/pkg/tmplexec/flow/vm.go b/pkg/tmplexec/flow/vm.go
index 2e22bd8e12..f1f7dbb84e 100644
--- a/pkg/tmplexec/flow/vm.go
+++ b/pkg/tmplexec/flow/vm.go
@@ -1,6 +1,7 @@
package flow
import (
+ "context"
"reflect"
"sync"
@@ -12,45 +13,43 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/flow/builtin"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
- "github.com/remeh/sizedwaitgroup"
+ "github.com/projectdiscovery/utils/sync/sizedpool"
)
-type jsWaitGroup struct {
- sync.Once
- sg sizedwaitgroup.SizedWaitGroup
+var jsOnce sync.Once
+
+// js runtime pool using sync.Pool
+var gojapool = &sync.Pool{
+ New: func() interface{} {
+ runtime := protocolstate.NewJSRuntime()
+ registerBuiltins(runtime)
+ return runtime
+ },
}
-var jsPool = &jsWaitGroup{}
+var sizedgojapool *sizedpool.SizedPool[*goja.Runtime]
// GetJSRuntime returns a new JS runtime from pool
func GetJSRuntime(opts *types.Options) *goja.Runtime {
- jsPool.Do(func() {
+ jsOnce.Do(func() {
if opts.JsConcurrency < 100 {
opts.JsConcurrency = 100
}
- jsPool.sg = sizedwaitgroup.New(opts.JsConcurrency)
+ sizedgojapool, _ = sizedpool.New[*goja.Runtime](
+ sizedpool.WithPool[*goja.Runtime](gojapool),
+ sizedpool.WithSize[*goja.Runtime](int64(opts.JsConcurrency)),
+ )
})
- jsPool.sg.Add()
- return gojapool.Get().(*goja.Runtime)
+ runtime, _ := sizedgojapool.Get(context.TODO())
+ return runtime
}
// PutJSRuntime returns a JS runtime to pool
func PutJSRuntime(runtime *goja.Runtime) {
- defer jsPool.sg.Done()
- gojapool.Put(runtime)
-}
-
-// js runtime pool using sync.Pool
-var gojapool = &sync.Pool{
- New: func() interface{} {
- runtime := protocolstate.NewJSRuntime()
- registerBuiltins(runtime)
- return runtime
- },
+ sizedgojapool.Put(runtime)
}
func registerBuiltins(runtime *goja.Runtime) {
-
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "log",
Description: "Logs a given object/message to stdout (only for debugging purposes)",
From 774db61655894d6b5e4f76e98ff09997ceece263 Mon Sep 17 00:00:00 2001
From: Mzack9999
Date: Wed, 3 Apr 2024 18:50:46 +0200
Subject: [PATCH 02/58] lightweight adaptivity on workpool
---
internal/runner/inputs.go | 14 ++++++--------
pkg/core/engine.go | 13 +++++++++----
pkg/core/execute_options.go | 4 +++-
pkg/core/executors.go | 6 ++++++
pkg/core/workpool.go | 25 +++++++++++++++++++++++++
pkg/js/compiler/pool.go | 5 +++++
6 files changed, 54 insertions(+), 13 deletions(-)
diff --git a/internal/runner/inputs.go b/internal/runner/inputs.go
index 75a869912b..60aa03199f 100644
--- a/internal/runner/inputs.go
+++ b/internal/runner/inputs.go
@@ -15,12 +15,11 @@ import (
syncutil "github.com/projectdiscovery/utils/sync"
)
-const probeBulkSize = 50
+var GlobalProbeBulkSize = 50
// initializeTemplatesHTTPInput initializes the http form of input
// for any loaded http templates if input is in non-standard format.
func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
-
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
if err != nil {
return nil, errors.Wrap(err, "could not create temporary input file")
@@ -31,11 +30,6 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
}
gologger.Info().Msgf("Running httpx on input host")
- var bulkSize = probeBulkSize
- if r.options.BulkSize > probeBulkSize {
- bulkSize = r.options.BulkSize
- }
-
httpxOptions := httpx.DefaultOptions
httpxOptions.RetryMax = r.options.Retries
httpxOptions.Timeout = time.Duration(r.options.Timeout) * time.Second
@@ -45,7 +39,7 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
}
// Probe the non-standard URLs and store them in cache
- swg, err := syncutil.New(syncutil.WithSize(bulkSize))
+ swg, err := syncutil.New(syncutil.WithSize(GlobalProbeBulkSize))
if err != nil {
return nil, errors.Wrap(err, "could not create adaptive group")
}
@@ -55,6 +49,10 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
return true
}
+ if swg.Size != GlobalProbeBulkSize {
+ swg.Resize(GlobalProbeBulkSize)
+ }
+
swg.Add()
go func(input *contextargs.MetaInput) {
defer swg.Done()
diff --git a/pkg/core/engine.go b/pkg/core/engine.go
index 93915bc2c6..4dfb8e0b93 100644
--- a/pkg/core/engine.go
+++ b/pkg/core/engine.go
@@ -30,14 +30,19 @@ func New(options *types.Options) *Engine {
return engine
}
-// GetWorkPool returns a workpool from options
-func (e *Engine) GetWorkPool() *WorkPool {
- return NewWorkPool(WorkPoolConfig{
+func (e *Engine) GetWorkPoolConfig() WorkPoolConfig {
+ config := WorkPoolConfig{
InputConcurrency: e.options.BulkSize,
TypeConcurrency: e.options.TemplateThreads,
HeadlessInputConcurrency: e.options.HeadlessBulkSize,
HeadlessTypeConcurrency: e.options.HeadlessTemplateThreads,
- })
+ }
+ return config
+}
+
+// GetWorkPool returns a workpool from options
+func (e *Engine) GetWorkPool() *WorkPool {
+ return NewWorkPool(e.GetWorkPoolConfig())
}
// SetExecuterOptions sets the executer options for the engine. This is required
diff --git a/pkg/core/execute_options.go b/pkg/core/execute_options.go
index 580b8b0a9d..93f197fc2b 100644
--- a/pkg/core/execute_options.go
+++ b/pkg/core/execute_options.go
@@ -108,8 +108,10 @@ func (e *Engine) executeTemplateSpray(templatesList []*templates.Template, targe
wp := e.GetWorkPool()
for _, template := range templatesList {
- templateType := template.Type()
+ // resize check point - nop if there are no changes
+ wp.RefreshWithConfig(e.GetWorkPoolConfig())
+ templateType := template.Type()
var wg *syncutil.AdaptiveWaitGroup
if templateType == types.HeadlessProtocol {
wg = wp.Headless
diff --git a/pkg/core/executors.go b/pkg/core/executors.go
index ace7acb208..a6421cd90c 100644
--- a/pkg/core/executors.go
+++ b/pkg/core/executors.go
@@ -158,6 +158,9 @@ func (e *Engine) executeTemplatesOnTarget(alltemplates []*templates.Template, ta
wp := e.GetWorkPool()
for _, tpl := range alltemplates {
+ // resize check point - nop if there are no changes
+ wp.RefreshWithConfig(e.GetWorkPoolConfig())
+
var sg *syncutil.AdaptiveWaitGroup
if tpl.Type() == types.HeadlessProtocol {
sg = wp.Headless
@@ -213,6 +216,9 @@ func (e *ChildExecuter) Close() *atomic.Bool {
func (e *ChildExecuter) Execute(template *templates.Template, value *contextargs.MetaInput) {
templateType := template.Type()
+ // resize check point - nop if there are no changes
+ e.e.workPool.RefreshWithConfig(e.e.GetWorkPoolConfig())
+
var wg *syncutil.AdaptiveWaitGroup
if templateType == types.HeadlessProtocol {
wg = e.e.workPool.Headless
diff --git a/pkg/core/workpool.go b/pkg/core/workpool.go
index cd17a0b367..810f99392d 100644
--- a/pkg/core/workpool.go
+++ b/pkg/core/workpool.go
@@ -57,3 +57,28 @@ func (w *WorkPool) InputPool(templateType types.ProtocolType) *syncutil.Adaptive
swg, _ := syncutil.New(syncutil.WithSize(count))
return swg
}
+
+func (w *WorkPool) RefreshWithConfig(config WorkPoolConfig) {
+ if w.config.TypeConcurrency != config.TypeConcurrency {
+ w.config.TypeConcurrency = config.TypeConcurrency
+ }
+ if w.config.HeadlessTypeConcurrency != config.HeadlessTypeConcurrency {
+ w.config.HeadlessTypeConcurrency = config.HeadlessTypeConcurrency
+ }
+ if w.config.InputConcurrency != config.InputConcurrency {
+ w.config.InputConcurrency = config.InputConcurrency
+ }
+ if w.config.HeadlessInputConcurrency != config.HeadlessInputConcurrency {
+ w.config.HeadlessInputConcurrency = config.HeadlessInputConcurrency
+ }
+ w.Refresh()
+}
+
+func (w *WorkPool) Refresh() {
+ if w.Default.Size != w.config.TypeConcurrency {
+ w.Default.Resize(w.config.TypeConcurrency)
+ }
+ if w.Headless.Size != w.config.HeadlessTypeConcurrency {
+ w.Headless.Resize(w.config.HeadlessTypeConcurrency)
+ }
+}
diff --git a/pkg/js/compiler/pool.go b/pkg/js/compiler/pool.go
index fc0b61639b..2b3bc2179d 100644
--- a/pkg/js/compiler/pool.go
+++ b/pkg/js/compiler/pool.go
@@ -100,6 +100,11 @@ func executeWithRuntime(runtime *goja.Runtime, p *goja.Program, args *ExecuteArg
// ExecuteProgram executes a compiled program with the default options.
// it deligates if a particular program should run in a pooled or non-pooled runtime
func ExecuteProgram(p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {
+ // resize check point
+ if pooljsc.Size != PoolingJsVmConcurrency {
+ pooljsc.Resize(PoolingJsVmConcurrency)
+ }
+
if opts.Source == nil {
// not-recommended anymore
return executeWithoutPooling(p, args, opts)
From 3c62b56fd9e267a594f7548c1674999768a66caa Mon Sep 17 00:00:00 2001
From: Mzack9999
Date: Wed, 3 Apr 2024 19:02:30 +0200
Subject: [PATCH 03/58] panic at the pool
---
pkg/js/compiler/pool.go | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/pkg/js/compiler/pool.go b/pkg/js/compiler/pool.go
index 2b3bc2179d..678b9afb0a 100644
--- a/pkg/js/compiler/pool.go
+++ b/pkg/js/compiler/pool.go
@@ -55,6 +55,12 @@ var (
lazySgInit = sync.OnceFunc(func() {
pooljsc, _ = syncutil.New(syncutil.WithSize(PoolingJsVmConcurrency))
})
+ sgResizeCheck = func() {
+ // resize check point
+ if pooljsc.Size != PoolingJsVmConcurrency {
+ pooljsc.Resize(PoolingJsVmConcurrency)
+ }
+ }
)
var gojapool = &sync.Pool{
@@ -100,11 +106,6 @@ func executeWithRuntime(runtime *goja.Runtime, p *goja.Program, args *ExecuteArg
// ExecuteProgram executes a compiled program with the default options.
// it deligates if a particular program should run in a pooled or non-pooled runtime
func ExecuteProgram(p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {
- // resize check point
- if pooljsc.Size != PoolingJsVmConcurrency {
- pooljsc.Resize(PoolingJsVmConcurrency)
- }
-
if opts.Source == nil {
// not-recommended anymore
return executeWithoutPooling(p, args, opts)
@@ -121,6 +122,8 @@ func executeWithPoolingProgram(p *goja.Program, args *ExecuteArgs, opts *Execute
// its unknown (most likely cannot be done) to limit max js runtimes at a moment without making it static
// unlike sync.Pool which reacts to GC and its purposes is to reuse objects rather than creating new ones
lazySgInit()
+ sgResizeCheck()
+
pooljsc.Add()
defer pooljsc.Done()
runtime := gojapool.Get().(*goja.Runtime)
From 620287f76b451ca7ce8978d2eaad0d8fe7c1753c Mon Sep 17 00:00:00 2001
From: Mzack9999
Date: Wed, 3 Apr 2024 19:28:39 +0200
Subject: [PATCH 04/58] deprecating rlm
---
cmd/nuclei/main.go | 8 +++++---
internal/runner/runner.go | 14 ++++++++++----
lib/multi.go | 13 +++++++++----
lib/sdk_private.go | 13 +++++++++----
pkg/testutils/testutils.go | 1 +
pkg/types/types.go | 4 ++++
6 files changed, 38 insertions(+), 15 deletions(-)
diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go
index 3caec21cbd..b6ecd1e452 100644
--- a/cmd/nuclei/main.go
+++ b/cmd/nuclei/main.go
@@ -15,6 +15,7 @@ import (
"github.com/projectdiscovery/utils/auth/pdcp"
"github.com/projectdiscovery/utils/env"
_ "github.com/projectdiscovery/utils/pprof"
+ stringsutil "github.com/projectdiscovery/utils/strings"
"github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/gologger"
@@ -329,7 +330,8 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.CreateGroup("rate-limit", "Rate-Limit",
flagSet.IntVarP(&options.RateLimit, "rate-limit", "rl", 150, "maximum number of requests to send per second"),
- flagSet.IntVarP(&options.RateLimitMinute, "rate-limit-minute", "rlm", 0, "maximum number of requests to send per minute"),
+ flagSet.DurationVarP(&options.RateLimitDuration, "rate-limit-duration", "rld", time.Second, "maximum number of requests to send per second"),
+ flagSet.IntVarP(&options.RateLimitMinute, "rate-limit-minute", "rlm", 0, "maximum number of requests to send per minute (DEPRECATED)"),
flagSet.IntVarP(&options.BulkSize, "bulk-size", "bs", 25, "maximum number of hosts to be analyzed in parallel per template"),
flagSet.IntVarP(&options.TemplateThreads, "concurrency", "c", 25, "maximum number of templates to be executed in parallel"),
flagSet.IntVarP(&options.HeadlessBulkSize, "headless-bulk-size", "hbs", 10, "maximum number of headless hosts to be analyzed in parallel per template"),
@@ -597,10 +599,10 @@ Note: Make sure you have backup of your custom nuclei-templates before proceedin
gologger.Fatal().Msgf("could not read response: %s", err)
}
resp = strings.TrimSpace(resp)
- if strings.EqualFold(resp, "y") || strings.EqualFold(resp, "yes") {
+ if stringsutil.EqualFoldAny(resp, "y", "yes") {
break
}
- if strings.EqualFold(resp, "n") || strings.EqualFold(resp, "no") || resp == "" {
+ if stringsutil.EqualFoldAny(resp, "n", "no", "") {
fmt.Println("Exiting...")
os.Exit(0)
}
diff --git a/internal/runner/runner.go b/internal/runner/runner.go
index 3c6aaf0b53..23443647ea 100644
--- a/internal/runner/runner.go
+++ b/internal/runner/runner.go
@@ -314,11 +314,17 @@ func New(options *types.Options) (*Runner, error) {
}
if options.RateLimitMinute > 0 {
- runner.rateLimiter = ratelimit.New(context.Background(), uint(options.RateLimitMinute), time.Minute)
- } else if options.RateLimit > 0 {
- runner.rateLimiter = ratelimit.New(context.Background(), uint(options.RateLimit), time.Second)
- } else {
+ gologger.Warning().Msgf("rate limit per minute is deprecated - use rate-limit-duration")
+ options.RateLimit = options.RateLimitMinute
+ options.RateLimitDuration = time.Minute
+ }
+ if options.RateLimit > 0 && options.RateLimitDuration == 0 {
+ options.RateLimitDuration = time.Second
+ }
+ if options.RateLimit == 0 && options.RateLimitDuration == 0 {
runner.rateLimiter = ratelimit.NewUnlimited(context.Background())
+ } else {
+ runner.rateLimiter = ratelimit.New(context.Background(), uint(options.RateLimit), options.RateLimitDuration)
}
if tmpDir, err := os.MkdirTemp("", "nuclei-tmp-*"); err == nil {
diff --git a/lib/multi.go b/lib/multi.go
index 3a573b16a9..6fa791a2f0 100644
--- a/lib/multi.go
+++ b/lib/multi.go
@@ -42,11 +42,16 @@ func createEphemeralObjects(base *NucleiEngine, opts *types.Options) (*unsafeOpt
Parser: base.parser,
}
if opts.RateLimitMinute > 0 {
- u.executerOpts.RateLimiter = ratelimit.New(context.Background(), uint(opts.RateLimitMinute), time.Minute)
- } else if opts.RateLimit > 0 {
- u.executerOpts.RateLimiter = ratelimit.New(context.Background(), uint(opts.RateLimit), time.Second)
- } else {
+ opts.RateLimit = opts.RateLimitMinute
+ opts.RateLimitDuration = time.Minute
+ }
+ if opts.RateLimit > 0 && opts.RateLimitDuration == 0 {
+ opts.RateLimitDuration = time.Second
+ }
+ if opts.RateLimit == 0 && opts.RateLimitDuration == 0 {
u.executerOpts.RateLimiter = ratelimit.NewUnlimited(context.Background())
+ } else {
+ u.executerOpts.RateLimiter = ratelimit.New(context.Background(), uint(opts.RateLimit), opts.RateLimitDuration)
}
u.engine = core.New(opts)
u.engine.SetExecuterOptions(u.executerOpts)
diff --git a/lib/sdk_private.go b/lib/sdk_private.go
index bc66d53fd7..c76970c91d 100644
--- a/lib/sdk_private.go
+++ b/lib/sdk_private.go
@@ -192,11 +192,16 @@ func (e *NucleiEngine) init() error {
if e.executerOpts.RateLimiter == nil {
if e.opts.RateLimitMinute > 0 {
- e.executerOpts.RateLimiter = ratelimit.New(context.Background(), uint(e.opts.RateLimitMinute), time.Minute)
- } else if e.opts.RateLimit > 0 {
- e.executerOpts.RateLimiter = ratelimit.New(context.Background(), uint(e.opts.RateLimit), time.Second)
- } else {
+ e.opts.RateLimit = e.opts.RateLimitMinute
+ e.opts.RateLimitDuration = time.Minute
+ }
+ if e.opts.RateLimit > 0 && e.opts.RateLimitDuration == 0 {
+ e.opts.RateLimitDuration = time.Second
+ }
+ if e.opts.RateLimit == 0 && e.opts.RateLimitDuration == 0 {
e.executerOpts.RateLimiter = ratelimit.NewUnlimited(context.Background())
+ } else {
+ e.executerOpts.RateLimiter = ratelimit.New(context.Background(), uint(e.opts.RateLimit), e.opts.RateLimitDuration)
}
}
diff --git a/pkg/testutils/testutils.go b/pkg/testutils/testutils.go
index 96d68e1d04..e59aa015e2 100644
--- a/pkg/testutils/testutils.go
+++ b/pkg/testutils/testutils.go
@@ -54,6 +54,7 @@ var DefaultOptions = &types.Options{
Timeout: 5,
Retries: 1,
RateLimit: 150,
+ RateLimitDuration: time.Second,
ProjectPath: "",
Severities: severity.Severities{},
Targets: []string{},
diff --git a/pkg/types/types.go b/pkg/types/types.go
index ce78380901..34f3c3be10 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -132,7 +132,10 @@ type Options struct {
Retries int
// Rate-Limit is the maximum number of requests per specified target
RateLimit int
+ // Rate Limit Duration interval between burst resets
+ RateLimitDuration time.Duration
// Rate-Limit is the maximum number of requests per minute for specified target
+ // Deprecated: Use RateLimitDuration - automatically set Rate Limit Duration to 60 seconds
RateLimitMinute int
// PageTimeout is the maximum time to wait for a page in seconds
PageTimeout int
@@ -410,6 +413,7 @@ func (options *Options) HasClientCertificates() bool {
func DefaultOptions() *Options {
return &Options{
RateLimit: 150,
+ RateLimitDuration: time.Second,
BulkSize: 25,
TemplateThreads: 25,
HeadlessBulkSize: 10,
From a140a4194e2a521ecac5bffb03573e6a120c4ad6 Mon Sep 17 00:00:00 2001
From: Mzack9999
Date: Wed, 3 Apr 2024 19:40:09 +0200
Subject: [PATCH 05/58] boh - placing resize in wrapped method
---
pkg/protocols/dns/request.go | 2 +-
pkg/protocols/http/request.go | 4 ++--
pkg/protocols/http/request_fuzz.go | 2 +-
pkg/protocols/protocols.go | 11 +++++++++++
4 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/pkg/protocols/dns/request.go b/pkg/protocols/dns/request.go
index ba9dd66621..8aa8c4a589 100644
--- a/pkg/protocols/dns/request.go
+++ b/pkg/protocols/dns/request.go
@@ -143,7 +143,7 @@ func (request *Request) execute(input *contextargs.Context, domain string, metad
}
}
- request.options.RateLimiter.Take()
+ request.options.RateLimitTake()
// Send the request to the target servers
response, err := dnsClient.Do(compiledRequest)
diff --git a/pkg/protocols/http/request.go b/pkg/protocols/http/request.go
index 2763c5c628..1b27aa7bf3 100644
--- a/pkg/protocols/http/request.go
+++ b/pkg/protocols/http/request.go
@@ -222,7 +222,7 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
return
case spmHandler.ResultChan <- func() error {
// putting ratelimiter here prevents any unnecessary waiting if any
- request.options.RateLimiter.Take()
+ request.options.RateLimitTake()
previous := make(map[string]interface{})
return request.executeRequest(input, httpRequest, previous, false, wrappedCallback, 0)
}():
@@ -366,7 +366,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.RateLimiter.Take()
+ request.options.RateLimitTake()
ctx := request.newContext(input)
ctxWithTimeout, cancel := context.WithTimeout(ctx, time.Duration(request.options.Options.Timeout)*time.Second)
diff --git a/pkg/protocols/http/request_fuzz.go b/pkg/protocols/http/request_fuzz.go
index d6a29c181c..aed17f54e0 100644
--- a/pkg/protocols/http/request_fuzz.go
+++ b/pkg/protocols/http/request_fuzz.go
@@ -145,7 +145,7 @@ func (request *Request) executeGeneratedFuzzingRequest(gr fuzz.GeneratedRequest,
if request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(input.MetaInput.Input) {
return false
}
- request.options.RateLimiter.Take()
+ request.options.RateLimitTake()
req := &generatedRequest{
request: gr.Request,
dynamicValues: gr.DynamicValues,
diff --git a/pkg/protocols/protocols.go b/pkg/protocols/protocols.go
index f9d9064200..5ac0a2f0b7 100644
--- a/pkg/protocols/protocols.go
+++ b/pkg/protocols/protocols.go
@@ -128,6 +128,17 @@ type ExecutorOptions struct {
ExportReqURLPattern bool
}
+// 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 (eo *ExecutorOptions) RateLimitTake() {
+ if eo.RateLimiter.GetLimit() != uint(eo.Options.RateLimit) {
+ eo.RateLimiter.SetLimit(uint(eo.Options.RateLimit))
+ eo.RateLimiter.SetDuration(eo.Options.RateLimitDuration)
+ }
+ eo.RateLimiter.Take()
+}
+
// GetThreadsForPayloadRequests returns the number of threads to use as default for
// given max-request of payloads
func (e *ExecutorOptions) GetThreadsForNPayloadRequests(totalRequests int, currentThreads int) int {
From af7450737acfbf2ffd918d8a566596bcc9519250 Mon Sep 17 00:00:00 2001
From: mzack
Date: Wed, 3 Apr 2024 23:06:08 +0200
Subject: [PATCH 06/58] making payload concurrency dynamic via direct int
change
---
pkg/protocols/dns/request.go | 9 +++++++++
pkg/protocols/http/httputils/spm.go | 10 ++++++++++
pkg/protocols/http/request.go | 9 +++++++++
pkg/protocols/javascript/js.go | 9 +++++++++
pkg/protocols/network/request.go | 9 +++++++++
pkg/protocols/protocols.go | 14 ++------------
6 files changed, 48 insertions(+), 12 deletions(-)
diff --git a/pkg/protocols/dns/request.go b/pkg/protocols/dns/request.go
index 8aa8c4a589..d4e70e13e9 100644
--- a/pkg/protocols/dns/request.go
+++ b/pkg/protocols/dns/request.go
@@ -62,6 +62,9 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
variablesMap := request.options.Variables.Evaluate(vars)
vars = generators.MergeMaps(vars, variablesMap, request.options.Constants)
+ // if request threads matches global payload concurrency we follow it
+ shouldFollowGlobal := request.Threads == request.options.Options.PayloadConcurrency
+
if request.generator != nil {
iterator := request.generator.NewIterator()
swg, err := syncutil.New(syncutil.WithSize(request.Threads))
@@ -76,6 +79,12 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
if !ok {
break
}
+
+ // resize check point - nop if there are no changes
+ if shouldFollowGlobal && swg.Size != request.options.Options.PayloadConcurrency {
+ swg.Resize(request.options.Options.PayloadConcurrency)
+ }
+
value = generators.MergeMaps(vars, value)
swg.Add()
go func(newVars map[string]interface{}) {
diff --git a/pkg/protocols/http/httputils/spm.go b/pkg/protocols/http/httputils/spm.go
index bca6c2ee59..52d13f06ff 100644
--- a/pkg/protocols/http/httputils/spm.go
+++ b/pkg/protocols/http/httputils/spm.go
@@ -143,6 +143,16 @@ func (h *StopAtFirstMatchHandler[T]) Release() {
}
}
+func (h *StopAtFirstMatchHandler[T]) Resize(size int) {
+ if h.sgPool.Size != size {
+ h.sgPool.Resize(size)
+ }
+}
+
+func (h *StopAtFirstMatchHandler[T]) Size() int {
+ return h.sgPool.Size
+}
+
// Wait waits for all work to be done
func (h *StopAtFirstMatchHandler[T]) Wait() {
switch h.poolType {
diff --git a/pkg/protocols/http/request.go b/pkg/protocols/http/request.go
index 1b27aa7bf3..5d64216f6a 100644
--- a/pkg/protocols/http/request.go
+++ b/pkg/protocols/http/request.go
@@ -165,6 +165,9 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
// Workers that keeps enqueuing new requests
maxWorkers := request.Threads
+ // if request threads matches global payload concurrency we follow it
+ shouldFollowGlobal := maxWorkers == request.options.Options.PayloadConcurrency
+
if protocolstate.IsLowOnMemory() {
maxWorkers = protocolstate.GuardThreadsOrDefault(request.Threads)
}
@@ -198,6 +201,12 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
if !ok {
break
}
+
+ // resize check point - nop if there are no changes
+ if shouldFollowGlobal && spmHandler.Size() != request.options.Options.PayloadConcurrency {
+ spmHandler.Resize(request.options.Options.PayloadConcurrency)
+ }
+
ctx := request.newContext(input)
generatedHttpRequest, err := generator.Make(ctx, input, inputData, payloads, dynamicValues)
if err != nil {
diff --git a/pkg/protocols/javascript/js.go b/pkg/protocols/javascript/js.go
index 48a1be485d..efabd503c4 100644
--- a/pkg/protocols/javascript/js.go
+++ b/pkg/protocols/javascript/js.go
@@ -404,6 +404,9 @@ func (request *Request) executeRequestParallel(ctxParent context.Context, hostPo
requestOptions := request.options
gotmatches := &atomic.Bool{}
+ // if request threads matches global payload concurrency we follow it
+ shouldFollowGlobal := threads == request.options.Options.PayloadConcurrency
+
sg, _ := syncutil.New(syncutil.WithSize(threads))
if request.generator != nil {
@@ -413,6 +416,12 @@ func (request *Request) executeRequestParallel(ctxParent context.Context, hostPo
if !ok {
break
}
+
+ // resize check point - nop if there are no changes
+ if shouldFollowGlobal && sg.Size != request.options.Options.PayloadConcurrency {
+ sg.Resize(request.options.Options.PayloadConcurrency)
+ }
+
sg.Add()
go func() {
defer sg.Done()
diff --git a/pkg/protocols/network/request.go b/pkg/protocols/network/request.go
index ef0ff01c7f..146c657072 100644
--- a/pkg/protocols/network/request.go
+++ b/pkg/protocols/network/request.go
@@ -174,6 +174,9 @@ func (request *Request) executeAddress(variables map[string]interface{}, actualA
return err
}
+ // if request threads matches global payload concurrency we follow it
+ shouldFollowGlobal := request.Threads == request.options.Options.PayloadConcurrency
+
if request.generator != nil {
iterator := request.generator.NewIterator()
var multiErr error
@@ -188,6 +191,12 @@ func (request *Request) executeAddress(variables map[string]interface{}, actualA
if !ok {
break
}
+
+ // resize check point - nop if there are no changes
+ if shouldFollowGlobal && swg.Size != request.options.Options.PayloadConcurrency {
+ swg.Resize(request.options.Options.PayloadConcurrency)
+ }
+
value = generators.MergeMaps(value, payloads)
swg.Add()
go func(vars map[string]interface{}) {
diff --git a/pkg/protocols/protocols.go b/pkg/protocols/protocols.go
index 5ac0a2f0b7..6328bc36a3 100644
--- a/pkg/protocols/protocols.go
+++ b/pkg/protocols/protocols.go
@@ -34,9 +34,6 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
-// Optional Callback to update Thread count in payloads across all requests
-type PayloadThreadSetterCallback func(opts *ExecutorOptions, totalRequests, currentThreads int) int
-
var (
MaxTemplateFileSizeForEncoding = 1024 * 1024
)
@@ -114,10 +111,6 @@ type ExecutorOptions struct {
// JsCompiler is abstracted javascript compiler which adds node modules and provides execution
// environment for javascript templates
JsCompiler *compiler.Compiler
- // Optional Callback function to update Thread count in payloads across all protocols
- // based on given logic. by default nuclei reverts to using value of `-c` when threads count
- // is not specified or is 0 in template
- OverrideThreadsCount PayloadThreadSetterCallback
// AuthProvider is a provider for auth strategies
AuthProvider authprovider.AuthProvider
//TemporaryDirectory is the directory to store temporary files
@@ -142,14 +135,11 @@ func (eo *ExecutorOptions) RateLimitTake() {
// GetThreadsForPayloadRequests returns the number of threads to use as default for
// given max-request of payloads
func (e *ExecutorOptions) GetThreadsForNPayloadRequests(totalRequests int, currentThreads int) int {
- if e.OverrideThreadsCount != nil {
- return e.OverrideThreadsCount(e, totalRequests, currentThreads)
- }
if currentThreads > 0 {
return currentThreads
- } else {
- return e.Options.PayloadConcurrency
}
+
+ return e.Options.PayloadConcurrency
}
// CreateTemplateCtxStore creates template context store (which contains templateCtx for every scan)
From 99a018d12ad0e479abed4a1c663239db90dcfc0a Mon Sep 17 00:00:00 2001
From: mzack
Date: Thu, 4 Apr 2024 20:19:46 +0200
Subject: [PATCH 07/58] adding speed change example
---
examples/with_withspeed_control/main.go | 99 +++++++++++++++++++++++++
go.mod | 4 +-
go.sum | 8 +-
lib/config.go | 42 +++++++++--
lib/sdk.go | 8 ++
5 files changed, 148 insertions(+), 13 deletions(-)
create mode 100644 examples/with_withspeed_control/main.go
diff --git a/examples/with_withspeed_control/main.go b/examples/with_withspeed_control/main.go
new file mode 100644
index 0000000000..a61e820f26
--- /dev/null
+++ b/examples/with_withspeed_control/main.go
@@ -0,0 +1,99 @@
+package main
+
+import (
+ "sync"
+ "time"
+
+ nuclei "github.com/projectdiscovery/nuclei/v3/lib"
+ "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
+)
+
+func main() {
+ ne, err := nuclei.NewNucleiEngine(
+ nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"header-command-injection"}}),
+ nuclei.EnableStatsWithOpts(nuclei.StatsOptions{MetricServerPort: 6064}),
+ nuclei.WithGlobalRateLimit(1, time.Second),
+ nuclei.WithConcurrency(nuclei.Concurrency{
+ TemplateConcurrency: 1,
+ HostConcurrency: 1,
+ HeadlessHostConcurrency: 1,
+ HeadlessTemplateConcurrency: 1,
+ JavascriptTemplateConcurrency: 1,
+ TemplatePayloadConcurrency: 1,
+ }),
+ )
+ if err != nil {
+ panic(err)
+ }
+ // load targets and optionally probe non http/https targets
+ ne.LoadTargets([]string{"http://honey.scanme.sh"}, false)
+
+ var wgtest sync.WaitGroup
+
+ // speed tests
+ // increase rate limit
+ wgtest.Add(1)
+ go func() {
+ defer wgtest.Done()
+ initialRate := ne.GetExecuterOptions().RateLimiter.GetLimit()
+ if initialRate != 1 {
+ panic("wrong initial rate limit")
+ }
+ time.Sleep(10 * time.Second)
+ ne.Options().RateLimit = 5
+ time.Sleep(10 * time.Second)
+ finalRate := ne.GetExecuterOptions().RateLimiter.GetLimit()
+ if finalRate != 5 {
+ panic("wrong final rate limit")
+ }
+ }()
+
+ // increase threads and bulk size
+ wgtest.Add(1)
+ go func() {
+ defer wgtest.Done()
+ initialTemplateThreads := ne.Options().TemplateThreads
+ initialBulkSize := ne.Options().BulkSize
+ if initialTemplateThreads != 1 || initialBulkSize != 1 {
+ panic("wrong initial standard concurrency")
+ }
+ time.Sleep(10 * time.Second)
+ ne.Options().TemplateThreads = 5
+ ne.Options().BulkSize = 25
+ time.Sleep(10 * time.Second)
+ // check new values via workpool
+ finalTemplateThreads := ne.Engine().WorkPool().Default.Size
+ finalBulkSize := ne.Engine().GetWorkPool().InputPool(types.HTTPProtocol).Size
+ if finalTemplateThreads != 5 && finalBulkSize != 25 {
+ panic("wrong final concurreny")
+ }
+ }()
+
+ // increase payload concurrency
+ wgtest.Add(1)
+ go func() {
+ defer wgtest.Done()
+ initialpayloadConcurrency := ne.Options().PayloadConcurrency
+ if initialpayloadConcurrency != 1 {
+ panic("wrong initial payload concurrency")
+ }
+ time.Sleep(10 * time.Second)
+ ne.Options().PayloadConcurrency = 5
+ time.Sleep(10 * time.Second)
+
+ // the ongoing and next payload iterations will retrieve parallelism from this function
+ // it should have the new set value, that will be cascade applied to all running adaptive wait groups
+ finalPayloadConcurrency := ne.GetExecuterOptions().GetThreadsForNPayloadRequests(100, 0)
+ if finalPayloadConcurrency != 5 {
+ panic("wrong initial payload concurrency")
+ }
+ }()
+
+ err = ne.ExecuteWithCallback(nil)
+ if err != nil {
+ panic(err)
+ }
+ defer ne.Close()
+
+ wgtest.Wait()
+}
diff --git a/go.mod b/go.mod
index 6a0e8bfccf..981d174836 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/errors v0.9.1
github.com/projectdiscovery/clistats v0.0.20
- github.com/projectdiscovery/fastdialer v0.0.64
+ github.com/projectdiscovery/fastdialer v0.0.65
github.com/projectdiscovery/hmap v0.0.41
github.com/projectdiscovery/interactsh v1.1.9
github.com/projectdiscovery/rawhttp v0.1.41
@@ -94,7 +94,7 @@ require (
github.com/projectdiscovery/tlsx v1.1.6
github.com/projectdiscovery/uncover v1.0.7
github.com/projectdiscovery/useragent v0.0.40
- github.com/projectdiscovery/utils v0.0.87
+ github.com/projectdiscovery/utils v0.0.88-0.20240404181359-663cfe2196d0
github.com/projectdiscovery/wappalyzergo v0.0.116
github.com/redis/go-redis/v9 v9.1.0
github.com/seh-msft/burpxml v1.0.1
diff --git a/go.sum b/go.sum
index 83551eb1c2..8557e5d49e 100644
--- a/go.sum
+++ b/go.sum
@@ -834,8 +834,8 @@ github.com/projectdiscovery/clistats v0.0.20 h1:5jO5SLiRJ7f0nDV0ndBNmBeesbROouPo
github.com/projectdiscovery/clistats v0.0.20/go.mod h1:GJ2av0KnOvK0AISQnP8hyDclYIji1LVkx2l0pwnzAu4=
github.com/projectdiscovery/dsl v0.0.50 h1:4SuAwTS9l6o1tqlIC/79+EcUwTM6CjaU7MpY/nDlFaM=
github.com/projectdiscovery/dsl v0.0.50/go.mod h1:6g740l4tH4d2j9UYtIchtxudb0Dphkq4o+VatpR4M6g=
-github.com/projectdiscovery/fastdialer v0.0.64 h1:xivkA4g14nwQElOVsxPkGMWsdcYPcp7DPhVjvI6yQkw=
-github.com/projectdiscovery/fastdialer v0.0.64/go.mod h1:S/7PAQRmVDYRaU7u4xXD0qA5a48NAZq2JcpcVoEVrlo=
+github.com/projectdiscovery/fastdialer v0.0.65 h1:msvKVJyILtP04CXSgSEWv4rUVsk0CCd3xhauo+H82IU=
+github.com/projectdiscovery/fastdialer v0.0.65/go.mod h1:wIE10NL7oa/zBCJfr1xAduv3q73aeuGbhfZ1Z8o4NUo=
github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA=
github.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw=
github.com/projectdiscovery/freeport v0.0.5 h1:jnd3Oqsl4S8n0KuFkE5Hm8WGDP24ITBvmyw5pFTHS8Q=
@@ -886,8 +886,8 @@ github.com/projectdiscovery/uncover v1.0.7 h1:ut+2lTuvmftmveqF5RTjMWAgyLj8ltPQC7
github.com/projectdiscovery/uncover v1.0.7/go.mod h1:HFXgm1sRPuoN0D4oATljPIdmbo/EEh1wVuxQqo/dwFE=
github.com/projectdiscovery/useragent v0.0.40 h1:1LUhReSGPkhqsM5n40OOC9dIoNqMGs1dyGFJcOmg2Fo=
github.com/projectdiscovery/useragent v0.0.40/go.mod h1:EvK1x3s948Gtqb/XOahXcauyejCL/rSgy5d1IAvsKT4=
-github.com/projectdiscovery/utils v0.0.87 h1:9+RiTEhpUB/vk6XJUVpysNWJ2aCTD7WuyoyAcNnbIzk=
-github.com/projectdiscovery/utils v0.0.87/go.mod h1:jGK450sL9AVDTjaPwEs9za8NVeEC9xE97IWNoK138kI=
+github.com/projectdiscovery/utils v0.0.88-0.20240404181359-663cfe2196d0 h1:2ZR0yiN0cUm/qYEMq79MfcbgM374lJSdftheYhMFxNo=
+github.com/projectdiscovery/utils v0.0.88-0.20240404181359-663cfe2196d0/go.mod h1:lAWzFdGXtJRPKdhUu1Z46d8B8JbASTk1Z69WY6H/3kA=
github.com/projectdiscovery/wappalyzergo v0.0.116 h1:xy+mBpwbYo/0PSzmJOQ/RXHomEh0D3nDBcbCxsW69m8=
github.com/projectdiscovery/wappalyzergo v0.0.116/go.mod h1:hc/o+fgM8KtdpFesjfBTmHTwsR+yBd+4kYZW/DGy/x8=
github.com/projectdiscovery/yamldoc-go v1.0.4 h1:eZoESapnMw6WAHiVgRwNqvbJEfNHEH148uthhFbG5jE=
diff --git a/lib/config.go b/lib/config.go
index 52a8e2328a..fd4e9c9a97 100644
--- a/lib/config.go
+++ b/lib/config.go
@@ -2,6 +2,7 @@ package nuclei
import (
"context"
+ "errors"
"time"
"github.com/projectdiscovery/goflags"
@@ -120,12 +121,37 @@ type Concurrency struct {
// WithConcurrency sets concurrency options
func WithConcurrency(opts Concurrency) NucleiSDKOptions {
return func(e *NucleiEngine) error {
- e.opts.TemplateThreads = opts.TemplateConcurrency
- e.opts.BulkSize = opts.HostConcurrency
- e.opts.HeadlessBulkSize = opts.HeadlessHostConcurrency
- e.opts.HeadlessTemplateThreads = opts.HeadlessTemplateConcurrency
- e.opts.JsConcurrency = opts.JavascriptTemplateConcurrency
- e.opts.PayloadConcurrency = opts.TemplatePayloadConcurrency
+ // minimum required is 1
+ if opts.TemplateConcurrency <= 0 {
+ return errors.New("template threads must be at least 1")
+ } else {
+ e.opts.TemplateThreads = opts.TemplateConcurrency
+ }
+ if opts.HostConcurrency <= 0 {
+ return errors.New("host concurrency must be at least 1")
+ } else {
+ e.opts.BulkSize = opts.HostConcurrency
+ }
+ if opts.HeadlessHostConcurrency <= 0 {
+ return errors.New("headless host concurrency must be at least 1")
+ } else {
+ e.opts.HeadlessBulkSize = opts.HeadlessHostConcurrency
+ }
+ if opts.HeadlessTemplateConcurrency <= 0 {
+ return errors.New("headless template threads must be at least 1")
+ } else {
+ e.opts.HeadlessTemplateThreads = opts.HeadlessTemplateConcurrency
+ }
+ if opts.JavascriptTemplateConcurrency <= 0 {
+ return errors.New("js must be at least 1")
+ } else {
+ e.opts.JsConcurrency = opts.JavascriptTemplateConcurrency
+ }
+ if opts.TemplatePayloadConcurrency <= 0 {
+ return errors.New("payload concurrency must be at least 1")
+ } else {
+ e.opts.PayloadConcurrency = opts.TemplatePayloadConcurrency
+ }
return nil
}
}
@@ -133,7 +159,9 @@ func WithConcurrency(opts Concurrency) NucleiSDKOptions {
// WithGlobalRateLimit sets global rate (i.e all hosts combined) limit options
func WithGlobalRateLimit(maxTokens int, duration time.Duration) NucleiSDKOptions {
return func(e *NucleiEngine) error {
- e.rateLimiter = ratelimit.New(context.Background(), uint(maxTokens), duration)
+ e.opts.RateLimit = maxTokens
+ e.opts.RateLimitDuration = duration
+ e.rateLimiter = ratelimit.New(context.Background(), uint(e.opts.RateLimit), e.opts.RateLimitDuration)
return nil
}
}
diff --git a/lib/sdk.go b/lib/sdk.go
index da0abae495..4bee921c31 100644
--- a/lib/sdk.go
+++ b/lib/sdk.go
@@ -215,6 +215,14 @@ func (e *NucleiEngine) ExecuteWithCallback(callback ...func(event *output.Result
return nil
}
+func (e *NucleiEngine) Options() *types.Options {
+ return e.opts
+}
+
+func (e *NucleiEngine) Engine() *core.Engine {
+ return e.engine
+}
+
// NewNucleiEngine creates a new nuclei engine instance
func NewNucleiEngine(options ...NucleiSDKOptions) (*NucleiEngine, error) {
// default options
From 96d7d02701363821cd7805601ff18dc824e15abf Mon Sep 17 00:00:00 2001
From: mzack
Date: Thu, 4 Apr 2024 20:22:28 +0200
Subject: [PATCH 08/58] adding speed example
---
.github/workflows/build-test.yml | 4 ++++
.../{with_withspeed_control => with_speed_control}/main.go | 0
2 files changed, 4 insertions(+)
rename examples/{with_withspeed_control => with_speed_control}/main.go (100%)
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 7ebdaee1c0..aa0d8aecfe 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -66,3 +66,7 @@ jobs:
- name: Example SDK Advanced
run: go run .
working-directory: examples/advanced/
+
+ - name: Example SDK with speed control
+ run: go run .
+ working-directory: examples/with_speed_control/
diff --git a/examples/with_withspeed_control/main.go b/examples/with_speed_control/main.go
similarity index 100%
rename from examples/with_withspeed_control/main.go
rename to examples/with_speed_control/main.go
From c4aa7dc1a20efa3ff02a036ce2acb0ae2274e8a7 Mon Sep 17 00:00:00 2001
From: mzack
Date: Thu, 4 Apr 2024 20:43:00 +0200
Subject: [PATCH 09/58] f tag
---
examples/with_speed_control/main.go | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/examples/with_speed_control/main.go b/examples/with_speed_control/main.go
index a61e820f26..ed3592bade 100644
--- a/examples/with_speed_control/main.go
+++ b/examples/with_speed_control/main.go
@@ -10,7 +10,10 @@ import (
func main() {
ne, err := nuclei.NewNucleiEngine(
- nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"header-command-injection"}}),
+ nuclei.WithTemplateFilters(nuclei.TemplateFilters{
+ IDs: []string{"header-command-injection"},
+ IncludeTags: []string{"fuzz"},
+ }),
nuclei.EnableStatsWithOpts(nuclei.StatsOptions{MetricServerPort: 6064}),
nuclei.WithGlobalRateLimit(1, time.Second),
nuclei.WithConcurrency(nuclei.Concurrency{
From aa06c9ef1798448da2670671b6c299b13e55064d Mon Sep 17 00:00:00 2001
From: mzack
Date: Thu, 4 Apr 2024 21:21:06 +0200
Subject: [PATCH 10/58] speed up
---
examples/with_speed_control/main.go | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/examples/with_speed_control/main.go b/examples/with_speed_control/main.go
index ed3592bade..8637209a6f 100644
--- a/examples/with_speed_control/main.go
+++ b/examples/with_speed_control/main.go
@@ -43,10 +43,10 @@ func main() {
panic("wrong initial rate limit")
}
time.Sleep(10 * time.Second)
- ne.Options().RateLimit = 5
+ ne.Options().RateLimit = 1000
time.Sleep(10 * time.Second)
finalRate := ne.GetExecuterOptions().RateLimiter.GetLimit()
- if finalRate != 5 {
+ if finalRate != 1000 {
panic("wrong final rate limit")
}
}()
@@ -81,13 +81,13 @@ func main() {
panic("wrong initial payload concurrency")
}
time.Sleep(10 * time.Second)
- ne.Options().PayloadConcurrency = 5
+ ne.Options().PayloadConcurrency = 100
time.Sleep(10 * time.Second)
// the ongoing and next payload iterations will retrieve parallelism from this function
// it should have the new set value, that will be cascade applied to all running adaptive wait groups
finalPayloadConcurrency := ne.GetExecuterOptions().GetThreadsForNPayloadRequests(100, 0)
- if finalPayloadConcurrency != 5 {
+ if finalPayloadConcurrency != 100 {
panic("wrong initial payload concurrency")
}
}()
From d0a0c6d0c3ab9a1c170716a48ade3c6c649190aa Mon Sep 17 00:00:00 2001
From: Mzack9999
Date: Fri, 5 Apr 2024 15:45:21 +0200
Subject: [PATCH 11/58] internal sync fix + speed up
---
examples/with_speed_control/main.go | 145 ++++++++++++++--------------
go.mod | 8 +-
go.sum | 15 +--
pkg/core/engine.go | 2 +
pkg/core/executors.go | 2 +-
5 files changed, 88 insertions(+), 84 deletions(-)
diff --git a/examples/with_speed_control/main.go b/examples/with_speed_control/main.go
index 8637209a6f..36a26cdd1c 100644
--- a/examples/with_speed_control/main.go
+++ b/examples/with_speed_control/main.go
@@ -1,6 +1,7 @@
package main
import (
+ "log"
"sync"
"time"
@@ -9,11 +10,32 @@ import (
)
func main() {
- ne, err := nuclei.NewNucleiEngine(
- nuclei.WithTemplateFilters(nuclei.TemplateFilters{
- IDs: []string{"header-command-injection"},
- IncludeTags: []string{"fuzz"},
- }),
+ ne, err := initializeNucleiEngine()
+ if err != nil {
+ panic(err)
+ }
+ defer ne.Close()
+
+ ne.LoadTargets([]string{"http://honey.scanme.sh"}, false)
+
+ var wg sync.WaitGroup
+ wg.Add(3)
+
+ go testRateLimit(&wg, ne)
+ go testThreadsAndBulkSize(&wg, ne)
+ go testPayloadConcurrency(&wg, ne)
+
+ err = ne.ExecuteWithCallback(nil)
+ if err != nil {
+ panic(err)
+ }
+
+ wg.Wait()
+}
+
+func initializeNucleiEngine() (*nuclei.NucleiEngine, error) {
+ return nuclei.NewNucleiEngine(
+ nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "http"}),
nuclei.EnableStatsWithOpts(nuclei.StatsOptions{MetricServerPort: 6064}),
nuclei.WithGlobalRateLimit(1, time.Second),
nuclei.WithConcurrency(nuclei.Concurrency{
@@ -25,78 +47,57 @@ func main() {
TemplatePayloadConcurrency: 1,
}),
)
- if err != nil {
- panic(err)
- }
- // load targets and optionally probe non http/https targets
- ne.LoadTargets([]string{"http://honey.scanme.sh"}, false)
-
- var wgtest sync.WaitGroup
+}
- // speed tests
- // increase rate limit
- wgtest.Add(1)
- go func() {
- defer wgtest.Done()
- initialRate := ne.GetExecuterOptions().RateLimiter.GetLimit()
- if initialRate != 1 {
- panic("wrong initial rate limit")
- }
- time.Sleep(10 * time.Second)
- ne.Options().RateLimit = 1000
- time.Sleep(10 * time.Second)
- finalRate := ne.GetExecuterOptions().RateLimiter.GetLimit()
- if finalRate != 1000 {
- panic("wrong final rate limit")
- }
- }()
+func testRateLimit(wg *sync.WaitGroup, ne *nuclei.NucleiEngine) {
+ defer wg.Done()
+ verifyRateLimit(ne, 1, 5000)
+}
- // increase threads and bulk size
- wgtest.Add(1)
- go func() {
- defer wgtest.Done()
- initialTemplateThreads := ne.Options().TemplateThreads
- initialBulkSize := ne.Options().BulkSize
- if initialTemplateThreads != 1 || initialBulkSize != 1 {
- panic("wrong initial standard concurrency")
- }
- time.Sleep(10 * time.Second)
- ne.Options().TemplateThreads = 5
- ne.Options().BulkSize = 25
- time.Sleep(10 * time.Second)
- // check new values via workpool
- finalTemplateThreads := ne.Engine().WorkPool().Default.Size
- finalBulkSize := ne.Engine().GetWorkPool().InputPool(types.HTTPProtocol).Size
- if finalTemplateThreads != 5 && finalBulkSize != 25 {
- panic("wrong final concurreny")
- }
- }()
+func testThreadsAndBulkSize(wg *sync.WaitGroup, ne *nuclei.NucleiEngine) {
+ defer wg.Done()
+ initialTemplateThreads, initialBulkSize := 1, 1
+ verifyThreadsAndBulkSize(ne, initialTemplateThreads, initialBulkSize, 25, 25)
+}
- // increase payload concurrency
- wgtest.Add(1)
- go func() {
- defer wgtest.Done()
- initialpayloadConcurrency := ne.Options().PayloadConcurrency
- if initialpayloadConcurrency != 1 {
- panic("wrong initial payload concurrency")
- }
- time.Sleep(10 * time.Second)
- ne.Options().PayloadConcurrency = 100
- time.Sleep(10 * time.Second)
+func testPayloadConcurrency(wg *sync.WaitGroup, ne *nuclei.NucleiEngine) {
+ defer wg.Done()
+ verifyPayloadConcurrency(ne, 1, 500)
+}
- // the ongoing and next payload iterations will retrieve parallelism from this function
- // it should have the new set value, that will be cascade applied to all running adaptive wait groups
- finalPayloadConcurrency := ne.GetExecuterOptions().GetThreadsForNPayloadRequests(100, 0)
- if finalPayloadConcurrency != 100 {
- panic("wrong initial payload concurrency")
- }
- }()
+func verifyRateLimit(ne *nuclei.NucleiEngine, initialRate, finalRate int) {
+ if ne.GetExecuterOptions().RateLimiter.GetLimit() != uint(initialRate) {
+ panic("wrong initial rate limit")
+ }
+ time.Sleep(5 * time.Second)
+ ne.Options().RateLimit = finalRate
+ time.Sleep(20 * time.Second)
+ if ne.GetExecuterOptions().RateLimiter.GetLimit() != uint(finalRate) {
+ panic("wrong final rate limit")
+ }
+}
- err = ne.ExecuteWithCallback(nil)
- if err != nil {
- panic(err)
+func verifyThreadsAndBulkSize(ne *nuclei.NucleiEngine, initialThreads, initialBulk, finalThreads, finalBulk int) {
+ if ne.Options().TemplateThreads != initialThreads || ne.Options().BulkSize != initialBulk {
+ panic("wrong initial standard concurrency")
}
- defer ne.Close()
+ time.Sleep(5 * time.Second)
+ ne.Options().TemplateThreads = finalThreads
+ ne.Options().BulkSize = finalBulk
+ time.Sleep(20 * time.Second)
+ if ne.Engine().GetWorkPool().InputPool(types.HTTPProtocol).Size != finalBulk || ne.Engine().WorkPool().Default.Size != finalThreads {
+ log.Fatal("wrong final concurrency", ne.Engine().WorkPool().Default.Size, finalThreads, ne.Engine().GetWorkPool().InputPool(types.HTTPProtocol).Size, finalBulk)
+ }
+}
- wgtest.Wait()
+func verifyPayloadConcurrency(ne *nuclei.NucleiEngine, initialPayloadConcurrency, finalPayloadConcurrency int) {
+ if ne.Options().PayloadConcurrency != initialPayloadConcurrency {
+ panic("wrong initial payload concurrency")
+ }
+ time.Sleep(5 * time.Second)
+ ne.Options().PayloadConcurrency = finalPayloadConcurrency
+ time.Sleep(20 * time.Second)
+ if ne.GetExecuterOptions().GetThreadsForNPayloadRequests(100, 0) != finalPayloadConcurrency {
+ panic("wrong final payload concurrency")
+ }
}
diff --git a/go.mod b/go.mod
index 981d174836..a116f9611a 100644
--- a/go.mod
+++ b/go.mod
@@ -38,7 +38,7 @@ require (
github.com/weppos/publicsuffix-go v0.30.2-0.20230730094716-a20f9abcc222
github.com/xanzy/go-gitlab v0.84.0
go.uber.org/multierr v1.11.0
- golang.org/x/net v0.21.0
+ golang.org/x/net v0.24.0
golang.org/x/oauth2 v0.11.0
golang.org/x/text v0.14.0
gopkg.in/yaml.v2 v2.4.0
@@ -100,7 +100,7 @@ require (
github.com/seh-msft/burpxml v1.0.1
github.com/stretchr/testify v1.9.0
github.com/zmap/zgrab2 v0.1.8-0.20230806160807-97ba87c0e706
- golang.org/x/term v0.17.0
+ golang.org/x/term v0.19.0
gopkg.in/yaml.v3 v3.0.1
moul.io/http2curl v1.0.0
)
@@ -302,10 +302,10 @@ require (
go.etcd.io/bbolt v1.3.8 // indirect
go.uber.org/zap v1.25.0 // indirect
goftp.io/server/v2 v2.0.1 // indirect
- golang.org/x/crypto v0.19.0 // indirect
+ golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
golang.org/x/mod v0.14.0 // indirect
- golang.org/x/sys v0.17.0 // indirect
+ golang.org/x/sys v0.19.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.17.0
google.golang.org/appengine v1.6.7 // indirect
diff --git a/go.sum b/go.sum
index 8557e5d49e..a179db9b28 100644
--- a/go.sum
+++ b/go.sum
@@ -1186,8 +1186,8 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
-golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1280,8 +1280,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
-golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
-golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1382,8 +1382,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -1395,8 +1396,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
-golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
-golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
+golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/pkg/core/engine.go b/pkg/core/engine.go
index 4dfb8e0b93..1b4155bbb5 100644
--- a/pkg/core/engine.go
+++ b/pkg/core/engine.go
@@ -58,5 +58,7 @@ func (e *Engine) ExecuterOptions() protocols.ExecutorOptions {
// WorkPool returns the worker pool for the engine
func (e *Engine) WorkPool() *WorkPool {
+ // resize check point - nop if there are no changes
+ e.workPool.RefreshWithConfig(e.GetWorkPoolConfig())
return e.workPool
}
diff --git a/pkg/core/executors.go b/pkg/core/executors.go
index a6421cd90c..14fb75c631 100644
--- a/pkg/core/executors.go
+++ b/pkg/core/executors.go
@@ -217,7 +217,7 @@ func (e *ChildExecuter) Execute(template *templates.Template, value *contextargs
templateType := template.Type()
// resize check point - nop if there are no changes
- e.e.workPool.RefreshWithConfig(e.e.GetWorkPoolConfig())
+ e.e.WorkPool().RefreshWithConfig(e.e.GetWorkPoolConfig())
var wg *syncutil.AdaptiveWaitGroup
if templateType == types.HeadlessProtocol {
From f886fb2cf4a4e5e4500b3d336cac499e423e9994 Mon Sep 17 00:00:00 2001
From: Mzack9999
Date: Fri, 5 Apr 2024 16:40:37 +0200
Subject: [PATCH 12/58] less templates in test
---
examples/with_speed_control/main.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/with_speed_control/main.go b/examples/with_speed_control/main.go
index 36a26cdd1c..88f7782cf1 100644
--- a/examples/with_speed_control/main.go
+++ b/examples/with_speed_control/main.go
@@ -35,7 +35,7 @@ func main() {
func initializeNucleiEngine() (*nuclei.NucleiEngine, error) {
return nuclei.NewNucleiEngine(
- nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "http"}),
+ nuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{"oast"}}),
nuclei.EnableStatsWithOpts(nuclei.StatsOptions{MetricServerPort: 6064}),
nuclei.WithGlobalRateLimit(1, time.Second),
nuclei.WithConcurrency(nuclei.Concurrency{
From 582a85d9c060ed74e308750f198ccb2f12864629 Mon Sep 17 00:00:00 2001
From: mzack
Date: Tue, 9 Apr 2024 18:31:22 +0200
Subject: [PATCH 13/58] mimic follow behavior
---
cmd/nuclei/main.go | 1 +
internal/runner/inputs.go | 11 +++++++++--
lib/config.go | 6 ++++++
pkg/types/types.go | 2 ++
4 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go
index b6ecd1e452..a90c4ed52e 100644
--- a/cmd/nuclei/main.go
+++ b/cmd/nuclei/main.go
@@ -338,6 +338,7 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.IntVarP(&options.HeadlessTemplateThreads, "headless-concurrency", "headc", 10, "maximum number of headless templates to be executed in parallel"),
flagSet.IntVarP(&options.JsConcurrency, "js-concurrency", "jsc", 120, "maximum number of javascript runtimes to be executed in parallel"),
flagSet.IntVarP(&options.PayloadConcurrency, "payload-concurrency", "pc", 25, "max payload concurrency for each template"),
+ flagSet.IntVarP(&options.ProbeConcurrency, "probe-concurrency", "prc", 50, "http probe concurrency with httpx"),
)
flagSet.CreateGroup("optimization", "Optimizations",
flagSet.IntVar(&options.Timeout, "timeout", 10, "time to wait in seconds before timeout"),
diff --git a/internal/runner/inputs.go b/internal/runner/inputs.go
index 60aa03199f..e7a7c29fae 100644
--- a/internal/runner/inputs.go
+++ b/internal/runner/inputs.go
@@ -30,6 +30,11 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
}
gologger.Info().Msgf("Running httpx on input host")
+ var bulkSize = GlobalProbeBulkSize
+ if r.options.BulkSize > GlobalProbeBulkSize {
+ bulkSize = r.options.BulkSize
+ }
+
httpxOptions := httpx.DefaultOptions
httpxOptions.RetryMax = r.options.Retries
httpxOptions.Timeout = time.Duration(r.options.Timeout) * time.Second
@@ -38,8 +43,10 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
return nil, errors.Wrap(err, "could not create httpx client")
}
+ shouldFollowGlobalProbeBulkSize := bulkSize == GlobalProbeBulkSize
+
// Probe the non-standard URLs and store them in cache
- swg, err := syncutil.New(syncutil.WithSize(GlobalProbeBulkSize))
+ swg, err := syncutil.New(syncutil.WithSize(bulkSize))
if err != nil {
return nil, errors.Wrap(err, "could not create adaptive group")
}
@@ -49,7 +56,7 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
return true
}
- if swg.Size != GlobalProbeBulkSize {
+ if shouldFollowGlobalProbeBulkSize && swg.Size != GlobalProbeBulkSize {
swg.Resize(GlobalProbeBulkSize)
}
diff --git a/lib/config.go b/lib/config.go
index fd4e9c9a97..5b112f8d25 100644
--- a/lib/config.go
+++ b/lib/config.go
@@ -116,6 +116,7 @@ type Concurrency struct {
HeadlessTemplateConcurrency int // number of templates to run concurrently for headless templates (per host in host-spray mode)
JavascriptTemplateConcurrency int // number of templates to run concurrently for javascript templates (per host in host-spray mode)
TemplatePayloadConcurrency int // max concurrent payloads to run for a template (a good default is 25)
+ ProbeConcurrency int // max concurrent http probes to run (a good default is 50)
}
// WithConcurrency sets concurrency options
@@ -152,6 +153,11 @@ func WithConcurrency(opts Concurrency) NucleiSDKOptions {
} else {
e.opts.PayloadConcurrency = opts.TemplatePayloadConcurrency
}
+ if opts.ProbeConcurrency <= 0 {
+ return errors.New("probe concurrency must be at least 1")
+ } else {
+ e.opts.ProbeConcurrency = opts.ProbeConcurrency
+ }
return nil
}
}
diff --git a/pkg/types/types.go b/pkg/types/types.go
index 34f3c3be10..81a2731689 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -385,6 +385,8 @@ type Options struct {
SkipFormatValidation bool
// PayloadConcurrency is the number of concurrent payloads to run per template
PayloadConcurrency int
+ // ProbeConcurrency is the number of concurrent http probes to run with httpx
+ ProbeConcurrency int
// Dast only runs DAST templates
DAST bool
}
From 70eb494a01ed17b665d6a4192e93174104365caa Mon Sep 17 00:00:00 2001
From: mzack
Date: Tue, 9 Apr 2024 18:32:40 +0200
Subject: [PATCH 14/58] warning
---
internal/runner/runner.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/internal/runner/runner.go b/internal/runner/runner.go
index 23443647ea..76678bcb71 100644
--- a/internal/runner/runner.go
+++ b/internal/runner/runner.go
@@ -314,7 +314,7 @@ func New(options *types.Options) (*Runner, error) {
}
if options.RateLimitMinute > 0 {
- gologger.Warning().Msgf("rate limit per minute is deprecated - use rate-limit-duration")
+ gologger.Print().Msgf("[%v] %v", aurora.BrightYellow("WRN"), fmt.Sprintf("rate limit per minute is deprecated - use rate-limit-duration"))
options.RateLimit = options.RateLimitMinute
options.RateLimitDuration = time.Minute
}
From 6eeb98c71bddb4acfa39911fbc0844d475caeb47 Mon Sep 17 00:00:00 2001
From: mzack
Date: Tue, 9 Apr 2024 18:34:04 +0200
Subject: [PATCH 15/58] removing printf
---
internal/runner/runner.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/internal/runner/runner.go b/internal/runner/runner.go
index 76678bcb71..fa9b85bf05 100644
--- a/internal/runner/runner.go
+++ b/internal/runner/runner.go
@@ -314,7 +314,7 @@ func New(options *types.Options) (*Runner, error) {
}
if options.RateLimitMinute > 0 {
- gologger.Print().Msgf("[%v] %v", aurora.BrightYellow("WRN"), fmt.Sprintf("rate limit per minute is deprecated - use rate-limit-duration"))
+ gologger.Print().Msgf("[%v] %v", aurora.BrightYellow("WRN"), "rate limit per minute is deprecated - use rate-limit-duration")
options.RateLimit = options.RateLimitMinute
options.RateLimitDuration = time.Minute
}
From 5fc08cec48cf27029598ed11679b2f9b0bfabf13 Mon Sep 17 00:00:00 2001
From: mzack
Date: Tue, 9 Apr 2024 18:51:25 +0200
Subject: [PATCH 16/58] updating example
---
examples/with_speed_control/main.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/examples/with_speed_control/main.go b/examples/with_speed_control/main.go
index 88f7782cf1..b56df967c5 100644
--- a/examples/with_speed_control/main.go
+++ b/examples/with_speed_control/main.go
@@ -45,6 +45,7 @@ func initializeNucleiEngine() (*nuclei.NucleiEngine, error) {
HeadlessTemplateConcurrency: 1,
JavascriptTemplateConcurrency: 1,
TemplatePayloadConcurrency: 1,
+ ProbeConcurrency: 1,
}),
)
}
From 2ed33e472367c9ffe77062a1723d5b893020e0b8 Mon Sep 17 00:00:00 2001
From: mzack
Date: Thu, 11 Apr 2024 18:57:50 +0200
Subject: [PATCH 17/58] adding dns srv type
---
pkg/protocols/dns/dns.go | 2 ++
pkg/protocols/dns/dns_types.go | 3 +++
2 files changed, 5 insertions(+)
diff --git a/pkg/protocols/dns/dns.go b/pkg/protocols/dns/dns.go
index 80c14d234f..0a1bbca6cb 100644
--- a/pkg/protocols/dns/dns.go
+++ b/pkg/protocols/dns/dns.go
@@ -275,6 +275,8 @@ func questionTypeToInt(questionType string) uint16 {
question = dns.TypeTLSA
case "ANY":
question = dns.TypeANY
+ case "SRV":
+ question = dns.TypeSRV
}
return question
}
diff --git a/pkg/protocols/dns/dns_types.go b/pkg/protocols/dns/dns_types.go
index 034f69b326..8201df2229 100644
--- a/pkg/protocols/dns/dns_types.go
+++ b/pkg/protocols/dns/dns_types.go
@@ -37,6 +37,8 @@ const (
TLSA
// name:ANY
ANY
+ // name:SRV
+ SRV
limit
)
@@ -54,6 +56,7 @@ var DNSRequestTypeMapping = map[DNSRequestType]string{
CAA: "CAA",
TLSA: "TLSA",
ANY: "ANY",
+ SRV: "SRV",
}
// GetSupportedDNSRequestTypes returns list of supported types
From 7b71886309f65880aaff351409409f678bea3caa Mon Sep 17 00:00:00 2001
From: mzack
Date: Thu, 11 Apr 2024 19:10:31 +0200
Subject: [PATCH 18/58] Fixing internal resolver override
---
pkg/protocols/common/protocolstate/state.go | 2 +-
pkg/protocols/dns/dnsclientpool/clientpool.go | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/pkg/protocols/common/protocolstate/state.go b/pkg/protocols/common/protocolstate/state.go
index e6bacb4915..08db87295b 100644
--- a/pkg/protocols/common/protocolstate/state.go
+++ b/pkg/protocols/common/protocolstate/state.go
@@ -124,7 +124,7 @@ func Init(options *types.Options) error {
opts.ResolversFile = true
opts.EnableFallback = true
}
- if options.ResolversFile != "" {
+ if options.ResolversFile != "" || len(options.InternalResolversList) > 0 {
opts.BaseResolvers = options.InternalResolversList
}
diff --git a/pkg/protocols/dns/dnsclientpool/clientpool.go b/pkg/protocols/dns/dnsclientpool/clientpool.go
index fd632bd110..3f5126c824 100644
--- a/pkg/protocols/dns/dnsclientpool/clientpool.go
+++ b/pkg/protocols/dns/dnsclientpool/clientpool.go
@@ -34,7 +34,7 @@ func Init(options *types.Options) error {
clientPool = make(map[string]*retryabledns.Client)
resolvers := defaultResolvers
- if options.ResolversFile != "" {
+ if options.ResolversFile != "" || len(options.InternalResolversList) > 0 {
resolvers = options.InternalResolversList
}
var err error
@@ -78,7 +78,7 @@ func Get(options *types.Options, configuration *Configuration) (*retryabledns.Cl
poolMutex.RUnlock()
resolvers := defaultResolvers
- if options.ResolversFile != "" {
+ if options.ResolversFile != "" || len(options.InternalResolversList) > 0 {
resolvers = options.InternalResolversList
} else if len(configuration.Resolvers) > 0 {
resolvers = configuration.Resolvers
From f4394b8e1158ce4efae5ee05ee4bf1e2231e036f Mon Sep 17 00:00:00 2001
From: mzack
Date: Thu, 11 Apr 2024 19:34:03 +0200
Subject: [PATCH 19/58] Adding networkpolicy to httpx probes
---
internal/runner/inputs.go | 2 ++
1 file changed, 2 insertions(+)
diff --git a/internal/runner/inputs.go b/internal/runner/inputs.go
index e7a7c29fae..87fbb573e1 100644
--- a/internal/runner/inputs.go
+++ b/internal/runner/inputs.go
@@ -10,6 +10,7 @@ import (
"github.com/projectdiscovery/httpx/common/httpx"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
stringsutil "github.com/projectdiscovery/utils/strings"
syncutil "github.com/projectdiscovery/utils/sync"
@@ -38,6 +39,7 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
httpxOptions := httpx.DefaultOptions
httpxOptions.RetryMax = r.options.Retries
httpxOptions.Timeout = time.Duration(r.options.Timeout) * time.Second
+ httpxOptions.NetworkPolicy = protocolstate.NetworkPolicy
httpxClient, err := httpx.New(&httpxOptions)
if err != nil {
return nil, errors.Wrap(err, "could not create httpx client")
From 0807113e6c445171b62a0a18a53a0bd09f950af5 Mon Sep 17 00:00:00 2001
From: mzack
Date: Fri, 12 Apr 2024 00:02:43 +0200
Subject: [PATCH 20/58] adding more query types test
---
cmd/integration-test/dns.go | 18 +++++++++++++++---
.../protocols/dns/{basic.yaml => a.yaml} | 6 +++---
integration_tests/protocols/dns/aaaa.yaml | 17 +++++++++++++++++
3 files changed, 35 insertions(+), 6 deletions(-)
rename integration_tests/protocols/dns/{basic.yaml => a.yaml} (69%)
create mode 100644 integration_tests/protocols/dns/aaaa.yaml
diff --git a/cmd/integration-test/dns.go b/cmd/integration-test/dns.go
index 982aab98b0..9f1883716a 100644
--- a/cmd/integration-test/dns.go
+++ b/cmd/integration-test/dns.go
@@ -5,7 +5,8 @@ import (
)
var dnsTestCases = []TestCaseInfo{
- {Path: "protocols/dns/basic.yaml", TestCase: &dnsBasic{}},
+ {Path: "protocols/dns/a.yaml", TestCase: &dnsA{}},
+ {Path: "protocols/dns/aaaa.yaml", TestCase: &dnsAAAA{}},
{Path: "protocols/dns/ptr.yaml", TestCase: &dnsPtr{}},
{Path: "protocols/dns/caa.yaml", TestCase: &dnsCAA{}},
{Path: "protocols/dns/tlsa.yaml", TestCase: &dnsTLSA{}},
@@ -14,10 +15,21 @@ var dnsTestCases = []TestCaseInfo{
{Path: "protocols/dns/dsl-matcher-variable.yaml", TestCase: &dnsDSLMatcherVariable{}},
}
-type dnsBasic struct{}
+type dnsA struct{}
// Execute executes a test case and returns an error if occurred
-func (h *dnsBasic) Execute(filePath string) error {
+func (h *dnsA) Execute(filePath string) error {
+ results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "one.one.one.one", debug)
+ if err != nil {
+ return err
+ }
+ return expectResultsCount(results, 1)
+}
+
+type dnsAAAA struct{}
+
+// Execute executes a test case and returns an error if occurred
+func (h *dnsAAAA) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "one.one.one.one", debug)
if err != nil {
return err
diff --git a/integration_tests/protocols/dns/basic.yaml b/integration_tests/protocols/dns/a.yaml
similarity index 69%
rename from integration_tests/protocols/dns/basic.yaml
rename to integration_tests/protocols/dns/a.yaml
index b0dbdf3b02..5389746262 100644
--- a/integration_tests/protocols/dns/basic.yaml
+++ b/integration_tests/protocols/dns/a.yaml
@@ -1,7 +1,7 @@
-id: basic-dns-example
+id: basic-dns-a-example
info:
- name: Test DNS Template
+ name: Test DNS A Query Template
author: pdteam
severity: info
@@ -14,4 +14,4 @@ dns:
matchers:
- type: word
words:
- - "1.1.1.1"
\ No newline at end of file
+ - "1.1.1.1"
diff --git a/integration_tests/protocols/dns/aaaa.yaml b/integration_tests/protocols/dns/aaaa.yaml
new file mode 100644
index 0000000000..3df3293a9b
--- /dev/null
+++ b/integration_tests/protocols/dns/aaaa.yaml
@@ -0,0 +1,17 @@
+id: basic-dns-aaaa-example
+
+info:
+ name: Test DNS AAAA Query Template
+ author: pdteam
+ severity: info
+
+dns:
+ - name: "{{FQDN}}"
+ type: AAAA
+ class: inet
+ recursion: true
+ retries: 3
+ matchers:
+ - type: word
+ words:
+ - "2606:4700:4700::1001"
From 8f084eedd63b3892f90d8d58b9d1ba73074d9fda Mon Sep 17 00:00:00 2001
From: mzack
Date: Fri, 12 Apr 2024 00:22:11 +0200
Subject: [PATCH 21/58] adding more tests
---
cmd/integration-test/dns.go | 23 +++++++------------
cmd/nuclei/srv.yaml | 18 +++++++++++++++
.../protocols/code/pre-condition.yaml | 2 +-
.../protocols/code/py-env-var.yaml | 2 +-
integration_tests/protocols/code/py-file.yaml | 2 +-
.../protocols/code/py-interactsh.yaml | 2 +-
.../protocols/code/py-snippet.yaml | 2 +-
integration_tests/protocols/dns/a.yaml | 2 +-
integration_tests/protocols/dns/aaaa.yaml | 2 +-
integration_tests/protocols/dns/cname.yaml | 18 +++++++++++++++
integration_tests/protocols/dns/ns.yaml | 18 +++++++++++++++
integration_tests/protocols/dns/srv.yaml | 18 +++++++++++++++
integration_tests/protocols/dns/txt.yaml | 18 +++++++++++++++
13 files changed, 105 insertions(+), 22 deletions(-)
create mode 100644 cmd/nuclei/srv.yaml
create mode 100644 integration_tests/protocols/dns/cname.yaml
create mode 100644 integration_tests/protocols/dns/ns.yaml
create mode 100644 integration_tests/protocols/dns/srv.yaml
create mode 100644 integration_tests/protocols/dns/txt.yaml
diff --git a/cmd/integration-test/dns.go b/cmd/integration-test/dns.go
index 9f1883716a..82cf9c91c4 100644
--- a/cmd/integration-test/dns.go
+++ b/cmd/integration-test/dns.go
@@ -5,8 +5,12 @@ import (
)
var dnsTestCases = []TestCaseInfo{
- {Path: "protocols/dns/a.yaml", TestCase: &dnsA{}},
- {Path: "protocols/dns/aaaa.yaml", TestCase: &dnsAAAA{}},
+ {Path: "protocols/dns/a.yaml", TestCase: &dnsBasic{}},
+ {Path: "protocols/dns/aaaa.yaml", TestCase: &dnsBasic{}},
+ {Path: "protocols/dns/cname.yaml", TestCase: &dnsBasic{}},
+ {Path: "protocols/dns/srv.yaml", TestCase: &dnsBasic{}},
+ {Path: "protocols/dns/ns.yaml", TestCase: &dnsBasic{}},
+ {Path: "protocols/dns/txt.yaml", TestCase: &dnsBasic{}},
{Path: "protocols/dns/ptr.yaml", TestCase: &dnsPtr{}},
{Path: "protocols/dns/caa.yaml", TestCase: &dnsCAA{}},
{Path: "protocols/dns/tlsa.yaml", TestCase: &dnsTLSA{}},
@@ -15,21 +19,10 @@ var dnsTestCases = []TestCaseInfo{
{Path: "protocols/dns/dsl-matcher-variable.yaml", TestCase: &dnsDSLMatcherVariable{}},
}
-type dnsA struct{}
+type dnsBasic struct{}
// Execute executes a test case and returns an error if occurred
-func (h *dnsA) Execute(filePath string) error {
- results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "one.one.one.one", debug)
- if err != nil {
- return err
- }
- return expectResultsCount(results, 1)
-}
-
-type dnsAAAA struct{}
-
-// Execute executes a test case and returns an error if occurred
-func (h *dnsAAAA) Execute(filePath string) error {
+func (h *dnsBasic) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "one.one.one.one", debug)
if err != nil {
return err
diff --git a/cmd/nuclei/srv.yaml b/cmd/nuclei/srv.yaml
new file mode 100644
index 0000000000..198b397cea
--- /dev/null
+++ b/cmd/nuclei/srv.yaml
@@ -0,0 +1,18 @@
+id: basic-dns-a-example
+
+info:
+ name: Test DNS A Query Template
+ author: pdteam
+ severity: info
+
+dns:
+ - name: "{{FQDN}}"
+ type: SRV
+ class: inet
+ recursion: true
+ retries: 3
+ matchers:
+ - type: word
+ part: all
+ words:
+ - "SRV"
diff --git a/integration_tests/protocols/code/pre-condition.yaml b/integration_tests/protocols/code/pre-condition.yaml
index a61b4f90d8..1c44e9579a 100644
--- a/integration_tests/protocols/code/pre-condition.yaml
+++ b/integration_tests/protocols/code/pre-condition.yaml
@@ -23,4 +23,4 @@ code:
- type: dsl
dsl:
- true
-# digest: 4a0a00473045022100c7215ce9f11e6a51c193bb54643a05cdd1cde18a3abb6c9983c5c7524d3ff03002203d93581c81d3ad5db463570cbbd2bdee529328d32a5b00e037610c211e448cef:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
+# digest: 490a004630440220192fb8f704b078c2885047b85ac1a0491be86485c033a976d201599683a35aab0220604b1c3781e9d97079d0e5c23c18e6a2d87493c8e2b930536e692ee7d06e9247:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
diff --git a/integration_tests/protocols/code/py-env-var.yaml b/integration_tests/protocols/code/py-env-var.yaml
index 4ccf3648bf..9ff947c208 100644
--- a/integration_tests/protocols/code/py-env-var.yaml
+++ b/integration_tests/protocols/code/py-env-var.yaml
@@ -20,4 +20,4 @@ code:
- type: word
words:
- "hello from input baz"
-# digest: 4a0a0047304502207e3a5eda5f3207c3c01c820562243281926c1215224a7c80ed7528559b9f52cb022100f6ef99bb45843f481705778630f2cfd8f4d1cc3acb96392ff016f22e06aa91af:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
+# digest: 4a0a00473045022033f72f1b9d5143f58a2dc79c2597000f34080251ac3702c36c3fad00917dfeeb0221009ba05c715c9e2e36dba471be6c0106a09ae3822d8a3e9e4bcf377e9f4a395a01:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
diff --git a/integration_tests/protocols/code/py-file.yaml b/integration_tests/protocols/code/py-file.yaml
index 9e0b041bcf..ad69371d05 100644
--- a/integration_tests/protocols/code/py-file.yaml
+++ b/integration_tests/protocols/code/py-file.yaml
@@ -18,4 +18,4 @@ code:
- type: word
words:
- "hello from input"
-# digest: 4a0a004730450220069673af9bd6d6677f9529d06f5d8bd46d543089a4731ed18ee806761d75fd60022100913a3e27b0a5809baf710ba9585bf9fe729634c0e19e3e13eef70a6bd100df34:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
+# digest: 4a0a004730450220377128cb11d9f6f0fee1f4dbd841e46783de26e90a216fa55a7609ee2bc823c60221009166ee0f85e3a1811588ab19e73ea96ab3d582dc8180dbcbbad0ea9ab7e9025d:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
diff --git a/integration_tests/protocols/code/py-interactsh.yaml b/integration_tests/protocols/code/py-interactsh.yaml
index 24e4b06241..76d14efb2e 100644
--- a/integration_tests/protocols/code/py-interactsh.yaml
+++ b/integration_tests/protocols/code/py-interactsh.yaml
@@ -26,4 +26,4 @@ code:
part: interactsh_protocol
words:
- "http"
-# digest: 490a00463044022003b8d069e3c84412729c43e33013a52ee04eabcf096d511979691d71d8e905f60220011f4475899abed4f86b4bd5e6c2423750759135206e4729826afe1ed8a44f4d:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
+# digest: 4b0a00483046022100d472d50bd83117d334f5217c7a40dcdf34138e90029eaace51697d902296bf37022100a393b49420a96f60d6d89b79b5135ee2233b2468d374851890eea114b08195d1:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
diff --git a/integration_tests/protocols/code/py-snippet.yaml b/integration_tests/protocols/code/py-snippet.yaml
index 287ca2c6eb..4837fa7e0e 100644
--- a/integration_tests/protocols/code/py-snippet.yaml
+++ b/integration_tests/protocols/code/py-snippet.yaml
@@ -21,4 +21,4 @@ code:
- type: word
words:
- "hello from input"
-# digest: 4a0a00473045022100c291615cf2a8005450c17a6554e81a9cdab14743b299f0679c644247929198b502206fdacc8ab173bde2b4015340012637916bf2659f66f320fcc06b97ac639072a1:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
+# digest: 4b0a004830460221008886054bb5dd6345e434e30f31c8fddce3c484a4f33aa6321b5185675866029d022100d188a83d0fde029f8b586061c65ab72b43755c3fb10fdd59501bb9bbadbb1ff7:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
diff --git a/integration_tests/protocols/dns/a.yaml b/integration_tests/protocols/dns/a.yaml
index 5389746262..0e51245817 100644
--- a/integration_tests/protocols/dns/a.yaml
+++ b/integration_tests/protocols/dns/a.yaml
@@ -1,4 +1,4 @@
-id: basic-dns-a-example
+id: dns-a-query-example
info:
name: Test DNS A Query Template
diff --git a/integration_tests/protocols/dns/aaaa.yaml b/integration_tests/protocols/dns/aaaa.yaml
index 3df3293a9b..58a2e496ce 100644
--- a/integration_tests/protocols/dns/aaaa.yaml
+++ b/integration_tests/protocols/dns/aaaa.yaml
@@ -1,4 +1,4 @@
-id: basic-dns-aaaa-example
+id: dns-aaaa-query-example
info:
name: Test DNS AAAA Query Template
diff --git a/integration_tests/protocols/dns/cname.yaml b/integration_tests/protocols/dns/cname.yaml
new file mode 100644
index 0000000000..f4ddb8a2ed
--- /dev/null
+++ b/integration_tests/protocols/dns/cname.yaml
@@ -0,0 +1,18 @@
+id: dns-cname-query-example
+
+info:
+ name: Test DNS CNAME Query Template
+ author: pdteam
+ severity: info
+
+dns:
+ - name: "{{FQDN}}"
+ type: CNAME
+ class: inet
+ recursion: true
+ retries: 3
+ matchers:
+ - type: word
+ part: all
+ words:
+ - "CNAME"
diff --git a/integration_tests/protocols/dns/ns.yaml b/integration_tests/protocols/dns/ns.yaml
new file mode 100644
index 0000000000..9d40655743
--- /dev/null
+++ b/integration_tests/protocols/dns/ns.yaml
@@ -0,0 +1,18 @@
+id: dns-ns-query-example
+
+info:
+ name: Test DNS NS Query Template
+ author: pdteam
+ severity: info
+
+dns:
+ - name: "{{FQDN}}"
+ type: NS
+ class: inet
+ recursion: true
+ retries: 3
+ matchers:
+ - type: word
+ part: all
+ words:
+ - "NS"
diff --git a/integration_tests/protocols/dns/srv.yaml b/integration_tests/protocols/dns/srv.yaml
new file mode 100644
index 0000000000..2669333c5e
--- /dev/null
+++ b/integration_tests/protocols/dns/srv.yaml
@@ -0,0 +1,18 @@
+id: dns-a-query-example
+
+info:
+ name: Test DNS SRV Query Template
+ author: pdteam
+ severity: info
+
+dns:
+ - name: "{{FQDN}}"
+ type: SRV
+ class: inet
+ recursion: true
+ retries: 3
+ matchers:
+ - type: word
+ part: all
+ words:
+ - "SRV"
diff --git a/integration_tests/protocols/dns/txt.yaml b/integration_tests/protocols/dns/txt.yaml
new file mode 100644
index 0000000000..273a53ab7d
--- /dev/null
+++ b/integration_tests/protocols/dns/txt.yaml
@@ -0,0 +1,18 @@
+id: dns-txt-query-example
+
+info:
+ name: Test DNS TXT Query Template
+ author: pdteam
+ severity: info
+
+dns:
+ - name: "{{FQDN}}"
+ type: TXT
+ class: inet
+ recursion: true
+ retries: 3
+ matchers:
+ - type: word
+ part: all
+ words:
+ - "TXT"
From 38e185c410c7ed85bb44ccc467780808eafe97db Mon Sep 17 00:00:00 2001
From: mzack
Date: Fri, 12 Apr 2024 00:32:06 +0200
Subject: [PATCH 22/58] simpler logic
---
pkg/protocols/common/protocolstate/state.go | 2 +-
pkg/protocols/dns/dnsclientpool/clientpool.go | 4 ++--
pkg/types/types.go | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/pkg/protocols/common/protocolstate/state.go b/pkg/protocols/common/protocolstate/state.go
index 08db87295b..3298c91366 100644
--- a/pkg/protocols/common/protocolstate/state.go
+++ b/pkg/protocols/common/protocolstate/state.go
@@ -124,7 +124,7 @@ func Init(options *types.Options) error {
opts.ResolversFile = true
opts.EnableFallback = true
}
- if options.ResolversFile != "" || len(options.InternalResolversList) > 0 {
+ if len(options.InternalResolversList) > 0 {
opts.BaseResolvers = options.InternalResolversList
}
diff --git a/pkg/protocols/dns/dnsclientpool/clientpool.go b/pkg/protocols/dns/dnsclientpool/clientpool.go
index 3f5126c824..4f019808fe 100644
--- a/pkg/protocols/dns/dnsclientpool/clientpool.go
+++ b/pkg/protocols/dns/dnsclientpool/clientpool.go
@@ -34,7 +34,7 @@ func Init(options *types.Options) error {
clientPool = make(map[string]*retryabledns.Client)
resolvers := defaultResolvers
- if options.ResolversFile != "" || len(options.InternalResolversList) > 0 {
+ if len(options.InternalResolversList) > 0 {
resolvers = options.InternalResolversList
}
var err error
@@ -78,7 +78,7 @@ func Get(options *types.Options, configuration *Configuration) (*retryabledns.Cl
poolMutex.RUnlock()
resolvers := defaultResolvers
- if options.ResolversFile != "" || len(options.InternalResolversList) > 0 {
+ if len(options.InternalResolversList) > 0 {
resolvers = options.InternalResolversList
} else if len(configuration.Resolvers) > 0 {
resolvers = configuration.Resolvers
diff --git a/pkg/types/types.go b/pkg/types/types.go
index 81a2731689..c251dc0c85 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -66,8 +66,8 @@ type Options struct {
IncludeIds goflags.StringSlice
// ExcludeIds contains templates ids to not be executed
ExcludeIds goflags.StringSlice
-
- InternalResolversList []string // normalized from resolvers flag as well as file provided.
+ // InternalResolversList is the list of internal resolvers to use
+ InternalResolversList []string
// ProjectPath allows nuclei to use a user defined project folder
ProjectPath string
// InteractshURL is the URL for the interactsh server.
From 0d5e26d7bdac2631c3f208765cd203e62097ce43 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Levente=20Kov=C3=A1ts?=
<22732484+tovask@users.noreply.github.com>
Date: Fri, 12 Apr 2024 00:50:11 +0200
Subject: [PATCH 23/58] run workflow subtemplates with new ScanContext (#5031)
fix projectdiscovery/nuclei#4933
---
cmd/integration-test/workflow.go | 26 +++++++++++++++++++
.../workflow/complex-conditions.yaml | 23 ++++++++++++++++
integration_tests/workflow/match-3.yaml | 16 ++++++++++++
pkg/core/workflow_execute.go | 8 ++++--
4 files changed, 71 insertions(+), 2 deletions(-)
create mode 100644 integration_tests/workflow/complex-conditions.yaml
create mode 100644 integration_tests/workflow/match-3.yaml
diff --git a/cmd/integration-test/workflow.go b/cmd/integration-test/workflow.go
index 3ffd28871e..8672283b6c 100644
--- a/cmd/integration-test/workflow.go
+++ b/cmd/integration-test/workflow.go
@@ -5,6 +5,7 @@ import (
"io"
"net/http"
"net/http/httptest"
+ "strings"
"github.com/julienschmidt/httprouter"
@@ -16,6 +17,7 @@ var workflowTestcases = []TestCaseInfo{
{Path: "workflow/condition-matched.yaml", TestCase: &workflowConditionMatched{}},
{Path: "workflow/condition-unmatched.yaml", TestCase: &workflowConditionUnmatch{}},
{Path: "workflow/matcher-name.yaml", TestCase: &workflowMatcherName{}},
+ {Path: "workflow/complex-conditions.yaml", TestCase: &workflowComplexConditions{}},
{Path: "workflow/http-value-share-workflow.yaml", TestCase: &workflowHttpKeyValueShare{}},
{Path: "workflow/dns-value-share-workflow.yaml", TestCase: &workflowDnsKeyValueShare{}},
{Path: "workflow/shared-cookie.yaml", TestCase: &workflowSharedCookies{}},
@@ -97,6 +99,30 @@ func (h *workflowMatcherName) Execute(filePath string) error {
return expectResultsCount(results, 1)
}
+type workflowComplexConditions struct{}
+
+// Execute executes a test case and returns an error if occurred
+func (h *workflowComplexConditions) Execute(filePath string) error {
+ router := httprouter.New()
+ router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+ fmt.Fprintf(w, "This is test matcher text")
+ })
+ ts := httptest.NewServer(router)
+ defer ts.Close()
+
+ results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
+ if err != nil {
+ return err
+ }
+
+ for _, result := range results {
+ if !strings.Contains(result, "test-matcher-3") {
+ return fmt.Errorf("incorrect result: the \"basic-get-third:test-matcher-3\" and only that should be matched!\nResults:\n\t%s", strings.Join(results, "\n\t"))
+ }
+ }
+ return expectResultsCount(results, 2)
+}
+
type workflowHttpKeyValueShare struct{}
// Execute executes a test case and returns an error if occurred
diff --git a/integration_tests/workflow/complex-conditions.yaml b/integration_tests/workflow/complex-conditions.yaml
new file mode 100644
index 0000000000..bd1e66be5d
--- /dev/null
+++ b/integration_tests/workflow/complex-conditions.yaml
@@ -0,0 +1,23 @@
+id: complex-conditions-workflow
+
+info:
+ name: Complex Conditions Workflow
+ author: tovask
+ severity: info
+ description: Workflow to test a complex scenario, e.g. race conditions when evaluating the results of the templates
+
+workflows:
+ - template: workflow/match-1.yaml
+ subtemplates:
+ - template: workflow/nomatch-1.yaml
+ subtemplates:
+ - template: workflow/match-2.yaml
+ - template: workflow/match-3.yaml
+ - template: workflow/match-2.yaml
+ matchers:
+ - name: test-matcher
+ subtemplates:
+ - template: workflow/nomatch-1.yaml
+ subtemplates:
+ - template: workflow/match-1.yaml
+ - template: workflow/match-3.yaml
diff --git a/integration_tests/workflow/match-3.yaml b/integration_tests/workflow/match-3.yaml
new file mode 100644
index 0000000000..b2e23fb993
--- /dev/null
+++ b/integration_tests/workflow/match-3.yaml
@@ -0,0 +1,16 @@
+id: basic-get-third
+
+info:
+ name: Basic 3rd GET Request
+ author: tovask
+ severity: info
+
+http:
+ - method: GET
+ path:
+ - "{{BaseURL}}"
+ matchers:
+ - type: word
+ name: test-matcher-3
+ words:
+ - "This is test matcher text"
diff --git a/pkg/core/workflow_execute.go b/pkg/core/workflow_execute.go
index 19d6f0d69d..c17a1af8d5 100644
--- a/pkg/core/workflow_execute.go
+++ b/pkg/core/workflow_execute.go
@@ -138,7 +138,9 @@ func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan
go func(subtemplate *workflows.WorkflowTemplate) {
defer swg.Done()
- if err := e.runWorkflowStep(subtemplate, ctx, results, swg, w); err != nil {
+ // create a new context with the same input but with unset callbacks
+ subCtx := scan.NewScanContext(ctx.Input)
+ if err := e.runWorkflowStep(subtemplate, subCtx, results, swg, w); err != nil {
gologger.Warning().Msgf(workflowStepExecutionError, subtemplate.Template, err)
}
}(subtemplate)
@@ -162,7 +164,9 @@ func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan
swg.Add()
go func(template *workflows.WorkflowTemplate) {
- if err := e.runWorkflowStep(template, ctx, results, swg, w); err != nil {
+ // create a new context with the same input but with unset callbacks
+ subCtx := scan.NewScanContext(ctx.Input)
+ if err := e.runWorkflowStep(template, subCtx, results, swg, w); err != nil {
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
}
swg.Done()
From c145adb3bf724218d283a35d6ffe1820b367bfcf Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 15 Apr 2024 05:35:43 +0000
Subject: [PATCH 24/58] chore(deps): bump github.com/projectdiscovery/goflags
Bumps [github.com/projectdiscovery/goflags](https://github.com/projectdiscovery/goflags) from 0.1.46 to 0.1.48.
- [Release notes](https://github.com/projectdiscovery/goflags/releases)
- [Commits](https://github.com/projectdiscovery/goflags/compare/v0.1.46...v0.1.48)
---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/goflags
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
go.mod | 4 ++--
go.sum | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/go.mod b/go.mod
index 2f239bc148..f7fb5beabf 100644
--- a/go.mod
+++ b/go.mod
@@ -81,7 +81,7 @@ require (
github.com/projectdiscovery/dsl v0.0.51
github.com/projectdiscovery/fasttemplate v0.0.2
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb
- github.com/projectdiscovery/goflags v0.1.46
+ github.com/projectdiscovery/goflags v0.1.48
github.com/projectdiscovery/gologger v1.1.12
github.com/projectdiscovery/gostruct v0.0.2
github.com/projectdiscovery/gozero v0.0.2
@@ -94,7 +94,7 @@ require (
github.com/projectdiscovery/tlsx v1.1.6
github.com/projectdiscovery/uncover v1.0.7
github.com/projectdiscovery/useragent v0.0.40
- github.com/projectdiscovery/utils v0.0.88-0.20240404181359-663cfe2196d0
+ github.com/projectdiscovery/utils v0.0.88
github.com/projectdiscovery/wappalyzergo v0.0.116
github.com/redis/go-redis/v9 v9.1.0
github.com/seh-msft/burpxml v1.0.1
diff --git a/go.sum b/go.sum
index bf4e00c357..607f14df2f 100644
--- a/go.sum
+++ b/go.sum
@@ -842,8 +842,8 @@ github.com/projectdiscovery/freeport v0.0.5 h1:jnd3Oqsl4S8n0KuFkE5Hm8WGDP24ITBvm
github.com/projectdiscovery/freeport v0.0.5/go.mod h1:PY0bxSJ34HVy67LHIeF3uIutiCSDwOqKD8ruBkdiCwE=
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb h1:rutG906Drtbpz4DwU5mhGIeOhRcktDH4cGQitGUMAsg=
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb/go.mod h1:FLjF1DmZ+POoGEiIQdWuYVwS++C/GwpX8YaCsTSm1RY=
-github.com/projectdiscovery/goflags v0.1.46 h1:JlYvFxJcimKJGWYbygiFBN052MWrbls/kKiwOKpLzEE=
-github.com/projectdiscovery/goflags v0.1.46/go.mod h1:X7A6ELNgczyOyEy2gyNC/tJTuhtwQk6ZLyzsnDVlZkw=
+github.com/projectdiscovery/goflags v0.1.48 h1:iJgq+YuQOEm0V7BqlV/0wx7GaI2rmGiFzPkvL/4sCj0=
+github.com/projectdiscovery/goflags v0.1.48/go.mod h1:C9Qqyivehk5iYCf3VAC3+85y60Qbde1ZBMqe5wHhcVw=
github.com/projectdiscovery/gologger v1.1.12 h1:uX/QkQdip4PubJjjG0+uk5DtyAi1ANPJUvpmimXqv4A=
github.com/projectdiscovery/gologger v1.1.12/go.mod h1:DI8nywPLERS5mo8QEA9E7gd5HZ3Je14SjJBH3F5/kLw=
github.com/projectdiscovery/gostruct v0.0.2 h1:s8gP8ApugGM4go1pA+sVlPDXaWqNP5BBDDSv7VEdG1M=
@@ -886,8 +886,8 @@ github.com/projectdiscovery/uncover v1.0.7 h1:ut+2lTuvmftmveqF5RTjMWAgyLj8ltPQC7
github.com/projectdiscovery/uncover v1.0.7/go.mod h1:HFXgm1sRPuoN0D4oATljPIdmbo/EEh1wVuxQqo/dwFE=
github.com/projectdiscovery/useragent v0.0.40 h1:1LUhReSGPkhqsM5n40OOC9dIoNqMGs1dyGFJcOmg2Fo=
github.com/projectdiscovery/useragent v0.0.40/go.mod h1:EvK1x3s948Gtqb/XOahXcauyejCL/rSgy5d1IAvsKT4=
-github.com/projectdiscovery/utils v0.0.88-0.20240404181359-663cfe2196d0 h1:2ZR0yiN0cUm/qYEMq79MfcbgM374lJSdftheYhMFxNo=
-github.com/projectdiscovery/utils v0.0.88-0.20240404181359-663cfe2196d0/go.mod h1:lAWzFdGXtJRPKdhUu1Z46d8B8JbASTk1Z69WY6H/3kA=
+github.com/projectdiscovery/utils v0.0.88 h1:oYfCXM+8VHNLyH/H6cOibkuDUwHUAOBAMRNPFX6NPrs=
+github.com/projectdiscovery/utils v0.0.88/go.mod h1:lAWzFdGXtJRPKdhUu1Z46d8B8JbASTk1Z69WY6H/3kA=
github.com/projectdiscovery/wappalyzergo v0.0.116 h1:xy+mBpwbYo/0PSzmJOQ/RXHomEh0D3nDBcbCxsW69m8=
github.com/projectdiscovery/wappalyzergo v0.0.116/go.mod h1:hc/o+fgM8KtdpFesjfBTmHTwsR+yBd+4kYZW/DGy/x8=
github.com/projectdiscovery/yamldoc-go v1.0.4 h1:eZoESapnMw6WAHiVgRwNqvbJEfNHEH148uthhFbG5jE=
From c8824ac71a51414977cf98592008d033fccb1893 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 15 Apr 2024 05:35:54 +0000
Subject: [PATCH 25/58] chore(deps): bump
github.com/projectdiscovery/fastdialer
Bumps [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer) from 0.0.66 to 0.0.67.
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.0.66...v0.0.67)
---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/fastdialer
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
go.mod | 4 ++--
go.sum | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/go.mod b/go.mod
index 2f239bc148..9c3ea9e3c8 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/errors v0.9.1
github.com/projectdiscovery/clistats v0.0.20
- github.com/projectdiscovery/fastdialer v0.0.66
+ github.com/projectdiscovery/fastdialer v0.0.67
github.com/projectdiscovery/hmap v0.0.41
github.com/projectdiscovery/interactsh v1.1.9
github.com/projectdiscovery/rawhttp v0.1.44
@@ -94,7 +94,7 @@ require (
github.com/projectdiscovery/tlsx v1.1.6
github.com/projectdiscovery/uncover v1.0.7
github.com/projectdiscovery/useragent v0.0.40
- github.com/projectdiscovery/utils v0.0.88-0.20240404181359-663cfe2196d0
+ github.com/projectdiscovery/utils v0.0.88
github.com/projectdiscovery/wappalyzergo v0.0.116
github.com/redis/go-redis/v9 v9.1.0
github.com/seh-msft/burpxml v1.0.1
diff --git a/go.sum b/go.sum
index bf4e00c357..22e511931e 100644
--- a/go.sum
+++ b/go.sum
@@ -834,8 +834,8 @@ github.com/projectdiscovery/clistats v0.0.20 h1:5jO5SLiRJ7f0nDV0ndBNmBeesbROouPo
github.com/projectdiscovery/clistats v0.0.20/go.mod h1:GJ2av0KnOvK0AISQnP8hyDclYIji1LVkx2l0pwnzAu4=
github.com/projectdiscovery/dsl v0.0.51 h1:7OQPumOrrUCFnCA7Y0nchhPvRo3IJGMIJ2Oy4DVTQsc=
github.com/projectdiscovery/dsl v0.0.51/go.mod h1:GYhusn+T9EL7t+iJ8zN/GXlp8ohLGU+Yv/nevAPlJZg=
-github.com/projectdiscovery/fastdialer v0.0.66 h1:DRpmok9TArLyQKaSjRWSzikt2N2Qyzx/z0BmTmDyJvI=
-github.com/projectdiscovery/fastdialer v0.0.66/go.mod h1:7uPrwFsIBhtUBkXd72K4VSo9lvcwqOzOGXIZ9UZXFYw=
+github.com/projectdiscovery/fastdialer v0.0.67 h1:NvBpZUiLr9Ne9N+Lvi6FFiNNLWuhk5Bc1H+oE9J8C1E=
+github.com/projectdiscovery/fastdialer v0.0.67/go.mod h1:GhSAKnojJN8N9K0JNjLmwLCmEDsQ5cBAStqSCm/tm84=
github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA=
github.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw=
github.com/projectdiscovery/freeport v0.0.5 h1:jnd3Oqsl4S8n0KuFkE5Hm8WGDP24ITBvmyw5pFTHS8Q=
@@ -886,8 +886,8 @@ github.com/projectdiscovery/uncover v1.0.7 h1:ut+2lTuvmftmveqF5RTjMWAgyLj8ltPQC7
github.com/projectdiscovery/uncover v1.0.7/go.mod h1:HFXgm1sRPuoN0D4oATljPIdmbo/EEh1wVuxQqo/dwFE=
github.com/projectdiscovery/useragent v0.0.40 h1:1LUhReSGPkhqsM5n40OOC9dIoNqMGs1dyGFJcOmg2Fo=
github.com/projectdiscovery/useragent v0.0.40/go.mod h1:EvK1x3s948Gtqb/XOahXcauyejCL/rSgy5d1IAvsKT4=
-github.com/projectdiscovery/utils v0.0.88-0.20240404181359-663cfe2196d0 h1:2ZR0yiN0cUm/qYEMq79MfcbgM374lJSdftheYhMFxNo=
-github.com/projectdiscovery/utils v0.0.88-0.20240404181359-663cfe2196d0/go.mod h1:lAWzFdGXtJRPKdhUu1Z46d8B8JbASTk1Z69WY6H/3kA=
+github.com/projectdiscovery/utils v0.0.88 h1:oYfCXM+8VHNLyH/H6cOibkuDUwHUAOBAMRNPFX6NPrs=
+github.com/projectdiscovery/utils v0.0.88/go.mod h1:lAWzFdGXtJRPKdhUu1Z46d8B8JbASTk1Z69WY6H/3kA=
github.com/projectdiscovery/wappalyzergo v0.0.116 h1:xy+mBpwbYo/0PSzmJOQ/RXHomEh0D3nDBcbCxsW69m8=
github.com/projectdiscovery/wappalyzergo v0.0.116/go.mod h1:hc/o+fgM8KtdpFesjfBTmHTwsR+yBd+4kYZW/DGy/x8=
github.com/projectdiscovery/yamldoc-go v1.0.4 h1:eZoESapnMw6WAHiVgRwNqvbJEfNHEH148uthhFbG5jE=
From 3b51a77bc7c2107efaf9fbc5f0c42406389afd58 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 15 Apr 2024 06:00:23 +0000
Subject: [PATCH 26/58] chore(deps): bump github.com/projectdiscovery/rawhttp
Bumps [github.com/projectdiscovery/rawhttp](https://github.com/projectdiscovery/rawhttp) from 0.1.44 to 0.1.45.
- [Release notes](https://github.com/projectdiscovery/rawhttp/releases)
- [Commits](https://github.com/projectdiscovery/rawhttp/compare/v0.1.44...v0.1.45)
---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/rawhttp
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index 9c3ea9e3c8..fd3fa51094 100644
--- a/go.mod
+++ b/go.mod
@@ -23,7 +23,7 @@ require (
github.com/projectdiscovery/fastdialer v0.0.67
github.com/projectdiscovery/hmap v0.0.41
github.com/projectdiscovery/interactsh v1.1.9
- github.com/projectdiscovery/rawhttp v0.1.44
+ github.com/projectdiscovery/rawhttp v0.1.45
github.com/projectdiscovery/retryabledns v1.0.58
github.com/projectdiscovery/retryablehttp-go v1.0.55
github.com/projectdiscovery/yamldoc-go v1.0.4
diff --git a/go.sum b/go.sum
index 22e511931e..23c56081d5 100644
--- a/go.sum
+++ b/go.sum
@@ -868,8 +868,8 @@ github.com/projectdiscovery/networkpolicy v0.0.8 h1:XvfBaBwSDNTesSfNQP9VLk3HX9I7
github.com/projectdiscovery/networkpolicy v0.0.8/go.mod h1:xnjNqhemxUPxU+UD5Jgsc3+K8IVmcqT1SJeo6UzMtkI=
github.com/projectdiscovery/ratelimit v0.0.35 h1:epEzFATOcXZ4tssV4Hax5Op9lrbUnQMEGMV5PoUpTKc=
github.com/projectdiscovery/ratelimit v0.0.35/go.mod h1:mPqa8UpV5I7eAN5/ZcsjLiXMhjtVvZRrHtpBRsTPuyA=
-github.com/projectdiscovery/rawhttp v0.1.44 h1:mkXTTUR65TTNisQGpLo5y5PRYRgNwZLW15KZNhNpsO8=
-github.com/projectdiscovery/rawhttp v0.1.44/go.mod h1:jaldbYYP0QihgQKk6Ar9ym9NPLAz5QkXp5TPET0sjYM=
+github.com/projectdiscovery/rawhttp v0.1.45 h1:5jssUybhRz2ljI3bW4kUO9MTvZ37ArG8pISYtgqzwBg=
+github.com/projectdiscovery/rawhttp v0.1.45/go.mod h1:oLWuZkyKi4rddPoLEN3TeAqyLvFNfr3dxmzV2kwpPvs=
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917 h1:m03X4gBVSorSzvmm0bFa7gDV4QNSOWPL/fgZ4kTXBxk=
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917/go.mod h1:JxXtZC9e195awe7EynrcnBJmFoad/BNDzW9mzFkK8Sg=
github.com/projectdiscovery/retryabledns v1.0.58 h1:ut1FSB9+GZ6zQIlKJFLqIz2RZs81EmkbsHTuIrWfYLE=
From f876e3290b371d1124a6f5e39392bcc3fdf798aa Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 15 Apr 2024 06:09:00 +0000
Subject: [PATCH 27/58] chore(deps): bump github.com/projectdiscovery/dsl from
0.0.51 to 0.0.52
Bumps [github.com/projectdiscovery/dsl](https://github.com/projectdiscovery/dsl) from 0.0.51 to 0.0.52.
- [Release notes](https://github.com/projectdiscovery/dsl/releases)
- [Commits](https://github.com/projectdiscovery/dsl/compare/v0.0.51...v0.0.52)
---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/dsl
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index 4533ce936f..700e11e33c 100644
--- a/go.mod
+++ b/go.mod
@@ -78,7 +78,7 @@ require (
github.com/mholt/archiver v3.1.1+incompatible
github.com/ory/dockertest/v3 v3.10.0
github.com/praetorian-inc/fingerprintx v1.1.9
- github.com/projectdiscovery/dsl v0.0.51
+ github.com/projectdiscovery/dsl v0.0.52
github.com/projectdiscovery/fasttemplate v0.0.2
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb
github.com/projectdiscovery/goflags v0.1.48
diff --git a/go.sum b/go.sum
index 3912760c97..053952fe1f 100644
--- a/go.sum
+++ b/go.sum
@@ -832,8 +832,8 @@ github.com/projectdiscovery/cdncheck v1.0.9 h1:BS15gzj9gb5AVSKqTDzPamfSgStu7nJQO
github.com/projectdiscovery/cdncheck v1.0.9/go.mod h1:18SSl1w7rMj53CGeRIZTbDoa286a6xZIxGbaiEo4Fxs=
github.com/projectdiscovery/clistats v0.0.20 h1:5jO5SLiRJ7f0nDV0ndBNmBeesbROouPooH+DGMgoWq4=
github.com/projectdiscovery/clistats v0.0.20/go.mod h1:GJ2av0KnOvK0AISQnP8hyDclYIji1LVkx2l0pwnzAu4=
-github.com/projectdiscovery/dsl v0.0.51 h1:7OQPumOrrUCFnCA7Y0nchhPvRo3IJGMIJ2Oy4DVTQsc=
-github.com/projectdiscovery/dsl v0.0.51/go.mod h1:GYhusn+T9EL7t+iJ8zN/GXlp8ohLGU+Yv/nevAPlJZg=
+github.com/projectdiscovery/dsl v0.0.52 h1:jvIvF+qN8+MbI1MHtWJJKfWqAZQlCExL3ob7SddQbZE=
+github.com/projectdiscovery/dsl v0.0.52/go.mod h1:xfcHwhy2HSaeGgh+1wqzOoCGm2XTdh5JzjBRBVHEMvI=
github.com/projectdiscovery/fastdialer v0.0.67 h1:NvBpZUiLr9Ne9N+Lvi6FFiNNLWuhk5Bc1H+oE9J8C1E=
github.com/projectdiscovery/fastdialer v0.0.67/go.mod h1:GhSAKnojJN8N9K0JNjLmwLCmEDsQ5cBAStqSCm/tm84=
github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA=
From f2c0b4b443d7242525350c07fdc1ac91a72b56b4 Mon Sep 17 00:00:00 2001
From: GitHub Action
Date: Mon, 15 Apr 2024 14:44:52 +0000
Subject: [PATCH 28/58] Auto Generate Syntax Docs + JSONSchema [Mon Apr 15
14:44:52 UTC 2024] :robot:
---
SYNTAX-REFERENCE.md | 2 ++
nuclei-jsonschema.json | 3 ++-
pkg/templates/templates_doc.go | 1 +
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/SYNTAX-REFERENCE.md b/SYNTAX-REFERENCE.md
index 9222e34aad..7fa62c5f44 100755
--- a/SYNTAX-REFERENCE.md
+++ b/SYNTAX-REFERENCE.md
@@ -2750,6 +2750,8 @@ Enum Values:
- TLSA
- ANY
+
+ - SRV
diff --git a/nuclei-jsonschema.json b/nuclei-jsonschema.json
index 9e14e531e0..46e1ab986d 100644
--- a/nuclei-jsonschema.json
+++ b/nuclei-jsonschema.json
@@ -84,7 +84,8 @@
"AAAA",
"CAA",
"TLSA",
- "ANY"
+ "ANY",
+ "SRV"
],
"title": "type of DNS request to make",
"description": "Type is the type of DNS request to make"
diff --git a/pkg/templates/templates_doc.go b/pkg/templates/templates_doc.go
index dbd865c4e7..42fa25b947 100644
--- a/pkg/templates/templates_doc.go
+++ b/pkg/templates/templates_doc.go
@@ -1166,6 +1166,7 @@ func init() {
"CAA",
"TLSA",
"ANY",
+ "SRV",
}
FILERequestDoc.Type = "file.Request"
From 431d3fa2d940f23ab3b4be86f008d3d81cb8291d Mon Sep 17 00:00:00 2001
From: guangwu
Date: Tue, 16 Apr 2024 17:23:49 +0800
Subject: [PATCH 29/58] fix: close res body (#5025)
---
pkg/reporting/exporters/es/elasticsearch.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/pkg/reporting/exporters/es/elasticsearch.go b/pkg/reporting/exporters/es/elasticsearch.go
index 68fb64e7e5..7b627492c9 100644
--- a/pkg/reporting/exporters/es/elasticsearch.go
+++ b/pkg/reporting/exporters/es/elasticsearch.go
@@ -131,7 +131,8 @@ func (exporter *Exporter) Export(event *output.ResultEvent) error {
if err != nil {
return err
}
-
+ defer res.Body.Close()
+
b, err = io.ReadAll(res.Body)
if err != nil {
return errors.New(err.Error() + "error thrown by elasticsearch " + string(b))
From bec7cb273a1375d64b401eeee05a7667fa4998d1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Levente=20Kov=C3=A1ts?=
<22732484+tovask@users.noreply.github.com>
Date: Tue, 16 Apr 2024 13:27:07 +0200
Subject: [PATCH 30/58] add context vars in code and multi (#5051)
make the extracted variables available in subsequence templates when executing in a workflow
fix projectdiscovery/nuclei#4797
---
cmd/integration-test/workflow.go | 70 +++++++++++++++++++
.../workflow/code-template-1.yaml | 22 ++++++
.../workflow/code-template-2.yaml | 21 ++++++
.../workflow/code-value-share-workflow.yaml | 12 ++++
.../multiprotocol-value-share-template.yaml | 22 ++++++
.../multiprotocol-value-share-workflow.yaml | 11 +++
pkg/protocols/code/code.go | 2 +
pkg/tmplexec/multiproto/multi.go | 6 ++
8 files changed, 166 insertions(+)
create mode 100644 integration_tests/workflow/code-template-1.yaml
create mode 100644 integration_tests/workflow/code-template-2.yaml
create mode 100644 integration_tests/workflow/code-value-share-workflow.yaml
create mode 100644 integration_tests/workflow/multiprotocol-value-share-template.yaml
create mode 100644 integration_tests/workflow/multiprotocol-value-share-workflow.yaml
diff --git a/cmd/integration-test/workflow.go b/cmd/integration-test/workflow.go
index 8672283b6c..e11575f90e 100644
--- a/cmd/integration-test/workflow.go
+++ b/cmd/integration-test/workflow.go
@@ -3,12 +3,15 @@ package main
import (
"fmt"
"io"
+ "log"
"net/http"
"net/http/httptest"
"strings"
"github.com/julienschmidt/httprouter"
+ "github.com/projectdiscovery/nuclei/v3/pkg/templates"
+ "github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
@@ -20,9 +23,37 @@ var workflowTestcases = []TestCaseInfo{
{Path: "workflow/complex-conditions.yaml", TestCase: &workflowComplexConditions{}},
{Path: "workflow/http-value-share-workflow.yaml", TestCase: &workflowHttpKeyValueShare{}},
{Path: "workflow/dns-value-share-workflow.yaml", TestCase: &workflowDnsKeyValueShare{}},
+ {Path: "workflow/code-value-share-workflow.yaml", TestCase: &workflowCodeKeyValueShare{}, DisableOn: isCodeDisabled}, // isCodeDisabled declared in code.go
+ {Path: "workflow/multiprotocol-value-share-workflow.yaml", TestCase: &workflowMultiProtocolKeyValueShare{}},
{Path: "workflow/shared-cookie.yaml", TestCase: &workflowSharedCookies{}},
}
+func init() {
+ // sign code templates (unless they are disabled)
+ if !isCodeDisabled() {
+ // allow local file access to load content of file references in template
+ // in order to sign them for testing purposes
+ templates.TemplateSignerLFA()
+
+ // testCertFile and testKeyFile are declared in code.go
+ tsigner, err := signer.NewTemplateSignerFromFiles(testCertFile, testKeyFile)
+ if err != nil {
+ panic(err)
+ }
+
+ // only the code templates are necessary to be signed
+ var templatesToSign = []string{
+ "workflow/code-template-1.yaml",
+ "workflow/code-template-2.yaml",
+ }
+ for _, templatePath := range templatesToSign {
+ if err := templates.SignTemplate(tsigner, templatePath); err != nil {
+ log.Fatalf("Could not sign template %v got: %s\n", templatePath, err)
+ }
+ }
+ }
+}
+
type workflowBasic struct{}
// Execute executes a test case and returns an error if occurred
@@ -159,6 +190,45 @@ func (h *workflowDnsKeyValueShare) Execute(filePath string) error {
return expectResultsCount(results, 1)
}
+type workflowCodeKeyValueShare struct{}
+
+// Execute executes a test case and returns an error if occurred
+func (h *workflowCodeKeyValueShare) Execute(filePath string) error {
+ // provide the Certificate File that the code templates are signed with
+ certEnvVar := signer.CertEnvVarName + "=" + testCertFile
+
+ results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, []string{certEnvVar}, "-workflows", filePath, "-target", "input", "-code")
+ if err != nil {
+ return err
+ }
+
+ return expectResultsCount(results, 1)
+}
+
+type workflowMultiProtocolKeyValueShare struct{}
+
+// Execute executes a test case and returns an error if occurred
+func (h *workflowMultiProtocolKeyValueShare) Execute(filePath string) error {
+ router := httprouter.New()
+ // the response of path1 contains a domain that will be extracted and shared with the second template
+ router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+ fmt.Fprintf(w, "href=\"blog.projectdiscovery.io\"")
+ })
+ // path2 responds with the value of the "extracted" query parameter, e.g.: /path2?extracted=blog.projectdiscovery.io => blog.projectdiscovery.io
+ router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+ fmt.Fprintf(w, "%s", r.URL.Query().Get("extracted"))
+ })
+ ts := httptest.NewServer(router)
+ defer ts.Close()
+
+ results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
+ if err != nil {
+ return err
+ }
+
+ return expectResultsCount(results, 2)
+}
+
type workflowSharedCookies struct{}
// Execute executes a test case and returns an error if occurred
diff --git a/integration_tests/workflow/code-template-1.yaml b/integration_tests/workflow/code-template-1.yaml
new file mode 100644
index 0000000000..d41a1a6957
--- /dev/null
+++ b/integration_tests/workflow/code-template-1.yaml
@@ -0,0 +1,22 @@
+id: code-template-1
+
+info:
+ name: code-template-1
+ author: tovask
+ severity: info
+ tags: code
+
+code:
+ - engine:
+ - py
+ - python3
+ - python
+ source: |
+ print("hello from first")
+ extractors:
+ - type: regex
+ name: extracted
+ regex:
+ - 'hello from (.*)'
+ group: 1
+# digest: 490a0046304402202c63d47bb0acdd40b3b852d95490d492ff5741b84071b2a8a40371be7797c13602202b6b977e157edf2ef70a402a2e57d4eb5a67c5ca91f0a2f9a10a966e8485ebaf:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
diff --git a/integration_tests/workflow/code-template-2.yaml b/integration_tests/workflow/code-template-2.yaml
new file mode 100644
index 0000000000..ddd7d36a4e
--- /dev/null
+++ b/integration_tests/workflow/code-template-2.yaml
@@ -0,0 +1,21 @@
+id: code-template-2
+
+info:
+ name: code-template-2
+ author: tovask
+ severity: info
+ tags: code
+
+code:
+ - engine:
+ - py
+ - python3
+ - python
+ source: |
+ import os
+ print("hello from " + os.getenv("extracted"))
+ matchers:
+ - type: word
+ words:
+ - "hello from first"
+# digest: 490a00463044022025661eab353b7f359c0d428a86b6287545d7f759375e8025cc8c9c77b616ca6502200bc2c019059622df3c88e7caa6dd7d1fb9b956010aa0de2ee2b9f7dd0a3c4954:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
diff --git a/integration_tests/workflow/code-value-share-workflow.yaml b/integration_tests/workflow/code-value-share-workflow.yaml
new file mode 100644
index 0000000000..a7c27b5eab
--- /dev/null
+++ b/integration_tests/workflow/code-value-share-workflow.yaml
@@ -0,0 +1,12 @@
+id: code-value-sharing-workflow
+
+info:
+ name: Code Value Sharing Workflow
+ author: tovask
+ severity: info
+ tags: code
+
+workflows:
+ - template: workflow/code-template-1.yaml
+ subtemplates:
+ - template: workflow/code-template-2.yaml
diff --git a/integration_tests/workflow/multiprotocol-value-share-template.yaml b/integration_tests/workflow/multiprotocol-value-share-template.yaml
new file mode 100644
index 0000000000..41a2469191
--- /dev/null
+++ b/integration_tests/workflow/multiprotocol-value-share-template.yaml
@@ -0,0 +1,22 @@
+id: multiprotocol-value-sharing-template
+
+info:
+ name: MultiProtocol Value Sharing Template
+ author: tovask
+ severity: info
+
+dns:
+ - name: "{{extracted}}"
+ type: PTR
+ matchers:
+ - type: word
+ words:
+ - "blog.projectdiscovery.io"
+
+http:
+ - path:
+ - "{{BaseURL}}/path2?extracted={{extracted}}"
+ matchers:
+ - type: word
+ words:
+ - "blog.projectdiscovery.io"
diff --git a/integration_tests/workflow/multiprotocol-value-share-workflow.yaml b/integration_tests/workflow/multiprotocol-value-share-workflow.yaml
new file mode 100644
index 0000000000..914ddbc4e7
--- /dev/null
+++ b/integration_tests/workflow/multiprotocol-value-share-workflow.yaml
@@ -0,0 +1,11 @@
+id: multiprotocol-value-sharing-workflow
+
+info:
+ name: MultiProtocol Value Sharing Workflow
+ author: tovask
+ severity: info
+
+workflows:
+ - template: workflow/http-value-share-template-1.yaml
+ subtemplates:
+ - template: workflow/multiprotocol-value-share-template.yaml
diff --git a/pkg/protocols/code/code.go b/pkg/protocols/code/code.go
index 43c6721c59..1b59de3efd 100644
--- a/pkg/protocols/code/code.go
+++ b/pkg/protocols/code/code.go
@@ -160,6 +160,8 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
if request.options.HasTemplateCtx(input.MetaInput) {
allvars = generators.MergeMaps(allvars, request.options.GetTemplateCtx(input.MetaInput).GetAll())
}
+ // add dynamic and previous variables
+ allvars = generators.MergeMaps(allvars, dynamicValues, previous)
// optionvars are vars passed from CLI or env variables
optionVars := generators.BuildPayloadFromOptions(request.options.Options)
variablesMap := request.options.Variables.Evaluate(allvars)
diff --git a/pkg/tmplexec/multiproto/multi.go b/pkg/tmplexec/multiproto/multi.go
index d164c03aab..997e62243d 100644
--- a/pkg/tmplexec/multiproto/multi.go
+++ b/pkg/tmplexec/multiproto/multi.go
@@ -46,6 +46,12 @@ func (m *MultiProtocol) Compile() error {
func (m *MultiProtocol) ExecuteWithResults(ctx *scan.ScanContext) error {
// put all readonly args into template context
m.options.GetTemplateCtx(ctx.Input.MetaInput).Merge(m.readOnlyArgs)
+
+ // add all input args to template context
+ ctx.Input.ForEach(func(key string, value interface{}) {
+ m.options.GetTemplateCtx(ctx.Input.MetaInput).Set(key, value)
+ })
+
// callback to process results from all protocols
multiProtoCallback := func(event *output.InternalWrappedEvent) {
if event == nil {
From ea2e13a4aa83ac349e3fde29bdbaa0814a2540f9 Mon Sep 17 00:00:00 2001
From: Tarun Koyalwar <45962551+tarunKoyalwar@users.noreply.github.com>
Date: Tue, 16 Apr 2024 16:57:32 +0530
Subject: [PATCH 31/58] nuclei 'stats' build : scan events + chart utils
(#5032)
* prototype new scan events
* scan-event: improvements + conditional build
* add scan charts server: make scan-charts
* scan-charts: bug fix
---
.gitignore | 3 +
Makefile | 9 +
cmd/scan-charts/main.go | 40 ++++
go.mod | 3 +
go.sum | 4 +
internal/runner/runner.go | 15 ++
pkg/scan/charts/charts.go | 87 ++++++++
pkg/scan/charts/echarts.go | 351 +++++++++++++++++++++++++++++++++
pkg/scan/events/scan_noop.go | 11 ++
pkg/scan/events/stats_build.go | 80 ++++++++
pkg/scan/events/utils.go | 45 +++++
pkg/scan/scan_context.go | 1 +
pkg/tmplexec/exec.go | 41 ++++
pkg/types/types.go | 2 +
14 files changed, 692 insertions(+)
create mode 100644 cmd/scan-charts/main.go
create mode 100644 pkg/scan/charts/charts.go
create mode 100644 pkg/scan/charts/echarts.go
create mode 100644 pkg/scan/events/scan_noop.go
create mode 100644 pkg/scan/events/stats_build.go
create mode 100644 pkg/scan/events/utils.go
diff --git a/.gitignore b/.gitignore
index 65815d8db6..9386137b54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,5 +36,8 @@ pkg/protocols/headless/engine/.cache
/fuzzplayground
integration_tests/fuzzplayground
/dsl.md
+/nuclei-stats
+/nuclei-stats-*
+/scan-charts
diff --git a/Makefile b/Makefile
index dadbe6205d..e916b980a6 100644
--- a/Makefile
+++ b/Makefile
@@ -11,9 +11,18 @@ ifneq ($(shell go env GOOS),darwin)
LDFLAGS := -extldflags "-static"
endif
+.PHONY: all build build-stats scan-charts docs test integration functional tidy devtools jsupdate ts fuzzplayground memogen dsl-docs
+
all: build
build:
+ rm -f nuclei 2>/dev/null
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "nuclei" cmd/nuclei/main.go
+build-stats:
+ rm -f nuclei-stats 2>/dev/null
+ $(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -tags=stats -o "nuclei-stats" cmd/nuclei/main.go
+scan-charts:
+ rm -f scan-charts 2>/dev/null
+ $(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "scan-charts" cmd/scan-charts/main.go
docs:
if ! which dstdocgen > /dev/null; then
echo -e "Command not found! Install? (y/n) \c"
diff --git a/cmd/scan-charts/main.go b/cmd/scan-charts/main.go
new file mode 100644
index 0000000000..644a1f004e
--- /dev/null
+++ b/cmd/scan-charts/main.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+ "flag"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/scan/charts"
+)
+
+var (
+ dir string
+ address string
+ output string
+)
+
+func main() {
+ flag.StringVar(&dir, "dir", "", "directory to scan")
+ flag.StringVar(&address, "address", ":9000", "address to run the server on")
+ flag.StringVar(&output, "output", "", "output filename of generated html file")
+ flag.Parse()
+
+ if dir == "" {
+ flag.Usage()
+ return
+ }
+
+ server, err := charts.NewScanEventsCharts(dir)
+ if err != nil {
+ panic(err)
+ }
+ server.PrintInfo()
+
+ if output != "" {
+ if err = server.GenerateHTML(output); err != nil {
+ panic(err)
+ }
+ return
+ }
+
+ server.Start(address)
+}
diff --git a/go.mod b/go.mod
index a533740474..4cf94ec00c 100644
--- a/go.mod
+++ b/go.mod
@@ -329,6 +329,7 @@ require (
github.com/aws/smithy-go v1.13.5 // indirect
github.com/dop251/goja_nodejs v0.0.0-20230821135201-94e508132562
github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/go-echarts/go-echarts/v2 v2.3.3
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
@@ -348,3 +349,5 @@ require (
// https://go.dev/ref/mod#go-mod-file-retract
retract v3.2.0 // retract due to broken js protocol issue
+
+replace github.com/go-echarts/go-echarts/v2 => github.com/tarunKoyalwar/go-echarts/v2 v2.1.1
diff --git a/go.sum b/go.sum
index 290fd9e482..888602a714 100644
--- a/go.sum
+++ b/go.sum
@@ -341,6 +341,8 @@ github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
+github.com/go-echarts/go-echarts/v2 v2.3.3 h1:uImZAk6qLkC6F9ju6mZ5SPBqTyK8xjZKwSmwnCg4bxg=
+github.com/go-echarts/go-echarts/v2 v2.3.3/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@@ -1013,6 +1015,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/tarunKoyalwar/go-echarts/v2 v2.1.1 h1:5fsXGPmK+i18J8cDgxy7AJkiXWBARpVTb0Gbv+bAzPo=
+github.com/tarunKoyalwar/go-echarts/v2 v2.1.1/go.mod h1:VEeyPT5Odx/UHeuxtIAHGu2+87MWGA5OBaZ120NFi/w=
github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
diff --git a/internal/runner/runner.go b/internal/runner/runner.go
index fa9b85bf05..174c06f0f7 100644
--- a/internal/runner/runner.go
+++ b/internal/runner/runner.go
@@ -18,6 +18,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/installer"
"github.com/projectdiscovery/nuclei/v3/pkg/loader/parser"
+ "github.com/projectdiscovery/nuclei/v3/pkg/scan/events"
uncoverlib "github.com/projectdiscovery/uncover"
pdcpauth "github.com/projectdiscovery/utils/auth/pdcp"
"github.com/projectdiscovery/utils/env"
@@ -553,6 +554,20 @@ func (r *Runner) RunEnumeration() error {
executorOpts.InputHelper.InputsHTTP = inputHelpers
}
+ // initialize stats worker ( this is no-op unless nuclei is built with stats build tag)
+ // during execution a directory with 2 files will be created in the current directory
+ // config.json - containing below info
+ // events.jsonl - containing all start and end times of all templates
+ events.InitWithConfig(&events.ScanConfig{
+ Name: "nuclei-stats", // make this configurable
+ TargetCount: int(r.inputProvider.Count()),
+ TemplatesCount: len(store.Templates()) + len(store.Workflows()),
+ TemplateConcurrency: r.options.TemplateThreads,
+ PayloadConcurrency: r.options.PayloadConcurrency,
+ JsConcurrency: r.options.JsConcurrency,
+ Retries: r.options.Retries,
+ }, "")
+
enumeration := false
var results *atomic.Bool
results, err = r.runStandardEnumeration(executorOpts, store, executorEngine)
diff --git a/pkg/scan/charts/charts.go b/pkg/scan/charts/charts.go
new file mode 100644
index 0000000000..03bfdcc6c2
--- /dev/null
+++ b/pkg/scan/charts/charts.go
@@ -0,0 +1,87 @@
+package charts
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/labstack/echo/v4"
+ "github.com/projectdiscovery/nuclei/v3/pkg/scan/events"
+ fileutil "github.com/projectdiscovery/utils/file"
+)
+
+// ScanEventsCharts is a struct for nuclei event charts
+type ScanEventsCharts struct {
+ eventsDir string
+ config *events.ScanConfig
+ data []events.ScanEvent
+}
+
+func (sc *ScanEventsCharts) PrintInfo() {
+ fmt.Printf("[+] Scan Info\n")
+ fmt.Printf(" - Name: %s\n", sc.config.Name)
+ fmt.Printf(" - Target Count: %d\n", sc.config.TargetCount)
+ fmt.Printf(" - Template Count: %d\n", sc.config.TemplatesCount)
+ fmt.Printf(" - Template Concurrency: %d\n", sc.config.TemplateConcurrency)
+ fmt.Printf(" - Payload Concurrency: %d\n", sc.config.PayloadConcurrency)
+ fmt.Printf(" - Retries: %v\n", sc.config.Retries)
+ fmt.Printf(" - Total Events: %d\n", len(sc.data))
+ fmt.Println()
+}
+
+// NewScanEventsCharts creates a new nuclei event charts
+func NewScanEventsCharts(eventsDir string) (*ScanEventsCharts, error) {
+ sc := &ScanEventsCharts{eventsDir: eventsDir}
+ if !fileutil.FolderExists(eventsDir) {
+ return nil, fmt.Errorf("events directory does not exist")
+ }
+ // open two files
+ // config.json
+ bin, err := os.ReadFile(filepath.Join(eventsDir, events.ConfigFile))
+ if err != nil {
+ return nil, err
+ }
+ var config events.ScanConfig
+ err = json.Unmarshal(bin, &config)
+ if err != nil {
+ return nil, err
+ }
+ sc.config = &config
+
+ // events.jsonl
+ f, err := os.Open(filepath.Join(eventsDir, events.EventsFile))
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ data := []events.ScanEvent{}
+ dec := json.NewDecoder(f)
+ for {
+ var event events.ScanEvent
+ if err := dec.Decode(&event); err != nil {
+ break
+ }
+ data = append(data, event)
+ }
+ sc.data = data
+
+ if len(data) == 0 {
+ return nil, fmt.Errorf("no events found in the events file")
+ }
+
+ return sc, nil
+}
+
+// Start starts the nuclei event charts server
+func (sc *ScanEventsCharts) Start(addr string) {
+ e := echo.New()
+ e.HideBanner = true
+ e.GET("/concurrency", sc.ConcurrencyVsTime)
+ e.GET("/requests", sc.TotalRequestsOverTime)
+ e.GET("/slow", sc.TopSlowTemplates)
+ e.GET("/rps", sc.RequestsVSInterval)
+ e.GET("/", sc.AllCharts)
+ e.Logger.Fatal(e.Start(addr))
+}
diff --git a/pkg/scan/charts/echarts.go b/pkg/scan/charts/echarts.go
new file mode 100644
index 0000000000..bf70fee710
--- /dev/null
+++ b/pkg/scan/charts/echarts.go
@@ -0,0 +1,351 @@
+package charts
+
+import (
+ "fmt"
+ "os"
+ "sort"
+ "time"
+
+ "github.com/go-echarts/go-echarts/v2/charts"
+ "github.com/go-echarts/go-echarts/v2/components"
+ "github.com/go-echarts/go-echarts/v2/opts"
+ "github.com/labstack/echo/v4"
+ "github.com/projectdiscovery/nuclei/v3/pkg/scan/events"
+ sliceutil "github.com/projectdiscovery/utils/slice"
+)
+
+const (
+ TopK = 50
+ SpacerHeight = "50px"
+)
+
+func (s *ScanEventsCharts) AllCharts(c echo.Context) error {
+ page := s.allCharts(c)
+ return page.Render(c.Response().Writer)
+}
+
+func (s *ScanEventsCharts) GenerateHTML(filePath string) error {
+ page := s.allCharts(nil)
+ output, err := os.Create(filePath)
+ if err != nil {
+ return err
+ }
+ return page.Render(output)
+}
+
+// AllCharts generates all the charts for the scan events and returns a page component
+func (s *ScanEventsCharts) allCharts(c echo.Context) *components.Page {
+ page := components.NewPage()
+ page.PageTitle = "Nuclei Charts"
+ line1 := s.totalRequestsOverTime(c)
+ line1.SetSpacerHeight(SpacerHeight)
+ kline := s.topSlowTemplates(c)
+ kline.SetSpacerHeight(SpacerHeight)
+ line2 := s.requestsVSInterval(c)
+ line2.SetSpacerHeight(SpacerHeight)
+ line3 := s.concurrencyVsTime(c)
+ line3.SetSpacerHeight(SpacerHeight)
+ page.AddCharts(line1, kline, line2, line3)
+ page.Validate()
+ page.SetLayout(components.PageCenterLayout)
+ return page
+}
+
+func (s *ScanEventsCharts) TotalRequestsOverTime(c echo.Context) error {
+ line := s.totalRequestsOverTime(c)
+ return line.Render(c.Response().Writer)
+}
+
+// totalRequestsOverTime generates a line chart showing total requests count over time
+func (s *ScanEventsCharts) totalRequestsOverTime(c echo.Context) *charts.Line {
+ line := charts.NewLine()
+ line.SetCaption("Chart Shows Total Requests Count Over Time (for each/all Protocols)")
+
+ var startTime time.Time = time.Now()
+ var endTime time.Time
+
+ for _, event := range s.data {
+ if event.Time.Before(startTime) {
+ startTime = event.Time
+ }
+ if event.Time.After(endTime) {
+ endTime = event.Time
+ }
+ }
+ data := getCategoryRequestCount(s.data)
+ max := 0
+ for _, v := range data {
+ if len(v) > max {
+ max = len(v)
+ }
+ }
+ line.SetXAxis(time.Now().Format(time.RFC3339))
+ for k, v := range data {
+ lineData := make([]opts.LineData, 0)
+ temp := 0
+ for _, scanEvent := range v {
+ temp += scanEvent.MaxRequests
+ val := scanEvent.Time.Sub(startTime)
+ lineData = append(lineData, opts.LineData{
+ Value: []interface{}{val.Milliseconds(), temp},
+ Name: scanEvent.TemplateID,
+ })
+ }
+ line.AddSeries(k, lineData, charts.WithLineChartOpts(opts.LineChart{Smooth: false}), charts.WithLabelOpts(opts.Label{Show: true, Position: "top"}))
+ }
+
+ line.SetGlobalOptions(
+ charts.WithTitleOpts(opts.Title{Title: "Nuclei: total-req vs time"}),
+ charts.WithXAxisOpts(opts.XAxis{Name: "Time", Type: "time", AxisLabel: &opts.AxisLabel{Show: true, ShowMaxLabel: true, Formatter: opts.FuncOpts(`function (date) { return (date/1000)+'s'; }`)}}),
+ charts.WithYAxisOpts(opts.YAxis{Name: "Requests Sent", Type: "value"}),
+ charts.WithInitializationOpts(opts.Initialization{Theme: "dark"}),
+ charts.WithDataZoomOpts(opts.DataZoom{Type: "slider", Start: 0, End: 100}),
+ charts.WithGridOpts(opts.Grid{Left: "10%", Right: "10%", Bottom: "15%", Top: "20%"}),
+ charts.WithToolboxOpts(opts.Toolbox{Show: true, Feature: &opts.ToolBoxFeature{
+ SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: true, Name: "save", Title: "save"},
+ DataZoom: &opts.ToolBoxFeatureDataZoom{Show: true, Title: map[string]string{"zoom": "zoom", "back": "back"}},
+ DataView: &opts.ToolBoxFeatureDataView{Show: true, Title: "raw", Lang: []string{"raw", "exit", "refresh"}},
+ }}),
+ )
+
+ line.Validate()
+ return line
+}
+
+func (s *ScanEventsCharts) TopSlowTemplates(c echo.Context) error {
+ kline := s.topSlowTemplates(c)
+ return kline.Render(c.Response().Writer)
+}
+
+// topSlowTemplates generates a Kline chart showing the top slow templates by time taken
+func (s *ScanEventsCharts) topSlowTemplates(c echo.Context) *charts.Kline {
+ kline := charts.NewKLine()
+ kline.SetCaption(fmt.Sprintf("Chart Shows Top Slow Templates (by time taken) (Top %v)", TopK))
+
+ ids := map[string][]int64{}
+ var startTime time.Time = time.Now()
+ for _, event := range s.data {
+ if event.Time.Before(startTime) {
+ startTime = event.Time
+ }
+ }
+ for _, event := range s.data {
+ ids[event.TemplateID] = append(ids[event.TemplateID], event.Time.Sub(startTime).Milliseconds())
+ }
+
+ type entry struct {
+ ID string
+ KlineData opts.KlineData
+ start int64
+ end int64
+ }
+ data := []entry{}
+
+ for a, b := range ids {
+ if len(b) < 2 {
+ continue // Prevents index out of range error
+ }
+ d := entry{
+ ID: a,
+ KlineData: opts.KlineData{Value: []int64{b[0], b[len(b)-1], b[0], b[len(b)-1]}}, // Adjusted to prevent index out of range error
+ start: b[0],
+ end: b[len(b)-1],
+ }
+ data = append(data, d)
+ }
+
+ sort.Slice(data, func(i, j int) bool {
+ return data[i].end-data[i].start > data[j].end-data[j].start
+ })
+
+ x := make([]string, 0)
+ y := make([]opts.KlineData, 0)
+ for _, event := range data[:TopK] {
+ x = append(x, event.ID)
+ y = append(y, event.KlineData)
+ }
+
+ kline.SetXAxis(x).AddSeries("templates", y)
+ kline.SetGlobalOptions(
+ charts.WithTitleOpts(opts.Title{Title: fmt.Sprintf("Nuclei: Top %v Slow Templates", TopK)}),
+ charts.WithXAxisOpts(opts.XAxis{
+ Type: "category",
+ Show: true,
+ AxisLabel: &opts.AxisLabel{Rotate: 90, Show: true, ShowMinLabel: true, ShowMaxLabel: true, Formatter: opts.FuncOpts(`function (value) { return value; }`)},
+ }),
+ charts.WithYAxisOpts(opts.YAxis{
+ Scale: true,
+ Type: "value",
+ Show: true,
+ AxisLabel: &opts.AxisLabel{Show: true, Formatter: opts.FuncOpts(`function (ms) { return Math.floor(ms/60000) + 'm' + Math.floor((ms/60000 - Math.floor(ms/60000))*60) + 's'; }`)},
+ }),
+ charts.WithDataZoomOpts(opts.DataZoom{Type: "slider", Start: 0, End: 100}),
+ charts.WithGridOpts(opts.Grid{Left: "10%", Right: "10%", Bottom: "40%", Top: "10%"}),
+ charts.WithTooltipOpts(opts.Tooltip{Show: true, Trigger: "events.ScanEvent", TriggerOn: "mousemove|click", Enterable: true, Formatter: opts.FuncOpts(`function (params) { return params.name ; }`)}),
+ charts.WithToolboxOpts(opts.Toolbox{Show: true, Feature: &opts.ToolBoxFeature{
+ SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: true, Name: "save", Title: "save"},
+ DataZoom: &opts.ToolBoxFeatureDataZoom{Show: true, Title: map[string]string{"zoom": "zoom", "back": "back"}},
+ DataView: &opts.ToolBoxFeatureDataView{Show: true, Title: "raw", Lang: []string{"raw", "exit", "refresh"}},
+ }}),
+ )
+
+ return kline
+}
+
+func (s *ScanEventsCharts) RequestsVSInterval(c echo.Context) error {
+ line := s.requestsVSInterval(c)
+ return line.Render(c.Response().Writer)
+}
+
+// requestsVSInterval generates a line chart showing requests per second over time
+func (s *ScanEventsCharts) requestsVSInterval(c echo.Context) *charts.Line {
+ line := charts.NewLine()
+ line.SetCaption("Chart Shows RPS (Requests Per Second) Over Time")
+
+ sort.Slice(s.data, func(i, j int) bool {
+ return s.data[i].Time.Before(s.data[j].Time)
+ })
+
+ var interval time.Duration
+
+ if c != nil {
+ interval, _ = time.ParseDuration(c.QueryParam("interval"))
+ }
+ if interval <= 3 {
+ interval = 5 * time.Second
+ }
+
+ data := []opts.LineData{}
+ temp := 0
+ if len(s.data) > 0 {
+ orig := s.data[0].Time
+ startTime := orig
+ xaxisData := []int64{}
+ for _, v := range s.data {
+ if v.Time.Sub(startTime) > interval {
+ millisec := v.Time.Sub(orig).Milliseconds()
+ xaxisData = append(xaxisData, millisec)
+ data = append(data, opts.LineData{Value: temp, Name: v.Time.Sub(orig).String()})
+ temp = 0
+ startTime = v.Time
+ }
+ temp += 1
+ }
+ // Handle last interval if exists
+ if temp > 0 {
+ millisec := s.data[len(s.data)-1].Time.Sub(orig).Milliseconds()
+ xaxisData = append(xaxisData, millisec)
+ data = append(data, opts.LineData{Value: temp, Name: s.data[len(s.data)-1].Time.Sub(orig).String()})
+ }
+ line.SetXAxis(xaxisData)
+ line.AddSeries("RPS", data, charts.WithLineChartOpts(opts.LineChart{Smooth: false}), charts.WithLabelOpts(opts.Label{Show: true, Position: "top"}))
+ }
+
+ line.SetGlobalOptions(
+ charts.WithTitleOpts(opts.Title{Title: "Nuclei: Template Execution", Subtitle: "Time Interval: " + interval.String()}),
+ charts.WithXAxisOpts(opts.XAxis{Name: "Time Intervals", Type: "category", AxisLabel: &opts.AxisLabel{Show: true, ShowMaxLabel: true, Formatter: opts.FuncOpts(`function (date) { return (date/1000)+'s'; }`)}}),
+ charts.WithYAxisOpts(opts.YAxis{Name: "RPS Value", Type: "value", Show: true}),
+ charts.WithInitializationOpts(opts.Initialization{Theme: "dark"}),
+ charts.WithDataZoomOpts(opts.DataZoom{Type: "slider", Start: 0, End: 100}),
+ charts.WithGridOpts(opts.Grid{Left: "10%", Right: "10%", Bottom: "15%", Top: "20%"}),
+ charts.WithToolboxOpts(opts.Toolbox{Show: true, Feature: &opts.ToolBoxFeature{
+ SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: true, Name: "save", Title: "save"},
+ DataZoom: &opts.ToolBoxFeatureDataZoom{Show: true, Title: map[string]string{"zoom": "zoom", "back": "back"}},
+ DataView: &opts.ToolBoxFeatureDataView{Show: true, Title: "raw", Lang: []string{"raw", "exit", "refresh"}},
+ }}),
+ )
+
+ line.Validate()
+ return line
+}
+
+func (s *ScanEventsCharts) ConcurrencyVsTime(c echo.Context) error {
+ line := s.concurrencyVsTime(c)
+ return line.Render(c.Response().Writer)
+}
+
+// concurrencyVsTime generates a line chart showing concurrency (total workers) over time
+func (s *ScanEventsCharts) concurrencyVsTime(c echo.Context) *charts.Line {
+ line := charts.NewLine()
+ line.SetCaption("Chart Shows Concurrency (Total Workers) Over Time")
+
+ dataset := sliceutil.Clone(s.data)
+
+ sort.Slice(dataset, func(i, j int) bool {
+ return dataset[i].Time.Before(dataset[j].Time)
+ })
+
+ var interval time.Duration
+ if c != nil {
+ interval, _ = time.ParseDuration(c.QueryParam("interval"))
+ }
+ if interval <= 3 {
+ interval = 5 * time.Second
+ }
+
+ // create array with time interval as x-axis and worker count as y-axis
+ // entry is a struct with time and poolsize
+ type entry struct {
+ Time time.Duration
+ poolsize int
+ }
+ allEntries := []entry{}
+
+ dataIndex := 0
+ maxIndex := len(dataset) - 1
+ currEntry := entry{}
+
+ lastTime := dataset[0].Time
+ for dataIndex <= maxIndex {
+ currTime := dataset[dataIndex].Time
+ if currTime.Sub(lastTime) > interval {
+ // next batch
+ currEntry.Time = interval
+ allEntries = append(allEntries, currEntry)
+ lastTime = dataset[dataIndex-1].Time
+ }
+ if dataset[dataIndex].EventType == events.ScanStarted {
+ currEntry.poolsize += 1
+ } else {
+ currEntry.poolsize -= 1
+ }
+ dataIndex += 1
+ }
+
+ plotData := []opts.LineData{}
+ xaxisData := []int64{}
+ tempTime := time.Duration(0)
+ for _, v := range allEntries {
+ tempTime += v.Time
+ plotData = append(plotData, opts.LineData{Value: v.poolsize, Name: tempTime.String()})
+ xaxisData = append(xaxisData, tempTime.Milliseconds())
+ }
+ line.SetXAxis(xaxisData)
+ line.AddSeries("Concurrency", plotData, charts.WithLineChartOpts(opts.LineChart{Smooth: false}), charts.WithLabelOpts(opts.Label{Show: true, Position: "top"}))
+
+ line.SetGlobalOptions(
+ charts.WithTitleOpts(opts.Title{Title: "Nuclei: WorkerPool", Subtitle: "Time Interval: " + interval.String()}),
+ charts.WithXAxisOpts(opts.XAxis{Name: "Time Intervals", Type: "category", AxisLabel: &opts.AxisLabel{Show: true, ShowMaxLabel: true, Formatter: opts.FuncOpts(`function (date) { return (date/1000)+'s'; }`)}}),
+ charts.WithYAxisOpts(opts.YAxis{Name: "Total Workers", Type: "value", Show: true}),
+ charts.WithInitializationOpts(opts.Initialization{Theme: "dark"}),
+ charts.WithDataZoomOpts(opts.DataZoom{Type: "slider", Start: 0, End: 100}),
+ charts.WithGridOpts(opts.Grid{Left: "10%", Right: "10%", Bottom: "15%", Top: "20%"}),
+ charts.WithToolboxOpts(opts.Toolbox{Show: true, Feature: &opts.ToolBoxFeature{
+ SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: true, Name: "save", Title: "save"},
+ DataZoom: &opts.ToolBoxFeatureDataZoom{Show: true, Title: map[string]string{"zoom": "zoom", "back": "back"}},
+ DataView: &opts.ToolBoxFeatureDataView{Show: true, Title: "raw", Lang: []string{"raw", "exit", "refresh"}},
+ }}),
+ )
+
+ line.Validate()
+ return line
+}
+
+// getCategoryRequestCount returns a map of category and request count
+func getCategoryRequestCount(values []events.ScanEvent) map[string][]events.ScanEvent {
+ mx := make(map[string][]events.ScanEvent)
+ for _, event := range values {
+ mx[event.TemplateType] = append(mx[event.TemplateType], event)
+ }
+ return mx
+}
diff --git a/pkg/scan/events/scan_noop.go b/pkg/scan/events/scan_noop.go
new file mode 100644
index 0000000000..a284657f52
--- /dev/null
+++ b/pkg/scan/events/scan_noop.go
@@ -0,0 +1,11 @@
+//go:build !stats
+// +build !stats
+
+package events
+
+// AddScanEvent is a no-op function
+func AddScanEvent(event ScanEvent) {
+}
+
+func InitWithConfig(config *ScanConfig, statsDirectory string) {
+}
diff --git a/pkg/scan/events/stats_build.go b/pkg/scan/events/stats_build.go
new file mode 100644
index 0000000000..0f01724411
--- /dev/null
+++ b/pkg/scan/events/stats_build.go
@@ -0,0 +1,80 @@
+//go:build stats
+// +build stats
+
+package events
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "sync"
+ "time"
+)
+
+var _ ScanEventWorker = &ScanStatsWorker{}
+
+var defaultWorker = &ScanStatsWorker{}
+
+// ScanStatsWorker is a worker for scanning stats
+// This tracks basic stats in jsonlines format
+// in given directory or a default directory with name stats_{timestamp} in the current directory
+type ScanStatsWorker struct {
+ config *ScanConfig
+ m *sync.Mutex
+ directory string
+ enc *json.Encoder
+}
+
+// Init initializes the scan stats worker
+func InitWithConfig(config *ScanConfig, statsDirectory string) {
+ currentTime := time.Now().Format("20060102150405")
+ dirName := fmt.Sprintf("nuclei-stats-%s", currentTime)
+ err := os.Mkdir(dirName, 0755)
+ if err != nil {
+ panic(err)
+ }
+ // save the config to the directory
+ bin, err := json.MarshalIndent(config, "", " ")
+ if err != nil {
+ panic(err)
+ }
+ err = os.WriteFile(filepath.Join(dirName, ConfigFile), bin, 0755)
+ if err != nil {
+ panic(err)
+ }
+ defaultWorker = &ScanStatsWorker{config: config, m: &sync.Mutex{}, directory: dirName}
+ err = defaultWorker.initEventsFile()
+ if err != nil {
+ panic(err)
+ }
+}
+
+// initEventsFile initializes the events file for the worker
+func (s *ScanStatsWorker) initEventsFile() error {
+ f, err := os.Create(filepath.Join(s.directory, EventsFile))
+ if err != nil {
+ return err
+ }
+ s.enc = json.NewEncoder(f)
+ return nil
+}
+
+// AddScanEvent adds a scan event to the worker
+func (s *ScanStatsWorker) AddScanEvent(event ScanEvent) {
+ s.m.Lock()
+ defer s.m.Unlock()
+
+ err := s.enc.Encode(event)
+ if err != nil {
+ panic(err)
+ }
+}
+
+// AddScanEvent adds a scan event to the worker
+func AddScanEvent(event ScanEvent) {
+ if defaultWorker == nil {
+ return
+ }
+ defaultWorker.AddScanEvent(event)
+}
diff --git a/pkg/scan/events/utils.go b/pkg/scan/events/utils.go
new file mode 100644
index 0000000000..edd7b09ae5
--- /dev/null
+++ b/pkg/scan/events/utils.go
@@ -0,0 +1,45 @@
+package events
+
+import (
+ "time"
+)
+
+type ScanEventWorker interface {
+ // AddScanEvent adds a scan event to the worker
+ AddScanEvent(event ScanEvent)
+}
+
+// Track scan start / finish status
+type ScanStatus string
+
+const (
+ ScanStarted ScanStatus = "scan_start"
+ ScanFinished ScanStatus = "scan_end"
+)
+
+const (
+ ConfigFile = "config.json"
+ EventsFile = "events.jsonl"
+)
+
+// ScanEvent represents a single scan event with its metadata
+type ScanEvent struct {
+ Target string `json:"target" yaml:"target"`
+ TemplateType string `json:"template_type" yaml:"template_type"`
+ TemplateID string `json:"template_id" yaml:"template_id"`
+ TemplatePath string `json:"template_path" yaml:"template_path"`
+ MaxRequests int `json:"max_requests" yaml:"max_requests"`
+ Time time.Time `json:"time" yaml:"time"`
+ EventType ScanStatus `json:"event_type" yaml:"event_type"`
+}
+
+// ScanConfig is only in context of scan event analysis
+type ScanConfig struct {
+ Name string `json:"name" yaml:"name"`
+ TargetCount int `json:"target_count" yaml:"target_count"`
+ TemplatesCount int `json:"templates_count" yaml:"templates_count"`
+ TemplateConcurrency int `json:"template_concurrency" yaml:"template_concurrency"`
+ PayloadConcurrency int `json:"payload_concurrency" yaml:"payload_concurrency"`
+ JsConcurrency int `json:"js_concurrency" yaml:"js_concurrency"`
+ Retries int `json:"retries" yaml:"retries"`
+}
diff --git a/pkg/scan/scan_context.go b/pkg/scan/scan_context.go
index a5e310b7c6..8851349019 100644
--- a/pkg/scan/scan_context.go
+++ b/pkg/scan/scan_context.go
@@ -10,6 +10,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
)
+
type ScanContextOption func(*ScanContext)
func WithEvents() ScanContextOption {
diff --git a/pkg/tmplexec/exec.go b/pkg/tmplexec/exec.go
index e434f2173c..fc504171e6 100644
--- a/pkg/tmplexec/exec.go
+++ b/pkg/tmplexec/exec.go
@@ -5,6 +5,7 @@ import (
"fmt"
"strings"
"sync/atomic"
+ "time"
"github.com/dop251/goja"
"github.com/projectdiscovery/gologger"
@@ -14,6 +15,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/writer"
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
+ "github.com/projectdiscovery/nuclei/v3/pkg/scan/events"
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/flow"
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/generic"
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/multiproto"
@@ -92,6 +94,31 @@ func (e *TemplateExecuter) Requests() int {
// Execute executes the protocol group and returns true or false if results were found.
func (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
+
+ // === when nuclei is built with -tags=stats ===
+ // Note: this is no-op (empty functions) when nuclei is built in normal or without -tags=stats
+ events.AddScanEvent(events.ScanEvent{
+ Target: ctx.Input.MetaInput.Input,
+ Time: time.Now(),
+ EventType: events.ScanStarted,
+ TemplateType: e.getTemplateType(),
+ TemplateID: e.options.TemplateID,
+ TemplatePath: e.options.TemplatePath,
+ MaxRequests: e.Requests(),
+ })
+ defer func() {
+ events.AddScanEvent(events.ScanEvent{
+ Target: ctx.Input.MetaInput.Input,
+ Time: time.Now(),
+ EventType: events.ScanFinished,
+ TemplateType: e.getTemplateType(),
+ TemplateID: e.options.TemplateID,
+ TemplatePath: e.options.TemplatePath,
+ MaxRequests: e.Requests(),
+ })
+ }()
+ // ==== end of stats ====
+
// executed contains status of execution if it was successfully executed or not
// doesn't matter if it was matched or not
executed := &atomic.Bool{}
@@ -182,3 +209,17 @@ func (e *TemplateExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.
ctx.LogError(err)
return ctx.GenerateResult(), err
}
+
+// getTemplateType returns the template type of the template
+func (e *TemplateExecuter) getTemplateType() string {
+ if len(e.requests) == 0 {
+ return "null"
+ }
+ if e.options.Flow != "" {
+ return "flow"
+ }
+ if len(e.requests) > 1 {
+ return "multiprotocol"
+ }
+ return e.requests[0].Type().String()
+}
diff --git a/pkg/types/types.go b/pkg/types/types.go
index c251dc0c85..b6bdcaee55 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -419,7 +419,9 @@ func DefaultOptions() *Options {
BulkSize: 25,
TemplateThreads: 25,
HeadlessBulkSize: 10,
+ PayloadConcurrency: 25,
HeadlessTemplateThreads: 10,
+ ProbeConcurrency: 50,
Timeout: 5,
Retries: 1,
MaxHostError: 30,
From 84582183dce9046fdeb086dd3d4251ee701cbfea Mon Sep 17 00:00:00 2001
From: lvyaoting <166296299+lvyaoting@users.noreply.github.com>
Date: Tue, 16 Apr 2024 19:27:51 +0800
Subject: [PATCH 32/58] chore: fix function names in comment (#5008)
Signed-off-by: lvyaoting
---
cmd/tmc/main.go | 2 +-
lib/config.go | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/cmd/tmc/main.go b/cmd/tmc/main.go
index cd552acf49..eae38a1ca7 100644
--- a/cmd/tmc/main.go
+++ b/cmd/tmc/main.go
@@ -266,7 +266,7 @@ func enhanceTemplate(data string) (string, bool, error) {
return data, false, errorutil.New("template enhance failed")
}
-// formatTemplateData formats template data using templateman format api
+// formatTemplate formats template data using templateman format api
func formatTemplate(data string) (string, bool, error) {
resp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf("%s/format", tmBaseUrl), "application/x-yaml", strings.NewReader(data))
if err != nil {
diff --git a/lib/config.go b/lib/config.go
index 5b112f8d25..ddcf3b88b4 100644
--- a/lib/config.go
+++ b/lib/config.go
@@ -314,7 +314,7 @@ func WithScanStrategy(strategy string) NucleiSDKOptions {
// OutputWriter
type OutputWriter output.Writer
-// UseWriter allows setting custom output writer
+// UseOutputWriter allows setting custom output writer
// by default a mock writer is used with user defined callback
// if outputWriter is used callback will be ignored
func UseOutputWriter(writer OutputWriter) NucleiSDKOptions {
From e432e4a4c2f884fc39eeec504abe28194679d6a2 Mon Sep 17 00:00:00 2001
From: 4shen0ne <33086594+zrquan@users.noreply.github.com>
Date: Wed, 17 Apr 2024 00:12:44 +0800
Subject: [PATCH 33/58] Fix typos (#5038)
* Fix typos
- Add a quantifier to make the description unambiguous
- Add a missing verb
* Fix href of FAQs
---
README.md | 2 +-
README_CN.md | 8 ++++----
README_ID.md | 2 +-
README_KR.md | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 07bce472b5..319f478c1b 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@
Install •
Documentation •
Credits •
- FAQs •
+ FAQs •
Join Discord
diff --git a/README_CN.md b/README_CN.md
index 095f9c109f..25e39b2ae6 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -23,8 +23,8 @@
对于安全工程师 •
对于开发者 •
文档 •
- 致谢 •
- 常见问题 •
+ 致谢 •
+ 常见问题 •
加入Discord
@@ -39,7 +39,7 @@
Nuclei使用零误报的定制模板向目标发送请求,同时可以对主机进行批量快速扫描。Nuclei提供TCP、DNS、HTTP、FILE等各类协议的扫描,通过强大且灵活的模板,可以使用Nuclei模拟各种安全检查。
-我们的[模板仓库](https://github.com/projectdiscovery/nuclei-templates)包含**超过300**安全研究员和工程师提供的模板。
+我们的[模板仓库](https://github.com/projectdiscovery/nuclei-templates)包含**超过300名**安全研究员和工程师提供的模板。
@@ -439,7 +439,7 @@ Nuclei构建很简单,通过数百名安全研究员的社区模板,Nuclei
-另外您可以其他类似的开源项目:
+另外您可以了解其他类似的开源项目:
[FFuF](https://github.com/ffuf/ffuf), [Qsfuzz](https://github.com/ameenmaali/qsfuzz), [Inception](https://github.com/proabiral/inception), [Snallygaster](https://github.com/hannob/snallygaster), [Gofingerprint](https://github.com/Static-Flow/gofingerprint), [Sn1per](https://github.com/1N3/Sn1per/tree/master/templates), [Google tsunami](https://github.com/google/tsunami-security-scanner), [Jaeles](https://github.com/jaeles-project/jaeles), [ChopChop](https://github.com/michelin/ChopChop)
diff --git a/README_ID.md b/README_ID.md
index 776c585480..73f0d86e8a 100644
--- a/README_ID.md
+++ b/README_ID.md
@@ -24,7 +24,7 @@
Untuk Pengembang •
Dokumentasi •
Kredit •
- Tanya Jawab •
+ Tanya Jawab •
Gabung Discord
diff --git a/README_KR.md b/README_KR.md
index 394ece3349..db6820dd64 100644
--- a/README_KR.md
+++ b/README_KR.md
@@ -23,7 +23,7 @@
개발자를 위한 •
문서 •
Credits •
- FAQs •
+ FAQs •
Discord 참가
From a4ba5cd1cb0be308fb5529ae81f2dab5ddde4c37 Mon Sep 17 00:00:00 2001
From: Ramana Reddy <90540245+RamanaReddy0M@users.noreply.github.com>
Date: Thu, 18 Apr 2024 15:06:15 +0530
Subject: [PATCH 34/58] Fix panic with template validation (#5065)
---
pkg/utils/insertion_ordered_map.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/pkg/utils/insertion_ordered_map.go b/pkg/utils/insertion_ordered_map.go
index 0e3b495d9b..20e6367362 100644
--- a/pkg/utils/insertion_ordered_map.go
+++ b/pkg/utils/insertion_ordered_map.go
@@ -40,6 +40,9 @@ func (insertionOrderedStringMap *InsertionOrderedStringMap) UnmarshalYAML(unmars
}
insertionOrderedStringMap.values = make(map[string]interface{})
for _, v := range data {
+ if v.Key == nil {
+ continue
+ }
insertionOrderedStringMap.Set(v.Key.(string), toString(v.Value))
}
return nil
From 3a3db67248937af9c5406105411effd404493cd0 Mon Sep 17 00:00:00 2001
From: Ice3man
Date: Thu, 18 Apr 2024 16:49:28 +0530
Subject: [PATCH 35/58] feat: katana jsonl input format not working fix (#5063)
---
pkg/input/formats/json/json.go | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/pkg/input/formats/json/json.go b/pkg/input/formats/json/json.go
index fecb1c6b99..69e628c684 100644
--- a/pkg/input/formats/json/json.go
+++ b/pkg/input/formats/json/json.go
@@ -28,9 +28,10 @@ var _ formats.Format = &JSONFormat{}
type proxifyRequest struct {
URL string `json:"url"`
Request struct {
- Header map[string]string `json:"header"`
- Body string `json:"body"`
- Raw string `json:"raw"`
+ Header map[string]string `json:"header"`
+ Body string `json:"body"`
+ Raw string `json:"raw"`
+ Endpoint string `json:"endpoint"`
} `json:"request"`
}
@@ -63,6 +64,9 @@ func (j *JSONFormat) Parse(input string, resultsCb formats.ParseReqRespCallback)
return errors.Wrap(err, "could not decode json file")
}
+ if request.URL == "" && request.Request.Endpoint != "" {
+ request.URL = request.Request.Endpoint
+ }
rawRequest, err := types.ParseRawRequestWithURL(request.Request.Raw, request.URL)
if err != nil {
gologger.Warning().Msgf("jsonl: Could not parse raw request %s: %s\n", request.URL, err)
From 66da73c1b1f320817a2a7d33404de5c8f9435fb6 Mon Sep 17 00:00:00 2001
From: Ramana Reddy <90540245+RamanaReddy0M@users.noreply.github.com>
Date: Thu, 18 Apr 2024 17:43:46 +0530
Subject: [PATCH 36/58] Fix panic err using flow templates with workflow
(#5064)
* Fix panic err using flow templates with workflows
* Misc update
* skip test if pdcp keys are not present
---------
Co-authored-by: Tarun Koyalwar
---
pkg/input/provider/list/hmap_test.go | 8 ++++++++
pkg/tmplexec/exec.go | 22 +++++++++++++++++++---
2 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/pkg/input/provider/list/hmap_test.go b/pkg/input/provider/list/hmap_test.go
index c3fb4a6699..95fc57f2d9 100644
--- a/pkg/input/provider/list/hmap_test.go
+++ b/pkg/input/provider/list/hmap_test.go
@@ -13,6 +13,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/expand"
+ "github.com/projectdiscovery/utils/auth/pdcp"
"github.com/stretchr/testify/require"
)
@@ -153,6 +154,13 @@ func Test_scanallips_normalizeStoreInputValue(t *testing.T) {
}
func Test_expandASNInputValue(t *testing.T) {
+ // skip this test if pdcp keys are not present
+ h := pdcp.PDCPCredHandler{}
+ creds, err := h.GetCreds()
+ if err != nil || creds == nil || creds.APIKey == "" {
+ t.Logf("Skipping asnmap test as pdcp keys are not present")
+ t.SkipNow()
+ }
tests := []struct {
asn string
expectedOutputFile string
diff --git a/pkg/tmplexec/exec.go b/pkg/tmplexec/exec.go
index fc504171e6..c510131da2 100644
--- a/pkg/tmplexec/exec.go
+++ b/pkg/tmplexec/exec.go
@@ -205,9 +205,25 @@ func (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
func (e *TemplateExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {
- err := e.engine.ExecuteWithResults(ctx)
- ctx.LogError(err)
- return ctx.GenerateResult(), err
+ var errx error
+ if e.options.Flow != "" {
+ flowexec, err := flow.NewFlowExecutor(e.requests, ctx, e.options, e.results, e.program)
+ if err != nil {
+ ctx.LogError(err)
+ return nil, fmt.Errorf("could not create flow executor: %s", err)
+ }
+ if err := flowexec.Compile(); err != nil {
+ ctx.LogError(err)
+ return nil, err
+ }
+ errx = flowexec.ExecuteWithResults(ctx)
+ } else {
+ errx = e.engine.ExecuteWithResults(ctx)
+ }
+ if errx != nil {
+ ctx.LogError(errx)
+ }
+ return ctx.GenerateResult(), errx
}
// getTemplateType returns the template type of the template
From bf0cae3a108773c260debab825f9ee4a8a2b71c6 Mon Sep 17 00:00:00 2001
From: Ramana Reddy <90540245+RamanaReddy0M@users.noreply.github.com>
Date: Tue, 23 Apr 2024 13:39:35 +0530
Subject: [PATCH 37/58] Fix panic with fuzz template (#5068)
* Fix panic with fuzz template
* Fix multiple mode in fuzzing
* Add test
---
cmd/integration-test/fuzz.go | 27 +++++++++++++++++++++
integration_tests/fuzz/fuzz-multi-mode.yaml | 27 +++++++++++++++++++++
pkg/fuzz/component/headers.go | 3 +++
pkg/fuzz/execute.go | 8 ++++++
4 files changed, 65 insertions(+)
create mode 100644 integration_tests/fuzz/fuzz-multi-mode.yaml
diff --git a/cmd/integration-test/fuzz.go b/cmd/integration-test/fuzz.go
index f5e71774d0..e4834d4b96 100644
--- a/cmd/integration-test/fuzz.go
+++ b/cmd/integration-test/fuzz.go
@@ -18,6 +18,7 @@ const (
var fuzzingTestCases = []TestCaseInfo{
{Path: "fuzz/fuzz-mode.yaml", TestCase: &fuzzModeOverride{}},
+ {Path: "fuzz/fuzz-multi-mode.yaml", TestCase: &fuzzMultipleMode{}},
{Path: "fuzz/fuzz-type.yaml", TestCase: &fuzzTypeOverride{}},
{Path: "fuzz/fuzz-query.yaml", TestCase: &httpFuzzQuery{}},
{Path: "fuzz/fuzz-headless.yaml", TestCase: &HeadlessFuzzingQuery{}},
@@ -174,3 +175,29 @@ func (h *HeadlessFuzzingQuery) Execute(filePath string) error {
}
return expectResultsCount(got, 2)
}
+
+type fuzzMultipleMode struct{}
+
+// Execute executes a test case and returns an error if occurred
+func (h *fuzzMultipleMode) Execute(filePath string) error {
+ router := httprouter.New()
+ router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+ xClientId := r.Header.Get("X-Client-Id")
+ xSecretId := r.Header.Get("X-Secret-Id")
+ if xClientId != "nuclei-v3" || xSecretId != "nuclei-v3" {
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+ w.Header().Set("Content-Type", "text/html")
+ resp := fmt.Sprintf("This is multi-mode fuzzing test: %v ", xClientId)
+ fmt.Fprint(w, resp)
+ })
+ ts := httptest.NewTLSServer(router)
+ defer ts.Close()
+
+ got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"?url=https://scanme.sh", debug, "-jsonl", "-fuzz")
+ if err != nil {
+ return err
+ }
+ return expectResultsCount(got, 1)
+}
\ No newline at end of file
diff --git a/integration_tests/fuzz/fuzz-multi-mode.yaml b/integration_tests/fuzz/fuzz-multi-mode.yaml
new file mode 100644
index 0000000000..b8a15aa2ec
--- /dev/null
+++ b/integration_tests/fuzz/fuzz-multi-mode.yaml
@@ -0,0 +1,27 @@
+id: fuzz-multi-mode-test
+
+info:
+ name: multi-mode fuzzing test
+ author: pdteam
+ severity: info
+
+http:
+ - payloads:
+ inject:
+ - nuclei-v1
+ - nuclei-v2
+ - nuclei-v3
+
+ fuzzing:
+ - part: header
+ type: replace
+ mode: multiple
+ fuzz:
+ X-Client-Id: "{{inject}}"
+ X-Secret-Id: "{{inject}}"
+
+ matchers-condition: or
+ matchers:
+ - type: word
+ words:
+ - "nuclei-v3"
\ No newline at end of file
diff --git a/pkg/fuzz/component/headers.go b/pkg/fuzz/component/headers.go
index fa0f5e1126..fd356b6314 100644
--- a/pkg/fuzz/component/headers.go
+++ b/pkg/fuzz/component/headers.go
@@ -83,6 +83,9 @@ func (q *Header) Delete(key string) error {
func (q *Header) Rebuild() (*retryablehttp.Request, error) {
cloned := q.req.Clone(context.Background())
q.value.parsed.Iterate(func(key string, value any) bool {
+ if strings.TrimSpace(key) == "" {
+ return true
+ }
if strings.EqualFold(key, "Host") {
return true
}
diff --git a/pkg/fuzz/execute.go b/pkg/fuzz/execute.go
index c8054bf80b..23b3e6e976 100644
--- a/pkg/fuzz/execute.go
+++ b/pkg/fuzz/execute.go
@@ -211,6 +211,14 @@ func (rule *Rule) executeRuleValues(input *ExecuteRuleInput, ruleComponent compo
})
// if mode is multiple now build and execute it
if rule.modeType == multipleModeType {
+ rule.Fuzz.KV.Iterate(func(key, value string) bool {
+ var evaluated string
+ evaluated, input.InteractURLs = rule.executeEvaluate(input, key, "", value, input.InteractURLs)
+ if err := ruleComponent.SetValue(key, evaluated); err != nil {
+ return true
+ }
+ return true
+ })
req, err := ruleComponent.Rebuild()
if err != nil {
return err
From 6cd0d0517415a2c20aeba628df2c8fb45231cdb9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 23 Apr 2024 13:41:47 +0530
Subject: [PATCH 38/58] chore(deps): bump
github.com/projectdiscovery/retryablehttp-go (#5073)
Bumps [github.com/projectdiscovery/retryablehttp-go](https://github.com/projectdiscovery/retryablehttp-go) from 1.0.55 to 1.0.57.
- [Release notes](https://github.com/projectdiscovery/retryablehttp-go/releases)
- [Commits](https://github.com/projectdiscovery/retryablehttp-go/compare/v1.0.55...v1.0.57)
---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/retryablehttp-go
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
go.mod | 4 ++--
go.sum | 10 ++++------
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/go.mod b/go.mod
index 4cf94ec00c..07f3bf8a38 100644
--- a/go.mod
+++ b/go.mod
@@ -25,7 +25,7 @@ require (
github.com/projectdiscovery/interactsh v1.1.9
github.com/projectdiscovery/rawhttp v0.1.45
github.com/projectdiscovery/retryabledns v1.0.58
- github.com/projectdiscovery/retryablehttp-go v1.0.55
+ github.com/projectdiscovery/retryablehttp-go v1.0.57
github.com/projectdiscovery/yamldoc-go v1.0.4
github.com/remeh/sizedwaitgroup v1.0.0
github.com/rs/xid v1.5.0
@@ -94,7 +94,7 @@ require (
github.com/projectdiscovery/tlsx v1.1.6
github.com/projectdiscovery/uncover v1.0.7
github.com/projectdiscovery/useragent v0.0.40
- github.com/projectdiscovery/utils v0.0.88
+ github.com/projectdiscovery/utils v0.0.89
github.com/projectdiscovery/wappalyzergo v0.0.116
github.com/redis/go-redis/v9 v9.1.0
github.com/seh-msft/burpxml v1.0.1
diff --git a/go.sum b/go.sum
index 888602a714..7e6a96bae7 100644
--- a/go.sum
+++ b/go.sum
@@ -341,8 +341,6 @@ github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
-github.com/go-echarts/go-echarts/v2 v2.3.3 h1:uImZAk6qLkC6F9ju6mZ5SPBqTyK8xjZKwSmwnCg4bxg=
-github.com/go-echarts/go-echarts/v2 v2.3.3/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@@ -876,8 +874,8 @@ github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917 h1:m03X4gB
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917/go.mod h1:JxXtZC9e195awe7EynrcnBJmFoad/BNDzW9mzFkK8Sg=
github.com/projectdiscovery/retryabledns v1.0.58 h1:ut1FSB9+GZ6zQIlKJFLqIz2RZs81EmkbsHTuIrWfYLE=
github.com/projectdiscovery/retryabledns v1.0.58/go.mod h1:RobmKoNBgngAVE4H9REQtaLP1pa4TCyypHy1MWHT1mY=
-github.com/projectdiscovery/retryablehttp-go v1.0.55 h1:ADgugnl9jKkNXn5m/Zd8TGPq1P7GplYlqUNKm/qTmls=
-github.com/projectdiscovery/retryablehttp-go v1.0.55/go.mod h1:Kpvh4ruFPOEPYaYxgbFmlvBJr4lJKqpcbGvx1j0r/Ng=
+github.com/projectdiscovery/retryablehttp-go v1.0.57 h1:OGfUXKXV4bE5msGxeRrNtMaDg2l8U1JcLXmwG7yXWrY=
+github.com/projectdiscovery/retryablehttp-go v1.0.57/go.mod h1:Lo2EU1wV1draQ/dHuiSkokW4gZ216F/qi/t12DIdMbA=
github.com/projectdiscovery/sarif v0.0.1 h1:C2Tyj0SGOKbCLgHrx83vaE6YkzXEVrMXYRGLkKCr/us=
github.com/projectdiscovery/sarif v0.0.1/go.mod h1:cEYlDu8amcPf6b9dSakcz2nNnJsoz4aR6peERwV+wuQ=
github.com/projectdiscovery/stringsutil v0.0.2 h1:uzmw3IVLJSMW1kEg8eCStG/cGbYYZAja8BH3LqqJXMA=
@@ -888,8 +886,8 @@ github.com/projectdiscovery/uncover v1.0.7 h1:ut+2lTuvmftmveqF5RTjMWAgyLj8ltPQC7
github.com/projectdiscovery/uncover v1.0.7/go.mod h1:HFXgm1sRPuoN0D4oATljPIdmbo/EEh1wVuxQqo/dwFE=
github.com/projectdiscovery/useragent v0.0.40 h1:1LUhReSGPkhqsM5n40OOC9dIoNqMGs1dyGFJcOmg2Fo=
github.com/projectdiscovery/useragent v0.0.40/go.mod h1:EvK1x3s948Gtqb/XOahXcauyejCL/rSgy5d1IAvsKT4=
-github.com/projectdiscovery/utils v0.0.88 h1:oYfCXM+8VHNLyH/H6cOibkuDUwHUAOBAMRNPFX6NPrs=
-github.com/projectdiscovery/utils v0.0.88/go.mod h1:lAWzFdGXtJRPKdhUu1Z46d8B8JbASTk1Z69WY6H/3kA=
+github.com/projectdiscovery/utils v0.0.89 h1:ruH2bSkpX/rB7EPp2EV/rWyAubQVxCVU38nRcLp4L1w=
+github.com/projectdiscovery/utils v0.0.89/go.mod h1:Dwh5cxn7y97jvyYG3GmBvj0negfH9IjH15qXnzFNtOI=
github.com/projectdiscovery/wappalyzergo v0.0.116 h1:xy+mBpwbYo/0PSzmJOQ/RXHomEh0D3nDBcbCxsW69m8=
github.com/projectdiscovery/wappalyzergo v0.0.116/go.mod h1:hc/o+fgM8KtdpFesjfBTmHTwsR+yBd+4kYZW/DGy/x8=
github.com/projectdiscovery/yamldoc-go v1.0.4 h1:eZoESapnMw6WAHiVgRwNqvbJEfNHEH148uthhFbG5jE=
From 60b9d4db4bb5033401fd30c20fd9e6ebebd9c292 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 23 Apr 2024 13:41:55 +0530
Subject: [PATCH 39/58] chore(deps): bump github.com/projectdiscovery/goflags
(#5074)
Bumps [github.com/projectdiscovery/goflags](https://github.com/projectdiscovery/goflags) from 0.1.48 to 0.1.49.
- [Release notes](https://github.com/projectdiscovery/goflags/releases)
- [Commits](https://github.com/projectdiscovery/goflags/compare/v0.1.48...v0.1.49)
---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/goflags
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index 07f3bf8a38..30ddbc6ecb 100644
--- a/go.mod
+++ b/go.mod
@@ -81,7 +81,7 @@ require (
github.com/projectdiscovery/dsl v0.0.52
github.com/projectdiscovery/fasttemplate v0.0.2
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb
- github.com/projectdiscovery/goflags v0.1.48
+ github.com/projectdiscovery/goflags v0.1.49
github.com/projectdiscovery/gologger v1.1.12
github.com/projectdiscovery/gostruct v0.0.2
github.com/projectdiscovery/gozero v0.0.2
diff --git a/go.sum b/go.sum
index 7e6a96bae7..dbcf574dc7 100644
--- a/go.sum
+++ b/go.sum
@@ -842,8 +842,8 @@ github.com/projectdiscovery/freeport v0.0.5 h1:jnd3Oqsl4S8n0KuFkE5Hm8WGDP24ITBvm
github.com/projectdiscovery/freeport v0.0.5/go.mod h1:PY0bxSJ34HVy67LHIeF3uIutiCSDwOqKD8ruBkdiCwE=
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb h1:rutG906Drtbpz4DwU5mhGIeOhRcktDH4cGQitGUMAsg=
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb/go.mod h1:FLjF1DmZ+POoGEiIQdWuYVwS++C/GwpX8YaCsTSm1RY=
-github.com/projectdiscovery/goflags v0.1.48 h1:iJgq+YuQOEm0V7BqlV/0wx7GaI2rmGiFzPkvL/4sCj0=
-github.com/projectdiscovery/goflags v0.1.48/go.mod h1:C9Qqyivehk5iYCf3VAC3+85y60Qbde1ZBMqe5wHhcVw=
+github.com/projectdiscovery/goflags v0.1.49 h1:0e9wya431WDeVm8ZtlyqBQ+rwnhDjUswDMcS0did9Tg=
+github.com/projectdiscovery/goflags v0.1.49/go.mod h1:f0zRbaa5QLrjfJQ5v0efvq8EhkDGhCm9h0hsahjjKFc=
github.com/projectdiscovery/gologger v1.1.12 h1:uX/QkQdip4PubJjjG0+uk5DtyAi1ANPJUvpmimXqv4A=
github.com/projectdiscovery/gologger v1.1.12/go.mod h1:DI8nywPLERS5mo8QEA9E7gd5HZ3Je14SjJBH3F5/kLw=
github.com/projectdiscovery/gostruct v0.0.2 h1:s8gP8ApugGM4go1pA+sVlPDXaWqNP5BBDDSv7VEdG1M=
From 2fc1be213a6c6914e3368248ea24eb258400881c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 23 Apr 2024 13:42:19 +0530
Subject: [PATCH 40/58] chore(deps): bump github.com/projectdiscovery/ratelimit
(#5077)
Bumps [github.com/projectdiscovery/ratelimit](https://github.com/projectdiscovery/ratelimit) from 0.0.35 to 0.0.38.
- [Release notes](https://github.com/projectdiscovery/ratelimit/releases)
- [Commits](https://github.com/projectdiscovery/ratelimit/compare/v0.0.35...v0.0.38)
---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/ratelimit
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index 30ddbc6ecb..2854eee23c 100644
--- a/go.mod
+++ b/go.mod
@@ -88,7 +88,7 @@ require (
github.com/projectdiscovery/httpx v1.6.0
github.com/projectdiscovery/mapcidr v1.1.16
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
- github.com/projectdiscovery/ratelimit v0.0.35
+ github.com/projectdiscovery/ratelimit v0.0.38
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917
github.com/projectdiscovery/sarif v0.0.1
github.com/projectdiscovery/tlsx v1.1.6
diff --git a/go.sum b/go.sum
index dbcf574dc7..6eda8f7d0b 100644
--- a/go.sum
+++ b/go.sum
@@ -866,8 +866,8 @@ github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 h1:L/e8z8yw
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5/go.mod h1:pGW2ncnTxTxHtP9wzcIJAB+3/NMp6IiuQWd2NK7K+oc=
github.com/projectdiscovery/networkpolicy v0.0.8 h1:XvfBaBwSDNTesSfNQP9VLk3HX9I7x7gHm028TJ5XwI8=
github.com/projectdiscovery/networkpolicy v0.0.8/go.mod h1:xnjNqhemxUPxU+UD5Jgsc3+K8IVmcqT1SJeo6UzMtkI=
-github.com/projectdiscovery/ratelimit v0.0.35 h1:epEzFATOcXZ4tssV4Hax5Op9lrbUnQMEGMV5PoUpTKc=
-github.com/projectdiscovery/ratelimit v0.0.35/go.mod h1:mPqa8UpV5I7eAN5/ZcsjLiXMhjtVvZRrHtpBRsTPuyA=
+github.com/projectdiscovery/ratelimit v0.0.38 h1:TCcXXjL3LwB+Es1dtW50FSoQsAQBHH0PqQ2VGZhp50U=
+github.com/projectdiscovery/ratelimit v0.0.38/go.mod h1:ig2wgB2qY2YKFKu0Lm1R/AxjXsGyFceEvC+ATAMOFgc=
github.com/projectdiscovery/rawhttp v0.1.45 h1:5jssUybhRz2ljI3bW4kUO9MTvZ37ArG8pISYtgqzwBg=
github.com/projectdiscovery/rawhttp v0.1.45/go.mod h1:oLWuZkyKi4rddPoLEN3TeAqyLvFNfr3dxmzV2kwpPvs=
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917 h1:m03X4gBVSorSzvmm0bFa7gDV4QNSOWPL/fgZ4kTXBxk=
From 5dacb255ac0b8557fd73da8fe76f43f9749dc9a8 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 23 Apr 2024 14:41:46 +0530
Subject: [PATCH 41/58] chore(deps): bump github.com/projectdiscovery/useragent
(#5075)
Bumps [github.com/projectdiscovery/useragent](https://github.com/projectdiscovery/useragent) from 0.0.40 to 0.0.47.
- [Release notes](https://github.com/projectdiscovery/useragent/releases)
- [Commits](https://github.com/projectdiscovery/useragent/compare/v0.0.40...v0.0.47)
---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/useragent
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index 2854eee23c..6dbdbf7dae 100644
--- a/go.mod
+++ b/go.mod
@@ -93,7 +93,7 @@ require (
github.com/projectdiscovery/sarif v0.0.1
github.com/projectdiscovery/tlsx v1.1.6
github.com/projectdiscovery/uncover v1.0.7
- github.com/projectdiscovery/useragent v0.0.40
+ github.com/projectdiscovery/useragent v0.0.47
github.com/projectdiscovery/utils v0.0.89
github.com/projectdiscovery/wappalyzergo v0.0.116
github.com/redis/go-redis/v9 v9.1.0
diff --git a/go.sum b/go.sum
index 6eda8f7d0b..4c781f73ce 100644
--- a/go.sum
+++ b/go.sum
@@ -884,8 +884,8 @@ github.com/projectdiscovery/tlsx v1.1.6 h1:iw2zwKbd2+kRQ8J1G4dLmS0CLyemd/tKz1Uzc
github.com/projectdiscovery/tlsx v1.1.6/go.mod h1:s7SRRFdrwIZBK/RXXZi4CR/CubqFSvp8h5Bk1srEZIo=
github.com/projectdiscovery/uncover v1.0.7 h1:ut+2lTuvmftmveqF5RTjMWAgyLj8ltPQC7siFy9sj0A=
github.com/projectdiscovery/uncover v1.0.7/go.mod h1:HFXgm1sRPuoN0D4oATljPIdmbo/EEh1wVuxQqo/dwFE=
-github.com/projectdiscovery/useragent v0.0.40 h1:1LUhReSGPkhqsM5n40OOC9dIoNqMGs1dyGFJcOmg2Fo=
-github.com/projectdiscovery/useragent v0.0.40/go.mod h1:EvK1x3s948Gtqb/XOahXcauyejCL/rSgy5d1IAvsKT4=
+github.com/projectdiscovery/useragent v0.0.47 h1:VEOU7uG7TutZNIE0DZNP7hGAGi4bwLPGM1X7Rny52s0=
+github.com/projectdiscovery/useragent v0.0.47/go.mod h1:Cfk9X9SISYSCmqpej0r9+paJbDHzNHic2YdWQtpdz2M=
github.com/projectdiscovery/utils v0.0.89 h1:ruH2bSkpX/rB7EPp2EV/rWyAubQVxCVU38nRcLp4L1w=
github.com/projectdiscovery/utils v0.0.89/go.mod h1:Dwh5cxn7y97jvyYG3GmBvj0negfH9IjH15qXnzFNtOI=
github.com/projectdiscovery/wappalyzergo v0.0.116 h1:xy+mBpwbYo/0PSzmJOQ/RXHomEh0D3nDBcbCxsW69m8=
From 61e9be530f3c9c6a27cd9b862c4d7e446f7ed7af Mon Sep 17 00:00:00 2001
From: Ramana Reddy <90540245+RamanaReddy0M@users.noreply.github.com>
Date: Tue, 23 Apr 2024 16:04:32 +0530
Subject: [PATCH 42/58] Fix: `skip-variables-check` option in self-contained
templates (#5053)
* fix: skip-variables-check option in self-contained templates
* Update build workflow envs
---
.github/workflows/build-test.yml | 2 ++
pkg/protocols/http/build_request.go | 4 ++--
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index aa0d8aecfe..75591aa8b8 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -42,6 +42,7 @@ jobs:
- name: Test
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+ PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}"
run: go test ./...
- name: Integration Tests
@@ -49,6 +50,7 @@ jobs:
env:
GH_ACTION: true
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+ PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}"
run: |
chmod +x run.sh
bash run.sh ${{ matrix.os }}
diff --git a/pkg/protocols/http/build_request.go b/pkg/protocols/http/build_request.go
index bae9a37af0..18b75c72bd 100644
--- a/pkg/protocols/http/build_request.go
+++ b/pkg/protocols/http/build_request.go
@@ -277,7 +277,7 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st
return nil, fmt.Errorf("malformed request supplied")
}
- if err := expressions.ContainsUnresolvedVariables(parts[1]); err != nil {
+ if err := expressions.ContainsUnresolvedVariables(parts[1]); err != nil && !r.request.SkipVariablesCheck {
return nil, ErrUnresolvedVars.Msgf(parts[1])
}
@@ -296,7 +296,7 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st
}
return r.generateRawRequest(ctx, data, parsed, values, payloads)
}
- if err := expressions.ContainsUnresolvedVariables(data); err != nil {
+ if err := expressions.ContainsUnresolvedVariables(data); err != nil && !r.request.SkipVariablesCheck {
// early exit: if there are any unresolved variables in `path` after evaluation
// then return early since this will definitely fail
return nil, ErrUnresolvedVars.Msgf(data)
From 465894df159c1ebf5a0ede6f3eef56c95a36b952 Mon Sep 17 00:00:00 2001
From: Dogan Can Bakir <65292895+dogancanbakir@users.noreply.github.com>
Date: Tue, 23 Apr 2024 13:34:52 +0300
Subject: [PATCH 43/58] disable thread count warning upon validate (#5078)
---
internal/runner/runner.go | 2 ++
pkg/protocols/http/http.go | 12 ++++++++++--
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/internal/runner/runner.go b/internal/runner/runner.go
index 174c06f0f7..ce8cf4030c 100644
--- a/internal/runner/runner.go
+++ b/internal/runner/runner.go
@@ -52,6 +52,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/uncover"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
+ httpProtocol "github.com/projectdiscovery/nuclei/v3/pkg/protocols/http"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
@@ -684,6 +685,7 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) {
} else {
stats.DisplayAsWarning(templates.SkippedCodeTmplTamperedStats)
}
+ stats.DisplayAsWarning(httpProtocol.SetThreadToCountZero)
stats.ForceDisplayWarning(templates.SkippedUnsignedStats)
stats.ForceDisplayWarning(templates.SkippedRequestSignatureStats)
diff --git a/pkg/protocols/http/http.go b/pkg/protocols/http/http.go
index b98fd917a5..c20cbbfa66 100644
--- a/pkg/protocols/http/http.go
+++ b/pkg/protocols/http/http.go
@@ -9,7 +9,6 @@ import (
json "github.com/json-iterator/go"
"github.com/pkg/errors"
- "github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz"
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
@@ -19,6 +18,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool"
httputil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http"
+ "github.com/projectdiscovery/nuclei/v3/pkg/utils/stats"
"github.com/projectdiscovery/rawhttp"
"github.com/projectdiscovery/retryablehttp-go"
fileutil "github.com/projectdiscovery/utils/file"
@@ -455,7 +455,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
}
}
if hasNamedInternalExtractor && hasMultipleRequests {
- gologger.Warning().Label(options.TemplateID).Msgf("Setting thread count to 0 because dynamic extractors are not supported with payloads yet")
+ stats.Increment(SetThreadToCountZero)
request.Threads = 0
} else {
// specifically for http requests high concurrency and and threads will lead to memory exausthion, hence reduce the maximum parallelism
@@ -490,3 +490,11 @@ func (request *Request) Requests() int {
}
return len(request.Path)
}
+
+const (
+ SetThreadToCountZero = "set-thread-count-to-zero"
+)
+
+func init() {
+ stats.NewEntry(SetThreadToCountZero, "Setting thread count to 0 for %d templates, dynamic extractors are not supported with payloads yet")
+}
From ea3705eb64188c1389b1d98adb91eb16e731bfdd Mon Sep 17 00:00:00 2001
From: Dogan Can Bakir <65292895+dogancanbakir@users.noreply.github.com>
Date: Tue, 23 Apr 2024 14:26:33 +0300
Subject: [PATCH 44/58] fix openapi import nil deref (#5080)
---
pkg/input/formats/openapi/generator.go | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/pkg/input/formats/openapi/generator.go b/pkg/input/formats/openapi/generator.go
index 090b761c3a..6f10c56d09 100644
--- a/pkg/input/formats/openapi/generator.go
+++ b/pkg/input/formats/openapi/generator.go
@@ -179,6 +179,10 @@ func generateRequestsFromOp(opts *generateReqOptions) error {
for _, parameter := range reqParams {
value := parameter.Value
+ if value.Schema == nil || value.Schema.Value == nil {
+ continue
+ }
+
// paramValue or default value to use
var paramValue interface{}
From 4b9c3b8d16ceca39f9892550467bca2fa1a68635 Mon Sep 17 00:00:00 2001
From: scottdharvey
Date: Tue, 23 Apr 2024 06:41:12 -0700
Subject: [PATCH 45/58] Feat 5059 (#5060)
* change catalog
* add usesuppliedcatalog option
* add catalog nil check
* Update config.go
---
lib/config.go | 9 +++++++++
lib/sdk.go | 4 ++--
lib/sdk_private.go | 4 +++-
3 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/lib/config.go b/lib/config.go
index ddcf3b88b4..a453fb74c7 100644
--- a/lib/config.go
+++ b/lib/config.go
@@ -10,6 +10,7 @@ import (
"github.com/projectdiscovery/ratelimit"
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
+ "github.com/projectdiscovery/nuclei/v3/pkg/catalog"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
@@ -425,3 +426,11 @@ func SignedTemplatesOnly() NucleiSDKOptions {
return nil
}
}
+
+// WithCatalog uses a supplied catalog
+func WithCatalog(cat catalog.Catalog) NucleiSDKOptions {
+ return func(e *NucleiEngine) error {
+ e.catalog = cat
+ return nil
+ }
+}
diff --git a/lib/sdk.go b/lib/sdk.go
index 4bee921c31..c80abb0615 100644
--- a/lib/sdk.go
+++ b/lib/sdk.go
@@ -6,7 +6,7 @@ import (
"io"
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
- "github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
+ "github.com/projectdiscovery/nuclei/v3/pkg/catalog"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
@@ -63,7 +63,7 @@ type NucleiEngine struct {
// unexported core fields
interactshClient *interactsh.Client
- catalog *disk.DiskCatalog
+ catalog catalog.Catalog
rateLimiter *ratelimit.Limiter
store *loader.Store
httpxClient providerTypes.InputLivenessProbe
diff --git a/lib/sdk_private.go b/lib/sdk_private.go
index c76970c91d..89628cc933 100644
--- a/lib/sdk_private.go
+++ b/lib/sdk_private.go
@@ -146,7 +146,9 @@ func (e *NucleiEngine) init() error {
return err
}
- e.catalog = disk.NewCatalog(config.DefaultConfig.TemplatesDirectory)
+ if e.catalog == nil {
+ e.catalog = disk.NewCatalog(config.DefaultConfig.TemplatesDirectory)
+ }
e.executerOpts = protocols.ExecutorOptions{
Output: e.customWriter,
From e480d131f1b8545070fb0e7fef7a4aaac423d485 Mon Sep 17 00:00:00 2001
From: Dogan Can Bakir <65292895+dogancanbakir@users.noreply.github.com>
Date: Tue, 23 Apr 2024 16:47:26 +0300
Subject: [PATCH 46/58] add query param (#4894)
* include params
* add query var
* override params for base url var
---
pkg/protocols/utils/variables.go | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/pkg/protocols/utils/variables.go b/pkg/protocols/utils/variables.go
index d1810d19b5..90c786923b 100644
--- a/pkg/protocols/utils/variables.go
+++ b/pkg/protocols/utils/variables.go
@@ -24,6 +24,7 @@ func init() {
Host: "Host",
Port: "Port",
Path: "Path",
+ Query: "Query",
File: "File",
Scheme: "Scheme",
Input: "Input",
@@ -44,6 +45,7 @@ const (
Host
Port
Path
+ Query
File
Scheme
Input
@@ -162,6 +164,12 @@ func generateVariables(inputURL *urlutil.URL, removeTrailingSlash bool) map[stri
knownVariables[v] = port
case Path:
knownVariables[v] = requestPath
+ case Query:
+ if queryParams := urlutil.GetParams(parsed.URL.Query()); len(queryParams) > 0 {
+ knownVariables[v] = "?" + queryParams.Encode()
+ } else {
+ knownVariables[v] = ""
+ }
case File:
knownVariables[v] = base
case Scheme:
From 8676cb6daf83f6dee18899f13cdb7faf0a0fbf69 Mon Sep 17 00:00:00 2001
From: Dogan Can Bakir <65292895+dogancanbakir@users.noreply.github.com>
Date: Wed, 24 Apr 2024 10:35:05 +0300
Subject: [PATCH 47/58] add response read timeout flag (#4944)
* add response read timeout flag
* fix test
* bump utils
* fix network tests
* fix incorrect unit of response-read-timeout unit
---------
Co-authored-by: Tarun Koyalwar
---
cmd/nuclei/main.go | 1 +
pkg/protocols/network/request.go | 13 ++-----------
pkg/testutils/testutils.go | 1 +
pkg/types/types.go | 3 +++
4 files changed, 7 insertions(+), 11 deletions(-)
diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go
index a90c4ed52e..9b68dc11b6 100644
--- a/cmd/nuclei/main.go
+++ b/cmd/nuclei/main.go
@@ -298,6 +298,7 @@ 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", 10*1024*1024, "max response size to read in bytes"),
flagSet.IntVarP(&options.ResponseSaveSize, "response-size-save", "rss", 1*1024*1024, "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"),
)
diff --git a/pkg/protocols/network/request.go b/pkg/protocols/network/request.go
index 146c657072..1f51ebe119 100644
--- a/pkg/protocols/network/request.go
+++ b/pkg/protocols/network/request.go
@@ -36,12 +36,6 @@ import (
syncutil "github.com/projectdiscovery/utils/sync"
)
-var (
- // TODO: make this configurable
- // DefaultReadTimeout is the default read timeout for network requests
- DefaultReadTimeout = time.Duration(5) * time.Second
-)
-
var _ protocols.Request = &Request{}
// Type returns the type of the protocol request
@@ -295,7 +289,7 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac
}
if input.Read > 0 {
- buffer, err := ConnReadNWithTimeout(conn, int64(input.Read), DefaultReadTimeout)
+ buffer, err := ConnReadNWithTimeout(conn, int64(input.Read), request.options.Options.ResponseReadTimeout)
if err != nil {
return errorutil.NewWithErr(err).Msgf("could not read response from connection")
}
@@ -345,7 +339,7 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac
bufferSize = -1
}
- final, err := ConnReadNWithTimeout(conn, int64(bufferSize), DefaultReadTimeout)
+ final, err := ConnReadNWithTimeout(conn, int64(bufferSize), request.options.Options.ResponseReadTimeout)
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)
@@ -446,9 +440,6 @@ func getAddress(toTest string) (string, error) {
}
func ConnReadNWithTimeout(conn net.Conn, n int64, timeout time.Duration) ([]byte, error) {
- if timeout == 0 {
- timeout = DefaultReadTimeout
- }
if n == -1 {
// if n is -1 then read all available data from connection
return reader.ConnReadNWithTimeout(conn, -1, timeout)
diff --git a/pkg/testutils/testutils.go b/pkg/testutils/testutils.go
index e59aa015e2..faa21938bb 100644
--- a/pkg/testutils/testutils.go
+++ b/pkg/testutils/testutils.go
@@ -72,6 +72,7 @@ var DefaultOptions = &types.Options{
InteractionsPollDuration: 5,
GitHubTemplateRepo: []string{},
GitHubToken: "",
+ ResponseReadTimeout: time.Second * 5,
}
// TemplateInfo contains info for a mock executed template.
diff --git a/pkg/types/types.go b/pkg/types/types.go
index b6bdcaee55..caa1ced75f 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -289,6 +289,8 @@ 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
@@ -427,6 +429,7 @@ func DefaultOptions() *Options {
MaxHostError: 30,
ResponseReadSize: 10 * 1024 * 1024,
ResponseSaveSize: 1024 * 1024,
+ ResponseReadTimeout: 5 * time.Second,
}
}
From 515f7c12bb6b274f8fe90ce0c669992ca9fc29f5 Mon Sep 17 00:00:00 2001
From: Tarun Koyalwar <45962551+tarunKoyalwar@users.noreply.github.com>
Date: Wed, 24 Apr 2024 13:05:40 +0530
Subject: [PATCH 48/58] fix go install failing (#5083)
* Fix panic with fuzz template
* Fix multiple mode in fuzzing
* Add test
* remove fork: use official go-echarts
* bump lint action to v4
---------
Co-authored-by: Ramana Reddy
---
.github/workflows/lint-test.yml | 2 +-
go.mod | 2 --
go.sum | 2 --
pkg/scan/charts/echarts.go | 41 +++++++++++++++++++++++++--------
4 files changed, 32 insertions(+), 15 deletions(-)
diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml
index 4292a42f19..8b4695dc83 100644
--- a/.github/workflows/lint-test.yml
+++ b/.github/workflows/lint-test.yml
@@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@v3
- name: Run golangci-lint
- uses: golangci/golangci-lint-action@v3.6.0
+ uses: golangci/golangci-lint-action@v4.0.0
with:
version: latest
args: --timeout 5m
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 6dbdbf7dae..7ffdcc20bd 100644
--- a/go.mod
+++ b/go.mod
@@ -349,5 +349,3 @@ require (
// https://go.dev/ref/mod#go-mod-file-retract
retract v3.2.0 // retract due to broken js protocol issue
-
-replace github.com/go-echarts/go-echarts/v2 => github.com/tarunKoyalwar/go-echarts/v2 v2.1.1
diff --git a/go.sum b/go.sum
index 4c781f73ce..07a7bc8d22 100644
--- a/go.sum
+++ b/go.sum
@@ -1013,8 +1013,6 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
-github.com/tarunKoyalwar/go-echarts/v2 v2.1.1 h1:5fsXGPmK+i18J8cDgxy7AJkiXWBARpVTb0Gbv+bAzPo=
-github.com/tarunKoyalwar/go-echarts/v2 v2.1.1/go.mod h1:VEeyPT5Odx/UHeuxtIAHGu2+87MWGA5OBaZ120NFi/w=
github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
diff --git a/pkg/scan/charts/echarts.go b/pkg/scan/charts/echarts.go
index bf70fee710..a2a9815888 100644
--- a/pkg/scan/charts/echarts.go
+++ b/pkg/scan/charts/echarts.go
@@ -38,16 +38,18 @@ func (s *ScanEventsCharts) allCharts(c echo.Context) *components.Page {
page := components.NewPage()
page.PageTitle = "Nuclei Charts"
line1 := s.totalRequestsOverTime(c)
- line1.SetSpacerHeight(SpacerHeight)
+ // line1.SetSpacerHeight(SpacerHeight)
kline := s.topSlowTemplates(c)
- kline.SetSpacerHeight(SpacerHeight)
+ // kline.SetSpacerHeight(SpacerHeight)
line2 := s.requestsVSInterval(c)
- line2.SetSpacerHeight(SpacerHeight)
+ // line2.SetSpacerHeight(SpacerHeight)
line3 := s.concurrencyVsTime(c)
- line3.SetSpacerHeight(SpacerHeight)
+ // line3.SetSpacerHeight(SpacerHeight)
page.AddCharts(line1, kline, line2, line3)
- page.Validate()
page.SetLayout(components.PageCenterLayout)
+ page.Theme = "dark"
+ page.Validate()
+
return page
}
@@ -59,7 +61,12 @@ func (s *ScanEventsCharts) TotalRequestsOverTime(c echo.Context) error {
// totalRequestsOverTime generates a line chart showing total requests count over time
func (s *ScanEventsCharts) totalRequestsOverTime(c echo.Context) *charts.Line {
line := charts.NewLine()
- line.SetCaption("Chart Shows Total Requests Count Over Time (for each/all Protocols)")
+ line.SetGlobalOptions(
+ charts.WithTitleOpts(opts.Title{
+ Title: "Nuclei: Total Requests vs Time",
+ Subtitle: "Chart Shows Total Requests Count Over Time (for each/all Protocols)",
+ }),
+ )
var startTime time.Time = time.Now()
var endTime time.Time
@@ -120,8 +127,12 @@ func (s *ScanEventsCharts) TopSlowTemplates(c echo.Context) error {
// topSlowTemplates generates a Kline chart showing the top slow templates by time taken
func (s *ScanEventsCharts) topSlowTemplates(c echo.Context) *charts.Kline {
kline := charts.NewKLine()
- kline.SetCaption(fmt.Sprintf("Chart Shows Top Slow Templates (by time taken) (Top %v)", TopK))
-
+ kline.SetGlobalOptions(
+ charts.WithTitleOpts(opts.Title{
+ Title: "Nuclei: Top Slow Templates",
+ Subtitle: fmt.Sprintf("Chart Shows Top Slow Templates (by time taken) (Top %v)", TopK),
+ }),
+ )
ids := map[string][]int64{}
var startTime time.Time = time.Now()
for _, event := range s.data {
@@ -200,7 +211,12 @@ func (s *ScanEventsCharts) RequestsVSInterval(c echo.Context) error {
// requestsVSInterval generates a line chart showing requests per second over time
func (s *ScanEventsCharts) requestsVSInterval(c echo.Context) *charts.Line {
line := charts.NewLine()
- line.SetCaption("Chart Shows RPS (Requests Per Second) Over Time")
+ line.SetGlobalOptions(
+ charts.WithTitleOpts(opts.Title{
+ Title: "Nuclei: Requests Per Second vs Time",
+ Subtitle: "Chart Shows RPS (Requests Per Second) Over Time",
+ }),
+ )
sort.Slice(s.data, func(i, j int) bool {
return s.data[i].Time.Before(s.data[j].Time)
@@ -267,7 +283,12 @@ func (s *ScanEventsCharts) ConcurrencyVsTime(c echo.Context) error {
// concurrencyVsTime generates a line chart showing concurrency (total workers) over time
func (s *ScanEventsCharts) concurrencyVsTime(c echo.Context) *charts.Line {
line := charts.NewLine()
- line.SetCaption("Chart Shows Concurrency (Total Workers) Over Time")
+ line.SetGlobalOptions(
+ charts.WithTitleOpts(opts.Title{
+ Title: "Nuclei: Concurrency vs Time",
+ Subtitle: "Chart Shows Concurrency (Total Workers) Over Time",
+ }),
+ )
dataset := sliceutil.Clone(s.data)
From cbe7322019dda42f9d1aee61120e3073ee49fada Mon Sep 17 00:00:00 2001
From: Mzack9999
Date: Wed, 24 Apr 2024 08:36:04 +0100
Subject: [PATCH 49/58] Exposing embedded api for settings control in CLI
modality (#5030)
* exposing settings api
* adding probe concurrency
* adding js pool size control
* adding json tags
---
cmd/nuclei/main.go | 1 +
internal/httpapi/apiendpoint.go | 112 ++++++++++++++++++++++++++++++++
internal/runner/inputs.go | 15 +----
internal/runner/runner.go | 17 ++++-
pkg/testutils/testutils.go | 1 +
pkg/types/types.go | 2 +
6 files changed, 134 insertions(+), 14 deletions(-)
create mode 100644 internal/httpapi/apiendpoint.go
diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go
index 9b68dc11b6..a084f270b4 100644
--- a/cmd/nuclei/main.go
+++ b/cmd/nuclei/main.go
@@ -301,6 +301,7 @@ on extensive configurability, massive extensibility and ease of use.`)
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"),
)
flagSet.CreateGroup("interactsh", "interactsh",
diff --git a/internal/httpapi/apiendpoint.go b/internal/httpapi/apiendpoint.go
new file mode 100644
index 0000000000..2cd055eeea
--- /dev/null
+++ b/internal/httpapi/apiendpoint.go
@@ -0,0 +1,112 @@
+package httpapi
+
+import (
+ "encoding/json"
+ "net/http"
+ "time"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/js/compiler"
+ "github.com/projectdiscovery/nuclei/v3/pkg/types"
+)
+
+type Concurrency struct {
+ BulkSize int `json:"bulk_size"`
+ Threads int `json:"threads"`
+ RateLimit int `json:"rate_limit"`
+ RateLimitDuration string `json:"rate_limit_duration"`
+ PayloadConcurrency int `json:"payload_concurrency"`
+ ProbeConcurrency int `json:"probe_concurrency"`
+ JavascriptConcurrency int `json:"javascript_concurrency"`
+}
+
+// Server represents the HTTP server that handles the concurrency settings endpoints.
+type Server struct {
+ addr string
+ config *types.Options
+}
+
+// New creates a new instance of Server.
+func New(addr string, config *types.Options) *Server {
+ return &Server{
+ addr: addr,
+ config: config,
+ }
+}
+
+// Start initializes the server and its routes, then starts listening on the specified address.
+func (s *Server) Start() error {
+ http.HandleFunc("/api/concurrency", s.handleConcurrency)
+ if err := http.ListenAndServe(s.addr, nil); err != nil {
+ return err
+ }
+ return nil
+}
+
+// handleConcurrency routes the request based on its method to the appropriate handler.
+func (s *Server) handleConcurrency(w http.ResponseWriter, r *http.Request) {
+ switch r.Method {
+ case http.MethodGet:
+ s.getSettings(w, r)
+ case http.MethodPut:
+ s.updateSettings(w, r)
+ default:
+ http.Error(w, "Unsupported HTTP method", http.StatusMethodNotAllowed)
+ }
+}
+
+// GetSettings handles GET requests and returns the current concurrency settings
+func (s *Server) getSettings(w http.ResponseWriter, _ *http.Request) {
+ concurrencySettings := Concurrency{
+ BulkSize: s.config.BulkSize,
+ Threads: s.config.TemplateThreads,
+ RateLimit: s.config.RateLimit,
+ RateLimitDuration: s.config.RateLimitDuration.String(),
+ PayloadConcurrency: s.config.PayloadConcurrency,
+ ProbeConcurrency: s.config.ProbeConcurrency,
+ JavascriptConcurrency: compiler.PoolingJsVmConcurrency,
+ }
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(concurrencySettings); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// UpdateSettings handles PUT requests to update the concurrency settings
+func (s *Server) updateSettings(w http.ResponseWriter, r *http.Request) {
+ var newSettings Concurrency
+ if err := json.NewDecoder(r.Body).Decode(&newSettings); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ if newSettings.RateLimitDuration != "" {
+ if duration, err := time.ParseDuration(newSettings.RateLimitDuration); err == nil {
+ s.config.RateLimitDuration = duration
+ } else {
+ http.Error(w, "Invalid duration format", http.StatusBadRequest)
+ return
+ }
+ }
+ if newSettings.BulkSize > 0 {
+ s.config.BulkSize = newSettings.BulkSize
+ }
+ if newSettings.Threads > 0 {
+ s.config.TemplateThreads = newSettings.Threads
+ }
+ if newSettings.RateLimit > 0 {
+ s.config.RateLimit = newSettings.RateLimit
+ }
+ if newSettings.PayloadConcurrency > 0 {
+ s.config.PayloadConcurrency = newSettings.PayloadConcurrency
+ }
+ if newSettings.ProbeConcurrency > 0 {
+ s.config.ProbeConcurrency = newSettings.ProbeConcurrency
+ }
+ if newSettings.JavascriptConcurrency > 0 {
+ compiler.PoolingJsVmConcurrency = newSettings.JavascriptConcurrency
+ s.config.JsConcurrency = newSettings.JavascriptConcurrency // no-op on speed change
+ }
+
+ w.WriteHeader(http.StatusOK)
+}
diff --git a/internal/runner/inputs.go b/internal/runner/inputs.go
index 87fbb573e1..047360aee3 100644
--- a/internal/runner/inputs.go
+++ b/internal/runner/inputs.go
@@ -16,8 +16,6 @@ import (
syncutil "github.com/projectdiscovery/utils/sync"
)
-var GlobalProbeBulkSize = 50
-
// initializeTemplatesHTTPInput initializes the http form of input
// for any loaded http templates if input is in non-standard format.
func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
@@ -31,11 +29,6 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
}
gologger.Info().Msgf("Running httpx on input host")
- var bulkSize = GlobalProbeBulkSize
- if r.options.BulkSize > GlobalProbeBulkSize {
- bulkSize = r.options.BulkSize
- }
-
httpxOptions := httpx.DefaultOptions
httpxOptions.RetryMax = r.options.Retries
httpxOptions.Timeout = time.Duration(r.options.Timeout) * time.Second
@@ -45,10 +38,8 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
return nil, errors.Wrap(err, "could not create httpx client")
}
- shouldFollowGlobalProbeBulkSize := bulkSize == GlobalProbeBulkSize
-
// Probe the non-standard URLs and store them in cache
- swg, err := syncutil.New(syncutil.WithSize(bulkSize))
+ swg, err := syncutil.New(syncutil.WithSize(r.options.BulkSize))
if err != nil {
return nil, errors.Wrap(err, "could not create adaptive group")
}
@@ -58,8 +49,8 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
return true
}
- if shouldFollowGlobalProbeBulkSize && swg.Size != GlobalProbeBulkSize {
- swg.Resize(GlobalProbeBulkSize)
+ if r.options.ProbeConcurrency > 0 && swg.Size != r.options.ProbeConcurrency {
+ swg.Resize(r.options.ProbeConcurrency)
}
swg.Add()
diff --git a/internal/runner/runner.go b/internal/runner/runner.go
index ce8cf4030c..ab2fe5de93 100644
--- a/internal/runner/runner.go
+++ b/internal/runner/runner.go
@@ -32,6 +32,7 @@ import (
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/internal/colorizer"
+ "github.com/projectdiscovery/nuclei/v3/internal/httpapi"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
@@ -89,8 +90,9 @@ type Runner struct {
pdcpUploadErrMsg string
inputProvider provider.InputProvider
//general purpose temporary directory
- tmpDir string
- parser parser.Parser
+ tmpDir string
+ parser parser.Parser
+ httpApiEndpoint *httpapi.Server
}
const pprofServerAddress = "127.0.0.1:8086"
@@ -222,6 +224,17 @@ func New(options *types.Options) (*Runner, error) {
}()
}
+ if options.HttpApiEndpoint != "" {
+ apiServer := httpapi.New(options.HttpApiEndpoint, options)
+ gologger.Info().Msgf("Listening api endpoint on: %s", options.HttpApiEndpoint)
+ runner.httpApiEndpoint = apiServer
+ go func() {
+ if err := apiServer.Start(); err != nil {
+ gologger.Error().Msgf("Failed to start API server: %s", err)
+ }
+ }()
+ }
+
if (len(options.Templates) == 0 || !options.NewTemplates || (options.TargetsFilePath == "" && !options.Stdin && len(options.Targets) == 0)) && options.UpdateTemplates {
os.Exit(0)
}
diff --git a/pkg/testutils/testutils.go b/pkg/testutils/testutils.go
index faa21938bb..68410f7f1d 100644
--- a/pkg/testutils/testutils.go
+++ b/pkg/testutils/testutils.go
@@ -55,6 +55,7 @@ var DefaultOptions = &types.Options{
Retries: 1,
RateLimit: 150,
RateLimitDuration: time.Second,
+ ProbeConcurrency: 50,
ProjectPath: "",
Severities: severity.Severities{},
Targets: []string{},
diff --git a/pkg/types/types.go b/pkg/types/types.go
index caa1ced75f..d7dd41f22d 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -391,6 +391,8 @@ type Options struct {
ProbeConcurrency int
// Dast only runs DAST templates
DAST bool
+ // HttpApiEndpoint is the experimental http api endpoint
+ HttpApiEndpoint string
}
// ShouldLoadResume resume file
From d85267df31a48b3786a0bdcf48d23d6afe056618 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 24 Apr 2024 13:32:18 +0530
Subject: [PATCH 50/58] chore(deps): bump github.com/projectdiscovery/mapcidr
(#5076)
Bumps [github.com/projectdiscovery/mapcidr](https://github.com/projectdiscovery/mapcidr) from 1.1.16 to 1.1.34.
- [Release notes](https://github.com/projectdiscovery/mapcidr/releases)
- [Changelog](https://github.com/projectdiscovery/mapcidr/blob/main/.goreleaser.yml)
- [Commits](https://github.com/projectdiscovery/mapcidr/compare/v1.1.16...v1.1.34)
---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/mapcidr
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
go.mod | 2 +-
go.sum | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/go.mod b/go.mod
index 7ffdcc20bd..ca84f14b92 100644
--- a/go.mod
+++ b/go.mod
@@ -86,7 +86,7 @@ require (
github.com/projectdiscovery/gostruct v0.0.2
github.com/projectdiscovery/gozero v0.0.2
github.com/projectdiscovery/httpx v1.6.0
- github.com/projectdiscovery/mapcidr v1.1.16
+ github.com/projectdiscovery/mapcidr v1.1.34
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
github.com/projectdiscovery/ratelimit v0.0.38
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917
diff --git a/go.sum b/go.sum
index 07a7bc8d22..e8de0c6ac6 100644
--- a/go.sum
+++ b/go.sum
@@ -860,8 +860,8 @@ github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb h1:M
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb/go.mod h1:vmgC0DTFCfoCLp0RAfsfYTZZan0QMVs+cmTbH6blfjk=
github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 h1:ZScLodGSezQVwsQDtBSMFp72WDq0nNN+KE/5DHKY5QE=
github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI=
-github.com/projectdiscovery/mapcidr v1.1.16 h1:rjj1w5D6hbTsUQXYClLcGdfBEy9bryclgi70t0vBggo=
-github.com/projectdiscovery/mapcidr v1.1.16/go.mod h1:rGqpBhStdwOQ2uS62QM9qPsybwMwIhT7CTd2bxoHs8Q=
+github.com/projectdiscovery/mapcidr v1.1.34 h1:udr83vQ7oz3kEOwlsU6NC6o08leJzSDQtls1wmXN/kM=
+github.com/projectdiscovery/mapcidr v1.1.34/go.mod h1:1+1R6OkKSAKtWDXE9RvxXtXPoajXTYX0eiEdkqlhQqQ=
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 h1:L/e8z8yw1pfT6bg35NiN7yd1XKtJap5Nk6lMwQ0RNi8=
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5/go.mod h1:pGW2ncnTxTxHtP9wzcIJAB+3/NMp6IiuQWd2NK7K+oc=
github.com/projectdiscovery/networkpolicy v0.0.8 h1:XvfBaBwSDNTesSfNQP9VLk3HX9I7x7gHm028TJ5XwI8=
From 21aec3062a8d2bdb2bbd8bdb6a915157b6a2cd6a Mon Sep 17 00:00:00 2001
From: Dogan Can Bakir <65292895+dogancanbakir@users.noreply.github.com>
Date: Wed, 24 Apr 2024 14:03:06 +0300
Subject: [PATCH 51/58] update utils (#5089)
---
go.mod | 4 ++--
go.sum | 5 +++++
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/go.mod b/go.mod
index ca84f14b92..35a39ccc08 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/errors v0.9.1
github.com/projectdiscovery/clistats v0.0.20
- github.com/projectdiscovery/fastdialer v0.0.67
+ github.com/projectdiscovery/fastdialer v0.0.68
github.com/projectdiscovery/hmap v0.0.41
github.com/projectdiscovery/interactsh v1.1.9
github.com/projectdiscovery/rawhttp v0.1.45
@@ -94,7 +94,7 @@ require (
github.com/projectdiscovery/tlsx v1.1.6
github.com/projectdiscovery/uncover v1.0.7
github.com/projectdiscovery/useragent v0.0.47
- github.com/projectdiscovery/utils v0.0.89
+ github.com/projectdiscovery/utils v0.0.91
github.com/projectdiscovery/wappalyzergo v0.0.116
github.com/redis/go-redis/v9 v9.1.0
github.com/seh-msft/burpxml v1.0.1
diff --git a/go.sum b/go.sum
index e8de0c6ac6..aed5e024e4 100644
--- a/go.sum
+++ b/go.sum
@@ -341,6 +341,7 @@ github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
+github.com/go-echarts/go-echarts/v2 v2.3.3/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@@ -836,6 +837,8 @@ github.com/projectdiscovery/dsl v0.0.52 h1:jvIvF+qN8+MbI1MHtWJJKfWqAZQlCExL3ob7S
github.com/projectdiscovery/dsl v0.0.52/go.mod h1:xfcHwhy2HSaeGgh+1wqzOoCGm2XTdh5JzjBRBVHEMvI=
github.com/projectdiscovery/fastdialer v0.0.67 h1:NvBpZUiLr9Ne9N+Lvi6FFiNNLWuhk5Bc1H+oE9J8C1E=
github.com/projectdiscovery/fastdialer v0.0.67/go.mod h1:GhSAKnojJN8N9K0JNjLmwLCmEDsQ5cBAStqSCm/tm84=
+github.com/projectdiscovery/fastdialer v0.0.68 h1:JuIrr8aVGdGWkEwL4axsJWAWDY2uviSqBB0TCekeCOo=
+github.com/projectdiscovery/fastdialer v0.0.68/go.mod h1:asHSBFJgmwrXpiegcrcAgOyd/QewCVgeI4idH55+v7M=
github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA=
github.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw=
github.com/projectdiscovery/freeport v0.0.5 h1:jnd3Oqsl4S8n0KuFkE5Hm8WGDP24ITBvmyw5pFTHS8Q=
@@ -888,6 +891,8 @@ github.com/projectdiscovery/useragent v0.0.47 h1:VEOU7uG7TutZNIE0DZNP7hGAGi4bwLP
github.com/projectdiscovery/useragent v0.0.47/go.mod h1:Cfk9X9SISYSCmqpej0r9+paJbDHzNHic2YdWQtpdz2M=
github.com/projectdiscovery/utils v0.0.89 h1:ruH2bSkpX/rB7EPp2EV/rWyAubQVxCVU38nRcLp4L1w=
github.com/projectdiscovery/utils v0.0.89/go.mod h1:Dwh5cxn7y97jvyYG3GmBvj0negfH9IjH15qXnzFNtOI=
+github.com/projectdiscovery/utils v0.0.91 h1:aHAAnC0qX9pJZrWq4Qpl2PSTYLrSCL1dm1QWLjprE2w=
+github.com/projectdiscovery/utils v0.0.91/go.mod h1:O/6U3ZoU+tNw4lKurdjyVMZPVXL5IYq0YeaDc15PRls=
github.com/projectdiscovery/wappalyzergo v0.0.116 h1:xy+mBpwbYo/0PSzmJOQ/RXHomEh0D3nDBcbCxsW69m8=
github.com/projectdiscovery/wappalyzergo v0.0.116/go.mod h1:hc/o+fgM8KtdpFesjfBTmHTwsR+yBd+4kYZW/DGy/x8=
github.com/projectdiscovery/yamldoc-go v1.0.4 h1:eZoESapnMw6WAHiVgRwNqvbJEfNHEH148uthhFbG5jE=
From e450dee03d9adac3f59fabba92243430b89b693e Mon Sep 17 00:00:00 2001
From: Ramana Reddy <90540245+RamanaReddy0M@users.noreply.github.com>
Date: Wed, 24 Apr 2024 18:44:59 +0530
Subject: [PATCH 52/58] Preserve reference links as case-sensitive while
unmarshalling (#5098)
---
pkg/model/model_test.go | 4 ++--
pkg/model/types/stringslice/stringslice_raw.go | 9 +++++++++
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/pkg/model/model_test.go b/pkg/model/model_test.go
index 3f87add2c9..cd2826405c 100644
--- a/pkg/model/model_test.go
+++ b/pkg/model/model_test.go
@@ -80,7 +80,7 @@ func TestUnmarshal(t *testing.T) {
templateName := "Test Template"
authors := []string{"forgedhallpass", "ice3man"}
tags := []string{"cve", "misc"}
- references := []string{"http://test.com", "http://domain.com"}
+ references := []string{"http://test.com", "http://Domain.com"}
dynamicKey1 := "customDynamicKey1"
dynamicKey2 := "customDynamicKey2"
@@ -109,7 +109,7 @@ func TestUnmarshal(t *testing.T) {
author: ` + strings.Join(authors, ", ") + `
tags: ` + strings.Join(tags, ", ") + `
severity: critical
- reference: ` + strings.Join(references, ", ") + `
+ reference: ` + strings.Join(references, ",") + `
metadata:
` + dynamicKey1 + `: ` + dynamicKeysMap[dynamicKey1].(string) + `
` + dynamicKey2 + `: ` + dynamicKeysMap[dynamicKey2].(string) + `
diff --git a/pkg/model/types/stringslice/stringslice_raw.go b/pkg/model/types/stringslice/stringslice_raw.go
index 7d9e470bfa..fa96c2cc3f 100644
--- a/pkg/model/types/stringslice/stringslice_raw.go
+++ b/pkg/model/types/stringslice/stringslice_raw.go
@@ -12,6 +12,15 @@ func (rawStringSlice *RawStringSlice) Normalize(value string) string {
return value
}
+func (rawStringSlice *RawStringSlice) UnmarshalYAML(unmarshal func(interface{}) error) error {
+ marshalledSlice, err := marshalStringToSlice(unmarshal)
+ if err != nil {
+ return err
+ }
+ rawStringSlice.Value = marshalledSlice
+ return nil
+}
+
func (rawStringSlice RawStringSlice) JSONSchemaAlias() any {
return StringOrSlice("")
}
From cfe6f5da581942d290a5c9c739c8791564b480d7 Mon Sep 17 00:00:00 2001
From: Dogan Can Bakir <65292895+dogancanbakir@users.noreply.github.com>
Date: Wed, 24 Apr 2024 16:19:39 +0300
Subject: [PATCH 53/58] fix tests (#5092)
---
pkg/protocols/dns/request_test.go | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pkg/protocols/dns/request_test.go b/pkg/protocols/dns/request_test.go
index 7275097b2c..f32a8c622b 100644
--- a/pkg/protocols/dns/request_test.go
+++ b/pkg/protocols/dns/request_test.go
@@ -37,7 +37,7 @@ func TestDNSExecuteWithResults(t *testing.T) {
Name: "test",
Part: "raw",
Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},
- Words: []string{"93.184.216.34"},
+ Words: []string{"93.184.215.14"},
}},
Extractors: []*extractors.Extractor{{
Part: "raw",
@@ -64,7 +64,7 @@ func TestDNSExecuteWithResults(t *testing.T) {
require.Equal(t, 1, len(finalEvent.Results), "could not get correct number of results")
require.Equal(t, "test", finalEvent.Results[0].MatcherName, "could not get correct matcher name of results")
require.Equal(t, 1, len(finalEvent.Results[0].ExtractedResults), "could not get correct number of extracted results")
- require.Equal(t, "93.184.216.34", finalEvent.Results[0].ExtractedResults[0], "could not get correct extracted results")
+ require.Equal(t, "93.184.215.14", finalEvent.Results[0].ExtractedResults[0], "could not get correct extracted results")
finalEvent = nil
t.Run("url-to-domain", func(t *testing.T) {
@@ -79,6 +79,6 @@ func TestDNSExecuteWithResults(t *testing.T) {
require.Equal(t, 1, len(finalEvent.Results), "could not get correct number of results")
require.Equal(t, "test", finalEvent.Results[0].MatcherName, "could not get correct matcher name of results")
require.Equal(t, 1, len(finalEvent.Results[0].ExtractedResults), "could not get correct number of extracted results")
- require.Equal(t, "93.184.216.34", finalEvent.Results[0].ExtractedResults[0], "could not get correct extracted results")
+ require.Equal(t, "93.184.215.14", finalEvent.Results[0].ExtractedResults[0], "could not get correct extracted results")
finalEvent = nil
}
From 3dfcec0a36dbf5ebc529ce0478076279cb975b71 Mon Sep 17 00:00:00 2001
From: Tarun Koyalwar <45962551+tarunKoyalwar@users.noreply.github.com>
Date: Wed, 24 Apr 2024 19:34:13 +0530
Subject: [PATCH 54/58] missing mhe check in http payloads (#5099)
* go mod tidy
* fix spm missing hosterrorcheck + improvements
---------
Co-authored-by: sandeep <8293321+ehsandeep@users.noreply.github.com>
---
go.sum | 6 +-
.../common/hosterrorscache/hosterrorscache.go | 2 +-
pkg/protocols/http/httputils/spm.go | 50 +++++++-
pkg/protocols/http/request.go | 118 +++++++++++++++---
4 files changed, 145 insertions(+), 31 deletions(-)
diff --git a/go.sum b/go.sum
index aed5e024e4..ce3f2b214d 100644
--- a/go.sum
+++ b/go.sum
@@ -341,6 +341,8 @@ github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
+github.com/go-echarts/go-echarts/v2 v2.3.3 h1:uImZAk6qLkC6F9ju6mZ5SPBqTyK8xjZKwSmwnCg4bxg=
+github.com/go-echarts/go-echarts/v2 v2.3.3/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI=
github.com/go-echarts/go-echarts/v2 v2.3.3/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
@@ -835,8 +837,6 @@ github.com/projectdiscovery/clistats v0.0.20 h1:5jO5SLiRJ7f0nDV0ndBNmBeesbROouPo
github.com/projectdiscovery/clistats v0.0.20/go.mod h1:GJ2av0KnOvK0AISQnP8hyDclYIji1LVkx2l0pwnzAu4=
github.com/projectdiscovery/dsl v0.0.52 h1:jvIvF+qN8+MbI1MHtWJJKfWqAZQlCExL3ob7SddQbZE=
github.com/projectdiscovery/dsl v0.0.52/go.mod h1:xfcHwhy2HSaeGgh+1wqzOoCGm2XTdh5JzjBRBVHEMvI=
-github.com/projectdiscovery/fastdialer v0.0.67 h1:NvBpZUiLr9Ne9N+Lvi6FFiNNLWuhk5Bc1H+oE9J8C1E=
-github.com/projectdiscovery/fastdialer v0.0.67/go.mod h1:GhSAKnojJN8N9K0JNjLmwLCmEDsQ5cBAStqSCm/tm84=
github.com/projectdiscovery/fastdialer v0.0.68 h1:JuIrr8aVGdGWkEwL4axsJWAWDY2uviSqBB0TCekeCOo=
github.com/projectdiscovery/fastdialer v0.0.68/go.mod h1:asHSBFJgmwrXpiegcrcAgOyd/QewCVgeI4idH55+v7M=
github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA=
@@ -889,8 +889,6 @@ github.com/projectdiscovery/uncover v1.0.7 h1:ut+2lTuvmftmveqF5RTjMWAgyLj8ltPQC7
github.com/projectdiscovery/uncover v1.0.7/go.mod h1:HFXgm1sRPuoN0D4oATljPIdmbo/EEh1wVuxQqo/dwFE=
github.com/projectdiscovery/useragent v0.0.47 h1:VEOU7uG7TutZNIE0DZNP7hGAGi4bwLPGM1X7Rny52s0=
github.com/projectdiscovery/useragent v0.0.47/go.mod h1:Cfk9X9SISYSCmqpej0r9+paJbDHzNHic2YdWQtpdz2M=
-github.com/projectdiscovery/utils v0.0.89 h1:ruH2bSkpX/rB7EPp2EV/rWyAubQVxCVU38nRcLp4L1w=
-github.com/projectdiscovery/utils v0.0.89/go.mod h1:Dwh5cxn7y97jvyYG3GmBvj0negfH9IjH15qXnzFNtOI=
github.com/projectdiscovery/utils v0.0.91 h1:aHAAnC0qX9pJZrWq4Qpl2PSTYLrSCL1dm1QWLjprE2w=
github.com/projectdiscovery/utils v0.0.91/go.mod h1:O/6U3ZoU+tNw4lKurdjyVMZPVXL5IYq0YeaDc15PRls=
github.com/projectdiscovery/wappalyzergo v0.0.116 h1:xy+mBpwbYo/0PSzmJOQ/RXHomEh0D3nDBcbCxsW69m8=
diff --git a/pkg/protocols/common/hosterrorscache/hosterrorscache.go b/pkg/protocols/common/hosterrorscache/hosterrorscache.go
index 2eb12cfcb4..3ada7718a8 100644
--- a/pkg/protocols/common/hosterrorscache/hosterrorscache.go
+++ b/pkg/protocols/common/hosterrorscache/hosterrorscache.go
@@ -124,7 +124,7 @@ func (c *Cache) MarkFailed(value string, err error) {
_ = c.failedTargets.Set(finalValue, existingCacheItemValue)
}
-var reCheckError = regexp.MustCompile(`(no address found for host|Client\.Timeout exceeded while awaiting headers|could not resolve host|connection refused|connection reset by peer)`)
+var reCheckError = regexp.MustCompile(`(no address found for host|Client\.Timeout exceeded while awaiting headers|could not resolve host|connection refused|connection reset by peer|i/o timeout|could not connect to any address found for host)`)
// checkError checks if an error represents a type that should be
// added to the host skipping table.
diff --git a/pkg/protocols/http/httputils/spm.go b/pkg/protocols/http/httputils/spm.go
index 52d13f06ff..5e20fea58d 100644
--- a/pkg/protocols/http/httputils/spm.go
+++ b/pkg/protocols/http/httputils/spm.go
@@ -5,6 +5,7 @@ import (
"sync"
syncutil "github.com/projectdiscovery/utils/sync"
+ "golang.org/x/exp/maps"
)
// WorkPoolType is the type of work pool to use
@@ -19,7 +20,7 @@ const (
// StopAtFirstMatchHandler is a handler that executes
// request and stops on first match
-type StopAtFirstMatchHandler[T any] struct {
+type StopAtFirstMatchHandler[T comparable] struct {
once sync.Once
// Result Channel
ResultChan chan T
@@ -33,12 +34,14 @@ type StopAtFirstMatchHandler[T any] struct {
ctx context.Context
cancel context.CancelFunc
internalWg *sync.WaitGroup
- results []T
+ results map[T]struct{}
+ onResult func(T)
stopEnabled bool
+ maxResults int
}
// NewBlockingSPMHandler creates a new stop at first match handler
-func NewBlockingSPMHandler[T any](ctx context.Context, size int, spm bool) *StopAtFirstMatchHandler[T] {
+func NewBlockingSPMHandler[T comparable](ctx context.Context, size int, maxResults int, spm bool) *StopAtFirstMatchHandler[T] {
ctx1, cancel := context.WithCancel(ctx)
awg, _ := syncutil.New(syncutil.WithSize(size))
@@ -51,6 +54,8 @@ func NewBlockingSPMHandler[T any](ctx context.Context, size int, spm bool) *Stop
ctx: ctx1,
cancel: cancel,
stopEnabled: spm,
+ results: make(map[T]struct{}),
+ maxResults: maxResults,
}
s.internalWg.Add(1)
go s.run(ctx)
@@ -58,7 +63,7 @@ func NewBlockingSPMHandler[T any](ctx context.Context, size int, spm bool) *Stop
}
// NewNonBlockingSPMHandler creates a new stop at first match handler
-func NewNonBlockingSPMHandler[T any](ctx context.Context, spm bool) *StopAtFirstMatchHandler[T] {
+func NewNonBlockingSPMHandler[T comparable](ctx context.Context, maxResults int, spm bool) *StopAtFirstMatchHandler[T] {
ctx1, cancel := context.WithCancel(ctx)
s := &StopAtFirstMatchHandler[T]{
ResultChan: make(chan T, 1),
@@ -68,6 +73,8 @@ func NewNonBlockingSPMHandler[T any](ctx context.Context, spm bool) *StopAtFirst
ctx: ctx1,
cancel: cancel,
stopEnabled: spm,
+ results: make(map[T]struct{}),
+ maxResults: maxResults,
}
s.internalWg.Add(1)
go s.run(ctx)
@@ -82,6 +89,25 @@ func (h *StopAtFirstMatchHandler[T]) Trigger() {
}
}
+// Cancel cancels spm context
+func (h *StopAtFirstMatchHandler[T]) Cancel() {
+ h.cancel()
+}
+
+// SetOnResult callback
+// this is not thread safe
+func (h *StopAtFirstMatchHandler[T]) SetOnResultCallback(fn func(T)) {
+ if h.onResult != nil {
+ tmp := h.onResult
+ h.onResult = func(t T) {
+ tmp(t)
+ fn(t)
+ }
+ } else {
+ h.onResult = fn
+ }
+}
+
// MatchCallback is called when a match is found
// input fn should be the callback that is intended to be called
// if stop at first is enabled and other conditions are met
@@ -104,7 +130,14 @@ func (h *StopAtFirstMatchHandler[T]) run(ctx context.Context) {
if !ok {
return
}
- h.results = append(h.results, val)
+ if h.onResult != nil {
+ h.onResult(val)
+ }
+ if len(h.results) >= h.maxResults {
+ // skip or do not store the result
+ continue
+ }
+ h.results[val] = struct{}{}
}
}
}
@@ -114,6 +147,11 @@ func (h *StopAtFirstMatchHandler[T]) Done() <-chan struct{} {
return h.ctx.Done()
}
+// Cancelled returns true if the context is cancelled
+func (h *StopAtFirstMatchHandler[T]) Cancelled() bool {
+ return h.ctx.Err() != nil
+}
+
// FoundFirstMatch returns true if first match was found
// in stop at first match mode
func (h *StopAtFirstMatchHandler[T]) FoundFirstMatch() bool {
@@ -168,5 +206,5 @@ func (h *StopAtFirstMatchHandler[T]) Wait() {
// CombinedResults returns the combined results
func (h *StopAtFirstMatchHandler[T]) CombinedResults() []T {
- return h.results
+ return maps.Keys(h.results)
}
diff --git a/pkg/protocols/http/request.go b/pkg/protocols/http/request.go
index 5d64216f6a..d966a4bb78 100644
--- a/pkg/protocols/http/request.go
+++ b/pkg/protocols/http/request.go
@@ -47,6 +47,9 @@ import (
const (
defaultMaxWorkers = 150
+ // max unique errors to store & combine
+ // when executing requests in parallel
+ maxErrorsWhenParallel = 3
)
var (
@@ -111,7 +114,7 @@ func (request *Request) executeRaceRequest(input *contextargs.Context, previous
}
shouldStop := (request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch || request.options.StopAtFirstMatch)
- spmHandler := httputils.NewNonBlockingSPMHandler[error](ctx, shouldStop)
+ spmHandler := httputils.NewNonBlockingSPMHandler[error](ctx, maxErrorsWhenParallel, shouldStop)
gotMatches := &atomic.Bool{}
// wrappedCallback is a callback that wraps the original callback
// to implement stop at first match logic
@@ -132,12 +135,29 @@ func (request *Request) executeRaceRequest(input *contextargs.Context, previous
}
}
+ // look for unresponsive hosts and cancel inflight requests as well
+ spmHandler.SetOnResultCallback(func(err error) {
+ if err == nil {
+ return
+ }
+ // marks thsi host as unresponsive if applicable
+ request.markUnresponsiveHost(input, err)
+ if request.isUnresponsiveHost(input) {
+ // stop all inflight requests
+ spmHandler.Cancel()
+ }
+ })
+
for i := 0; i < request.RaceNumberRequests; i++ {
+ if spmHandler.FoundFirstMatch() || request.isUnresponsiveHost(input) {
+ // stop sending more requests condition is met
+ break
+ }
spmHandler.Acquire()
// execute http request
go func(httpRequest *generatedRequest) {
defer spmHandler.Release()
- if spmHandler.FoundFirstMatch() {
+ if spmHandler.FoundFirstMatch() || request.isUnresponsiveHost(input) {
// stop sending more requests condition is met
return
}
@@ -175,7 +195,7 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
// Stop-at-first-match logic while executing requests
// parallely using threads
shouldStop := (request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch || request.options.StopAtFirstMatch)
- spmHandler := httputils.NewBlockingSPMHandler[error](context.Background(), maxWorkers, shouldStop)
+ spmHandler := httputils.NewBlockingSPMHandler[error](context.Background(), maxWorkers, maxErrorsWhenParallel, shouldStop)
// wrappedCallback is a callback that wraps the original callback
// to implement stop at first match logic
wrappedCallback := func(event *output.InternalWrappedEvent) {
@@ -194,6 +214,19 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
}
}
+ // look for unresponsive hosts and cancel inflight requests as well
+ spmHandler.SetOnResultCallback(func(err error) {
+ if err == nil {
+ return
+ }
+ // marks thsi host as unresponsive if applicable
+ request.markUnresponsiveHost(input, err)
+ if request.isUnresponsiveHost(input) {
+ // stop all inflight requests
+ spmHandler.Cancel()
+ }
+ })
+
// iterate payloads and make requests
generator := request.newGenerator(false)
for {
@@ -207,6 +240,11 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
spmHandler.Resize(request.options.Options.PayloadConcurrency)
}
+ // break if stop at first match is found or host is unresponsive
+ if spmHandler.FoundFirstMatch() || request.isUnresponsiveHost(input) {
+ break
+ }
+
ctx := request.newContext(input)
generatedHttpRequest, err := generator.Make(ctx, input, inputData, payloads, dynamicValues)
if err != nil {
@@ -222,19 +260,21 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
spmHandler.Acquire()
go func(httpRequest *generatedRequest) {
defer spmHandler.Release()
- if spmHandler.FoundFirstMatch() {
+ if spmHandler.FoundFirstMatch() || request.isUnresponsiveHost(input) || spmHandler.Cancelled() {
+ return
+ }
+ // putting ratelimiter here prevents any unnecessary waiting if any
+ request.options.RateLimitTake()
+
+ // after ratelimit take, check if we need to stop
+ if spmHandler.FoundFirstMatch() || request.isUnresponsiveHost(input) || spmHandler.Cancelled() {
return
}
select {
case <-spmHandler.Done():
return
- case spmHandler.ResultChan <- func() error {
- // putting ratelimiter here prevents any unnecessary waiting if any
- request.options.RateLimitTake()
- previous := make(map[string]interface{})
- return request.executeRequest(input, httpRequest, previous, false, wrappedCallback, 0)
- }():
+ case spmHandler.ResultChan <- request.executeRequest(input, httpRequest, make(map[string]interface{}), false, wrappedCallback, 0):
return
}
}(generatedHttpRequest)
@@ -276,12 +316,10 @@ func (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValu
maxWorkers = pipeOptions.MaxPendingRequests
}
- // Stop-at-first-match logic while executing requests
- // parallely using threads
// Stop-at-first-match logic while executing requests
// parallely using threads
shouldStop := (request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch || request.options.StopAtFirstMatch)
- spmHandler := httputils.NewBlockingSPMHandler[error](context.Background(), maxWorkers, shouldStop)
+ spmHandler := httputils.NewBlockingSPMHandler[error](context.Background(), maxWorkers, maxErrorsWhenParallel, shouldStop)
// wrappedCallback is a callback that wraps the original callback
// to implement stop at first match logic
wrappedCallback := func(event *output.InternalWrappedEvent) {
@@ -300,11 +338,29 @@ func (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValu
}
}
+ // look for unresponsive hosts and cancel inflight requests as well
+ spmHandler.SetOnResultCallback(func(err error) {
+ if err == nil {
+ return
+ }
+ // marks thsi host as unresponsive if applicable
+ request.markUnresponsiveHost(input, err)
+ if request.isUnresponsiveHost(input) {
+ // stop all inflight requests
+ spmHandler.Cancel()
+ }
+ })
+
for {
inputData, payloads, ok := generator.nextValue()
if !ok {
break
}
+ if spmHandler.FoundFirstMatch() || request.isUnresponsiveHost(input) || spmHandler.Cancelled() {
+ // skip if first match is found
+ break
+ }
+
ctx := request.newContext(input)
generatedHttpRequest, err := generator.Make(ctx, input, inputData, payloads, dynamicValues)
if err != nil {
@@ -318,11 +374,10 @@ func (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValu
spmHandler.Acquire()
go func(httpRequest *generatedRequest) {
defer spmHandler.Release()
- if spmHandler.FoundFirstMatch() {
+ if spmHandler.FoundFirstMatch() || request.isUnresponsiveHost(input) {
// skip if first match is found
return
}
-
select {
case <-spmHandler.Done():
return
@@ -398,7 +453,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
input.MetaInput.Input = generatedHttpRequest.URL()
}
// Check if hosts keep erroring
- if request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(input.MetaInput.ID()) {
+ if request.isUnresponsiveHost(input) {
return true, nil
}
var gotMatches bool
@@ -437,12 +492,13 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
return true, nil
}
if execReqErr != nil {
- if request.options.HostErrorsCache != nil {
- request.options.HostErrorsCache.MarkFailed(input.MetaInput.ID(), err)
- }
+ // if applicable mark the host as unresponsive
+ request.markUnresponsiveHost(input, execReqErr)
requestErr = errorutil.NewWithErr(execReqErr).Msgf("got err while executing %v", generatedHttpRequest.URL())
+ request.options.Progress.IncrementFailedRequestsBy(1)
+ } else {
+ request.options.Progress.IncrementRequests()
}
- request.options.Progress.IncrementRequests()
// If this was a match, and we want to stop at first match, skip all further requests.
shouldStopAtFirstMatch := generatedHttpRequest.original.options.Options.StopAtFirstMatch || generatedHttpRequest.original.options.StopAtFirstMatch || request.StopAtFirstMatch
@@ -482,6 +538,10 @@ const drainReqSize = int64(8 * 1024)
// executeRequest executes the actual generated request and returns error if occurred
func (request *Request) executeRequest(input *contextargs.Context, generatedRequest *generatedRequest, previousEvent output.InternalEvent, hasInteractMatchers bool, processEvent protocols.OutputEventCallback, requestCount int) (err error) {
+ // Check if hosts keep erroring
+ if request.isUnresponsiveHost(input) {
+ return fmt.Errorf("hostErrorsCache : host %s is unresponsive", input.MetaInput.Input)
+ }
// wrap one more callback for validation and fixing event
callback := func(event *output.InternalWrappedEvent) {
@@ -985,3 +1045,21 @@ func (request *Request) newContext(input *contextargs.Context) context.Context {
}
return context.Background()
}
+
+// markUnresponsiveHost checks if the error is a unreponsive host error and marks it
+func (request *Request) markUnresponsiveHost(input *contextargs.Context, err error) {
+ if err == nil {
+ return
+ }
+ if request.options.HostErrorsCache != nil {
+ request.options.HostErrorsCache.MarkFailed(input.MetaInput.ID(), err)
+ }
+}
+
+// isUnresponsiveHost checks if the error is a unreponsive based on its execution history
+func (request *Request) isUnresponsiveHost(input *contextargs.Context) bool {
+ if request.options.HostErrorsCache != nil {
+ return request.options.HostErrorsCache.Check(input.MetaInput.ID())
+ }
+ return false
+}
From 0b82e8b7aa84ec637f0e2cb33da0757622c956ef Mon Sep 17 00:00:00 2001
From: Ice3man
Date: Thu, 25 Apr 2024 15:37:56 +0530
Subject: [PATCH 55/58] feat: added support for context cancellation to engine
(#5096)
* feat: added support for context cancellation to engine
* misc
* feat: added contexts everywhere
* misc
* misc
* use granular http timeouts and increase http timeout to 30s using multiplier
* track response header timeout in mhe
* update responseHeaderTimeout to 5sec
* skip failing windows test
---------
Co-authored-by: Tarun Koyalwar
---
cmd/integration-test/library.go | 2 +-
internal/runner/lazy.go | 4 +-
internal/runner/runner.go | 2 +-
lib/multi.go | 2 +-
lib/sdk.go | 3 +-
pkg/core/execute_options.go | 37 +++++++++++++------
pkg/core/executors.go | 35 +++++++++++++-----
pkg/core/workflow_execute.go | 6 +--
pkg/core/workflow_execute_test.go | 25 +++++++------
pkg/input/provider/list/hmap_test.go | 4 ++
pkg/js/compiler/compiler.go | 8 ++--
pkg/protocols/code/code.go | 1 +
pkg/protocols/code/code_test.go | 3 +-
.../common/automaticscan/automaticscan.go | 9 +++--
.../common/contextargs/contextargs.go | 16 ++++++--
.../common/hosterrorscache/hosterrorscache.go | 2 +-
pkg/protocols/dns/request.go | 6 +++
pkg/protocols/dns/request_test.go | 5 ++-
pkg/protocols/file/request_test.go | 3 +-
.../headless/engine/page_actions_test.go | 5 ++-
pkg/protocols/headless/request.go | 2 +-
pkg/protocols/http/build_request_test.go | 16 ++++----
.../http/httpclientpool/clientpool.go | 25 +++++++++----
pkg/protocols/http/request.go | 27 ++++++++++++--
pkg/protocols/http/request_annotations.go | 3 +-
pkg/protocols/http/request_fuzz.go | 12 ++++++
pkg/protocols/http/request_test.go | 9 +++--
pkg/protocols/javascript/js.go | 17 ++++++++-
pkg/protocols/network/request.go | 12 ++++++
pkg/protocols/network/request_test.go | 7 ++--
pkg/protocols/protocols.go | 3 +-
pkg/protocols/ssl/ssl_test.go | 3 +-
pkg/protocols/utils/variables_test.go | 3 +-
pkg/scan/scan_context.go | 13 +++++--
pkg/templates/cluster.go | 2 +-
pkg/tmplexec/flow/flow_executor.go | 6 +++
pkg/tmplexec/flow/flow_executor_test.go | 28 +++++++-------
pkg/tmplexec/generic/exec.go | 6 +++
pkg/tmplexec/multiproto/multi.go | 12 ++++++
pkg/tmplexec/multiproto/multi_test.go | 8 ++--
40 files changed, 279 insertions(+), 113 deletions(-)
diff --git a/cmd/integration-test/library.go b/cmd/integration-test/library.go
index a714b29abb..1324688e05 100644
--- a/cmd/integration-test/library.go
+++ b/cmd/integration-test/library.go
@@ -128,7 +128,7 @@ func executeNucleiAsLibrary(templatePath, templateURL string) ([]string, error)
}
store.Load()
- _ = engine.Execute(store.Templates(), provider.NewSimpleInputProviderWithUrls(templateURL))
+ _ = engine.Execute(context.Background(), store.Templates(), provider.NewSimpleInputProviderWithUrls(templateURL))
engine.WorkPool().Wait() // Wait for the scan to finish
return results, nil
diff --git a/internal/runner/lazy.go b/internal/runner/lazy.go
index b61dd5515a..eb41374513 100644
--- a/internal/runner/lazy.go
+++ b/internal/runner/lazy.go
@@ -1,6 +1,7 @@
package runner
import (
+ "context"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx"
@@ -71,7 +72,8 @@ func GetLazyAuthFetchCallback(opts *AuthLazyFetchOptions) authx.LazyFetchSecret
tmpl := tmpls[0]
// add args to tmpl here
vars := map[string]interface{}{}
- ctx := scan.NewScanContext(contextargs.NewWithInput(d.Input))
+ mainCtx := context.Background()
+ ctx := scan.NewScanContext(mainCtx, contextargs.NewWithInput(mainCtx, d.Input))
for _, v := range d.Variables {
vars[v.Key] = v.Value
ctx.Input.Add(v.Key, v.Value)
diff --git a/internal/runner/runner.go b/internal/runner/runner.go
index ab2fe5de93..43f16ff10b 100644
--- a/internal/runner/runner.go
+++ b/internal/runner/runner.go
@@ -669,7 +669,7 @@ func (r *Runner) executeTemplatesInput(store *loader.Store, engine *core.Engine)
if r.inputProvider == nil {
return nil, errors.New("no input provider found")
}
- results := engine.ExecuteScanWithOpts(finalTemplates, r.inputProvider, r.options.DisableClustering)
+ results := engine.ExecuteScanWithOpts(context.Background(), finalTemplates, r.inputProvider, r.options.DisableClustering)
return results, nil
}
diff --git a/lib/multi.go b/lib/multi.go
index 6fa791a2f0..bdcee79677 100644
--- a/lib/multi.go
+++ b/lib/multi.go
@@ -138,7 +138,7 @@ func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOpts(targets []string, opts ..
engine := core.New(tmpEngine.opts)
engine.SetExecuterOptions(unsafeOpts.executerOpts)
- _ = engine.ExecuteScanWithOpts(store.Templates(), inputProvider, false)
+ _ = engine.ExecuteScanWithOpts(context.Background(), store.Templates(), inputProvider, false)
engine.WorkPool().Wait()
return nil
diff --git a/lib/sdk.go b/lib/sdk.go
index c80abb0615..f6b5ea7c43 100644
--- a/lib/sdk.go
+++ b/lib/sdk.go
@@ -3,6 +3,7 @@ package nuclei
import (
"bufio"
"bytes"
+ "context"
"io"
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
@@ -210,7 +211,7 @@ func (e *NucleiEngine) ExecuteWithCallback(callback ...func(event *output.Result
}
e.resultCallbacks = append(e.resultCallbacks, filtered...)
- _ = e.engine.ExecuteScanWithOpts(e.store.Templates(), e.inputProvider, false)
+ _ = e.engine.ExecuteScanWithOpts(context.Background(), e.store.Templates(), e.inputProvider, false)
defer e.engine.WorkPool().Wait()
return nil
}
diff --git a/pkg/core/execute_options.go b/pkg/core/execute_options.go
index 93f197fc2b..4d27b5f660 100644
--- a/pkg/core/execute_options.go
+++ b/pkg/core/execute_options.go
@@ -1,6 +1,7 @@
package core
import (
+ "context"
"sync"
"sync/atomic"
@@ -20,18 +21,18 @@ import (
//
// All the execution logic for the templates/workflows happens in this part
// of the engine.
-func (e *Engine) Execute(templates []*templates.Template, target provider.InputProvider) *atomic.Bool {
- return e.ExecuteScanWithOpts(templates, target, false)
+func (e *Engine) Execute(ctx context.Context, templates []*templates.Template, target provider.InputProvider) *atomic.Bool {
+ return e.ExecuteScanWithOpts(ctx, templates, target, false)
}
// ExecuteWithResults a list of templates with results
-func (e *Engine) ExecuteWithResults(templatesList []*templates.Template, target provider.InputProvider, callback func(*output.ResultEvent)) *atomic.Bool {
+func (e *Engine) ExecuteWithResults(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider, callback func(*output.ResultEvent)) *atomic.Bool {
e.Callback = callback
- return e.ExecuteScanWithOpts(templatesList, target, false)
+ return e.ExecuteScanWithOpts(ctx, templatesList, target, false)
}
// ExecuteScanWithOpts executes scan with given scanStrategy
-func (e *Engine) ExecuteScanWithOpts(templatesList []*templates.Template, target provider.InputProvider, noCluster bool) *atomic.Bool {
+func (e *Engine) ExecuteScanWithOpts(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider, noCluster bool) *atomic.Bool {
results := &atomic.Bool{}
selfcontainedWg := &sync.WaitGroup{}
@@ -83,14 +84,14 @@ func (e *Engine) ExecuteScanWithOpts(templatesList []*templates.Template, target
}
// Execute All SelfContained in parallel
- e.executeAllSelfContained(selfContained, results, selfcontainedWg)
+ e.executeAllSelfContained(ctx, selfContained, results, selfcontainedWg)
strategyResult := &atomic.Bool{}
switch e.options.ScanStrategy {
case scanstrategy.TemplateSpray.String():
- strategyResult = e.executeTemplateSpray(filtered, target)
+ strategyResult = e.executeTemplateSpray(ctx, filtered, target)
case scanstrategy.HostSpray.String():
- strategyResult = e.executeHostSpray(filtered, target)
+ strategyResult = e.executeHostSpray(ctx, filtered, target)
}
results.CompareAndSwap(false, strategyResult.Load())
@@ -100,7 +101,7 @@ func (e *Engine) ExecuteScanWithOpts(templatesList []*templates.Template, target
}
// executeTemplateSpray executes scan using template spray strategy where targets are iterated over each template
-func (e *Engine) executeTemplateSpray(templatesList []*templates.Template, target provider.InputProvider) *atomic.Bool {
+func (e *Engine) executeTemplateSpray(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider) *atomic.Bool {
results := &atomic.Bool{}
// wp is workpool that contains different waitgroups for
@@ -108,6 +109,12 @@ func (e *Engine) executeTemplateSpray(templatesList []*templates.Template, targe
wp := e.GetWorkPool()
for _, template := range templatesList {
+ select {
+ case <-ctx.Done():
+ return results
+ default:
+ }
+
// resize check point - nop if there are no changes
wp.RefreshWithConfig(e.GetWorkPoolConfig())
@@ -125,7 +132,7 @@ func (e *Engine) executeTemplateSpray(templatesList []*templates.Template, targe
// All other request types are executed here
// Note: executeTemplateWithTargets creates goroutines and blocks
// given template is executed on all targets
- e.executeTemplateWithTargets(tpl, target, results)
+ e.executeTemplateWithTargets(ctx, tpl, target, results)
}(template)
}
wp.Wait()
@@ -133,15 +140,21 @@ func (e *Engine) executeTemplateSpray(templatesList []*templates.Template, targe
}
// executeHostSpray executes scan using host spray strategy where templates are iterated over each target
-func (e *Engine) executeHostSpray(templatesList []*templates.Template, target provider.InputProvider) *atomic.Bool {
+func (e *Engine) executeHostSpray(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider) *atomic.Bool {
results := &atomic.Bool{}
wp, _ := syncutil.New(syncutil.WithSize(e.options.BulkSize + e.options.HeadlessBulkSize))
target.Iterate(func(value *contextargs.MetaInput) bool {
+ select {
+ case <-ctx.Done():
+ return false
+ default:
+ }
+
wp.Add()
go func(targetval *contextargs.MetaInput) {
defer wp.Done()
- e.executeTemplatesOnTarget(templatesList, targetval, results)
+ e.executeTemplatesOnTarget(ctx, templatesList, targetval, results)
}(value)
return true
})
diff --git a/pkg/core/executors.go b/pkg/core/executors.go
index 14fb75c631..89c85b2ad7 100644
--- a/pkg/core/executors.go
+++ b/pkg/core/executors.go
@@ -1,6 +1,7 @@
package core
import (
+ "context"
"sync"
"sync/atomic"
@@ -17,14 +18,14 @@ import (
// Executors are low level executors that deals with template execution on a target
// executeAllSelfContained executes all self contained templates that do not use `target`
-func (e *Engine) executeAllSelfContained(alltemplates []*templates.Template, results *atomic.Bool, sg *sync.WaitGroup) {
+func (e *Engine) executeAllSelfContained(ctx context.Context, alltemplates []*templates.Template, results *atomic.Bool, sg *sync.WaitGroup) {
for _, v := range alltemplates {
sg.Add(1)
go func(template *templates.Template) {
defer sg.Done()
var err error
var match bool
- ctx := scan.NewScanContext(contextargs.New())
+ ctx := scan.NewScanContext(ctx, contextargs.New(ctx))
if e.Callback != nil {
if results, err := template.Executer.ExecuteWithResults(ctx); err != nil {
for _, result := range results {
@@ -45,7 +46,7 @@ func (e *Engine) executeAllSelfContained(alltemplates []*templates.Template, res
}
// executeTemplateWithTarget executes a given template on x targets (with a internal targetpool(i.e concurrency))
-func (e *Engine) executeTemplateWithTargets(template *templates.Template, target provider.InputProvider, results *atomic.Bool) {
+func (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templates.Template, target provider.InputProvider, results *atomic.Bool) {
// this is target pool i.e max target to execute
wg := e.workPool.InputPool(template.Type())
@@ -77,6 +78,12 @@ func (e *Engine) executeTemplateWithTargets(template *templates.Template, target
}
target.Iterate(func(scannedValue *contextargs.MetaInput) bool {
+ select {
+ case <-ctx.Done():
+ return false // exit
+ default:
+ }
+
// Best effort to track the host progression
// skips indexes lower than the minimum in-flight at interruption time
var skip bool
@@ -114,9 +121,9 @@ func (e *Engine) executeTemplateWithTargets(template *templates.Template, target
var match bool
var err error
- ctxArgs := contextargs.New()
+ ctxArgs := contextargs.New(ctx)
ctxArgs.MetaInput = value
- ctx := scan.NewScanContext(ctxArgs)
+ ctx := scan.NewScanContext(ctx, ctxArgs)
switch template.Type() {
case types.WorkflowProtocol:
match = e.executeWorkflow(ctx, template.CompiledWorkflow)
@@ -149,7 +156,7 @@ func (e *Engine) executeTemplateWithTargets(template *templates.Template, target
}
// executeTemplatesOnTarget execute given templates on given single target
-func (e *Engine) executeTemplatesOnTarget(alltemplates []*templates.Template, target *contextargs.MetaInput, results *atomic.Bool) {
+func (e *Engine) executeTemplatesOnTarget(ctx context.Context, alltemplates []*templates.Template, target *contextargs.MetaInput, results *atomic.Bool) {
// all templates are executed on single target
// wp is workpool that contains different waitgroups for
@@ -158,6 +165,12 @@ func (e *Engine) executeTemplatesOnTarget(alltemplates []*templates.Template, ta
wp := e.GetWorkPool()
for _, tpl := range alltemplates {
+ select {
+ case <-ctx.Done():
+ return
+ default:
+ }
+
// resize check point - nop if there are no changes
wp.RefreshWithConfig(e.GetWorkPoolConfig())
@@ -173,9 +186,9 @@ func (e *Engine) executeTemplatesOnTarget(alltemplates []*templates.Template, ta
var match bool
var err error
- ctxArgs := contextargs.New()
+ ctxArgs := contextargs.New(ctx)
ctxArgs.MetaInput = value
- ctx := scan.NewScanContext(ctxArgs)
+ ctx := scan.NewScanContext(ctx, ctxArgs)
switch template.Type() {
case types.WorkflowProtocol:
match = e.executeWorkflow(ctx, template.CompiledWorkflow)
@@ -230,9 +243,11 @@ func (e *ChildExecuter) Execute(template *templates.Template, value *contextargs
go func(tpl *templates.Template) {
defer wg.Done()
- ctxArgs := contextargs.New()
+ // TODO: Workflows are a no-op for now. We need to
+ // implement them in the future with context cancellation
+ ctxArgs := contextargs.New(context.Background())
ctxArgs.MetaInput = value
- ctx := scan.NewScanContext(ctxArgs)
+ ctx := scan.NewScanContext(context.Background(), ctxArgs)
match, err := template.Executer.Execute(ctx)
if err != nil {
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.e.executerOpts.Colorizer.BrightBlue(template.ID), err)
diff --git a/pkg/core/workflow_execute.go b/pkg/core/workflow_execute.go
index c17a1af8d5..d0d3ede004 100644
--- a/pkg/core/workflow_execute.go
+++ b/pkg/core/workflow_execute.go
@@ -21,7 +21,7 @@ func (e *Engine) executeWorkflow(ctx *scan.ScanContext, w *workflows.Workflow) b
// at this point we should be at the start root execution of a workflow tree, hence we create global shared instances
workflowCookieJar, _ := cookiejar.New(nil)
- ctxArgs := contextargs.New()
+ ctxArgs := contextargs.New(ctx.Context())
ctxArgs.MetaInput = ctx.Input.MetaInput
ctxArgs.CookieJar = workflowCookieJar
@@ -139,7 +139,7 @@ func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan
defer swg.Done()
// create a new context with the same input but with unset callbacks
- subCtx := scan.NewScanContext(ctx.Input)
+ subCtx := scan.NewScanContext(ctx.Context(), ctx.Input)
if err := e.runWorkflowStep(subtemplate, subCtx, results, swg, w); err != nil {
gologger.Warning().Msgf(workflowStepExecutionError, subtemplate.Template, err)
}
@@ -165,7 +165,7 @@ func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan
go func(template *workflows.WorkflowTemplate) {
// create a new context with the same input but with unset callbacks
- subCtx := scan.NewScanContext(ctx.Input)
+ subCtx := scan.NewScanContext(ctx.Context(), ctx.Input)
if err := e.runWorkflowStep(template, subCtx, results, swg, w); err != nil {
gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)
}
diff --git a/pkg/core/workflow_execute_test.go b/pkg/core/workflow_execute_test.go
index f3d6a7f231..0c478a5e13 100644
--- a/pkg/core/workflow_execute_test.go
+++ b/pkg/core/workflow_execute_test.go
@@ -1,6 +1,7 @@
package core
import (
+ "context"
"testing"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice"
@@ -25,8 +26,8 @@ func TestWorkflowsSimple(t *testing.T) {
}}
engine := &Engine{}
- input := contextargs.NewWithInput("https://test.com")
- ctx := scan.NewScanContext(input)
+ input := contextargs.NewWithInput(context.Background(), "https://test.com")
+ ctx := scan.NewScanContext(context.Background(), input)
matched := engine.executeWorkflow(ctx, workflow)
require.True(t, matched, "could not get correct match value")
}
@@ -49,8 +50,8 @@ func TestWorkflowsSimpleMultiple(t *testing.T) {
}}
engine := &Engine{}
- input := contextargs.NewWithInput("https://test.com")
- ctx := scan.NewScanContext(input)
+ input := contextargs.NewWithInput(context.Background(), "https://test.com")
+ ctx := scan.NewScanContext(context.Background(), input)
matched := engine.executeWorkflow(ctx, workflow)
require.True(t, matched, "could not get correct match value")
@@ -77,8 +78,8 @@ func TestWorkflowsSubtemplates(t *testing.T) {
}}
engine := &Engine{}
- input := contextargs.NewWithInput("https://test.com")
- ctx := scan.NewScanContext(input)
+ input := contextargs.NewWithInput(context.Background(), "https://test.com")
+ ctx := scan.NewScanContext(context.Background(), input)
matched := engine.executeWorkflow(ctx, workflow)
require.True(t, matched, "could not get correct match value")
@@ -103,8 +104,8 @@ func TestWorkflowsSubtemplatesNoMatch(t *testing.T) {
}}
engine := &Engine{}
- input := contextargs.NewWithInput("https://test.com")
- ctx := scan.NewScanContext(input)
+ input := contextargs.NewWithInput(context.Background(), "https://test.com")
+ ctx := scan.NewScanContext(context.Background(), input)
matched := engine.executeWorkflow(ctx, workflow)
require.False(t, matched, "could not get correct match value")
@@ -134,8 +135,8 @@ func TestWorkflowsSubtemplatesWithMatcher(t *testing.T) {
}}
engine := &Engine{}
- input := contextargs.NewWithInput("https://test.com")
- ctx := scan.NewScanContext(input)
+ input := contextargs.NewWithInput(context.Background(), "https://test.com")
+ ctx := scan.NewScanContext(context.Background(), input)
matched := engine.executeWorkflow(ctx, workflow)
require.True(t, matched, "could not get correct match value")
@@ -165,8 +166,8 @@ func TestWorkflowsSubtemplatesWithMatcherNoMatch(t *testing.T) {
}}
engine := &Engine{}
- input := contextargs.NewWithInput("https://test.com")
- ctx := scan.NewScanContext(input)
+ input := contextargs.NewWithInput(context.Background(), "https://test.com")
+ ctx := scan.NewScanContext(context.Background(), input)
matched := engine.executeWorkflow(ctx, workflow)
require.False(t, matched, "could not get correct match value")
diff --git a/pkg/input/provider/list/hmap_test.go b/pkg/input/provider/list/hmap_test.go
index 95fc57f2d9..b08782e124 100644
--- a/pkg/input/provider/list/hmap_test.go
+++ b/pkg/input/provider/list/hmap_test.go
@@ -3,6 +3,7 @@ package list
import (
"net"
"os"
+ "runtime"
"strconv"
"strings"
"testing"
@@ -77,6 +78,9 @@ func (m *mockDnsHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
}
func Test_scanallips_normalizeStoreInputValue(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skip("Skipping test see: https://github.com/projectdiscovery/nuclei/issues/5097")
+ }
srv := &dns.Server{Addr: ":" + strconv.Itoa(61234), Net: "udp"}
srv.Handler = &mockDnsHandler{}
diff --git a/pkg/js/compiler/compiler.go b/pkg/js/compiler/compiler.go
index 265bbaf04b..05eacda6c6 100644
--- a/pkg/js/compiler/compiler.go
+++ b/pkg/js/compiler/compiler.go
@@ -37,6 +37,8 @@ type ExecuteOptions struct {
// Source is original source of the script
Source *string
+ Context context.Context
+
// Manually exported objects
exports map[string]interface{}
}
@@ -77,13 +79,13 @@ func (c *Compiler) Execute(code string, args *ExecuteArgs) (ExecuteResult, error
if err != nil {
return nil, err
}
- return c.ExecuteWithOptions(p, args, &ExecuteOptions{})
+ 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 {
- opts = &ExecuteOptions{}
+ opts = &ExecuteOptions{Context: context.Background()}
}
if args == nil {
args = NewExecuteArgs()
@@ -105,7 +107,7 @@ func (c *Compiler) ExecuteWithOptions(program *goja.Program, args *ExecuteArgs,
}
// execute with context and timeout
- ctx, cancel := context.WithTimeout(context.Background(), time.Duration(opts.Timeout)*time.Second)
+ ctx, cancel := context.WithTimeout(opts.Context, time.Duration(opts.Timeout)*time.Second)
defer cancel()
// execute the script
results, err := contextutil.ExecFuncWithTwoReturns(ctx, func() (val goja.Value, err error) {
diff --git a/pkg/protocols/code/code.go b/pkg/protocols/code/code.go
index 1b59de3efd..70dda72548 100644
--- a/pkg/protocols/code/code.go
+++ b/pkg/protocols/code/code.go
@@ -199,6 +199,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
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)
diff --git a/pkg/protocols/code/code_test.go b/pkg/protocols/code/code_test.go
index 1ace1388f3..320f7c5481 100644
--- a/pkg/protocols/code/code_test.go
+++ b/pkg/protocols/code/code_test.go
@@ -3,6 +3,7 @@
package code
import (
+ "context"
"testing"
"github.com/stretchr/testify/require"
@@ -31,7 +32,7 @@ func TestCodeProtocol(t *testing.T) {
require.Nil(t, err, "could not compile code request")
var gotEvent output.InternalEvent
- ctxArgs := contextargs.NewWithInput("")
+ ctxArgs := contextargs.NewWithInput(context.Background(), "")
err = request.ExecuteWithResults(ctxArgs, nil, nil, func(event *output.InternalWrappedEvent) {
gotEvent = event.InternalEvent
})
diff --git a/pkg/protocols/common/automaticscan/automaticscan.go b/pkg/protocols/common/automaticscan/automaticscan.go
index 7119377b01..4567f498d9 100644
--- a/pkg/protocols/common/automaticscan/automaticscan.go
+++ b/pkg/protocols/common/automaticscan/automaticscan.go
@@ -1,6 +1,7 @@
package automaticscan
import (
+ "context"
"io"
"net/http"
"os"
@@ -189,7 +190,7 @@ func (s *Service) executeAutomaticScanOnTarget(input *contextargs.MetaInput) {
execOptions.Progress = &testutils.MockProgressClient{} // stats are not supported yet due to centralized logic and cannot be reinitialized
eng.SetExecuterOptions(execOptions)
- tmp := eng.ExecuteScanWithOpts(finalTemplates, provider.NewSimpleInputProviderWithUrls(input.Input), true)
+ tmp := eng.ExecuteScanWithOpts(context.Background(), finalTemplates, provider.NewSimpleInputProviderWithUrls(input.Input), true)
s.hasResults.Store(tmp.Load())
}
@@ -244,7 +245,9 @@ func (s *Service) getTagsUsingWappalyzer(input *contextargs.MetaInput) []string
// getTagsUsingDetectionTemplates returns tags using detection templates
func (s *Service) getTagsUsingDetectionTemplates(input *contextargs.MetaInput) ([]string, int) {
- ctxArgs := contextargs.NewWithInput(input.Input)
+ ctx := context.Background()
+
+ ctxArgs := contextargs.NewWithInput(ctx, input.Input)
// execute tech detection templates on target
tags := map[string]struct{}{}
@@ -256,7 +259,7 @@ func (s *Service) getTagsUsingDetectionTemplates(input *contextargs.MetaInput) (
sg.Add()
go func(template *templates.Template) {
defer sg.Done()
- ctx := scan.NewScanContext(ctxArgs)
+ ctx := scan.NewScanContext(ctx, ctxArgs)
ctx.OnResult = func(event *output.InternalWrappedEvent) {
if event == nil {
return
diff --git a/pkg/protocols/common/contextargs/contextargs.go b/pkg/protocols/common/contextargs/contextargs.go
index 4ebaa15615..8e8f0361c4 100644
--- a/pkg/protocols/common/contextargs/contextargs.go
+++ b/pkg/protocols/common/contextargs/contextargs.go
@@ -1,6 +1,7 @@
package contextargs
import (
+ "context"
"net/http/cookiejar"
"strings"
"sync/atomic"
@@ -19,6 +20,8 @@ var (
// Context implements a shared context struct to share information across multiple templates within a workflow
type Context struct {
+ ctx context.Context
+
// Meta is the target for the executor
MetaInput *MetaInput
@@ -30,17 +33,18 @@ type Context struct {
}
// Create a new contextargs instance
-func New() *Context {
- return NewWithInput("")
+func New(ctx context.Context) *Context {
+ return NewWithInput(ctx, "")
}
// Create a new contextargs instance with input string
-func NewWithInput(input string) *Context {
+func NewWithInput(ctx context.Context, input string) *Context {
jar, err := cookiejar.New(nil)
if err != nil {
gologger.Error().Msgf("contextargs: could not create cookie jar: %s\n", err)
}
return &Context{
+ ctx: ctx,
MetaInput: &MetaInput{Input: input},
CookieJar: jar,
args: &mapsutil.SyncLockMap[string, interface{}]{
@@ -50,6 +54,11 @@ func NewWithInput(input string) *Context {
}
}
+// Context returns the context of the current contextargs
+func (ctx *Context) Context() context.Context {
+ return ctx.ctx
+}
+
// Set the specific key-value pair
func (ctx *Context) Set(key string, value interface{}) {
_ = ctx.args.Set(key, value)
@@ -158,6 +167,7 @@ func (ctx *Context) HasArgs() bool {
func (ctx *Context) Clone() *Context {
newCtx := &Context{
+ ctx: ctx.ctx,
MetaInput: ctx.MetaInput.Clone(),
args: ctx.args.Clone(),
CookieJar: ctx.CookieJar,
diff --git a/pkg/protocols/common/hosterrorscache/hosterrorscache.go b/pkg/protocols/common/hosterrorscache/hosterrorscache.go
index 3ada7718a8..3abd1ec55f 100644
--- a/pkg/protocols/common/hosterrorscache/hosterrorscache.go
+++ b/pkg/protocols/common/hosterrorscache/hosterrorscache.go
@@ -124,7 +124,7 @@ func (c *Cache) MarkFailed(value string, err error) {
_ = c.failedTargets.Set(finalValue, existingCacheItemValue)
}
-var reCheckError = regexp.MustCompile(`(no address found for host|Client\.Timeout exceeded while awaiting headers|could not resolve host|connection refused|connection reset by peer|i/o timeout|could not connect to any address found for host)`)
+var reCheckError = regexp.MustCompile(`(no address found for host|Client\.Timeout exceeded while awaiting headers|could not resolve host|connection refused|connection reset by peer|i/o timeout|could not connect to any address found for host|timeout awaiting response headers)`)
// checkError checks if an error represents a type that should be
// added to the host skipping table.
diff --git a/pkg/protocols/dns/request.go b/pkg/protocols/dns/request.go
index d4e70e13e9..608e4730ac 100644
--- a/pkg/protocols/dns/request.go
+++ b/pkg/protocols/dns/request.go
@@ -80,6 +80,12 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
break
}
+ select {
+ case <-input.Context().Done():
+ return input.Context().Err()
+ default:
+ }
+
// resize check point - nop if there are no changes
if shouldFollowGlobal && swg.Size != request.options.Options.PayloadConcurrency {
swg.Resize(request.options.Options.PayloadConcurrency)
diff --git a/pkg/protocols/dns/request_test.go b/pkg/protocols/dns/request_test.go
index f32a8c622b..81f0b98ab2 100644
--- a/pkg/protocols/dns/request_test.go
+++ b/pkg/protocols/dns/request_test.go
@@ -1,6 +1,7 @@
package dns
import (
+ "context"
"testing"
"github.com/stretchr/testify/require"
@@ -54,7 +55,7 @@ func TestDNSExecuteWithResults(t *testing.T) {
t.Run("domain-valid", func(t *testing.T) {
metadata := make(output.InternalEvent)
previous := make(output.InternalEvent)
- ctxArgs := contextargs.NewWithInput("example.com")
+ ctxArgs := contextargs.NewWithInput(context.Background(), "example.com")
err := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
finalEvent = event
})
@@ -70,7 +71,7 @@ func TestDNSExecuteWithResults(t *testing.T) {
t.Run("url-to-domain", func(t *testing.T) {
metadata := make(output.InternalEvent)
previous := make(output.InternalEvent)
- err := request.ExecuteWithResults(contextargs.NewWithInput("https://example.com"), metadata, previous, func(event *output.InternalWrappedEvent) {
+ err := request.ExecuteWithResults(contextargs.NewWithInput(context.Background(), "https://example.com"), metadata, previous, func(event *output.InternalWrappedEvent) {
finalEvent = event
})
require.Nil(t, err, "could not execute dns request")
diff --git a/pkg/protocols/file/request_test.go b/pkg/protocols/file/request_test.go
index ff41e3e8be..7c69cf5bce 100644
--- a/pkg/protocols/file/request_test.go
+++ b/pkg/protocols/file/request_test.go
@@ -1,6 +1,7 @@
package file
import (
+ "context"
"os"
"path/filepath"
"testing"
@@ -67,7 +68,7 @@ func TestFileExecuteWithResults(t *testing.T) {
t.Run("valid", func(t *testing.T) {
metadata := make(output.InternalEvent)
previous := make(output.InternalEvent)
- ctxArgs := contextargs.NewWithInput(tempDir)
+ ctxArgs := contextargs.NewWithInput(context.Background(), tempDir)
err := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
finalEvent = event
})
diff --git a/pkg/protocols/headless/engine/page_actions_test.go b/pkg/protocols/headless/engine/page_actions_test.go
index a4b69eeff0..e6699d6408 100644
--- a/pkg/protocols/headless/engine/page_actions_test.go
+++ b/pkg/protocols/headless/engine/page_actions_test.go
@@ -1,6 +1,7 @@
package engine
import (
+ "context"
"fmt"
"io"
"math/rand"
@@ -595,7 +596,7 @@ func testHeadless(t *testing.T, actions []*Action, timeout time.Duration, handle
ts := httptest.NewServer(http.HandlerFunc(handler))
defer ts.Close()
- input := contextargs.NewWithInput(ts.URL)
+ input := contextargs.NewWithInput(context.Background(), ts.URL)
input.CookieJar, err = cookiejar.New(nil)
require.Nil(t, err)
@@ -674,7 +675,7 @@ func TestBlockedHeadlessURLS(t *testing.T) {
{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},
}
- data, page, err := instance.Run(contextargs.NewWithInput(ts.URL), actions, nil, &Options{Timeout: 20 * time.Second, Options: opts}) // allow file access in test
+ data, page, err := instance.Run(contextargs.NewWithInput(context.Background(), ts.URL), actions, nil, &Options{Timeout: 20 * time.Second, Options: opts}) // allow file access in test
require.Error(t, err, "expected error for url %s got %v", testcase, data)
require.True(t, stringsutil.ContainsAny(err.Error(), "net::ERR_ACCESS_DENIED", "failed to parse url", "Cannot navigate to invalid URL", "net::ERR_ABORTED", "net::ERR_INVALID_URL"), "found different error %v for testcases %v", err, testcase)
require.Len(t, data, 0, "expected no data for url %s got %v", testcase, data)
diff --git a/pkg/protocols/headless/request.go b/pkg/protocols/headless/request.go
index f45afdd35e..05bf5d3548 100644
--- a/pkg/protocols/headless/request.go
+++ b/pkg/protocols/headless/request.go
@@ -44,7 +44,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
if err != nil {
return err
}
- input = contextargs.NewWithInput(url)
+ input = contextargs.NewWithInput(input.Context(), url)
}
if request.options.Browser.UserAgent() == "" {
diff --git a/pkg/protocols/http/build_request_test.go b/pkg/protocols/http/build_request_test.go
index bc87549f03..4405bd10b7 100644
--- a/pkg/protocols/http/build_request_test.go
+++ b/pkg/protocols/http/build_request_test.go
@@ -40,7 +40,7 @@ func TestMakeRequestFromModal(t *testing.T) {
generator := request.newGenerator(false)
inputData, payloads, _ := generator.nextValue()
- req, err := generator.Make(context.Background(), contextargs.NewWithInput("https://example.com"), inputData, payloads, map[string]interface{}{})
+ req, err := generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), "https://example.com"), inputData, payloads, map[string]interface{}{})
require.Nil(t, err, "could not make http request")
if req.request.URL == nil {
t.Fatalf("url is nil in generator make")
@@ -70,13 +70,13 @@ func TestMakeRequestFromModalTrimSuffixSlash(t *testing.T) {
generator := request.newGenerator(false)
inputData, payloads, _ := generator.nextValue()
- req, err := generator.Make(context.Background(), contextargs.NewWithInput("https://example.com/test.php"), inputData, payloads, map[string]interface{}{})
+ req, err := generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), "https://example.com/test.php"), inputData, payloads, map[string]interface{}{})
require.Nil(t, err, "could not make http request")
require.Equal(t, "https://example.com/test.php?query=example", req.request.URL.String(), "could not get correct request path")
generator = request.newGenerator(false)
inputData, payloads, _ = generator.nextValue()
- req, err = generator.Make(context.Background(), contextargs.NewWithInput("https://example.com/test/"), inputData, payloads, map[string]interface{}{})
+ req, err = generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), "https://example.com/test/"), inputData, payloads, map[string]interface{}{})
require.Nil(t, err, "could not make http request")
require.Equal(t, "https://example.com/test/?query=example", req.request.URL.String(), "could not get correct request path")
}
@@ -110,13 +110,13 @@ Accept-Encoding: gzip`},
generator := request.newGenerator(false)
inputData, payloads, _ := generator.nextValue()
- req, err := generator.Make(context.Background(), contextargs.NewWithInput("https://example.com"), inputData, payloads, map[string]interface{}{})
+ req, err := generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), "https://example.com"), inputData, payloads, map[string]interface{}{})
require.Nil(t, err, "could not make http request")
authorization := req.request.Header.Get("Authorization")
require.Equal(t, "Basic admin:admin", authorization, "could not get correct authorization headers from raw")
inputData, payloads, _ = generator.nextValue()
- req, err = generator.Make(context.Background(), contextargs.NewWithInput("https://example.com"), inputData, payloads, map[string]interface{}{})
+ req, err = generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), "https://example.com"), inputData, payloads, map[string]interface{}{})
require.Nil(t, err, "could not make http request")
authorization = req.request.Header.Get("Authorization")
require.Equal(t, "Basic admin:guest", authorization, "could not get correct authorization headers from raw")
@@ -151,13 +151,13 @@ Accept-Encoding: gzip`},
generator := request.newGenerator(false)
inputData, payloads, _ := generator.nextValue()
- req, err := generator.Make(context.Background(), contextargs.NewWithInput("https://example.com"), inputData, payloads, map[string]interface{}{})
+ req, err := generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), "https://example.com"), inputData, payloads, map[string]interface{}{})
require.Nil(t, err, "could not make http request")
authorization := req.request.Header.Get("Authorization")
require.Equal(t, "Basic YWRtaW46YWRtaW4=", authorization, "could not get correct authorization headers from raw")
inputData, payloads, _ = generator.nextValue()
- req, err = generator.Make(context.Background(), contextargs.NewWithInput("https://example.com"), inputData, payloads, map[string]interface{}{})
+ req, err = generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), "https://example.com"), inputData, payloads, map[string]interface{}{})
require.Nil(t, err, "could not make http request")
authorization = req.request.Header.Get("Authorization")
require.Equal(t, "Basic YWRtaW46Z3Vlc3Q=", authorization, "could not get correct authorization headers from raw")
@@ -195,7 +195,7 @@ func TestMakeRequestFromModelUniqueInteractsh(t *testing.T) {
require.Nil(t, err, "could not create interactsh client")
inputData, payloads, _ := generator.nextValue()
- got, err := generator.Make(context.Background(), contextargs.NewWithInput("https://example.com"), inputData, payloads, map[string]interface{}{})
+ got, err := generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), "https://example.com"), inputData, payloads, map[string]interface{}{})
require.Nil(t, err, "could not make http request")
// check if all the interactsh markers are replaced with unique urls
diff --git a/pkg/protocols/http/httpclientpool/clientpool.go b/pkg/protocols/http/httpclientpool/clientpool.go
index 1560e787a6..3e2baf55a5 100644
--- a/pkg/protocols/http/httpclientpool/clientpool.go
+++ b/pkg/protocols/http/httpclientpool/clientpool.go
@@ -35,8 +35,18 @@ var (
forceMaxRedirects int
normalClient *retryablehttp.Client
clientPool *mapsutil.SyncLockMap[string, *retryablehttp.Client]
+ // ResponseHeaderTimeout is the timeout for response headers
+ // to be read from the server (this prevents infinite hang started by server if any)
+ ResponseHeaderTimeout = time.Duration(5) * 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.
@@ -139,7 +149,7 @@ func GetRawHTTP(options *types.Options) *rawhttp.Client {
} else if Dialer != nil {
rawHttpOptions.FastDialer = Dialer
}
- rawHttpOptions.Timeout = time.Duration(options.Timeout) * time.Second
+ rawHttpOptions.Timeout = GetHttpTimeout(options)
rawHttpClient = rawhttp.NewClient(rawHttpOptions)
}
return rawHttpClient
@@ -237,11 +247,12 @@ func wrappedGet(options *types.Options, configuration *Configuration) (*retryabl
}
return Dialer.DialTLS(ctx, network, addr)
},
- MaxIdleConns: maxIdleConns,
- MaxIdleConnsPerHost: maxIdleConnsPerHost,
- MaxConnsPerHost: maxConnsPerHost,
- TLSClientConfig: tlsConfig,
- DisableKeepAlives: disableKeepAlives,
+ MaxIdleConns: maxIdleConns,
+ MaxIdleConnsPerHost: maxIdleConnsPerHost,
+ MaxConnsPerHost: maxConnsPerHost,
+ TLSClientConfig: tlsConfig,
+ DisableKeepAlives: disableKeepAlives,
+ ResponseHeaderTimeout: ResponseHeaderTimeout,
}
if types.ProxyURL != "" {
@@ -288,7 +299,7 @@ func wrappedGet(options *types.Options, configuration *Configuration) (*retryabl
CheckRedirect: makeCheckRedirectFunc(redirectFlow, maxRedirects),
}
if !configuration.NoTimeout {
- httpclient.Timeout = time.Duration(options.Timeout) * time.Second
+ httpclient.Timeout = GetHttpTimeout(options)
}
client := retryablehttp.NewWithHTTPClient(httpclient, retryableHttpOptions)
if jar != nil {
diff --git a/pkg/protocols/http/request.go b/pkg/protocols/http/request.go
index d966a4bb78..545247ef56 100644
--- a/pkg/protocols/http/request.go
+++ b/pkg/protocols/http/request.go
@@ -235,6 +235,12 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
break
}
+ select {
+ case <-input.Context().Done():
+ return input.Context().Err()
+ default:
+ }
+
// resize check point - nop if there are no changes
if shouldFollowGlobal && spmHandler.Size() != request.options.Options.PayloadConcurrency {
spmHandler.Resize(request.options.Options.PayloadConcurrency)
@@ -356,6 +362,13 @@ func (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValu
if !ok {
break
}
+
+ select {
+ case <-input.Context().Done():
+ return input.Context().Err()
+ default:
+ }
+
if spmHandler.FoundFirstMatch() || request.isUnresponsiveHost(input) || spmHandler.Cancelled() {
// skip if first match is found
break
@@ -433,8 +446,9 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
request.options.RateLimitTake()
ctx := request.newContext(input)
- ctxWithTimeout, cancel := context.WithTimeout(ctx, time.Duration(request.options.Options.Timeout)*time.Second)
+ ctxWithTimeout, cancel := context.WithTimeout(ctx, httpclientpool.GetHttpTimeout(request.options.Options))
defer cancel()
+
generatedHttpRequest, err := generator.Make(ctxWithTimeout, input, data, payloads, dynamicValue)
if err != nil {
if err == types.ErrNoMoreRequests {
@@ -512,6 +526,13 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
if !ok {
break
}
+
+ select {
+ case <-input.Context().Done():
+ return input.Context().Err()
+ default:
+ }
+
var gotErr error
var skip bool
if len(gotDynamicValues) > 0 {
@@ -1041,9 +1062,9 @@ func (request *Request) pruneSignatureInternalValues(maps ...map[string]interfac
func (request *Request) newContext(input *contextargs.Context) context.Context {
if input.MetaInput.CustomIP != "" {
- return context.WithValue(context.Background(), fastdialer.IP, input.MetaInput.CustomIP)
+ return context.WithValue(input.Context(), fastdialer.IP, input.MetaInput.CustomIP)
}
- return context.Background()
+ return input.Context()
}
// markUnresponsiveHost checks if the error is a unreponsive host error and marks it
diff --git a/pkg/protocols/http/request_annotations.go b/pkg/protocols/http/request_annotations.go
index 6325e4e734..67bf7c1671 100644
--- a/pkg/protocols/http/request_annotations.go
+++ b/pkg/protocols/http/request_annotations.go
@@ -9,6 +9,7 @@ import (
"time"
"github.com/projectdiscovery/fastdialer/fastdialer"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool"
"github.com/projectdiscovery/retryablehttp-go"
iputil "github.com/projectdiscovery/utils/ip"
stringsutil "github.com/projectdiscovery/utils/strings"
@@ -124,7 +125,7 @@ func (r *Request) parseAnnotations(rawRequest string, request *retryablehttp.Req
}
} else {
//nolint:govet // cancelled automatically by withTimeout
- ctx, overrides.cancelFunc = context.WithTimeout(context.Background(), time.Duration(r.options.Options.Timeout)*time.Second)
+ ctx, overrides.cancelFunc = context.WithTimeout(context.Background(), httpclientpool.GetHttpTimeout(r.options.Options))
request = request.Clone(ctx)
}
}
diff --git a/pkg/protocols/http/request_fuzz.go b/pkg/protocols/http/request_fuzz.go
index aed17f54e0..fc2a4e7576 100644
--- a/pkg/protocols/http/request_fuzz.go
+++ b/pkg/protocols/http/request_fuzz.go
@@ -110,9 +110,21 @@ func (request *Request) executeFuzzingRule(input *contextargs.Context, previous
func (request *Request) executeAllFuzzingRules(input *contextargs.Context, values map[string]interface{}, baseRequest *retryablehttp.Request, callback protocols.OutputEventCallback) error {
applicable := false
for _, rule := range request.Fuzzing {
+ select {
+ case <-input.Context().Done():
+ return input.Context().Err()
+ default:
+ }
+
err := rule.Execute(&fuzz.ExecuteRuleInput{
Input: input,
Callback: func(gr fuzz.GeneratedRequest) bool {
+ select {
+ case <-input.Context().Done():
+ return false
+ default:
+ }
+
// TODO: replace this after scanContext Refactor
return request.executeGeneratedFuzzingRequest(gr, input, callback)
},
diff --git a/pkg/protocols/http/request_test.go b/pkg/protocols/http/request_test.go
index cd8e860b07..c0bd2bb346 100644
--- a/pkg/protocols/http/request_test.go
+++ b/pkg/protocols/http/request_test.go
@@ -1,6 +1,7 @@
package http
import (
+ "context"
"fmt"
"net/http"
"net/http/httptest"
@@ -83,7 +84,7 @@ Disallow: /c`))
t.Run("test", func(t *testing.T) {
metadata := make(output.InternalEvent)
previous := make(output.InternalEvent)
- ctxArgs := contextargs.NewWithInput(ts.URL)
+ ctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)
err := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
if event.OperatorsResult != nil && event.OperatorsResult.Matched {
matchCount++
@@ -159,7 +160,7 @@ func TestDisableTE(t *testing.T) {
t.Run("test", func(t *testing.T) {
metadata := make(output.InternalEvent)
previous := make(output.InternalEvent)
- ctxArgs := contextargs.NewWithInput(ts.URL)
+ ctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)
err := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
if event.OperatorsResult != nil && event.OperatorsResult.Matched {
matchCount++
@@ -172,7 +173,7 @@ func TestDisableTE(t *testing.T) {
t.Run("test2", func(t *testing.T) {
metadata := make(output.InternalEvent)
previous := make(output.InternalEvent)
- ctxArgs := contextargs.NewWithInput(ts.URL)
+ ctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)
err := request2.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
if event.OperatorsResult != nil && event.OperatorsResult.Matched {
matchCount++
@@ -242,7 +243,7 @@ func TestReqURLPattern(t *testing.T) {
t.Run("test", func(t *testing.T) {
metadata := make(output.InternalEvent)
previous := make(output.InternalEvent)
- ctxArgs := contextargs.NewWithInput(ts.URL)
+ ctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)
err := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
if event.OperatorsResult != nil && event.OperatorsResult.Matched {
matchCount++
diff --git a/pkg/protocols/javascript/js.go b/pkg/protocols/javascript/js.go
index 54bb378839..61257a968b 100644
--- a/pkg/protocols/javascript/js.go
+++ b/pkg/protocols/javascript/js.go
@@ -154,6 +154,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
opts := &compiler.ExecuteOptions{
Timeout: request.Timeout,
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
@@ -343,7 +344,7 @@ 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})
+ &compiler.ExecuteOptions{Timeout: request.Timeout, Source: &request.PreCondition, Context: target.Context()})
if err != nil {
return errorutil.NewWithTag(request.TemplateID, "could not execute pre-condition: %s", err)
}
@@ -373,6 +374,12 @@ func (request *Request) ExecuteWithResults(target *contextargs.Context, dynamicV
return nil
}
+ select {
+ case <-input.Context().Done():
+ return input.Context().Err()
+ default:
+ }
+
if err := request.executeRequestWithPayloads(hostPort, input, hostname, value, payloadValues, func(result *output.InternalWrappedEvent) {
if result.OperatorsResult != nil && result.OperatorsResult.Matched {
gotMatches = true
@@ -419,6 +426,12 @@ func (request *Request) executeRequestParallel(ctxParent context.Context, hostPo
break
}
+ select {
+ case <-input.Context().Done():
+ return
+ default:
+ }
+
// resize check point - nop if there are no changes
if shouldFollowGlobal && sg.Size != request.options.Options.PayloadConcurrency {
sg.Resize(request.options.Options.PayloadConcurrency)
@@ -486,7 +499,7 @@ 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})
+ &compiler.ExecuteOptions{Timeout: request.Timeout, 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 1f51ebe119..23eed8cb0f 100644
--- a/pkg/protocols/network/request.go
+++ b/pkg/protocols/network/request.go
@@ -139,6 +139,12 @@ func (request *Request) executeOnTarget(input *contextargs.Context, visited maps
variables = generators.MergeMaps(variablesMap, variables, request.options.Constants)
for _, kv := range request.addresses {
+ select {
+ case <-input.Context().Done():
+ return input.Context().Err()
+ default:
+ }
+
actualAddress := replacer.Replace(kv.address, variables)
if visited.Has(actualAddress) && !request.options.Options.DisableClustering {
@@ -186,6 +192,12 @@ func (request *Request) executeAddress(variables map[string]interface{}, actualA
break
}
+ select {
+ case <-input.Context().Done():
+ return input.Context().Err()
+ default:
+ }
+
// resize check point - nop if there are no changes
if shouldFollowGlobal && swg.Size != request.options.Options.PayloadConcurrency {
swg.Resize(request.options.Options.PayloadConcurrency)
diff --git a/pkg/protocols/network/request_test.go b/pkg/protocols/network/request_test.go
index bf0cd531d4..1945888e9b 100644
--- a/pkg/protocols/network/request_test.go
+++ b/pkg/protocols/network/request_test.go
@@ -1,6 +1,7 @@
package network
import (
+ "context"
"encoding/hex"
"fmt"
"net/http"
@@ -65,7 +66,7 @@ func TestNetworkExecuteWithResults(t *testing.T) {
t.Run("domain-valid", func(t *testing.T) {
metadata := make(output.InternalEvent)
previous := make(output.InternalEvent)
- ctxArgs := contextargs.NewWithInput(parsed.Host)
+ ctxArgs := contextargs.NewWithInput(context.Background(), parsed.Host)
err := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
finalEvent = event
})
@@ -81,7 +82,7 @@ func TestNetworkExecuteWithResults(t *testing.T) {
t.Run("invalid-port-override", func(t *testing.T) {
metadata := make(output.InternalEvent)
previous := make(output.InternalEvent)
- ctxArgs := contextargs.NewWithInput("127.0.0.1:11211")
+ ctxArgs := contextargs.NewWithInput(context.Background(), "127.0.0.1:11211")
err := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
finalEvent = event
})
@@ -95,7 +96,7 @@ func TestNetworkExecuteWithResults(t *testing.T) {
t.Run("hex-to-string", func(t *testing.T) {
metadata := make(output.InternalEvent)
previous := make(output.InternalEvent)
- ctxArgs := contextargs.NewWithInput(parsed.Host)
+ ctxArgs := contextargs.NewWithInput(context.Background(), parsed.Host)
err := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
finalEvent = event
})
diff --git a/pkg/protocols/protocols.go b/pkg/protocols/protocols.go
index 6328bc36a3..6b4904c8d8 100644
--- a/pkg/protocols/protocols.go
+++ b/pkg/protocols/protocols.go
@@ -1,6 +1,7 @@
package protocols
import (
+ "context"
"encoding/base64"
"sync/atomic"
@@ -173,7 +174,7 @@ func (e *ExecutorOptions) GetTemplateCtx(input *contextargs.MetaInput) *contexta
templateCtx, ok := e.templateCtxStore.Get(scanId)
if !ok {
// if template context does not exist create new and add it to store and return it
- templateCtx = contextargs.New()
+ templateCtx = contextargs.New(context.Background())
templateCtx.MetaInput = input
_ = e.templateCtxStore.Set(scanId, templateCtx)
}
diff --git a/pkg/protocols/ssl/ssl_test.go b/pkg/protocols/ssl/ssl_test.go
index 009cf98d32..59c9f85f3e 100644
--- a/pkg/protocols/ssl/ssl_test.go
+++ b/pkg/protocols/ssl/ssl_test.go
@@ -1,6 +1,7 @@
package ssl
import (
+ "context"
"testing"
"github.com/stretchr/testify/require"
@@ -28,7 +29,7 @@ func TestSSLProtocol(t *testing.T) {
require.Nil(t, err, "could not compile ssl request")
var gotEvent output.InternalEvent
- ctxArgs := contextargs.NewWithInput("scanme.sh:443")
+ ctxArgs := contextargs.NewWithInput(context.Background(), "scanme.sh:443")
err = request.ExecuteWithResults(ctxArgs, nil, nil, func(event *output.InternalWrappedEvent) {
gotEvent = event.InternalEvent
})
diff --git a/pkg/protocols/utils/variables_test.go b/pkg/protocols/utils/variables_test.go
index c24305017c..b83529499d 100644
--- a/pkg/protocols/utils/variables_test.go
+++ b/pkg/protocols/utils/variables_test.go
@@ -1,6 +1,7 @@
package utils
import (
+ "context"
"reflect"
"testing"
@@ -49,7 +50,7 @@ func TestHTTPVariables(t *testing.T) {
require.Equal(t, values["Hostname"], "foobar.com", "incorrect hostname")
baseURL = "http://scanme.sh"
- ctxArgs := contextargs.NewWithInput(baseURL)
+ ctxArgs := contextargs.NewWithInput(context.Background(), baseURL)
ctxArgs.MetaInput.CustomIP = "1.2.3.4"
values = GenerateVariablesWithContextArgs(ctxArgs, true)
diff --git a/pkg/scan/scan_context.go b/pkg/scan/scan_context.go
index 8851349019..69fbfde4c0 100644
--- a/pkg/scan/scan_context.go
+++ b/pkg/scan/scan_context.go
@@ -10,7 +10,6 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
)
-
type ScanContextOption func(*ScanContext)
func WithEvents() ScanContextOption {
@@ -20,7 +19,8 @@ func WithEvents() ScanContextOption {
}
type ScanContext struct {
- context.Context
+ ctx context.Context
+
// exported / configurable fields
Input *contextargs.Context
@@ -43,8 +43,13 @@ type ScanContext struct {
}
// NewScanContext creates a new scan context using input
-func NewScanContext(input *contextargs.Context) *ScanContext {
- return &ScanContext{Input: input}
+func NewScanContext(ctx context.Context, input *contextargs.Context) *ScanContext {
+ return &ScanContext{ctx: ctx, Input: input}
+}
+
+// Context returns the context of the scan
+func (s *ScanContext) Context() context.Context {
+ return s.ctx
}
// GenerateResult returns final results slice from all events
diff --git a/pkg/templates/cluster.go b/pkg/templates/cluster.go
index b1e0b56e2d..97e7b67bd0 100644
--- a/pkg/templates/cluster.go
+++ b/pkg/templates/cluster.go
@@ -303,7 +303,7 @@ func (e *ClusterExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
func (e *ClusterExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {
- scanCtx := scan.NewScanContext(ctx.Input)
+ scanCtx := scan.NewScanContext(ctx.Context(), ctx.Input)
dynamicValues := make(map[string]interface{})
inputItem := ctx.Input.Clone()
diff --git a/pkg/tmplexec/flow/flow_executor.go b/pkg/tmplexec/flow/flow_executor.go
index 31aa9f0f4d..c3f0014cab 100644
--- a/pkg/tmplexec/flow/flow_executor.go
+++ b/pkg/tmplexec/flow/flow_executor.go
@@ -174,6 +174,12 @@ func (f *FlowExecutor) Compile() error {
// ExecuteWithResults executes the flow and returns results
func (f *FlowExecutor) ExecuteWithResults(ctx *scan.ScanContext) error {
+ select {
+ case <-ctx.Context().Done():
+ return ctx.Context().Err()
+ default:
+ }
+
f.ctx.Input = ctx.Input
// -----Load all types of variables-----
// add all input args to template context
diff --git a/pkg/tmplexec/flow/flow_executor_test.go b/pkg/tmplexec/flow/flow_executor_test.go
index bd3a526d2f..b47b38a2a0 100644
--- a/pkg/tmplexec/flow/flow_executor_test.go
+++ b/pkg/tmplexec/flow/flow_executor_test.go
@@ -55,8 +55,8 @@ func TestFlowTemplateWithIndex(t *testing.T) {
err = Template.Executer.Compile()
require.Nil(t, err, "could not compile template")
- input := contextargs.NewWithInput("hackerone.com")
- ctx := scan.NewScanContext(input)
+ input := contextargs.NewWithInput(context.Background(), "hackerone.com")
+ ctx := scan.NewScanContext(context.Background(), input)
gotresults, err := Template.Executer.Execute(ctx)
require.Nil(t, err, "could not execute template")
require.True(t, gotresults)
@@ -74,8 +74,8 @@ func TestFlowTemplateWithID(t *testing.T) {
err = Template.Executer.Compile()
require.Nil(t, err, "could not compile template")
- target := contextargs.NewWithInput("hackerone.com")
- ctx := scan.NewScanContext(target)
+ target := contextargs.NewWithInput(context.Background(), "hackerone.com")
+ ctx := scan.NewScanContext(context.Background(), target)
gotresults, err := Template.Executer.Execute(ctx)
require.Nil(t, err, "could not execute template")
require.True(t, gotresults)
@@ -96,8 +96,8 @@ func TestFlowWithProtoPrefix(t *testing.T) {
err = Template.Executer.Compile()
require.Nil(t, err, "could not compile template")
- input := contextargs.NewWithInput("hackerone.com")
- ctx := scan.NewScanContext(input)
+ input := contextargs.NewWithInput(context.Background(), "hackerone.com")
+ ctx := scan.NewScanContext(context.Background(), input)
gotresults, err := Template.Executer.Execute(ctx)
require.Nil(t, err, "could not execute template")
require.True(t, gotresults)
@@ -116,8 +116,8 @@ func TestFlowWithConditionNegative(t *testing.T) {
err = Template.Executer.Compile()
require.Nil(t, err, "could not compile template")
- input := contextargs.NewWithInput("scanme.sh")
- ctx := scan.NewScanContext(input)
+ input := contextargs.NewWithInput(context.Background(), "scanme.sh")
+ ctx := scan.NewScanContext(context.Background(), input)
// expect no results and verify thant dns request is executed and http is not
gotresults, err := Template.Executer.Execute(ctx)
require.Nil(t, err, "could not execute template")
@@ -137,8 +137,8 @@ func TestFlowWithConditionPositive(t *testing.T) {
err = Template.Executer.Compile()
require.Nil(t, err, "could not compile template")
- input := contextargs.NewWithInput("blog.projectdiscovery.io")
- ctx := scan.NewScanContext(input)
+ input := contextargs.NewWithInput(context.Background(), "blog.projectdiscovery.io")
+ ctx := scan.NewScanContext(context.Background(), input)
// positive match . expect results also verify that both dns() and http() were executed
gotresults, err := Template.Executer.Execute(ctx)
require.Nil(t, err, "could not execute template")
@@ -158,8 +158,8 @@ func TestFlowWithNoMatchers(t *testing.T) {
err = Template.Executer.Compile()
require.Nil(t, err, "could not compile template")
- input := contextargs.NewWithInput("blog.projectdiscovery.io")
- ctx := scan.NewScanContext(input)
+ input := contextargs.NewWithInput(context.Background(), "blog.projectdiscovery.io")
+ ctx := scan.NewScanContext(context.Background(), input)
// positive match . expect results also verify that both dns() and http() were executed
gotresults, err := Template.Executer.Execute(ctx)
require.Nil(t, err, "could not execute template")
@@ -174,8 +174,8 @@ func TestFlowWithNoMatchers(t *testing.T) {
err = Template.Executer.Compile()
require.Nil(t, err, "could not compile template")
- anotherInput := contextargs.NewWithInput("blog.projectdiscovery.io")
- anotherCtx := scan.NewScanContext(anotherInput)
+ anotherInput := contextargs.NewWithInput(context.Background(), "blog.projectdiscovery.io")
+ anotherCtx := scan.NewScanContext(context.Background(), anotherInput)
// positive match . expect results also verify that both dns() and http() were executed
gotresults, err = Template.Executer.Execute(anotherCtx)
require.Nil(t, err, "could not execute template")
diff --git a/pkg/tmplexec/generic/exec.go b/pkg/tmplexec/generic/exec.go
index e47cfd7305..29ace4477d 100644
--- a/pkg/tmplexec/generic/exec.go
+++ b/pkg/tmplexec/generic/exec.go
@@ -45,6 +45,12 @@ func (g *Generic) ExecuteWithResults(ctx *scan.ScanContext) error {
previous := mapsutil.NewSyncLockMap[string, any]()
for _, req := range g.requests {
+ select {
+ case <-ctx.Context().Done():
+ return ctx.Context().Err()
+ default:
+ }
+
inputItem := ctx.Input.Clone()
if g.options.InputHelper != nil && ctx.Input.MetaInput.Input != "" {
if inputItem.MetaInput.Input = g.options.InputHelper.Transform(inputItem.MetaInput.Input, req.Type()); inputItem.MetaInput.Input == "" {
diff --git a/pkg/tmplexec/multiproto/multi.go b/pkg/tmplexec/multiproto/multi.go
index 997e62243d..58858f971e 100644
--- a/pkg/tmplexec/multiproto/multi.go
+++ b/pkg/tmplexec/multiproto/multi.go
@@ -44,6 +44,12 @@ func (m *MultiProtocol) Compile() error {
// ExecuteWithResults executes the template and returns results
func (m *MultiProtocol) ExecuteWithResults(ctx *scan.ScanContext) error {
+ select {
+ case <-ctx.Context().Done():
+ return ctx.Context().Err()
+ default:
+ }
+
// put all readonly args into template context
m.options.GetTemplateCtx(ctx.Input.MetaInput).Merge(m.readOnlyArgs)
@@ -96,6 +102,12 @@ func (m *MultiProtocol) ExecuteWithResults(ctx *scan.ScanContext) error {
// execute all protocols in the queue
for _, req := range m.requests {
+ select {
+ case <-ctx.Context().Done():
+ return ctx.Context().Err()
+ default:
+ }
+
values := m.options.GetTemplateCtx(ctx.Input.MetaInput).GetAll()
err := req.ExecuteWithResults(ctx.Input, output.InternalEvent(values), nil, multiProtoCallback)
// if error skip execution of next protocols
diff --git a/pkg/tmplexec/multiproto/multi_test.go b/pkg/tmplexec/multiproto/multi_test.go
index b632680298..4f2aa25e4e 100644
--- a/pkg/tmplexec/multiproto/multi_test.go
+++ b/pkg/tmplexec/multiproto/multi_test.go
@@ -54,8 +54,8 @@ func TestMultiProtoWithDynamicExtractor(t *testing.T) {
err = Template.Executer.Compile()
require.Nil(t, err, "could not compile template")
- input := contextargs.NewWithInput("blog.projectdiscovery.io")
- ctx := scan.NewScanContext(input)
+ input := contextargs.NewWithInput(context.Background(), "blog.projectdiscovery.io")
+ ctx := scan.NewScanContext(context.Background(), input)
gotresults, err := Template.Executer.Execute(ctx)
require.Nil(t, err, "could not execute template")
require.True(t, gotresults)
@@ -71,8 +71,8 @@ func TestMultiProtoWithProtoPrefix(t *testing.T) {
err = Template.Executer.Compile()
require.Nil(t, err, "could not compile template")
- input := contextargs.NewWithInput("blog.projectdiscovery.io")
- ctx := scan.NewScanContext(input)
+ input := contextargs.NewWithInput(context.Background(), "blog.projectdiscovery.io")
+ ctx := scan.NewScanContext(context.Background(), input)
gotresults, err := Template.Executer.Execute(ctx)
require.Nil(t, err, "could not execute template")
require.True(t, gotresults)
From c8cda14e4183e1c2fe4551368cc7bd84ae479ff8 Mon Sep 17 00:00:00 2001
From: Dogan Can Bakir <65292895+dogancanbakir@users.noreply.github.com>
Date: Thu, 25 Apr 2024 13:58:37 +0300
Subject: [PATCH 56/58] remove default val in CLI and increase `MaxBodyRead` to
10mb (#5100)
Co-authored-by: Tarun Koyalwar
---
cmd/nuclei/main.go | 2 +-
pkg/protocols/http/request.go | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go
index a084f270b4..242a902f55 100644
--- a/cmd/nuclei/main.go
+++ b/cmd/nuclei/main.go
@@ -296,7 +296,7 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.StringVarP(&options.Interface, "interface", "i", "", "network interface to use for network scan"),
flagSet.StringVarP(&options.AttackType, "attack-type", "at", "", "type of payload combinations to perform (batteringram,pitchfork,clusterbomb)"),
flagSet.StringVarP(&options.SourceIP, "source-ip", "sip", "", "source ip address to use for network scan"),
- flagSet.IntVarP(&options.ResponseReadSize, "response-size-read", "rsr", 10*1024*1024, "max response size to read in bytes"),
+ flagSet.IntVarP(&options.ResponseReadSize, "response-size-read", "rsr", 0, "max response size to read in bytes"),
flagSet.IntVarP(&options.ResponseSaveSize, "response-size-save", "rss", 1*1024*1024, "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)"),
diff --git a/pkg/protocols/http/request.go b/pkg/protocols/http/request.go
index 545247ef56..b2628f007a 100644
--- a/pkg/protocols/http/request.go
+++ b/pkg/protocols/http/request.go
@@ -53,7 +53,7 @@ const (
)
var (
- MaxBodyRead = int64(1 << 22) // 4MB using shift operator
+ MaxBodyRead = int64(10 * 1024 * 1024) // 10MB
// ErrMissingVars is error occured when variables are missing
ErrMissingVars = errors.New("stop execution due to unresolved variables")
)
From 295f45807e65e1bee16dce573b217a0dfff67002 Mon Sep 17 00:00:00 2001
From: sandeep <8293321+ehsandeep@users.noreply.github.com>
Date: Thu, 25 Apr 2024 16:29:52 +0530
Subject: [PATCH 57/58] version update
---
pkg/catalog/config/constants.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/catalog/config/constants.go b/pkg/catalog/config/constants.go
index 3dca0be63b..2b6793634f 100644
--- a/pkg/catalog/config/constants.go
+++ b/pkg/catalog/config/constants.go
@@ -31,7 +31,7 @@ const (
CLIConfigFileName = "config.yaml"
ReportingConfigFilename = "reporting-config.yaml"
// Version is the current version of nuclei
- Version = `v3.2.4`
+ Version = `v3.2.5`
// Directory Names of custom templates
CustomS3TemplatesDirName = "s3"
CustomGitHubTemplatesDirName = "github"
From 7ce1b3e43d92a56a33ddf2ebf3e15876fb39388d Mon Sep 17 00:00:00 2001
From: Tarun Koyalwar <45962551+tarunKoyalwar@users.noreply.github.com>
Date: Thu, 25 Apr 2024 16:55:48 +0530
Subject: [PATCH 58/58] flow: fix empty template.xxx in flow (#5106)
---
pkg/tmplexec/flow/flow_executor.go | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/pkg/tmplexec/flow/flow_executor.go b/pkg/tmplexec/flow/flow_executor.go
index c3f0014cab..6a1813efd9 100644
--- a/pkg/tmplexec/flow/flow_executor.go
+++ b/pkg/tmplexec/flow/flow_executor.go
@@ -226,7 +226,11 @@ func (f *FlowExecutor) ExecuteWithResults(ctx *scan.ScanContext) error {
}
}
// register template object
- if err := runtime.Set("template", f.options.GetTemplateCtx(f.ctx.Input.MetaInput).GetAll()); err != nil {
+ tmplObj := f.options.GetTemplateCtx(f.ctx.Input.MetaInput).GetAll()
+ if tmplObj == nil {
+ tmplObj = map[string]interface{}{}
+ }
+ if err := runtime.Set("template", tmplObj); err != nil {
return err
}