mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-11-09 17:43:08 -10:00
Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1571002607 | ||
|
|
59b7caa941 | ||
|
|
e432e4a4c2 | ||
|
|
84582183dc | ||
|
|
ea2e13a4aa | ||
|
|
bec7cb273a | ||
|
|
431d3fa2d9 | ||
|
|
d576db3893 | ||
|
|
f2c0b4b443 | ||
|
|
998a25d744 | ||
|
|
373a598e93 | ||
|
|
17f0ca2576 | ||
|
|
f876e3290b | ||
|
|
480968fd78 | ||
|
|
3b51a77bc7 | ||
|
|
88ee422256 | ||
|
|
c8824ac71a | ||
|
|
c145adb3bf | ||
|
|
0d5e26d7bd | ||
|
|
38e185c410 | ||
|
|
8f084eedd6 | ||
|
|
0807113e6c | ||
|
|
4d0204f89a | ||
|
|
f4394b8e11 | ||
|
|
7b71886309 | ||
|
|
2ed33e4723 | ||
|
|
721ddda915 | ||
|
|
5fc08cec48 | ||
|
|
6eeb98c71b | ||
|
|
70eb494a01 | ||
|
|
582a85d9c0 | ||
|
|
7e363984b2 | ||
|
|
79bbd51eac | ||
|
|
2875fb06b6 | ||
|
|
4bb48d9949 | ||
|
|
c8394ce1fb | ||
|
|
3ba11aaf74 | ||
|
|
f886fb2cf4 | ||
|
|
d0a0c6d0c3 | ||
|
|
aa06c9ef17 | ||
|
|
c4aa7dc1a2 | ||
|
|
96d7d02701 | ||
|
|
99a018d12a | ||
|
|
af7450737a | ||
|
|
a140a4194e | ||
|
|
620287f76b | ||
|
|
3c62b56fd9 | ||
|
|
774db61655 | ||
|
|
a8d1393e96 |
No files matched your search
@@ -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/
|
||||
@@ -36,5 +36,8 @@ pkg/protocols/headless/engine/.cache
|
||||
/fuzzplayground
|
||||
integration_tests/fuzzplayground
|
||||
/dsl.md
|
||||
/nuclei-stats
|
||||
/nuclei-stats-*
|
||||
/scan-charts
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<a href="#install-nuclei">Install</a> •
|
||||
<a href="https://docs.projectdiscovery.io/tools/nuclei/">Documentation</a> •
|
||||
<a href="#credits">Credits</a> •
|
||||
<a href="https://nuclei.projectdiscovery.io/faq/nuclei/">FAQs</a> •
|
||||
<a href="https://docs.projectdiscovery.io/tools/nuclei/faq">FAQs</a> •
|
||||
<a href="https://discord.gg/projectdiscovery">Join Discord</a>
|
||||
</p>
|
||||
|
||||
|
||||
+4
-4
@@ -23,8 +23,8 @@
|
||||
<a href="#对于安全工程师">对于安全工程师</a> •
|
||||
<a href="#对于开发和组织">对于开发者</a> •
|
||||
<a href="https://nuclei.projectdiscovery.io/nuclei/get-started/">文档</a> •
|
||||
<a href="#c致谢">致谢</a> •
|
||||
<a href="https://nuclei.projectdiscovery.io/faq/nuclei/">常见问题</a> •
|
||||
<a href="#致谢">致谢</a> •
|
||||
<a href="https://docs.projectdiscovery.io/tools/nuclei/faq">常见问题</a> •
|
||||
<a href="https://discord.gg/projectdiscovery">加入Discord</a>
|
||||
</p>
|
||||
|
||||
@@ -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
|
||||
</a>
|
||||
</p>
|
||||
|
||||
另外您可以其他类似的开源项目:
|
||||
另外您可以了解其他类似的开源项目:
|
||||
|
||||
[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)
|
||||
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<a href="#untuk-pengembang-dan-organisasi">Untuk Pengembang</a> •
|
||||
<a href="https://nuclei.projectdiscovery.io/nuclei/get-started/">Dokumentasi</a> •
|
||||
<a href="#kredit">Kredit</a> •
|
||||
<a href="https://nuclei.projectdiscovery.io/faq/nuclei/">Tanya Jawab</a> •
|
||||
<a href="https://docs.projectdiscovery.io/tools/nuclei/faq">Tanya Jawab</a> •
|
||||
<a href="https://discord.gg/projectdiscovery">Gabung Discord</a>
|
||||
</p>
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
<a href="#개발자를-위한">개발자를 위한</a> •
|
||||
<a href="https://nuclei.projectdiscovery.io/nuclei/get-started/">문서</a> •
|
||||
<a href="#credits">Credits</a> •
|
||||
<a href="https://nuclei.projectdiscovery.io/faq/nuclei/">FAQs</a> •
|
||||
<a href="https://docs.projectdiscovery.io/tools/nuclei/faq">FAQs</a> •
|
||||
<a href="https://discord.gg/projectdiscovery"> Discord 참가</a>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -2750,6 +2750,8 @@ Enum Values:
|
||||
- <code>TLSA</code>
|
||||
|
||||
- <code>ANY</code>
|
||||
|
||||
- <code>SRV</code>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
@@ -5,7 +5,12 @@ import (
|
||||
)
|
||||
|
||||
var dnsTestCases = []TestCaseInfo{
|
||||
{Path: "protocols/dns/basic.yaml", TestCase: &dnsBasic{}},
|
||||
{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{}},
|
||||
|
||||
@@ -3,11 +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"
|
||||
)
|
||||
|
||||
@@ -16,11 +20,40 @@ 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/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
|
||||
@@ -97,6 +130,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
|
||||
@@ -133,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
|
||||
|
||||
+11
-3
@@ -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,13 +330,15 @@ 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"),
|
||||
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"),
|
||||
@@ -412,6 +415,11 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
flagSet.BoolVarP(&options.PreFetchSecrets, "prefetch-secrets", "ps", false, "prefetch secrets from the secrets file"),
|
||||
)
|
||||
|
||||
flagSet.CreateGroup("tests", "Tests",
|
||||
flagSet.BoolVarP(&options.AutogenerateTests, "autogenerate-tests", "agt", false, "enable autogeneration of nuclei test-cases (http only now)"),
|
||||
flagSet.BoolVarP(&options.RunNucleiTests, "run-tests", "rts", false, "run generated nuclei test-cases"),
|
||||
)
|
||||
|
||||
flagSet.SetCustomHelpText(`EXAMPLES:
|
||||
Run nuclei on single host:
|
||||
$ nuclei -target example.com
|
||||
@@ -597,10 +605,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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
}
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
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{Tags: []string{"oast"}}),
|
||||
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,
|
||||
ProbeConcurrency: 1,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func testRateLimit(wg *sync.WaitGroup, ne *nuclei.NucleiEngine) {
|
||||
defer wg.Done()
|
||||
verifyRateLimit(ne, 1, 5000)
|
||||
}
|
||||
|
||||
func testThreadsAndBulkSize(wg *sync.WaitGroup, ne *nuclei.NucleiEngine) {
|
||||
defer wg.Done()
|
||||
initialTemplateThreads, initialBulkSize := 1, 1
|
||||
verifyThreadsAndBulkSize(ne, initialTemplateThreads, initialBulkSize, 25, 25)
|
||||
}
|
||||
|
||||
func testPayloadConcurrency(wg *sync.WaitGroup, ne *nuclei.NucleiEngine) {
|
||||
defer wg.Done()
|
||||
verifyPayloadConcurrency(ne, 1, 500)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
func verifyThreadsAndBulkSize(ne *nuclei.NucleiEngine, initialThreads, initialBulk, finalThreads, finalBulk int) {
|
||||
if ne.Options().TemplateThreads != initialThreads || ne.Options().BulkSize != initialBulk {
|
||||
panic("wrong initial standard concurrency")
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,10 @@ 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
|
||||
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
|
||||
@@ -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
|
||||
@@ -70,6 +70,7 @@ require (
|
||||
github.com/go-ldap/ldap/v3 v3.4.5
|
||||
github.com/go-pg/pg v8.0.7+incompatible
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/goccy/go-yaml v1.11.3
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/labstack/echo/v4 v4.10.2
|
||||
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa
|
||||
@@ -78,16 +79,19 @@ 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/freeport v0.0.5
|
||||
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
|
||||
github.com/projectdiscovery/httpx v1.6.0
|
||||
github.com/projectdiscovery/mapcidr v1.1.16
|
||||
github.com/projectdiscovery/martian/v3 v3.0.0-20240219194442-fed3b744f477
|
||||
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
|
||||
github.com/projectdiscovery/proxify v0.0.13-0.20240407134611-de309e8af9ca
|
||||
github.com/projectdiscovery/ratelimit v0.0.35
|
||||
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917
|
||||
github.com/projectdiscovery/sarif v0.0.1
|
||||
@@ -100,7 +104,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
|
||||
)
|
||||
@@ -114,8 +118,10 @@ require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/Shopify/sarama v1.38.1 // indirect
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect
|
||||
@@ -142,7 +148,12 @@ 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/fatih/color v1.15.0 // indirect
|
||||
github.com/eapache/channels v1.1.0 // indirect
|
||||
github.com/eapache/go-resiliency v1.3.0 // indirect
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 // indirect
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/elastic/go-elasticsearch/v7 v7.17.10 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/geoffgarside/ber v1.1.0 // indirect
|
||||
@@ -161,9 +172,12 @@ require (
|
||||
github.com/google/go-github/v30 v30.1.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.6 // indirect
|
||||
github.com/haxii/fastproxy v0.5.37 // indirect
|
||||
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf // indirect
|
||||
github.com/invopop/yaml v0.2.0 // indirect
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
|
||||
@@ -198,11 +212,13 @@ require (
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/projectdiscovery/asnmap v1.1.0 // indirect
|
||||
github.com/projectdiscovery/cdncheck v1.0.9 // indirect
|
||||
github.com/projectdiscovery/freeport v0.0.5 // indirect
|
||||
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb // indirect
|
||||
github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 // indirect
|
||||
github.com/projectdiscovery/roundrobin v0.0.6 // indirect
|
||||
github.com/projectdiscovery/stringsutil v0.0.2 // indirect
|
||||
github.com/projectdiscovery/tinydns v0.0.31 // indirect
|
||||
github.com/quic-go/quic-go v0.42.0 // indirect
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
|
||||
github.com/refraction-networking/utls v1.6.1 // indirect
|
||||
github.com/sashabaranov/go-openai v1.15.3 // indirect
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
@@ -232,6 +248,7 @@ require (
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
gopkg.in/djherbis/times.v1 v1.3.0 // indirect
|
||||
mellium.im/sasl v0.3.1 // indirect
|
||||
)
|
||||
@@ -300,10 +317,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
|
||||
@@ -327,6 +344,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
|
||||
@@ -346,3 +364,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
|
||||
@@ -85,7 +85,12 @@ github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAc
|
||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
github.com/RumbleDiscovery/rumble-tools v0.0.0-20201105153123-f2adbb3244d2/go.mod h1:jD2+mU+E2SZUuAOHZvZj4xP4frlOo+N/YrXDvASFhkE=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/sarama v1.38.1 h1:lqqPUPQZ7zPqYlWpTh+LQ9bhYNu2xJL6k1SJN4WVe2A=
|
||||
github.com/Shopify/sarama v1.38.1/go.mod h1:iwv9a67Ha8VNa+TifujYoWGxWnu2kNVAQdSdZ4X2o5g=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/Shopify/toxiproxy/v2 v2.5.0 h1:i4LPT+qrSlKNtQf5QliVjdP08GyAH8+BUIc9gT0eahc=
|
||||
github.com/Shopify/toxiproxy/v2 v2.5.0/go.mod h1:yhM2epWtAmel9CB8r2+L+PCmhH6yH2pITaPAo7jxJl0=
|
||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
@@ -296,10 +301,19 @@ 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-resiliency v1.3.0 h1:RRL0nge+cWGlxXbUzJ7yMcq6w2XBEr19dCN6HECGaT0=
|
||||
github.com/eapache/go-resiliency v1.3.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 h1:8yY/I9ndfrgrXUbOGObLHKBR4Fl3nZXwM2c7OYTT8hM=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
|
||||
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/elastic/go-elasticsearch/v7 v7.17.10 h1:TCQ8i4PmIJuBunvBS6bwT2ybzVFxxUhhltAs3Gyu1yo=
|
||||
github.com/elastic/go-elasticsearch/v7 v7.17.10/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
@@ -310,10 +324,12 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
@@ -404,6 +420,8 @@ github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk=
|
||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I=
|
||||
github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@@ -523,6 +541,8 @@ github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy
|
||||
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
||||
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
@@ -531,6 +551,8 @@ github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrj
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
@@ -554,6 +576,9 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/haxii/fastproxy v0.5.37 h1:grfso8V9sNO8jZjI/vkizFXzz8fE/yrsgl+XxTb3fio=
|
||||
github.com/haxii/fastproxy v0.5.37/go.mod h1:VFy3M4EmTbeKu+IccQ6UiJ1W4sPeQ75GV+1JDa8h864=
|
||||
github.com/haxii/log v1.0.0/go.mod h1:y9MlOm+u2ny65yQxScWfSGZFOhRVLXz2vJlkiIx2jfI=
|
||||
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf h1:umfGUaWdFP2s6457fz1+xXYIWDxdGc7HdkLS9aJ1skk=
|
||||
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf/go.mod h1:V99KdStnMHZsvVOwIvhfcUzYgYkRZeQWUtumtL+SKxA=
|
||||
github.com/hdm/jarm-go v0.0.7 h1:Eq0geenHrBSYuKrdVhrBdMMzOmA+CAMLzN2WrF3eL6A=
|
||||
@@ -829,18 +854,18 @@ 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/fastdialer v0.0.66 h1:DRpmok9TArLyQKaSjRWSzikt2N2Qyzx/z0BmTmDyJvI=
|
||||
github.com/projectdiscovery/fastdialer v0.0.66/go.mod h1:7uPrwFsIBhtUBkXd72K4VSo9lvcwqOzOGXIZ9UZXFYw=
|
||||
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=
|
||||
github.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw=
|
||||
github.com/projectdiscovery/freeport v0.0.5 h1:jnd3Oqsl4S8n0KuFkE5Hm8WGDP24ITBvmyw5pFTHS8Q=
|
||||
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=
|
||||
@@ -859,24 +884,32 @@ github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 h1:ZScL
|
||||
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/martian/v3 v3.0.0-20240219194442-fed3b744f477 h1:VJaBELAC5Hw+kc+ylrFF5nSf7Wasnb9mZxMlF/kR3gg=
|
||||
github.com/projectdiscovery/martian/v3 v3.0.0-20240219194442-fed3b744f477/go.mod h1:wPvVUl2C/XOFacugXwsUp65GN0upmKfwKMyimA/AAaM=
|
||||
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=
|
||||
github.com/projectdiscovery/networkpolicy v0.0.8/go.mod h1:xnjNqhemxUPxU+UD5Jgsc3+K8IVmcqT1SJeo6UzMtkI=
|
||||
github.com/projectdiscovery/proxify v0.0.13-0.20240407134611-de309e8af9ca h1:Adduz+Lli837Ga2K1NqxVFOFxRYsWjK4zRWzHQWZtV4=
|
||||
github.com/projectdiscovery/proxify v0.0.13-0.20240407134611-de309e8af9ca/go.mod h1:9x1db4kybvc6BpSvpvbMzAuWjNFrt6Foy1t1VI7CUTc=
|
||||
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=
|
||||
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/roundrobin v0.0.6 h1:zoJAFRgP9XK7B+iKSjR+djRAuDYxnc57+Fx+qpoPvds=
|
||||
github.com/projectdiscovery/roundrobin v0.0.6/go.mod h1:vTxcWqNLyMH6VE2Q/hsNNvDHFLiIzHozC1rLLT/vocQ=
|
||||
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=
|
||||
github.com/projectdiscovery/stringsutil v0.0.2/go.mod h1:EJ3w6bC5fBYjVou6ryzodQq37D5c6qbAYQpGmAy+DC0=
|
||||
github.com/projectdiscovery/tinydns v0.0.31 h1:iQL1ze2/4PHWH+Nzsxg35kVIWkvEIatAZCDcP0c1/v4=
|
||||
github.com/projectdiscovery/tinydns v0.0.31/go.mod h1:6rj/dEzXZPn+nLl4O1wUMJdfkdUQ9eCV7NsDWPakYv8=
|
||||
github.com/projectdiscovery/tlsx v1.1.6 h1:iw2zwKbd2+kRQ8J1G4dLmS0CLyemd/tKz1UzcNsC77A=
|
||||
github.com/projectdiscovery/tlsx v1.1.6/go.mod h1:s7SRRFdrwIZBK/RXXZi4CR/CubqFSvp8h5Bk1srEZIo=
|
||||
github.com/projectdiscovery/uncover v1.0.7 h1:ut+2lTuvmftmveqF5RTjMWAgyLj8ltPQC7siFy9sj0A=
|
||||
@@ -924,6 +957,8 @@ github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB
|
||||
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY=
|
||||
github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c=
|
||||
github.com/refraction-networking/utls v1.6.1 h1:n1JG5karzdGWsI6iZmGrOv3SNzR4c+4M8J6KWGsk3lA=
|
||||
@@ -941,6 +976,7 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.11.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||
@@ -1010,6 +1046,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=
|
||||
@@ -1183,8 +1221,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=
|
||||
@@ -1277,8 +1315,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=
|
||||
@@ -1379,8 +1417,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=
|
||||
@@ -1392,8 +1431,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=
|
||||
@@ -1473,6 +1512,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
@@ -1529,6 +1570,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20220706185917-7780775163c4 h1:7YDGQC/0sigNGzsEWyb9s72jTxlFdwVEYNJHbfQ+Dtg=
|
||||
google.golang.org/genproto v0.0.0-20220706185917-7780775163c4/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
@@ -1546,6 +1589,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w=
|
||||
google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
||||
@@ -23,4 +23,4 @@ code:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- true
|
||||
# digest: 4a0a00473045022100c7215ce9f11e6a51c193bb54643a05cdd1cde18a3abb6c9983c5c7524d3ff03002203d93581c81d3ad5db463570cbbd2bdee529328d32a5b00e037610c211e448cef:4a3eb6b4988d95847d4203be25ed1d46
|
||||
# digest: 490a004630440220192fb8f704b078c2885047b85ac1a0491be86485c033a976d201599683a35aab0220604b1c3781e9d97079d0e5c23c18e6a2d87493c8e2b930536e692ee7d06e9247:4a3eb6b4988d95847d4203be25ed1d46
|
||||
@@ -20,4 +20,4 @@ code:
|
||||
- type: word
|
||||
words:
|
||||
- "hello from input baz"
|
||||
# digest: 4a0a0047304502207e3a5eda5f3207c3c01c820562243281926c1215224a7c80ed7528559b9f52cb022100f6ef99bb45843f481705778630f2cfd8f4d1cc3acb96392ff016f22e06aa91af:4a3eb6b4988d95847d4203be25ed1d46
|
||||
# digest: 4a0a00473045022033f72f1b9d5143f58a2dc79c2597000f34080251ac3702c36c3fad00917dfeeb0221009ba05c715c9e2e36dba471be6c0106a09ae3822d8a3e9e4bcf377e9f4a395a01:4a3eb6b4988d95847d4203be25ed1d46
|
||||
@@ -18,4 +18,4 @@ code:
|
||||
- type: word
|
||||
words:
|
||||
- "hello from input"
|
||||
# digest: 4a0a004730450220069673af9bd6d6677f9529d06f5d8bd46d543089a4731ed18ee806761d75fd60022100913a3e27b0a5809baf710ba9585bf9fe729634c0e19e3e13eef70a6bd100df34:4a3eb6b4988d95847d4203be25ed1d46
|
||||
# digest: 4a0a004730450220377128cb11d9f6f0fee1f4dbd841e46783de26e90a216fa55a7609ee2bc823c60221009166ee0f85e3a1811588ab19e73ea96ab3d582dc8180dbcbbad0ea9ab7e9025d:4a3eb6b4988d95847d4203be25ed1d46
|
||||
@@ -26,4 +26,4 @@ code:
|
||||
part: interactsh_protocol
|
||||
words:
|
||||
- "http"
|
||||
# digest: 490a00463044022003b8d069e3c84412729c43e33013a52ee04eabcf096d511979691d71d8e905f60220011f4475899abed4f86b4bd5e6c2423750759135206e4729826afe1ed8a44f4d:4a3eb6b4988d95847d4203be25ed1d46
|
||||
# digest: 4b0a00483046022100d472d50bd83117d334f5217c7a40dcdf34138e90029eaace51697d902296bf37022100a393b49420a96f60d6d89b79b5135ee2233b2468d374851890eea114b08195d1:4a3eb6b4988d95847d4203be25ed1d46
|
||||
@@ -21,4 +21,4 @@ code:
|
||||
- type: word
|
||||
words:
|
||||
- "hello from input"
|
||||
# digest: 4a0a00473045022100c291615cf2a8005450c17a6554e81a9cdab14743b299f0679c644247929198b502206fdacc8ab173bde2b4015340012637916bf2659f66f320fcc06b97ac639072a1:4a3eb6b4988d95847d4203be25ed1d46
|
||||
# digest: 4b0a004830460221008886054bb5dd6345e434e30f31c8fddce3c484a4f33aa6321b5185675866029d022100d188a83d0fde029f8b586061c65ab72b43755c3fb10fdd59501bb9bbadbb1ff7:4a3eb6b4988d95847d4203be25ed1d46
|
||||
@@ -1,7 +1,7 @@
|
||||
id: basic-dns-example
|
||||
id: dns-a-query-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"
|
||||
- "1.1.1.1"
|
||||
@@ -0,0 +1,17 @@
|
||||
id: dns-aaaa-query-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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -10,17 +10,17 @@ 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"
|
||||
"github.com/remeh/sizedwaitgroup"
|
||||
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,33 +31,43 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
|
||||
}
|
||||
gologger.Info().Msgf("Running httpx on input host")
|
||||
|
||||
var bulkSize = probeBulkSize
|
||||
if r.options.BulkSize > probeBulkSize {
|
||||
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
|
||||
httpxOptions.NetworkPolicy = protocolstate.NetworkPolicy
|
||||
httpxClient, err := httpx.New(&httpxOptions)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create httpx client")
|
||||
}
|
||||
|
||||
shouldFollowGlobalProbeBulkSize := bulkSize == GlobalProbeBulkSize
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
if shouldFollowGlobalProbeBulkSize && swg.Size != GlobalProbeBulkSize {
|
||||
swg.Resize(GlobalProbeBulkSize)
|
||||
}
|
||||
|
||||
swg.Add()
|
||||
go func(input *contextargs.MetaInput) {
|
||||
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 +75,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
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonl"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/sarif"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/testing"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
|
||||
fileutil "github.com/projectdiscovery/utils/file"
|
||||
@@ -50,6 +51,8 @@ func ConfigureOptions() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var proxyServer *testing.ProxyServer
|
||||
|
||||
// ParseOptions parses the command line flags provided by a user
|
||||
func ParseOptions(options *types.Options) {
|
||||
// Check if stdin pipe was given
|
||||
@@ -83,6 +86,19 @@ func ParseOptions(options *types.Options) {
|
||||
if err := ValidateOptions(options); err != nil {
|
||||
gologger.Fatal().Msgf("Program exiting: %s\n", err)
|
||||
}
|
||||
if options.AutogenerateTests {
|
||||
ps, err := testing.NewProxyServer()
|
||||
if err != nil {
|
||||
gologger.Fatal().Msgf("Could not create proxy server: %s\n", err)
|
||||
}
|
||||
proxyServer = ps
|
||||
|
||||
// Add the proxy server to the options
|
||||
proxyURL := fmt.Sprintf("http://%s", proxyServer.ListenAddr)
|
||||
options.Proxy = goflags.StringSlice([]string{proxyURL})
|
||||
types.ProxyURL = proxyURL
|
||||
gologger.Info().Msgf("Using %s as proxy server for automatic test-generation\n", proxyServer.ListenAddr)
|
||||
}
|
||||
|
||||
// Load the resolvers if user asked for them
|
||||
loadResolvers(options)
|
||||
|
||||
+158
-4
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -18,6 +19,8 @@ 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/testing"
|
||||
"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"
|
||||
@@ -314,11 +317,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.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
|
||||
}
|
||||
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 {
|
||||
@@ -338,6 +347,9 @@ func (r *Runner) runStandardEnumeration(executerOpts protocols.ExecutorOptions,
|
||||
|
||||
// Close releases all the resources and cleans up
|
||||
func (r *Runner) Close() {
|
||||
if proxyServer != nil {
|
||||
proxyServer.Close()
|
||||
}
|
||||
if r.output != nil {
|
||||
r.output.Close()
|
||||
}
|
||||
@@ -501,6 +513,14 @@ func (r *Runner) RunEnumeration() error {
|
||||
return nil // exit
|
||||
}
|
||||
store.Load()
|
||||
|
||||
if len(store.Templates()) > 1 && r.options.AutogenerateTests {
|
||||
return errors.New("autogenerate-tests flag can only be used with a single template")
|
||||
}
|
||||
if r.inputProvider.Count() > 1 && r.options.AutogenerateTests {
|
||||
return errors.New("autogenerate-tests flag can only be used with a single input")
|
||||
}
|
||||
|
||||
// TODO: remove below functions after v3 or update warning messages
|
||||
disk.PrintDeprecatedPathsMsgIfApplicable(r.options.Silent)
|
||||
templates.PrintDeprecatedProtocolNameMsgIfApplicable(r.options.Silent, r.options.Verbose)
|
||||
@@ -528,6 +548,17 @@ func (r *Runner) RunEnumeration() error {
|
||||
// display execution info like version , templates used etc
|
||||
r.displayExecutionInfo(store)
|
||||
|
||||
// If prompted, we need to run the tests for all the loaded
|
||||
// templates by first trying to detect test definitions and then
|
||||
// executing them with the relevant inputs.
|
||||
if r.options.RunNucleiTests {
|
||||
gologger.Info().Msgf("Running nuclei-templates mock-generated tests")
|
||||
if err := r.runNucleiTests(store, executorEngine); err != nil {
|
||||
return errors.Wrap(err, "could not run nuclei tests")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// prefetch secrets if enabled
|
||||
if executorOpts.AuthProvider != nil && r.options.PreFetchSecrets {
|
||||
gologger.Info().Msgf("Pre-fetching secrets from authprovider[s]")
|
||||
@@ -547,6 +578,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)
|
||||
@@ -556,10 +601,17 @@ func (r *Runner) RunEnumeration() error {
|
||||
return err
|
||||
}
|
||||
|
||||
var isInteractMatcher bool
|
||||
if r.interactsh != nil {
|
||||
matched := r.interactsh.Close()
|
||||
if matched {
|
||||
results.CompareAndSwap(false, true)
|
||||
isInteractMatcher = true
|
||||
}
|
||||
}
|
||||
if r.options.AutogenerateTests && proxyServer != nil {
|
||||
if err := r.handleTestAutogenerate(results, isInteractMatcher, store); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if executorOpts.InputHelper != nil {
|
||||
@@ -579,6 +631,108 @@ func (r *Runner) RunEnumeration() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Runner) runNucleiTests(store *loader.Store, engine *core.Engine) error {
|
||||
// For each loaded template, check to see if there's
|
||||
// a corresponding test file for that template with extension
|
||||
// .nuclei_test
|
||||
testFiles := make(map[string]string)
|
||||
templatePathToTemplates := make(map[string]*templates.Template)
|
||||
|
||||
for _, template := range store.Templates() {
|
||||
testFile := fmt.Sprintf("%s.nuclei_test", template.Path)
|
||||
if !fileutil.FileExists(testFile) {
|
||||
continue
|
||||
}
|
||||
testFiles[template.Path] = testFile
|
||||
templatePathToTemplates[template.Path] = template
|
||||
|
||||
gologger.Verbose().Msgf("Found test file for template %s: %s", template.ID, filepath.Base(testFile))
|
||||
}
|
||||
|
||||
if len(testFiles) == 0 {
|
||||
gologger.Info().Msgf("No test files found for nuclei-templates")
|
||||
return nil
|
||||
}
|
||||
|
||||
gologger.Info().Msgf("Loaded %d test files for nuclei-templates", len(testFiles))
|
||||
|
||||
for templatePath, testFile := range testFiles {
|
||||
testTemplate, err := testing.ReadNucleiTestTemplate(testFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not read nuclei test template")
|
||||
}
|
||||
|
||||
mockHandler, err := testTemplate.MockServer()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not create mock handler for template")
|
||||
}
|
||||
|
||||
testServer := httptest.NewServer(mockHandler)
|
||||
template, ok := templatePathToTemplates[templatePath]
|
||||
if !ok {
|
||||
testServer.Close()
|
||||
return errors.Errorf("no template found for path %s", templatePath)
|
||||
}
|
||||
|
||||
// If we have interactsh matcher, we need to handle it separately
|
||||
// by creating a new interactsh client and closing it after the test
|
||||
// waiting for callbacks.
|
||||
// TODO: Parallelize
|
||||
if testTemplate.IsInteractshMatcher {
|
||||
interactClient, err := interactsh.New(interactsh.DefaultOptions(r.output, r.issuesClient, r.progress))
|
||||
if err != nil {
|
||||
testServer.Close()
|
||||
return errors.Wrap(err, "could not create interactsh client")
|
||||
}
|
||||
template.Options.Interactsh = interactClient
|
||||
}
|
||||
|
||||
results := engine.ExecuteScanWithOpts([]*templates.Template{template}, provider.NewSimpleInputProviderWithUrls(testServer.URL), r.options.DisableClustering)
|
||||
if !results.Load() && !testTemplate.IsInteractshMatcher {
|
||||
testServer.Close()
|
||||
return errors.Errorf("no results found for template %s", template.ID)
|
||||
}
|
||||
|
||||
if testTemplate.IsInteractshMatcher {
|
||||
matched := template.Options.Interactsh.Close()
|
||||
if !matched {
|
||||
testServer.Close()
|
||||
return errors.Errorf("no results found for template %s", template.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
gologger.Info().Msgf("All tests passed successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) handleTestAutogenerate(results *atomic.Bool, isInteractMatcher bool, store *loader.Store) error {
|
||||
if !results.Load() {
|
||||
return errors.New("no results found to autogenerate tests")
|
||||
}
|
||||
|
||||
intercepted := proxyServer.Intercepted()
|
||||
if len(intercepted) == 0 {
|
||||
return errors.New("no intercepted requests")
|
||||
}
|
||||
gologger.Verbose().Msgf("Intercepted requests and responses (%d)", len(intercepted))
|
||||
for _, pair := range intercepted {
|
||||
if r.options.Debug {
|
||||
fmt.Printf("Pair: %+v\n", pair)
|
||||
}
|
||||
}
|
||||
var gotInput string
|
||||
r.inputProvider.Iterate(func(value *contextargs.MetaInput) bool {
|
||||
gotInput = value.Input
|
||||
return false
|
||||
})
|
||||
err := testing.GenerateTestsFromPair(intercepted, store.Templates()[0], gotInput, isInteractMatcher)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not generate tests from intercepted requests")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) isInputNonHTTP() bool {
|
||||
var nonURLInput bool
|
||||
r.inputProvider.Iterate(func(value *contextargs.MetaInput) bool {
|
||||
|
||||
+42
-8
@@ -2,6 +2,7 @@ package nuclei
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/projectdiscovery/goflags"
|
||||
@@ -115,17 +116,48 @@ 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
|
||||
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
|
||||
}
|
||||
if opts.ProbeConcurrency <= 0 {
|
||||
return errors.New("probe concurrency must be at least 1")
|
||||
} else {
|
||||
e.opts.ProbeConcurrency = opts.ProbeConcurrency
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -133,7 +165,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
|
||||
}
|
||||
}
|
||||
@@ -280,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 {
|
||||
|
||||
+9
-4
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
+9
-4
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
+11
-4
@@ -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
|
||||
@@ -53,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
|
||||
}
|
||||
@@ -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
|
||||
@@ -109,9 +108,11 @@ 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())
|
||||
|
||||
var wg *sizedwaitgroup.SizedWaitGroup
|
||||
templateType := template.Type()
|
||||
var wg *syncutil.AdaptiveWaitGroup
|
||||
if templateType == types.HeadlessProtocol {
|
||||
wg = wp.Headless
|
||||
} else {
|
||||
@@ -134,7 +135,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()
|
||||
|
||||
+13
-7
@@ -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,17 @@ func (e *Engine) executeTemplatesOnTarget(alltemplates []*templates.Template, ta
|
||||
wp := e.GetWorkPool()
|
||||
|
||||
for _, tpl := range alltemplates {
|
||||
var sg *sizedwaitgroup.SizedWaitGroup
|
||||
// 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
|
||||
} 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 +216,10 @@ func (e *ChildExecuter) Close() *atomic.Bool {
|
||||
func (e *ChildExecuter) Execute(template *templates.Template, value *contextargs.MetaInput) {
|
||||
templateType := template.Type()
|
||||
|
||||
var wg *sizedwaitgroup.SizedWaitGroup
|
||||
// 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
|
||||
} else {
|
||||
|
||||
@@ -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
|
||||
@@ -139,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)
|
||||
@@ -163,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()
|
||||
|
||||
+35
-16
@@ -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,39 @@ 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
+11
-3
@@ -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,10 +51,16 @@ 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))
|
||||
})
|
||||
sgResizeCheck = func() {
|
||||
// resize check point
|
||||
if pooljsc.Size != PoolingJsVmConcurrency {
|
||||
pooljsc.Resize(PoolingJsVmConcurrency)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
var gojapool = &sync.Pool{
|
||||
@@ -116,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -124,7 +124,7 @@ func Init(options *types.Options) error {
|
||||
opts.ResolversFile = true
|
||||
opts.EnableFallback = true
|
||||
}
|
||||
if options.ResolversFile != "" {
|
||||
if len(options.InternalResolversList) > 0 {
|
||||
opts.BaseResolvers = options.InternalResolversList
|
||||
}
|
||||
|
||||
|
||||
@@ -275,6 +275,8 @@ func questionTypeToInt(questionType string) uint16 {
|
||||
question = dns.TypeTLSA
|
||||
case "ANY":
|
||||
question = dns.TypeANY
|
||||
case "SRV":
|
||||
question = dns.TypeSRV
|
||||
}
|
||||
return question
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -34,7 +34,7 @@ func Init(options *types.Options) error {
|
||||
clientPool = make(map[string]*retryabledns.Client)
|
||||
|
||||
resolvers := defaultResolvers
|
||||
if options.ResolversFile != "" {
|
||||
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 != "" {
|
||||
if len(options.InternalResolversList) > 0 {
|
||||
resolvers = options.InternalResolversList
|
||||
} else if len(configuration.Resolvers) > 0 {
|
||||
resolvers = configuration.Resolvers
|
||||
|
||||
@@ -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{}
|
||||
@@ -62,9 +62,15 @@ 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 := sizedwaitgroup.New(request.Threads)
|
||||
swg, err := syncutil.New(syncutil.WithSize(request.Threads))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var multiErr error
|
||||
m := &sync.Mutex{}
|
||||
|
||||
@@ -73,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{}) {
|
||||
@@ -140,7 +152,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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
@@ -140,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 {
|
||||
|
||||
@@ -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 {
|
||||
@@ -222,7 +231,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 +375,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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -34,8 +34,8 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
errorutil "github.com/projectdiscovery/utils/errors"
|
||||
iputil "github.com/projectdiscovery/utils/ip"
|
||||
syncutil "github.com/projectdiscovery/utils/sync"
|
||||
urlutil "github.com/projectdiscovery/utils/url"
|
||||
"github.com/remeh/sizedwaitgroup"
|
||||
)
|
||||
|
||||
// Request is a request for the javascript protocol
|
||||
@@ -406,7 +406,11 @@ func (request *Request) executeRequestParallel(ctxParent context.Context, hostPo
|
||||
requestOptions := request.options
|
||||
gotmatches := &atomic.Bool{}
|
||||
|
||||
sg := sizedwaitgroup.New(threads)
|
||||
// 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 {
|
||||
iterator := request.generator.NewIterator()
|
||||
for {
|
||||
@@ -414,6 +418,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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
@@ -174,17 +174,29 @@ 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
|
||||
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()
|
||||
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{}) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+13
-12
@@ -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
|
||||
@@ -128,17 +121,25 @@ 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 {
|
||||
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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
|
||||
)
|
||||
|
||||
|
||||
type ScanContextOption func(*ScanContext)
|
||||
|
||||
func WithEvents() ScanContextOption {
|
||||
|
||||
@@ -1166,6 +1166,7 @@ func init() {
|
||||
"CAA",
|
||||
"TLSA",
|
||||
"ANY",
|
||||
"SRV",
|
||||
}
|
||||
|
||||
FILERequestDoc.Type = "file.Request"
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/projectdiscovery/freeport"
|
||||
"github.com/projectdiscovery/martian/v3"
|
||||
martianlog "github.com/projectdiscovery/martian/v3/log"
|
||||
"github.com/projectdiscovery/proxify"
|
||||
"github.com/projectdiscovery/proxify/pkg/certs"
|
||||
"github.com/projectdiscovery/proxify/pkg/logger/elastic"
|
||||
"github.com/projectdiscovery/proxify/pkg/logger/kafka"
|
||||
"github.com/projectdiscovery/proxify/pkg/types"
|
||||
httputils "github.com/projectdiscovery/utils/http"
|
||||
)
|
||||
|
||||
// ProxyServer is an intercepting proxy launched through nuclei
|
||||
// using proxify for logging requests and responses.
|
||||
//
|
||||
// This is intended for unit testing of http templates in nuclei.
|
||||
type ProxyServer struct {
|
||||
ListenAddr string
|
||||
|
||||
intercepted []RequestResponsePair
|
||||
mutex *sync.RWMutex
|
||||
|
||||
proxy *proxify.Proxy
|
||||
tempdir string
|
||||
}
|
||||
|
||||
// RequestResponsePair is a pair of request and response
|
||||
type RequestResponsePair struct {
|
||||
Request string `json:"request" yaml:"request"`
|
||||
Response string `json:"response" yaml:"response"`
|
||||
Protocol string `json:"protocol" yaml:"protocol"`
|
||||
}
|
||||
|
||||
func responsePairFromResp(resp *http.Response) RequestResponsePair {
|
||||
if resp.Request.Method == http.MethodConnect {
|
||||
return RequestResponsePair{}
|
||||
}
|
||||
|
||||
save := resp.Body
|
||||
savecl := resp.ContentLength
|
||||
|
||||
var err error
|
||||
if resp.Body != nil {
|
||||
save, resp.Body, err = drainBody(resp.Body)
|
||||
if err != nil {
|
||||
return RequestResponsePair{}
|
||||
}
|
||||
}
|
||||
chain := httputils.NewResponseChain(resp, -1)
|
||||
defer chain.Close()
|
||||
|
||||
if err := chain.Fill(); err != nil {
|
||||
log.Printf("[error] [proxy] Could not fill response chain: %s\n", err)
|
||||
return RequestResponsePair{}
|
||||
}
|
||||
resp.Body = save
|
||||
resp.ContentLength = savecl
|
||||
|
||||
if !chain.Has() {
|
||||
return RequestResponsePair{}
|
||||
}
|
||||
respDump := chain.FullResponse()
|
||||
|
||||
return RequestResponsePair{
|
||||
Response: respDump.String(),
|
||||
}
|
||||
}
|
||||
|
||||
// Intercepted returns the intercepted requests and responsesq
|
||||
func (p *ProxyServer) Intercepted() []RequestResponsePair {
|
||||
p.mutex.RLock()
|
||||
defer p.mutex.RUnlock()
|
||||
return p.intercepted
|
||||
}
|
||||
|
||||
// AddIntercepted adds a new request response pair to the intercepted list
|
||||
func (p *ProxyServer) AddIntercepted(pair RequestResponsePair) {
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
p.intercepted = append(p.intercepted, pair)
|
||||
}
|
||||
|
||||
// NewProxyServer creates a new proxy server instance
|
||||
func NewProxyServer() (*ProxyServer, error) {
|
||||
ps := &ProxyServer{
|
||||
mutex: &sync.RWMutex{},
|
||||
}
|
||||
|
||||
onRequestFunc := func(req *http.Request, ctx *martian.Context) error {
|
||||
dumped, err := httputil.DumpRequestOut(req, true)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
ctx.Set(ctx.ID(), RequestResponsePair{
|
||||
Request: string(dumped),
|
||||
})
|
||||
ctx.Set("user-data", types.UserData{})
|
||||
return nil
|
||||
}
|
||||
|
||||
onResponseFunc := func(resp *http.Response, ctx *martian.Context) error {
|
||||
pair := responsePairFromResp(resp)
|
||||
if pair.Response == "" {
|
||||
return nil
|
||||
}
|
||||
req, ok := ctx.Get(ctx.ID())
|
||||
if !ok {
|
||||
log.Printf("[error] [proxy] Could not get request from context\n")
|
||||
return nil
|
||||
}
|
||||
valid, ok := req.(RequestResponsePair)
|
||||
if !ok {
|
||||
log.Printf("[error] [proxy] Could not validate request from context\n")
|
||||
return nil
|
||||
}
|
||||
pair.Request = valid.Request
|
||||
pair.Protocol = "http"
|
||||
ps.AddIntercepted(pair)
|
||||
return nil
|
||||
}
|
||||
_, _ = onRequestFunc, onResponseFunc
|
||||
|
||||
tempdir, err := os.MkdirTemp("", "proxify-*")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ps.tempdir = tempdir
|
||||
|
||||
err = certs.LoadCerts(tempdir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostname := "127.0.0.1"
|
||||
port, err := freeport.GetFreePort(hostname, freeport.TCP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ps.ListenAddr = fmt.Sprintf("%s:%d", hostname, port.Port)
|
||||
|
||||
martianlog.SetLogger(&noopMartianLogger{})
|
||||
opts := &proxify.Options{
|
||||
OutputJsonl: true,
|
||||
MaxSize: math.MaxInt,
|
||||
Verbosity: types.VerbosityDefault,
|
||||
CertCacheSize: 256,
|
||||
Directory: tempdir,
|
||||
ListenAddrHTTP: ps.ListenAddr,
|
||||
OnRequestCallback: onRequestFunc,
|
||||
OnResponseCallback: onResponseFunc,
|
||||
UpstreamProxyRequestsNumber: 1,
|
||||
Elastic: &elastic.Options{},
|
||||
Kafka: &kafka.Options{},
|
||||
}
|
||||
proxy, err := proxify.NewProxy(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ps.proxy = proxy
|
||||
|
||||
go func() {
|
||||
err = proxy.Run()
|
||||
if err != nil {
|
||||
log.Printf("[error] [proxy] Could not run proxy: %s\n", err)
|
||||
}
|
||||
}()
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
type noopMartianLogger struct{}
|
||||
|
||||
func (l *noopMartianLogger) Infof(format string, args ...interface{}) {}
|
||||
func (l *noopMartianLogger) Debugf(format string, args ...interface{}) {}
|
||||
func (l *noopMartianLogger) Errorf(format string, args ...interface{}) {}
|
||||
|
||||
func (p *ProxyServer) Close() {
|
||||
_ = os.RemoveAll(p.tempdir)
|
||||
p.proxy.Stop()
|
||||
}
|
||||
|
||||
// from net/http/httputil.DumpResponse
|
||||
func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
|
||||
if b == nil || b == http.NoBody {
|
||||
// No copying needed. Preserve the magic sentinel meaning of NoBody.
|
||||
return http.NoBody, http.NoBody, nil
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if _, err = buf.ReadFrom(b); err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
if err = b.Close(); err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
return io.NopCloser(&buf), io.NopCloser(bytes.NewReader(buf.Bytes())), nil
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/gologger/levels"
|
||||
)
|
||||
|
||||
func Test_Proxy_Template(t *testing.T) {
|
||||
gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)
|
||||
|
||||
pairs, err := doIntercept()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Intercepted: %+v\n", len(pairs))
|
||||
|
||||
tmpl := &NucleiTestTemplate{
|
||||
Requests: pairs,
|
||||
TemplateID: "test-template",
|
||||
}
|
||||
handler, err := tmpl.MockServer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("valid_mock_server", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "http://scanme.sh/demo", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set("X-Custom-Header", "myvalue")
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
t.Run("invalid_mock_server", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "http://scanme.sh/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
resp := w.Result()
|
||||
if resp.StatusCode != 404 {
|
||||
t.Fatalf("expected 404, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func doIntercept() ([]RequestResponsePair, error) {
|
||||
ps, err := NewProxyServer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer ps.Close()
|
||||
|
||||
// Make a http request by using the proxy server
|
||||
// and check the intercepted requests and responses
|
||||
// to see if the request was intercepted and correctly
|
||||
// logged.
|
||||
req, err := http.NewRequest("GET", "http://scanme.sh/demo", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("X-Custom-Header", "myvalue")
|
||||
|
||||
proxyURL := "http://" + ps.ListenAddr
|
||||
parsed, err := url.Parse(proxyURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf("Proxy URL: %s\n", parsed.String())
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(parsed),
|
||||
},
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
_, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
intercepted := ps.Intercepted()
|
||||
if len(intercepted) == 0 {
|
||||
return nil, fmt.Errorf("no requests were intercepted")
|
||||
}
|
||||
return intercepted, nil
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
)
|
||||
|
||||
// NucleiTestTemplate is a template for testing nuclei templates
|
||||
type NucleiTestTemplate struct {
|
||||
Requests []RequestResponsePair `yaml:"requests"`
|
||||
TemplateID string `yaml:"template_id"`
|
||||
IsInteractshMatcher bool `yaml:"is_interactsh_matcher,omitempty"`
|
||||
}
|
||||
|
||||
type internalRouteDetails struct {
|
||||
Method string
|
||||
|
||||
ResponseStatus int
|
||||
ResponseHeaders map[string][]string
|
||||
ResponseBody string
|
||||
}
|
||||
|
||||
// MockServer creates a mock server from the test template
|
||||
func (n *NucleiTestTemplate) MockServer() (http.HandlerFunc, error) {
|
||||
requestPathToMethods := make(map[string][]internalRouteDetails)
|
||||
for _, reqResp := range n.Requests {
|
||||
parsed, err := types.ParseRawRequest(strings.TrimLeft(reqResp.Request, "\r\n"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not parse request")
|
||||
}
|
||||
|
||||
parsedResponse, err := http.ReadResponse(bufio.NewReader(strings.NewReader(strings.TrimLeft(reqResp.Response, "\r\n"))), nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not parse response")
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(parsedResponse.Body)
|
||||
if err != nil {
|
||||
_ = parsedResponse.Body.Close()
|
||||
return nil, err
|
||||
}
|
||||
_ = parsedResponse.Body.Close()
|
||||
|
||||
if _, ok := requestPathToMethods[parsed.URL.Path]; !ok {
|
||||
requestPathToMethods[parsed.URL.Path] = []internalRouteDetails{}
|
||||
}
|
||||
|
||||
requestPathToMethods[parsed.URL.Path] = append(requestPathToMethods[parsed.URL.Path], internalRouteDetails{
|
||||
Method: parsed.Request.Method,
|
||||
|
||||
ResponseStatus: parsedResponse.StatusCode,
|
||||
ResponseHeaders: parsedResponse.Header,
|
||||
ResponseBody: string(data),
|
||||
})
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
routeDetails, ok := requestPathToMethods[r.URL.Path]
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
for _, route := range routeDetails {
|
||||
if route.Method != r.Method {
|
||||
continue
|
||||
}
|
||||
|
||||
if n.IsInteractshMatcher {
|
||||
dumped, _ := httputil.DumpRequest(r, true)
|
||||
decoded, _ := url.QueryUnescape(string(dumped))
|
||||
|
||||
for _, url := range extractURLs(decoded) {
|
||||
go func(url string) {
|
||||
if err := doCallbackToInteractsh(url); err != nil {
|
||||
gologger.Warning().Msgf("[test-server] Could not send request to interactsh: %s", err)
|
||||
}
|
||||
}(url)
|
||||
}
|
||||
}
|
||||
for k, v := range route.ResponseHeaders {
|
||||
// Ignore certain headers
|
||||
if k == "Content-Encoding" {
|
||||
continue
|
||||
}
|
||||
w.Header()[k] = v
|
||||
}
|
||||
|
||||
w.WriteHeader(route.ResponseStatus)
|
||||
_, _ = w.Write([]byte(route.ResponseBody))
|
||||
return
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
// ReadNucleiTestTemplate reads a nuclei test template from a file
|
||||
func ReadNucleiTestTemplate(path string) (*NucleiTestTemplate, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var testTemplate NucleiTestTemplate
|
||||
dec := yaml.NewDecoder(file)
|
||||
if err := dec.Decode(&testTemplate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &testTemplate, nil
|
||||
}
|
||||
|
||||
// GenerateTestsFromPair generates a test from a pair of request and response
|
||||
// and a template object
|
||||
func GenerateTestsFromPair(pair []RequestResponsePair, template *templates.Template, target string, isInteractshMatcher bool) error {
|
||||
host := target
|
||||
|
||||
if strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") {
|
||||
parsed, err := url.Parse(target)
|
||||
if err == nil {
|
||||
host = parsed.Hostname()
|
||||
}
|
||||
}
|
||||
if strings.Contains(host, ":") {
|
||||
host = strings.Split(host, ":")[0]
|
||||
}
|
||||
|
||||
testTemplate := NucleiTestTemplate{
|
||||
TemplateID: template.ID,
|
||||
IsInteractshMatcher: isInteractshMatcher,
|
||||
}
|
||||
for _, reqResp := range pair {
|
||||
testTemplate.Requests = append(testTemplate.Requests, RequestResponsePair{
|
||||
Request: redactUserInput(host, reqResp.Request),
|
||||
Response: redactUserInput(host, reqResp.Response),
|
||||
Protocol: reqResp.Protocol,
|
||||
})
|
||||
}
|
||||
|
||||
baseDirectory := filepath.Dir(template.Path)
|
||||
filename := filepath.Base(template.Path)
|
||||
|
||||
newFile := fmt.Sprintf("%s.nuclei_test", filename)
|
||||
gologger.Info().Msgf("Writing test template=%s and target=%s to %s", template.ID, target, newFile)
|
||||
newFilename := filepath.Join(baseDirectory, newFile)
|
||||
|
||||
file, err := os.Create(newFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
enc := yaml.NewEncoder(file, yaml.UseLiteralStyleIfMultiline(true), yaml.UseSingleQuote(true))
|
||||
if err := enc.Encode(testTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
defer enc.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
urlsRegex = regexp.MustCompile(`https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)`)
|
||||
ipRegex = regexp.MustCompile(`((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}`)
|
||||
)
|
||||
|
||||
const redactedTestDomain = "redacted.nuclei-test.domain"
|
||||
|
||||
func redactUserInput(input string, value string) string {
|
||||
urlMatches := urlsRegex.FindAllString(value, -1)
|
||||
for _, match := range urlMatches {
|
||||
parsed, err := url.Parse(match)
|
||||
if err == nil {
|
||||
parsed.Host = redactedTestDomain
|
||||
value = strings.ReplaceAll(value, match, parsed.String())
|
||||
}
|
||||
}
|
||||
ips := ipRegex.FindAllString(value, -1)
|
||||
for _, ip := range ips {
|
||||
value = strings.ReplaceAll(value, ip, "127.0.0.1")
|
||||
}
|
||||
return strings.ReplaceAll(value, input, redactedTestDomain)
|
||||
}
|
||||
|
||||
func extractURLs(text string) []string {
|
||||
matches := domainRegex.FindAllString(text, -1)
|
||||
|
||||
for i, match := range matches {
|
||||
if !strings.HasPrefix(match, "http://") && !strings.HasPrefix(match, "https://") {
|
||||
matches[i] = "http://" + match
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
var domainRegex = regexp.MustCompile(`(?:https?:\/\/)?[A-Za-z0-9-_\.]+oast\.(?:pro|today|live|site|online|fun|me)`)
|
||||
|
||||
func doCallbackToInteractsh(URL string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", URL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
_, err = io.ReadAll(resp.Body)
|
||||
return err
|
||||
}
|
||||
@@ -54,6 +54,7 @@ var DefaultOptions = &types.Options{
|
||||
Timeout: 5,
|
||||
Retries: 1,
|
||||
RateLimit: 150,
|
||||
RateLimitDuration: time.Second,
|
||||
ProjectPath: "",
|
||||
Severities: severity.Severities{},
|
||||
Targets: []string{},
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
+25
-26
@@ -1,6 +1,7 @@
|
||||
package flow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
@@ -12,33 +13,10 @@ 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 jsPool = &jsWaitGroup{}
|
||||
|
||||
// GetJSRuntime returns a new JS runtime from pool
|
||||
func GetJSRuntime(opts *types.Options) *goja.Runtime {
|
||||
jsPool.Do(func() {
|
||||
if opts.JsConcurrency < 100 {
|
||||
opts.JsConcurrency = 100
|
||||
}
|
||||
jsPool.sg = sizedwaitgroup.New(opts.JsConcurrency)
|
||||
})
|
||||
jsPool.sg.Add()
|
||||
return gojapool.Get().(*goja.Runtime)
|
||||
}
|
||||
|
||||
// PutJSRuntime returns a JS runtime to pool
|
||||
func PutJSRuntime(runtime *goja.Runtime) {
|
||||
defer jsPool.sg.Done()
|
||||
gojapool.Put(runtime)
|
||||
}
|
||||
var jsOnce sync.Once
|
||||
|
||||
// js runtime pool using sync.Pool
|
||||
var gojapool = &sync.Pool{
|
||||
@@ -49,8 +27,29 @@ var gojapool = &sync.Pool{
|
||||
},
|
||||
}
|
||||
|
||||
func registerBuiltins(runtime *goja.Runtime) {
|
||||
var sizedgojapool *sizedpool.SizedPool[*goja.Runtime]
|
||||
|
||||
// GetJSRuntime returns a new JS runtime from pool
|
||||
func GetJSRuntime(opts *types.Options) *goja.Runtime {
|
||||
jsOnce.Do(func() {
|
||||
if opts.JsConcurrency < 100 {
|
||||
opts.JsConcurrency = 100
|
||||
}
|
||||
sizedgojapool, _ = sizedpool.New[*goja.Runtime](
|
||||
sizedpool.WithPool[*goja.Runtime](gojapool),
|
||||
sizedpool.WithSize[*goja.Runtime](int64(opts.JsConcurrency)),
|
||||
)
|
||||
})
|
||||
runtime, _ := sizedgojapool.Get(context.TODO())
|
||||
return runtime
|
||||
}
|
||||
|
||||
// PutJSRuntime returns a JS runtime to pool
|
||||
func PutJSRuntime(runtime *goja.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)",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+16
-2
@@ -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.
|
||||
@@ -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
|
||||
@@ -258,6 +261,12 @@ type Options struct {
|
||||
AllowLocalFileAccess bool
|
||||
// RestrictLocalNetworkAccess restricts local network access from templates requests
|
||||
RestrictLocalNetworkAccess bool
|
||||
// AutogenerateTests enables autogeneration of tests for templates (currently http)
|
||||
// by using a type of proxy and recording requests and responses, generating test
|
||||
// cases from them.
|
||||
AutogenerateTests bool
|
||||
// RunNucleiTests enables running nuclei tests
|
||||
RunNucleiTests bool
|
||||
// ShowMatchLine enables display of match line number
|
||||
ShowMatchLine bool
|
||||
// EnablePprof enables exposing pprof runtime information with a webserver.
|
||||
@@ -382,6 +391,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
|
||||
}
|
||||
@@ -410,10 +421,13 @@ func (options *Options) HasClientCertificates() bool {
|
||||
func DefaultOptions() *Options {
|
||||
return &Options{
|
||||
RateLimit: 150,
|
||||
RateLimitDuration: time.Second,
|
||||
BulkSize: 25,
|
||||
TemplateThreads: 25,
|
||||
HeadlessBulkSize: 10,
|
||||
PayloadConcurrency: 25,
|
||||
HeadlessTemplateThreads: 10,
|
||||
ProbeConcurrency: 50,
|
||||
Timeout: 5,
|
||||
Retries: 1,
|
||||
MaxHostError: 30,
|
||||
|
||||
Reference in new issue
Block a user