Compare commits

...
Author SHA1 Message Date
sandeep 1571002607 go mod tidy 2024-04-17 17:57:40 +05:30
Sandeep Singh 59b7caa941 Merge branch 'dev' into add-template-proxy-tests 2024-04-17 17:52:47 +05:30
4shen0ne e432e4a4c2 Fix typos (#5038)
* Fix typos

- Add a quantifier to make the description unambiguous
- Add a missing verb

* Fix href of FAQs
2024-04-16 21:42:44 +05:30
lvyaoting 84582183dc chore: fix function names in comment (#5008)
Signed-off-by: lvyaoting <[email protected]>
2024-04-16 16:57:51 +05:30
Tarun Koyalwar ea2e13a4aa nuclei 'stats' build : scan events + chart utils (#5032)
* prototype new scan events

* scan-event: improvements + conditional build

* add scan charts server: make scan-charts

* scan-charts: bug fix
2024-04-16 16:57:32 +05:30
Levente Kováts bec7cb273a add context vars in code and multi (#5051)
make the extracted variables available in subsequence templates when executing in a workflow

fix projectdiscovery/nuclei#4797
2024-04-16 16:57:07 +05:30
guangwu 431d3fa2d9 fix: close res body (#5025) 2024-04-16 14:53:49 +05:30
Mzack9999 d576db3893 Merge pull request #5035 from projectdiscovery/bugfix-4947-internal-resolvers
Fixing internal resolver override
2024-04-15 17:02:09 +01:00
GitHub Action f2c0b4b443 Auto Generate Syntax Docs + JSONSchema [Mon Apr 15 14:44:52 UTC 2024] 🤖 2024-04-15 14:44:52 +00:00
Mzack9999 998a25d744 Merge pull request #5034 from projectdiscovery/feat-4948-dns-srv
adding dns srv type
2024-04-15 15:43:44 +01:00
dependabot[bot] 373a598e93 Merge pull request #5041 from projectdiscovery/dependabot/go_modules/dev/github.com/projectdiscovery/dsl-0.0.52 2024-04-15 06:38:50 +00:00
dependabot[bot] 17f0ca2576 Merge pull request #5040 from projectdiscovery/dependabot/go_modules/dev/github.com/projectdiscovery/rawhttp-0.1.45 2024-04-15 06:26:10 +00:00
dependabot[bot] f876e3290b chore(deps): bump github.com/projectdiscovery/dsl from 0.0.51 to 0.0.52
Bumps [github.com/projectdiscovery/dsl](https://github.com/projectdiscovery/dsl) from 0.0.51 to 0.0.52.
- [Release notes](https://github.com/projectdiscovery/dsl/releases)
- [Commits](https://github.com/projectdiscovery/dsl/compare/v0.0.51...v0.0.52)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/dsl
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-04-15 06:09:00 +00:00
dependabot[bot] 480968fd78 Merge pull request #5042 from projectdiscovery/dependabot/go_modules/dev/github.com/projectdiscovery/goflags-0.1.48 2024-04-15 06:06:56 +00:00
dependabot[bot] 3b51a77bc7 chore(deps): bump github.com/projectdiscovery/rawhttp
Bumps [github.com/projectdiscovery/rawhttp](https://github.com/projectdiscovery/rawhttp) from 0.1.44 to 0.1.45.
- [Release notes](https://github.com/projectdiscovery/rawhttp/releases)
- [Commits](https://github.com/projectdiscovery/rawhttp/compare/v0.1.44...v0.1.45)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/rawhttp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-04-15 06:00:23 +00:00
dependabot[bot] 88ee422256 Merge pull request #5044 from projectdiscovery/dependabot/go_modules/dev/github.com/projectdiscovery/fastdialer-0.0.67 2024-04-15 05:57:32 +00:00
dependabot[bot] c8824ac71a chore(deps): bump github.com/projectdiscovery/fastdialer
Bumps [github.com/projectdiscovery/fastdialer](https://github.com/projectdiscovery/fastdialer) from 0.0.66 to 0.0.67.
- [Release notes](https://github.com/projectdiscovery/fastdialer/releases)
- [Commits](https://github.com/projectdiscovery/fastdialer/compare/v0.0.66...v0.0.67)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/fastdialer
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-04-15 05:35:54 +00:00
dependabot[bot] c145adb3bf chore(deps): bump github.com/projectdiscovery/goflags
Bumps [github.com/projectdiscovery/goflags](https://github.com/projectdiscovery/goflags) from 0.1.46 to 0.1.48.
- [Release notes](https://github.com/projectdiscovery/goflags/releases)
- [Commits](https://github.com/projectdiscovery/goflags/compare/v0.1.46...v0.1.48)

---
updated-dependencies:
- dependency-name: github.com/projectdiscovery/goflags
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-04-15 05:35:43 +00:00
Levente Kováts 0d5e26d7bd run workflow subtemplates with new ScanContext (#5031)
fix projectdiscovery/nuclei#4933
2024-04-12 04:20:11 +05:30
mzack 38e185c410 simpler logic 2024-04-12 00:32:06 +02:00
mzack 8f084eedd6 adding more tests 2024-04-12 00:22:11 +02:00
mzack 0807113e6c adding more query types test 2024-04-12 00:02:43 +02:00
Mzack9999 4d0204f89a Merge pull request #5036 from projectdiscovery/bugfix-networkpolicy-httpx
Adding networkpolicy to httpx probes
2024-04-11 21:18:10 +01:00
mzack f4394b8e11 Adding networkpolicy to httpx probes 2024-04-11 19:34:03 +02:00
mzack 7b71886309 Fixing internal resolver override 2024-04-11 19:10:31 +02:00
mzack 2ed33e4723 adding dns srv type 2024-04-11 18:57:50 +02:00
Mzack9999 721ddda915 Merge pull request #4986 from projectdiscovery/feat-3072-init-adaptive-speed
Initial Refactor for speed control
2024-04-10 00:49:33 +01:00
mzack 5fc08cec48 updating example 2024-04-09 18:51:25 +02:00
mzack 6eeb98c71b removing printf 2024-04-09 18:34:04 +02:00
mzack 70eb494a01 warning 2024-04-09 18:32:40 +02:00
mzack 582a85d9c0 mimic follow behavior 2024-04-09 18:31:22 +02:00
mzack 7e363984b2 Merge branch 'dev' into feat-3072-init-adaptive-speed 2024-04-09 15:19:51 +02:00
Ice3man 79bbd51eac Merge branch 'dev' of https://github.com/projectdiscovery/nuclei into add-template-proxy-tests 2024-04-08 23:21:10 +05:30
Ice3man 2875fb06b6 feat: misc changes 2024-04-08 22:20:24 +05:30
Ice3man 4bb48d9949 feat: added interactsh support + misc 2024-04-08 22:18:25 +05:30
Ice3man c8394ce1fb feat: working test-generation + execution implementation 2024-04-08 17:40:26 +05:30
Ice3man 3ba11aaf74 feat: added auto proxy based test generation for templates 2024-04-07 23:38:17 +05:30
Mzack9999 f886fb2cf4 less templates in test 2024-04-05 16:40:37 +02:00
Mzack9999 d0a0c6d0c3 internal sync fix + speed up 2024-04-05 15:45:21 +02:00
mzack aa06c9ef17 speed up 2024-04-04 21:21:06 +02:00
mzack c4aa7dc1a2 f tag 2024-04-04 20:43:00 +02:00
mzack 96d7d02701 adding speed example 2024-04-04 20:22:28 +02:00
mzack 99a018d12a adding speed change example 2024-04-04 20:19:46 +02:00
mzack af7450737a making payload concurrency dynamic via direct int change 2024-04-03 23:06:08 +02:00
Mzack9999 a140a4194e boh - placing resize in wrapped method 2024-04-03 19:40:09 +02:00
Mzack9999 620287f76b deprecating rlm 2024-04-03 19:28:39 +02:00
Mzack9999 3c62b56fd9 panic at the pool 2024-04-03 19:02:30 +02:00
Mzack9999 774db61655 lightweight adaptivity on workpool 2024-04-03 18:50:46 +02:00
Mzack9999 a8d1393e96 init- using resizable components 2024-04-03 17:50:57 +02:00
84 changed files with 2311 additions and 206 deletions

No files matched your search

+4
View File
@@ -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/
+3
View File
@@ -36,5 +36,8 @@ pkg/protocols/headless/engine/.cache
/fuzzplayground
integration_tests/fuzzplayground
/dsl.md
/nuclei-stats
/nuclei-stats-*
/scan-charts
+9
View File
@@ -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"
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+2
View File
@@ -2750,6 +2750,8 @@ Enum Values:
- <code>TLSA</code>
- <code>ANY</code>
- <code>SRV</code>
</div>
<hr />
+6 -1
View File
@@ -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{}},
+96
View File
@@ -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
View File
@@ -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)
}
+18
View File
@@ -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"
+40
View File
@@ -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
View File
@@ -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 {
+5 -2
View File
@@ -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()
+104
View File
@@ -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")
}
}
+30 -10
View File
@@ -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
+62 -17
View File
@@ -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"
+17
View File
@@ -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"
+18
View File
@@ -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"
+18
View File
@@ -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"
+18
View File
@@ -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
+16
View File
@@ -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
+19 -9
View File
@@ -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
}
+16
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+8
View File
@@ -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
View File
@@ -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)
}
}
+2 -1
View File
@@ -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
View File
@@ -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
}
+6 -5
View File
@@ -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
View File
@@ -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 {
+10 -7
View File
@@ -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
View File
@@ -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)
}
}
+3 -3
View File
@@ -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
View File
@@ -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)
+2
View File
@@ -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 {
+1 -1
View File
@@ -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
}
+2
View File
@@ -275,6 +275,8 @@ func questionTypeToInt(questionType string) uint16 {
question = dns.TypeTLSA
case "ANY":
question = dns.TypeANY
case "SRV":
question = dns.TypeSRV
}
return question
}
+3
View File
@@ -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
+15 -3
View File
@@ -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)
+6 -3
View File
@@ -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()
+14 -14
View File
@@ -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"
+16 -3
View File
@@ -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 {
+11 -2
View File
@@ -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)
+1 -1
View File
@@ -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,
+12 -2
View File
@@ -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
+14 -2
View File
@@ -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{}) {
+7 -4
View File
@@ -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
View File
@@ -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)
+2 -1
View File
@@ -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))
+87
View File
@@ -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))
}
+351
View File
@@ -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
}
+11
View File
@@ -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) {
}
+80
View File
@@ -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)
}
+45
View File
@@ -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"`
}
+1
View File
@@ -10,6 +10,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
)
type ScanContextOption func(*ScanContext)
func WithEvents() ScanContextOption {
+1
View File
@@ -1166,6 +1166,7 @@ func init() {
"CAA",
"TLSA",
"ANY",
"SRV",
}
FILERequestDoc.Type = "file.Request"
+208
View File
@@ -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
}
+107
View File
@@ -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
}
+229
View File
@@ -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
}
+1
View File
@@ -54,6 +54,7 @@ var DefaultOptions = &types.Options{
Timeout: 5,
Retries: 1,
RateLimit: 150,
RateLimitDuration: time.Second,
ProjectPath: "",
Severities: severity.Severities{},
Targets: []string{},
+41
View File
@@ -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
View File
@@ -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)",
+6
View File
@@ -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
View File
@@ -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,