Merge pull request #3239 from projectdiscovery/dev

nuclei v2.8.8
dev
Sandeep Singh 2023-01-24 23:31:28 +05:30 committed by GitHub
commit 8b578ba429
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 914 additions and 733 deletions

View File

@ -1,8 +1,8 @@
FROM golang:1.19.4-alpine as build-env
FROM golang:1.19.5-alpine as build-env
RUN apk add build-base
RUN go install -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei@latest
FROM alpine:3.17.0
FROM alpine:3.17.1
RUN apk add --no-cache bind-tools ca-certificates chromium
COPY --from=build-env /go/bin/nuclei /usr/local/bin/nuclei
ENTRYPOINT ["nuclei"]

View File

@ -21,7 +21,7 @@
<a href="#how-it-works">How</a>
<a href="#install-nuclei">Install</a>
<a href="#for-security-engineers">For Security Engineers</a>
<a href="#for-developers-and-organisations">For Developers</a>
<a href="#for-developers-and-organizations">For Developers</a>
<a href="https://nuclei.projectdiscovery.io/nuclei/get-started/">Documentation</a>
<a href="#credits">Credits</a>
<a href="https://nuclei.projectdiscovery.io/faq/nuclei/">FAQs</a>
@ -221,6 +221,7 @@ OPTIMIZATIONS:
-retries int number of times to retry a failed request (default 1)
-ldp, -leave-default-ports leave default HTTP/HTTPS ports (eg. host:80,host:443)
-mhe, -max-host-error int max errors for a host before skipping from scan (default 30)
-nmhe, -no-mhe disable skipping host from scan based on errors
-project use a project folder to avoid sending same request multiple times
-project-path string set a specific project path
-spm, -stop-at-first-match stop processing HTTP requests after the first match (may break template/workflow logic)
@ -296,7 +297,7 @@ http://uat.example.com
# For Security Engineers
Nuclei offers great number of features that are helpful for security engineers to customise workflow in their organisation. With the varieties of scan capabilities (like DNS, HTTP, TCP), security engineers can easily create their suite of custom checks with Nuclei.
Nuclei offers great number of features that are helpful for security engineers to customise workflow in their organization. With the varieties of scan capabilities (like DNS, HTTP, TCP), security engineers can easily create their suite of custom checks with Nuclei.
- Varieties of protocols supported: TCP, DNS, HTTP, File, etc
- Achieve complex vulnerability steps with workflows and [dynamic requests.](https://blog.projectdiscovery.io/nuclei-unleashed-quickly-write-complex-exploits/)
@ -343,14 +344,14 @@ Pen-testers get the full power of our public templates and customization capabil
</table>
# For Developers and Organisations
# For Developers and Organizations
Nuclei is built with simplicity in mind, with the community backed templates by hundreds of security researchers, it allows you to stay updated with the latest security threats using continuous Nuclei scanning on the hosts. It is designed to be easily integrated into regression tests cycle, to verify the fixes and eliminate vulnerabilities from occurring in the future.
- **CI/CD:** Engineers are already utilising Nuclei within their CI/CD pipeline, it allows them to constantly monitor their staging and production environments with customised templates.
- **Continuous Regression Cycle:** With Nuclei, you can create your custom template on every new identified vulnerability and put into Nuclei engine to eliminate in the continuous regression cycle.
We have [a discussion thread around this](https://github.com/projectdiscovery/nuclei-templates/discussions/693), there are already some bug bounty programs giving incentives to hackers on writing nuclei templates with every submission, that helps them to eliminate the vulnerability across all their assets, as well as to eliminate future risk in reappearing on productions. If you're interested in implementing it in your organisation, feel free to [reach out to us](mailto:contact@projectdiscovery.io). We will be more than happy to help you in the getting started process, or you can also post into the [discussion thread for any help](https://github.com/projectdiscovery/nuclei-templates/discussions/693).
We have [a discussion thread around this](https://github.com/projectdiscovery/nuclei-templates/discussions/693), there are already some bug bounty programs giving incentives to hackers on writing nuclei templates with every submission, that helps them to eliminate the vulnerability across all their assets, as well as to eliminate future risk in reappearing on productions. If you're interested in implementing it in your organization, feel free to [reach out to us](mailto:contact@projectdiscovery.io). We will be more than happy to help you in the getting started process, or you can also post into the [discussion thread for any help](https://github.com/projectdiscovery/nuclei-templates/discussions/693).
<h3 align="center">
<img src="static/regression-with-nuclei.jpg" alt="regression-cycle-with-nuclei" width="1100px"></a>

View File

@ -189,6 +189,7 @@ Nuclei是一款注重于可配置性、可扩展性和易用性的基于模板
-retries int 重试次数默认1
-ldp, -leave-default-ports 指定HTTP/HTTPS默认端口例如host:80host:443
-mhe, -max-host-error int 某主机扫描失败次数跳过该主机默认30
-nmhe, -no-mhe disable skipping host from scan based on errors
-project 使用项目文件夹避免多次发送同一请求
-project-path string 设置特定的项目文件夹
-spm, -stop-at-first-path 得到一个结果后停止(或许会中断模板和工作流的逻辑)

View File

@ -188,6 +188,7 @@ OPTIMIZATIONS:
-retries int number of times to retry a failed request (default 1)
-ldp, -leave-default-ports leave default HTTP/HTTPS ports (eg. host:80,host:443
-mhe, -max-host-error int max errors for a host before skipping from scan (default 30)
-nmhe, -no-mhe disable skipping host from scan based on errors
-project use a project folder to avoid sending same request multiple times
-project-path string set a specific project path
-spm, -stop-at-first-path stop processing HTTP requests after the first match (may break template/workflow logic)

View File

@ -178,6 +178,7 @@ OPTIMIZATIONS:
-retries int 실패한 요청을 재시도하는 횟수 (기본 1)
-ldp, -leave-default-ports leave default HTTP/HTTPS ports (eg. host:80,host:443
-mhe, -max-host-error int 스캔을 건너뛰기 전에 호스트에 대한 최대 오류 수 (기본 30)
-nmhe, -no-mhe disable skipping host from scan based on errors
-project 프로젝트 폴더를 사용하여 동일한 요청을 여러 번 보내지 않음
-project-path string 특정 프로젝트 경로 설정
-spm, -stop-at-first-match 첫 번째 일치 후 HTTP 요청 처리 중지 (template/workflow 로직이 중단될 수 있음)

18
integration_tests/debug.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
echo "::group::Build nuclei"
rm nuclei 2>/dev/null
cd ../v2/cmd/nuclei
go build .
mv nuclei ../../../integration_tests/nuclei
echo "::endgroup::"
cd ../../../integration_tests
pwd
./integration-test -protocol $1 -template $2
if [ $? -eq 0 ]
then
exit 0
else
exit 1
fi

View File

@ -1,6 +1,7 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
@ -35,9 +36,23 @@ var (
"offlineHttp": offlineHttpTestcases,
"customConfigDir": customConfigDirTestCases,
}
// For debug purposes
runProtocol = ""
runTemplate = ""
)
func main() {
flag.StringVar(&runProtocol, "protocol", "", "run integration tests of given protocol")
flag.StringVar(&runTemplate, "template", "", "run integration test of given template")
flag.Parse()
if runProtocol != "" {
debug = true
debugTests()
os.Exit(1)
}
failedTestTemplatePaths := runTests(toMap(toSlice(customTests)))
if len(failedTestTemplatePaths) > 0 {
@ -52,6 +67,17 @@ func main() {
}
}
func debugTests() {
for tpath, testcase := range protocolTests[runProtocol] {
if runTemplate != "" && !strings.Contains(tpath, runTemplate) {
continue
}
if err := testcase.Execute(tpath); err != nil {
panic(err)
}
}
}
func runTests(customTemplatePaths map[string]struct{}) map[string]struct{} {
failedTestTemplatePaths := map[string]struct{}{}

View File

@ -24,6 +24,7 @@ import (
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
"github.com/projectdiscovery/nuclei/v2/pkg/types"
"github.com/projectdiscovery/nuclei/v2/pkg/utils/monitor"
errorutil "github.com/projectdiscovery/utils/errors"
fileutil "github.com/projectdiscovery/utils/file"
)
@ -243,6 +244,7 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.IntVar(&options.Retries, "retries", 1, "number of times to retry a failed request"),
flagSet.BoolVarP(&options.LeaveDefaultPorts, "leave-default-ports", "ldp", false, "leave default HTTP/HTTPS ports (eg. host:80,host:443)"),
flagSet.IntVarP(&options.MaxHostError, "max-host-error", "mhe", 30, "max errors for a host before skipping from scan"),
flagSet.BoolVarP(&options.NoHostErrors, "no-mhe", "nmhe", false, "disable skipping host from scan based on errors"),
flagSet.BoolVar(&options.Project, "project", false, "use a project folder to avoid sending same request multiple times"),
flagSet.StringVar(&options.ProjectPath, "project-path", os.TempDir(), "set a specific project path"),
flagSet.BoolVarP(&options.StopAtFirstMatch, "stop-at-first-match", "spm", false, "stop processing HTTP requests after the first match (may break template/workflow logic)"),
@ -378,3 +380,10 @@ func cleanupOldResumeFiles() {
}
_ = fileutil.DeleteFilesOlderThan(root, filter)
}
func init() {
// print stacktrace of errors in debug mode
if os.Getenv("DEBUG") != "" {
errorutil.ShowStackTrace = true
}
}

View File

@ -6,7 +6,7 @@ require (
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible
github.com/alecthomas/jsonschema v0.0.0-20211022214203-8b29eab41725
github.com/andygrunwald/go-jira v1.16.0
github.com/antchfx/htmlquery v1.2.5
github.com/antchfx/htmlquery v1.2.6
github.com/apex/log v1.9.0
github.com/blang/semver v3.5.1+incompatible
github.com/bluele/gcache v0.0.2
@ -23,13 +23,13 @@ require (
github.com/miekg/dns v1.1.50
github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/errors v0.9.1
github.com/projectdiscovery/clistats v0.0.9
github.com/projectdiscovery/fastdialer v0.0.19
github.com/projectdiscovery/clistats v0.0.11
github.com/projectdiscovery/fastdialer v0.0.21
github.com/projectdiscovery/hmap v0.0.6
github.com/projectdiscovery/interactsh v1.0.6-0.20220827132222-460cc6270053
github.com/projectdiscovery/rawhttp v0.1.4
github.com/projectdiscovery/retryabledns v1.0.17
github.com/projectdiscovery/retryablehttp-go v1.0.7
github.com/projectdiscovery/rawhttp v0.1.7
github.com/projectdiscovery/retryabledns v1.0.20
github.com/projectdiscovery/retryablehttp-go v1.0.10-0.20230123170312-75b58f90739a
github.com/projectdiscovery/stringsutil v0.0.2
github.com/projectdiscovery/yamldoc-go v1.0.3-0.20211126104922-00d2c6bb43b6
github.com/remeh/sizedwaitgroup v1.0.0
@ -42,8 +42,7 @@ require (
github.com/tj/go-update v2.2.5-0.20200519121640-62b4b798fd68+incompatible
github.com/valyala/fasttemplate v1.2.2
github.com/weppos/publicsuffix-go v0.15.1-0.20220724114530-e087fba66a37
github.com/xanzy/go-gitlab v0.77.0
go.uber.org/atomic v1.10.0
github.com/xanzy/go-gitlab v0.78.0
go.uber.org/multierr v1.9.0
golang.org/x/net v0.5.0
golang.org/x/oauth2 v0.4.0
@ -54,11 +53,11 @@ require (
require (
github.com/DataDog/gostackparse v0.6.0
github.com/antchfx/xmlquery v1.3.13
github.com/antchfx/xmlquery v1.3.14
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/aws/aws-sdk-go-v2 v1.17.3
github.com/aws/aws-sdk-go-v2/config v1.18.7
github.com/aws/aws-sdk-go-v2/credentials v1.13.7
github.com/aws/aws-sdk-go-v2/config v1.18.8
github.com/aws/aws-sdk-go-v2/credentials v1.13.8
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.46
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.6
github.com/docker/go-units v0.5.0
@ -80,8 +79,8 @@ require (
github.com/projectdiscovery/sarif v0.0.1
github.com/projectdiscovery/tlsx v1.0.1
github.com/projectdiscovery/uncover v1.0.2
github.com/projectdiscovery/utils v0.0.4-0.20230104145529-50cace956b0a
github.com/projectdiscovery/wappalyzergo v0.0.77
github.com/projectdiscovery/utils v0.0.6-0.20230123093357-4dd69195db7e
github.com/projectdiscovery/wappalyzergo v0.0.79
github.com/stretchr/testify v1.8.1
gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/yaml.v3 v3.0.1
@ -106,7 +105,7 @@ require (
github.com/nxadm/tail v1.4.8 // indirect
github.com/pjbgf/sha1cd v0.2.3 // indirect
github.com/projectdiscovery/asnmap v0.0.1 // indirect
github.com/projectdiscovery/sliceutil v0.0.1 // indirect
github.com/projectdiscovery/freeport v0.0.4 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.39.0 // indirect
@ -123,6 +122,7 @@ require (
github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4 // indirect
go.uber.org/atomic v1.10.0 // indirect
gopkg.in/djherbis/times.v1 v1.3.0 // indirect
)
@ -136,7 +136,7 @@ require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/andybalholm/cascadia v1.1.0 // indirect
github.com/antchfx/xpath v1.2.1 // indirect
github.com/antchfx/xpath v1.2.2 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/c4milo/unpackit v0.1.0 // indirect
github.com/caddyserver/certmagic v0.16.3 // indirect
@ -157,7 +157,7 @@ require (
github.com/gobwas/pool v0.2.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-querystring v1.1.0 // indirect
@ -191,7 +191,7 @@ require (
github.com/projectdiscovery/networkpolicy v0.0.3
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/trivago/tgo v1.0.7 // indirect
@ -212,7 +212,7 @@ require (
golang.org/x/exp v0.0.0-20221230185412-738e83a70c30
golang.org/x/mod v0.6.0 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/time v0.2.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
@ -230,9 +230,9 @@ require (
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.0 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect

View File

@ -50,12 +50,12 @@ github.com/andygrunwald/go-jira v1.16.0/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIi
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antchfx/htmlquery v1.2.5 h1:1lXnx46/1wtv1E/kzmH8vrfMuUKYgkdDBA9pIdMJnk4=
github.com/antchfx/htmlquery v1.2.5/go.mod h1:2MCVBzYVafPBmKbrmwB9F5xdd+IEgRY61ci2oOsOQVw=
github.com/antchfx/xmlquery v1.3.13 h1:wqhTv2BN5MzYg9rnPVtZb3IWP8kW6WV/ebAY0FCTI7Y=
github.com/antchfx/xmlquery v1.3.13/go.mod h1:3w2RvQvTz+DaT5fSgsELkSJcdNgkmg6vuXDEuhdwsPQ=
github.com/antchfx/xpath v1.2.1 h1:qhp4EW6aCOVr5XIkT+l6LJ9ck/JsUH/yyauNgTQkBF8=
github.com/antchfx/xpath v1.2.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/htmlquery v1.2.6 h1:Ee7+vpVb7qbgQ4QffP6TVZrw+XMjCbth0pVKv7jqpB8=
github.com/antchfx/htmlquery v1.2.6/go.mod h1:kYx/LosPyRriF4TVOAYmKrBgi1mfAhrwJExTcwKg530=
github.com/antchfx/xmlquery v1.3.14 h1:JVLQF1UIstQytN6MVES7D8gCiqIazZA+A2NWryaHwYk=
github.com/antchfx/xmlquery v1.3.14/go.mod h1:yPRBXRdd2Xqz9c2Z61qvMKbK+u3NXXydp6nqEfw4VdI=
github.com/antchfx/xpath v1.2.2 h1:fsKX4sHfxhsGpDMYjsvCmGC0EGdiT7XA0af/6PP6Oa0=
github.com/antchfx/xpath v1.2.2/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0=
github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA=
github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
@ -71,10 +71,12 @@ github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/Pg
github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10/go.mod h1:VeTZetY5KRJLuD/7fkQXMU6Mw7H5m/KP2J5Iy9osMno=
github.com/aws/aws-sdk-go-v2/config v1.18.7 h1:V94lTcix6jouwmAsgQMAEBozVAGJMFhVj+6/++xfe3E=
github.com/aws/aws-sdk-go-v2/config v1.18.7/go.mod h1:OZYsyHFL5PB9UpyS78NElgKs11qI/B5KJau2XOJDXHA=
github.com/aws/aws-sdk-go-v2/credentials v1.13.7 h1:qUUcNS5Z1092XBFT66IJM7mYkMwgZ8fcC8YDIbEwXck=
github.com/aws/aws-sdk-go-v2/config v1.18.8 h1:lDpy0WM8AHsywOnVrOHaSMfpaiV2igOw8D7svkFkXVA=
github.com/aws/aws-sdk-go-v2/config v1.18.8/go.mod h1:5XCmmyutmzzgkpk/6NYTjeWb6lgo9N170m1j6pQkIBs=
github.com/aws/aws-sdk-go-v2/credentials v1.13.7/go.mod h1:AdCcbZXHQCjJh6NaH3pFaw8LUeBFn5+88BZGMVGuBT8=
github.com/aws/aws-sdk-go-v2/credentials v1.13.8 h1:vTrwTvv5qAwjWIGhZDSBH/oQHuIQjGmD232k01FUh6A=
github.com/aws/aws-sdk-go-v2/credentials v1.13.8/go.mod h1:lVa4OHbvgjVot4gmh1uouF1ubgexSCN92P6CJQpT0t8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 h1:j9wi1kQ8b+e0FBVHxCqCGo4kxDU175hoDHcWAi0sauU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21/go.mod h1:ugwW57Z5Z48bpvUyZuaPy4Kv+vEfJWnIrky7RmkBvJg=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.46 h1:OCX1pQ4pcqhsDV7B92HzdLWjHWOQsILvjLinpaUWhcc=
@ -97,12 +99,15 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.21 h1:vY5siRXvW5TrO
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.21/go.mod h1:WZvNXT1XuH8dnJM0HvOlvk+RNn7NbAPvA/ACO0QarSc=
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.6 h1:W8pLcSn6Uy0eXgDBUUl8M8Kxv7JCoP68ZKTD04OXLEA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.6/go.mod h1:L2l2/q76teehcW7YEsgsDjqdsDTERJeX3nOMIFlgGUE=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 h1:gItLq3zBYyRDPmqAClgzTH8PBjDQGeyptYGHIwtYYNA=
github.com/aws/aws-sdk-go-v2/service/sso v1.11.28/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 h1:KCacyVSs/wlcPGx37hcbT3IGYO8P8Jx+TgSDhAXtQMY=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 h1:/2gzjhQowRLarkkBOGPXSRnb8sQ2RVsjdG1C/UliK/c=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.0/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8=
github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 h1:9Mtq1KM6nD8/+HStvWcvYnixJ5N85DX+P+OY3kI3W2k=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 h1:Jfly6mRxk2ZOSlbCvZfKNS7TukSx1mIzhSsqZ/IGSZI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8=
github.com/aws/aws-sdk-go-v2/service/sts v1.17.7/go.mod h1:+lGbb3+1ugwKrNTWcf2RT05Xmp543B06zDFTwiTLp7I=
github.com/aws/aws-sdk-go-v2/service/sts v1.18.0 h1:kOO++CYo50RcTFISESluhWEi5Prhg+gaSs4whWabiZU=
github.com/aws/aws-sdk-go-v2/service/sts v1.18.0/go.mod h1:+lGbb3+1ugwKrNTWcf2RT05Xmp543B06zDFTwiTLp7I=
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
@ -263,8 +268,8 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -548,14 +553,16 @@ github.com/projectdiscovery/asnmap v0.0.1 h1:n4YCz1ljUaDA3dOUCkjI/bUOtiS7ge1KJ39
github.com/projectdiscovery/asnmap v0.0.1/go.mod h1:CjCVDhQPVtmlE247L6YFeIVX9c4m8pOX8V8BmB0JkX8=
github.com/projectdiscovery/blackrock v0.0.0-20220628111055-35616c71b2dc h1:jqZK68yPOnNNRmwuXqytl+T9EbwneEUCvMDRjLe0J04=
github.com/projectdiscovery/blackrock v0.0.0-20220628111055-35616c71b2dc/go.mod h1:5tNGQP9kOfW+X5+40pZP8aqPYLHs45nJkFaSHLxdeH8=
github.com/projectdiscovery/clistats v0.0.9 h1:8sA17+2qP+cTnY7LaaGURJW5stSM8UwQiygwHQjxHx4=
github.com/projectdiscovery/clistats v0.0.9/go.mod h1:7F1RdeGAoLf05rhsZesL0+qoXJpOA/vxuRj2JRIAzU4=
github.com/projectdiscovery/fastdialer v0.0.19 h1:0E9trACMtYq1JgkY+sM8b6XABjITRwHBSWwhH9csmgY=
github.com/projectdiscovery/fastdialer v0.0.19/go.mod h1:9zV1eivctLQkBUykwBj5BaPkYz8A8j1Sou2UCgdCQ5I=
github.com/projectdiscovery/clistats v0.0.11 h1:0tx2eff4OtoA8kw9AqFieAUEaFNVCLJWiFigsDeAIRU=
github.com/projectdiscovery/clistats v0.0.11/go.mod h1:9luKJj+7Hjq3+a7g129sKWRYx4SbTdkUWZQxabn3H5Y=
github.com/projectdiscovery/fastdialer v0.0.21 h1:KMUNcGtncvgaChm+YkcTzuQEId3OBAVOviwHofNZCXU=
github.com/projectdiscovery/fastdialer v0.0.21/go.mod h1:RqBOi1z+HlU+U+6Tqqh/SNFNl3/ue0sVmVlWiLA8gN0=
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/fileutil v0.0.3 h1:GSsoey4p8ZHIRxWF2VXh4mhLr+wfEkpJwvF0Dxpn/gg=
github.com/projectdiscovery/fileutil v0.0.3/go.mod h1:GLejWd3YerG3RNYD/Hk2pJlytlYRgHdkWfWUAdCH2YQ=
github.com/projectdiscovery/freeport v0.0.4 h1:H4VrK/7hUcC1zbg46zv9iSMBACBDpUqcHkV+FUyXISw=
github.com/projectdiscovery/freeport v0.0.4/go.mod h1:PY0bxSJ34HVy67LHIeF3uIutiCSDwOqKD8ruBkdiCwE=
github.com/projectdiscovery/goflags v0.1.6 h1:EXigzX4lJmn/fLMnULdc03O7WW+DjiYZhNgdGvfg+Z4=
github.com/projectdiscovery/goflags v0.1.6/go.mod h1:yILgA7gbrHuTpIvMfikbivzoxkyxBD1Y5/PRHiGTIFk=
github.com/projectdiscovery/gologger v1.1.6-0.20230104075207-1c357ca8209b h1:OL6uH2ystqHFnfoeei0VbOpsHQkCTpVBVo1BbrNKgfA=
@ -574,28 +581,27 @@ github.com/projectdiscovery/nvd v1.0.9 h1:2DdMm7lu3GnCQsyYDEQiQ/LRYDmpEm654kvGQS
github.com/projectdiscovery/nvd v1.0.9/go.mod h1:nGHAo7o6G4V4kscZlm488qKp/ZrZYiBoKqAQrn3X4Og=
github.com/projectdiscovery/ratelimit v0.0.4 h1:2TOAhuOUMlKrzwissru2mFnSd8eg2WddIQKcAyYEkGs=
github.com/projectdiscovery/ratelimit v0.0.4/go.mod h1:QK9+yt3ArGWINdj6unGjehtJA/NdlAiF59gaj2FtfEs=
github.com/projectdiscovery/rawhttp v0.1.4 h1:zdOqU1DUY2dGoyCzBqzHnHVwPyv32j+Hke8KfLRUouI=
github.com/projectdiscovery/rawhttp v0.1.4/go.mod h1:mhSXo96awUUr20VdReDYUKxldsvR5841FRgiaoaxDCY=
github.com/projectdiscovery/rawhttp v0.1.7 h1:NEqSGrU293vqzPw+1ldqUVjDOX530/KOBgmeUwcVj9g=
github.com/projectdiscovery/rawhttp v0.1.7/go.mod h1:/bZzn4+e6BbR1O4kYcSR6VTThDUR6JckI0nsW9RDzbQ=
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.17 h1:XKzI26UKYt2g7YLJ/EcyYmM04sfD1vurETecPEpeA1w=
github.com/projectdiscovery/retryabledns v1.0.17/go.mod h1:Dyhq/f0sGmXueso0+Ah3LbJfsX4PXpBrpfiyjZZ8SDk=
github.com/projectdiscovery/retryablehttp-go v1.0.7 h1:4Nd87QN+3yUMxYwQQgG/v+BLQt1fYqXu9PjEoar8LGs=
github.com/projectdiscovery/retryablehttp-go v1.0.7/go.mod h1:ODXs70i/PgqfqFvycLfQG6QCqWqZMLoX8X68wu6Bg8M=
github.com/projectdiscovery/retryabledns v1.0.20 h1:grRyh4EzuyqsaK07iNkJKgrGLu/qDJwfDJ+83SBo6yo=
github.com/projectdiscovery/retryabledns v1.0.20/go.mod h1:97Et22Kw2iPyvz/Vn41/i3dSbhLMHfeWP/S7EaLgmtg=
github.com/projectdiscovery/retryablehttp-go v1.0.10-0.20230123170312-75b58f90739a h1:KUHx4Yxx7S+qX94TtCegLj/01obmohdVDeiG86FCHjM=
github.com/projectdiscovery/retryablehttp-go v1.0.10-0.20230123170312-75b58f90739a/go.mod h1:a5bmSbaxgHvC0P80csOymMOwKaJirMnsS6otRUH/vcU=
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/sliceutil v0.0.1 h1:YoCqCMcdwz+gqNfW5hFY8UvNHoA6SfyBSNkVahatleg=
github.com/projectdiscovery/sliceutil v0.0.1/go.mod h1:0wBmhU5uTDwMfrEZfvwH9qa5k60Q4shPVOC9E6LGsDI=
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/tlsx v1.0.1 h1:rpWDka7yGYV8LFLMCdPMkXmvqN8RJMqbE6pJArBCbJA=
github.com/projectdiscovery/tlsx v1.0.1/go.mod h1:WW+PdBImrqnMl18v4Brp3OsbnO4A1tqYPUcfiVtjNLM=
github.com/projectdiscovery/uncover v1.0.2 h1:mRFzflYyvwKkHd3XKufMlDRrb6p1mjFZTSHoNAUpFwo=
github.com/projectdiscovery/uncover v1.0.2/go.mod h1:lz4QYfArSA6jJkXyB71kN2/Pc7IW7nJB8c95n7xtwqY=
github.com/projectdiscovery/utils v0.0.4-0.20230104145529-50cace956b0a h1:fHztw99lR4QO931no6Zsj8/RYGA4otFQH5BF8OqfTss=
github.com/projectdiscovery/utils v0.0.4-0.20230104145529-50cace956b0a/go.mod h1:PCwA5YuCYWPgHaGiZmr53/SA9iGQmAnw7DSHuhr8VPQ=
github.com/projectdiscovery/wappalyzergo v0.0.77 h1:PjWTwa4T8AoR+QpxLVY/4agQba+l/4FKy33WMGII+GI=
github.com/projectdiscovery/wappalyzergo v0.0.77/go.mod h1:HvYuW0Be4JCjVds/+XAEaMSqRG9yrI97UmZq0TPk6A0=
github.com/projectdiscovery/utils v0.0.6-0.20230123093357-4dd69195db7e h1:t+e8/DKf/cPZRtS8Com52fIwjwAMqrYp2JTOR384bGI=
github.com/projectdiscovery/utils v0.0.6-0.20230123093357-4dd69195db7e/go.mod h1:PCwA5YuCYWPgHaGiZmr53/SA9iGQmAnw7DSHuhr8VPQ=
github.com/projectdiscovery/wappalyzergo v0.0.79 h1:hWMxNysxC/P6fxnu6c+opqf5L27hHQ9wD1QzPRCb+I8=
github.com/projectdiscovery/wappalyzergo v0.0.79/go.mod h1:HvYuW0Be4JCjVds/+XAEaMSqRG9yrI97UmZq0TPk6A0=
github.com/projectdiscovery/yamldoc-go v1.0.3-0.20211126104922-00d2c6bb43b6 h1:DvWRQpw7Ib2CRL3ogYm/BWM+X0UGPfz1n9Ix9YKgFM8=
github.com/projectdiscovery/yamldoc-go v1.0.3-0.20211126104922-00d2c6bb43b6/go.mod h1:8OfZj8p/axkUM/TJoS/O9LDjj/S8u17rxRbqluE9CU4=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
@ -622,8 +628,9 @@ github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/segmentio/ksuid v1.0.3/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
@ -743,8 +750,8 @@ github.com/weppos/publicsuffix-go v0.15.1-0.20220724114530-e087fba66a37/go.mod h
github.com/weppos/publicsuffix-go/publicsuffix/generator v0.0.0-20220704091424-e0182326a282/go.mod h1:GHfoeIdZLdZmLjMlzBftbTDntahTttUMWjxZwQJhULE=
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ=
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
github.com/xanzy/go-gitlab v0.77.0 h1:UrbGlxkWVCbkpa6Fk6cM8ARh+rLACWemkJnsawT7t98=
github.com/xanzy/go-gitlab v0.77.0/go.mod h1:d/a0vswScO7Agg1CZNz15Ic6SSvBG9vfw8egL99t4kA=
github.com/xanzy/go-gitlab v0.78.0 h1:8jUHfQVAprG04Av5g0PxVd3CNsZ5hCbojIax7Hba1mE=
github.com/xanzy/go-gitlab v0.78.0/go.mod h1:DlByVTSXhPsJMYL6+cm8e8fTJjeBmhrXdC/yvkKKt6M=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
@ -853,7 +860,6 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@ -863,7 +869,6 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
@ -929,7 +934,6 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -952,6 +956,7 @@ golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuX
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -962,8 +967,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -11,6 +11,7 @@ import (
"os"
"path/filepath"
"strings"
"sync/atomic"
"time"
"github.com/klauspost/compress/zlib"
@ -23,7 +24,6 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v2/pkg/types"
"go.uber.org/atomic"
)
// runStandardEnumeration runs standard enumeration
@ -115,7 +115,7 @@ func (r *Runner) runCloudEnumeration(store *loader.Store, cloudTemplates, cloudT
err = r.cloudClient.GetResults(taskID, true, limit, func(re *output.ResultEvent) {
r.progress.IncrementMatched()
results.CompareAndSwap(false, true)
_ = count.Inc()
_ = count.Add(1)
if outputErr := r.output.Write(re); outputErr != nil {
gologger.Warning().Msgf("Could not write output: %s", err)

View File

@ -3,8 +3,6 @@ package runner
import (
"bufio"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
@ -24,6 +22,7 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/types"
"github.com/projectdiscovery/stringsutil"
fileutil "github.com/projectdiscovery/utils/file"
logutil "github.com/projectdiscovery/utils/log"
)
func ConfigureOptions() error {
@ -260,8 +259,7 @@ func configureOutput(options *types.Options) {
}
// disable standard logger (ref: https://github.com/golang/go/issues/19895)
log.SetFlags(0)
log.SetOutput(io.Discard)
logutil.DisableDefaultLogger()
}
// loadResolvers loads resolvers from both user provided flag and file

View File

@ -13,6 +13,7 @@ import (
"path/filepath"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/projectdiscovery/nuclei/v2/internal/runner/nucleicloud"
@ -21,7 +22,6 @@ import (
"github.com/logrusorgru/aurora"
"github.com/pkg/errors"
"github.com/projectdiscovery/ratelimit"
"go.uber.org/atomic"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v2/internal/colorizer"
@ -188,6 +188,9 @@ func New(options *types.Options) (*Runner, error) {
hmapInput, err := hybrid.New(&hybrid.Options{
Options: options,
NotFoundCallback: func(target string) bool {
if !options.Cloud {
return false
}
parsed, parseErr := strconv.ParseInt(target, 10, 64)
if parseErr != nil {
if err := runner.cloudClient.ExistsDataSourceItem(nucleicloud.ExistsDataSourceItemRequest{Contents: target, Type: "targets"}); err == nil {
@ -389,12 +392,6 @@ func (r *Runner) RunEnumeration() error {
r.options.ExcludeTags = append(r.options.ExcludeTags, ignoreFile.Tags...)
r.options.ExcludedTemplates = append(r.options.ExcludedTemplates, ignoreFile.Files...)
}
var cache *hosterrorscache.Cache
if r.options.MaxHostError > 0 {
cache = hosterrorscache.New(r.options.MaxHostError, hosterrorscache.DefaultMaxHostsCount)
cache.SetVerbose(r.options.Verbose)
}
r.hostErrors = cache
// Create the executer options which will be used throughout the execution
// stage by the nuclei engine modules.
@ -408,12 +405,19 @@ func (r *Runner) RunEnumeration() error {
Interactsh: r.interactsh,
ProjectFile: r.projectFile,
Browser: r.browser,
HostErrorsCache: cache,
Colorizer: r.colorizer,
ResumeCfg: r.resumeCfg,
ExcludeMatchers: excludematchers.New(r.options.ExcludeMatchers),
InputHelper: input.NewHelper(),
}
if r.options.ShouldUseHostError() {
cache := hosterrorscache.New(r.options.MaxHostError, hosterrorscache.DefaultMaxHostsCount)
cache.SetVerbose(r.options.Verbose)
r.hostErrors = cache
executerOpts.HostErrorsCache = cache
}
engine := core.New(r.options)
engine.SetExecuterOptions(executerOpts)
@ -634,7 +638,7 @@ func (r *Runner) executeTemplatesInput(store *loader.Store, engine *core.Engine)
totalRequests += int64(t.Executer.Requests()) * r.hmapInputProvider.Count()
}
if totalRequests < unclusteredRequests {
gologger.Info().Msgf("Templates clustered: %d (Reduced %d HTTP Requests)", clusterCount, unclusteredRequests-totalRequests)
gologger.Info().Msgf("Templates clustered: %d (Reduced %d Requests)", clusterCount, unclusteredRequests-totalRequests)
}
workflowCount := len(store.Workflows())
templateCount := originalTemplatesCount + workflowCount

View File

@ -32,7 +32,7 @@ type Config struct {
const nucleiConfigFilename = ".templates-config.json"
// Version is the current version of nuclei
const Version = `2.8.7`
const Version = `2.8.8`
var customConfigDirectory string

View File

@ -1,6 +1,7 @@
package core
import (
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v2/pkg/types"
@ -18,6 +19,7 @@ type Engine struct {
workPool *WorkPool
options *types.Options
executerOpts protocols.ExecuterOptions
Callback func(*output.ResultEvent) // Executed on results
}
// InputProvider is an input providing interface for the nuclei execution
@ -37,19 +39,23 @@ type InputProvider interface {
// New returns a new Engine instance
func New(options *types.Options) *Engine {
workPool := NewWorkPool(WorkPoolConfig{
InputConcurrency: options.BulkSize,
TypeConcurrency: options.TemplateThreads,
HeadlessInputConcurrency: options.HeadlessBulkSize,
HeadlessTypeConcurrency: options.HeadlessTemplateThreads,
})
engine := &Engine{
options: options,
workPool: workPool,
options: options,
}
engine.workPool = engine.GetWorkPool()
return engine
}
// GetWorkPool returns a workpool from options
func (e *Engine) GetWorkPool() *WorkPool {
return NewWorkPool(WorkPoolConfig{
InputConcurrency: e.options.BulkSize,
TypeConcurrency: e.options.TemplateThreads,
HeadlessInputConcurrency: e.options.HeadlessBulkSize,
HeadlessTypeConcurrency: e.options.HeadlessTemplateThreads,
})
}
// SetExecuterOptions sets the executer options for the engine. This is required
// before using the engine to perform any execution.
func (e *Engine) SetExecuterOptions(options protocols.ExecuterOptions) {

View File

@ -1,311 +0,0 @@
package core
import (
"github.com/remeh/sizedwaitgroup"
"go.uber.org/atomic"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v2/pkg/core/inputs"
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
"github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
generalTypes "github.com/projectdiscovery/nuclei/v2/pkg/types"
stringsutil "github.com/projectdiscovery/utils/strings"
)
// Execute takes a list of templates/workflows that have been compiled
// and executes them based on provided concurrency options.
//
// All the execution logic for the templates/workflows happens in this part
// of the engine.
func (e *Engine) Execute(templates []*templates.Template, target InputProvider) *atomic.Bool {
return e.ExecuteScanWithOpts(templates, target, false)
}
// executeTemplateSpray executes scan using template spray strategy where targets are iterated over each template
func (e *Engine) executeTemplateSpray(templatesList []*templates.Template, target InputProvider) *atomic.Bool {
results := &atomic.Bool{}
for _, template := range templatesList {
templateType := template.Type()
var wg *sizedwaitgroup.SizedWaitGroup
if templateType == types.HeadlessProtocol {
wg = e.workPool.Headless
} else {
wg = e.workPool.Default
}
wg.Add()
go func(tpl *templates.Template) {
defer wg.Done()
switch {
case tpl.SelfContained:
// Self Contained requests are executed here separately
e.executeSelfContainedTemplateWithInput(tpl, results)
default:
// All other request types are executed here
e.executeModelWithInput(templateType, tpl, target, results)
}
}(template)
}
e.workPool.Wait()
return results
}
// executeHostSpray executes scan using host spray strategy where templates are iterated over each target
func (e *Engine) executeHostSpray(templatesList []*templates.Template, target InputProvider) *atomic.Bool {
results := &atomic.Bool{}
hostwg := sizedwaitgroup.New(e.options.BulkSize)
target.Scan(func(value *contextargs.MetaInput) bool {
host := inputs.SimpleInputProvider{
Inputs: []*contextargs.MetaInput{
value,
},
}
hostwg.Add()
go func(result *atomic.Bool) {
defer hostwg.Done()
status := e.executeTemplateSpray(templatesList, &host)
results.CompareAndSwap(false, status.Load())
}(results)
return true
})
hostwg.Wait()
return results
}
// ExecuteScanWithOpts executes scan with given scanStatergy
func (e *Engine) ExecuteScanWithOpts(templatesList []*templates.Template, target InputProvider, noCluster bool) *atomic.Bool {
var results *atomic.Bool
var finalTemplates []*templates.Template
if !noCluster {
finalTemplates, _ = templates.ClusterTemplates(templatesList, e.executerOpts)
} else {
finalTemplates = templatesList
}
if stringsutil.EqualFoldAny(e.options.ScanStrategy, "auto", "") {
// TODO: this is only a placeholder, auto scan strategy should choose scan strategy
// based on no of hosts , templates , stream and other optimization parameters
e.options.ScanStrategy = "template-spray"
}
switch e.options.ScanStrategy {
case "template-spray":
results = e.executeTemplateSpray(finalTemplates, target)
case "host-spray":
results = e.executeHostSpray(finalTemplates, target)
}
return results
}
// processSelfContainedTemplates execute a self-contained template.
func (e *Engine) executeSelfContainedTemplateWithInput(template *templates.Template, results *atomic.Bool) {
match, err := template.Executer.Execute(contextargs.New())
if err != nil {
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), err)
}
results.CompareAndSwap(false, match)
}
// executeModelWithInput executes a type of template with input
func (e *Engine) executeModelWithInput(templateType types.ProtocolType, template *templates.Template, target InputProvider, results *atomic.Bool) {
wg := e.workPool.InputPool(templateType)
var (
index uint32
)
e.executerOpts.ResumeCfg.Lock()
currentInfo, ok := e.executerOpts.ResumeCfg.Current[template.ID]
if !ok {
currentInfo = &generalTypes.ResumeInfo{}
e.executerOpts.ResumeCfg.Current[template.ID] = currentInfo
}
if currentInfo.InFlight == nil {
currentInfo.InFlight = make(map[uint32]struct{})
}
resumeFromInfo, ok := e.executerOpts.ResumeCfg.ResumeFrom[template.ID]
if !ok {
resumeFromInfo = &generalTypes.ResumeInfo{}
e.executerOpts.ResumeCfg.ResumeFrom[template.ID] = resumeFromInfo
}
e.executerOpts.ResumeCfg.Unlock()
// track progression
cleanupInFlight := func(index uint32) {
currentInfo.Lock()
delete(currentInfo.InFlight, index)
currentInfo.Unlock()
}
target.Scan(func(scannedValue *contextargs.MetaInput) bool {
// Best effort to track the host progression
// skips indexes lower than the minimum in-flight at interruption time
var skip bool
if resumeFromInfo.Completed { // the template was completed
gologger.Debug().Msgf("[%s] Skipping \"%s\": Resume - Template already completed\n", template.ID, scannedValue)
skip = true
} else if index < resumeFromInfo.SkipUnder { // index lower than the sliding window (bulk-size)
gologger.Debug().Msgf("[%s] Skipping \"%s\": Resume - Target already processed\n", template.ID, scannedValue)
skip = true
} else if _, isInFlight := resumeFromInfo.InFlight[index]; isInFlight { // the target wasn't completed successfully
gologger.Debug().Msgf("[%s] Repeating \"%s\": Resume - Target wasn't completed\n", template.ID, scannedValue)
// skip is already false, but leaving it here for clarity
skip = false
} else if index > resumeFromInfo.DoAbove { // index above the sliding window (bulk-size)
// skip is already false - but leaving it here for clarity
skip = false
}
currentInfo.Lock()
currentInfo.InFlight[index] = struct{}{}
currentInfo.Unlock()
// Skip if the host has had errors
if e.executerOpts.HostErrorsCache != nil && e.executerOpts.HostErrorsCache.Check(scannedValue.ID()) {
return true
}
wg.WaitGroup.Add()
go func(index uint32, skip bool, value *contextargs.MetaInput) {
defer wg.WaitGroup.Done()
defer cleanupInFlight(index)
if skip {
return
}
var match bool
var err error
switch templateType {
case types.WorkflowProtocol:
match = e.executeWorkflow(value, template.CompiledWorkflow)
default:
ctxArgs := contextargs.New()
ctxArgs.MetaInput = value
match, err = template.Executer.Execute(ctxArgs)
}
if err != nil {
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), err)
}
results.CompareAndSwap(false, match)
}(index, skip, scannedValue)
index++
return true
})
wg.WaitGroup.Wait()
// on completion marks the template as completed
currentInfo.Lock()
currentInfo.Completed = true
currentInfo.Unlock()
}
// ExecuteWithResults a list of templates with results
func (e *Engine) ExecuteWithResults(templatesList []*templates.Template, target InputProvider, callback func(*output.ResultEvent)) *atomic.Bool {
results := &atomic.Bool{}
for _, template := range templatesList {
templateType := template.Type()
var wg *sizedwaitgroup.SizedWaitGroup
if templateType == types.HeadlessProtocol {
wg = e.workPool.Headless
} else {
wg = e.workPool.Default
}
wg.Add()
go func(tpl *templates.Template) {
e.executeModelWithInputAndResult(templateType, tpl, target, results, callback)
wg.Done()
}(template)
}
e.workPool.Wait()
return results
}
// executeModelWithInputAndResult executes a type of template with input and result
func (e *Engine) executeModelWithInputAndResult(templateType types.ProtocolType, template *templates.Template, target InputProvider, results *atomic.Bool, callback func(*output.ResultEvent)) {
wg := e.workPool.InputPool(templateType)
target.Scan(func(scannedValue *contextargs.MetaInput) bool {
// Skip if the host has had errors
if e.executerOpts.HostErrorsCache != nil && e.executerOpts.HostErrorsCache.Check(scannedValue.ID()) {
return true
}
wg.WaitGroup.Add()
go func(value *contextargs.MetaInput) {
defer wg.WaitGroup.Done()
var match bool
var err error
switch templateType {
case types.WorkflowProtocol:
match = e.executeWorkflow(value, template.CompiledWorkflow)
default:
ctxArgs := contextargs.New()
ctxArgs.MetaInput = value
err = template.Executer.ExecuteWithResults(ctxArgs, func(event *output.InternalWrappedEvent) {
for _, result := range event.Results {
callback(result)
}
})
}
if err != nil {
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), err)
}
results.CompareAndSwap(false, match)
}(scannedValue)
return true
})
wg.WaitGroup.Wait()
}
type ChildExecuter struct {
e *Engine
results *atomic.Bool
}
// Close closes the executer returning bool results
func (e *ChildExecuter) Close() *atomic.Bool {
e.e.workPool.Wait()
return e.results
}
// Execute executes a template and URLs
func (e *ChildExecuter) Execute(template *templates.Template, value *contextargs.MetaInput) {
templateType := template.Type()
var wg *sizedwaitgroup.SizedWaitGroup
if templateType == types.HeadlessProtocol {
wg = e.e.workPool.Headless
} else {
wg = e.e.workPool.Default
}
wg.Add()
go func(tpl *templates.Template) {
defer wg.Done()
ctxArgs := contextargs.New()
ctxArgs.MetaInput = value
match, err := template.Executer.Execute(ctxArgs)
if err != nil {
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.e.executerOpts.Colorizer.BrightBlue(template.ID), err)
}
e.results.CompareAndSwap(false, match)
}(template)
}
// ExecuteWithOpts executes with the full options
func (e *Engine) ChildExecuter() *ChildExecuter {
return &ChildExecuter{
e: e,
results: &atomic.Bool{},
}
}

View File

@ -0,0 +1,120 @@
package core
import (
"sync"
"sync/atomic"
"github.com/remeh/sizedwaitgroup"
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
"github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
stringsutil "github.com/projectdiscovery/utils/strings"
)
// Execute takes a list of templates/workflows that have been compiled
// and executes them based on provided concurrency options.
//
// All the execution logic for the templates/workflows happens in this part
// of the engine.
func (e *Engine) Execute(templates []*templates.Template, target InputProvider) *atomic.Bool {
return e.ExecuteScanWithOpts(templates, target, false)
}
// ExecuteWithResults a list of templates with results
func (e *Engine) ExecuteWithResults(templatesList []*templates.Template, target InputProvider, callback func(*output.ResultEvent)) *atomic.Bool {
e.Callback = callback
return e.ExecuteScanWithOpts(templatesList, target, false)
}
// ExecuteScanWithOpts executes scan with given scanStatergy
func (e *Engine) ExecuteScanWithOpts(templatesList []*templates.Template, target InputProvider, noCluster bool) *atomic.Bool {
results := &atomic.Bool{}
selfcontainedWg := &sync.WaitGroup{}
var finalTemplates []*templates.Template
if !noCluster {
finalTemplates, _ = templates.ClusterTemplates(templatesList, e.executerOpts)
} else {
finalTemplates = templatesList
}
if stringsutil.EqualFoldAny(e.options.ScanStrategy, "auto", "") {
// TODO: this is only a placeholder, auto scan strategy should choose scan strategy
// based on no of hosts , templates , stream and other optimization parameters
e.options.ScanStrategy = "template-spray"
}
filtered := []*templates.Template{}
selfContained := []*templates.Template{}
// Filter Self Contained templates since they are not bound to target
for _, v := range finalTemplates {
if v.SelfContained {
selfContained = append(selfContained, v)
} else {
filtered = append(filtered, v)
}
}
// Execute All SelfContained in parallel
e.executeAllSelfContained(selfContained, results, selfcontainedWg)
switch e.options.ScanStrategy {
case "template-spray":
results = e.executeTemplateSpray(filtered, target)
case "host-spray":
results = e.executeHostSpray(filtered, target)
}
selfcontainedWg.Wait()
return results
}
// executeTemplateSpray executes scan using template spray strategy where targets are iterated over each template
func (e *Engine) executeTemplateSpray(templatesList []*templates.Template, target InputProvider) *atomic.Bool {
results := &atomic.Bool{}
// wp is workpool that contains different waitgroups for
// headless and non-headless templates
wp := e.GetWorkPool()
for _, template := range templatesList {
templateType := template.Type()
var wg *sizedwaitgroup.SizedWaitGroup
if templateType == types.HeadlessProtocol {
wg = wp.Headless
} else {
wg = wp.Default
}
wg.Add()
go func(tpl *templates.Template) {
defer wg.Done()
// All other request types are executed here
// Note: executeTemplateWithTargets creates goroutines and blocks
// given template is executed on all targets
e.executeTemplateWithTargets(tpl, target, results)
}(template)
}
wp.Wait()
return results
}
// executeHostSpray executes scan using host spray strategy where templates are iterated over each target
func (e *Engine) executeHostSpray(templatesList []*templates.Template, target InputProvider) *atomic.Bool {
results := &atomic.Bool{}
wp := sizedwaitgroup.New(e.options.BulkSize + e.options.HeadlessBulkSize)
target.Scan(func(value *contextargs.MetaInput) bool {
wp.Add()
go func(targetval *contextargs.MetaInput) {
defer wp.Done()
e.executeTemplatesOnTarget(templatesList, targetval, results)
}(value)
return true
})
wp.Wait()
return results
}

240
v2/pkg/core/executors.go Normal file
View File

@ -0,0 +1,240 @@
package core
import (
"sync"
"sync/atomic"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
"github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
generalTypes "github.com/projectdiscovery/nuclei/v2/pkg/types"
"github.com/remeh/sizedwaitgroup"
)
/*
Executors are low level executors that deals with template execution on a target
*/
// executeAllSelfContained executes all self contained templates that do not use `target`
func (e *Engine) executeAllSelfContained(alltemplates []*templates.Template, results *atomic.Bool, sg *sync.WaitGroup) {
for _, v := range alltemplates {
sg.Add(1)
go func(template *templates.Template) {
defer sg.Done()
var err error
var match bool
if e.Callback != nil {
err = template.Executer.ExecuteWithResults(contextargs.New(), func(event *output.InternalWrappedEvent) {
for _, result := range event.Results {
e.Callback(result)
}
})
match = true
} else {
match, err = template.Executer.Execute(contextargs.New())
}
if err != nil {
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), err)
}
results.CompareAndSwap(false, match)
}(v)
}
}
// executeTemplateWithTarget executes a given template on x targets (with a internal targetpool(i.e concurrency))
func (e *Engine) executeTemplateWithTargets(template *templates.Template, target InputProvider, results *atomic.Bool) {
// this is target pool i.e max target to execute
wg := e.workPool.InputPool(template.Type())
var (
index uint32
)
e.executerOpts.ResumeCfg.Lock()
currentInfo, ok := e.executerOpts.ResumeCfg.Current[template.ID]
if !ok {
currentInfo = &generalTypes.ResumeInfo{}
e.executerOpts.ResumeCfg.Current[template.ID] = currentInfo
}
if currentInfo.InFlight == nil {
currentInfo.InFlight = make(map[uint32]struct{})
}
resumeFromInfo, ok := e.executerOpts.ResumeCfg.ResumeFrom[template.ID]
if !ok {
resumeFromInfo = &generalTypes.ResumeInfo{}
e.executerOpts.ResumeCfg.ResumeFrom[template.ID] = resumeFromInfo
}
e.executerOpts.ResumeCfg.Unlock()
// track progression
cleanupInFlight := func(index uint32) {
currentInfo.Lock()
delete(currentInfo.InFlight, index)
currentInfo.Unlock()
}
target.Scan(func(scannedValue *contextargs.MetaInput) bool {
// Best effort to track the host progression
// skips indexes lower than the minimum in-flight at interruption time
var skip bool
if resumeFromInfo.Completed { // the template was completed
gologger.Debug().Msgf("[%s] Skipping \"%s\": Resume - Template already completed\n", template.ID, scannedValue)
skip = true
} else if index < resumeFromInfo.SkipUnder { // index lower than the sliding window (bulk-size)
gologger.Debug().Msgf("[%s] Skipping \"%s\": Resume - Target already processed\n", template.ID, scannedValue)
skip = true
} else if _, isInFlight := resumeFromInfo.InFlight[index]; isInFlight { // the target wasn't completed successfully
gologger.Debug().Msgf("[%s] Repeating \"%s\": Resume - Target wasn't completed\n", template.ID, scannedValue)
// skip is already false, but leaving it here for clarity
skip = false
} else if index > resumeFromInfo.DoAbove { // index above the sliding window (bulk-size)
// skip is already false - but leaving it here for clarity
skip = false
}
currentInfo.Lock()
currentInfo.InFlight[index] = struct{}{}
currentInfo.Unlock()
// Skip if the host has had errors
if e.executerOpts.HostErrorsCache != nil && e.executerOpts.HostErrorsCache.Check(scannedValue.ID()) {
return true
}
wg.WaitGroup.Add()
go func(index uint32, skip bool, value *contextargs.MetaInput) {
defer wg.WaitGroup.Done()
defer cleanupInFlight(index)
if skip {
return
}
var match bool
var err error
switch template.Type() {
case types.WorkflowProtocol:
match = e.executeWorkflow(value, template.CompiledWorkflow)
default:
ctxArgs := contextargs.New()
ctxArgs.MetaInput = value
if e.Callback != nil {
err = template.Executer.ExecuteWithResults(ctxArgs, func(event *output.InternalWrappedEvent) {
for _, result := range event.Results {
e.Callback(result)
}
})
match = true
} else {
match, err = template.Executer.Execute(ctxArgs)
}
}
if err != nil {
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), err)
}
results.CompareAndSwap(false, match)
}(index, skip, scannedValue)
index++
return true
})
wg.WaitGroup.Wait()
// on completion marks the template as completed
currentInfo.Lock()
currentInfo.Completed = true
currentInfo.Unlock()
}
// executeTemplatesOnTarget execute given templates on given single target
func (e *Engine) executeTemplatesOnTarget(alltemplates []*templates.Template, target *contextargs.MetaInput, results *atomic.Bool) {
// all templates are executed on single target
// wp is workpool that contains different waitgroups for
// headless and non-headless templates
// global waitgroup should not be used here
wp := e.GetWorkPool()
for _, tpl := range alltemplates {
var sg *sizedwaitgroup.SizedWaitGroup
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) {
defer wg.Done()
var match bool
var err error
switch template.Type() {
case types.WorkflowProtocol:
match = e.executeWorkflow(value, template.CompiledWorkflow)
default:
ctxArgs := contextargs.New()
ctxArgs.MetaInput = value
if e.Callback != nil {
err = template.Executer.ExecuteWithResults(ctxArgs, func(event *output.InternalWrappedEvent) {
for _, result := range event.Results {
e.Callback(result)
}
})
match = true
} else {
match, err = template.Executer.Execute(ctxArgs)
}
}
if err != nil {
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), err)
}
results.CompareAndSwap(false, match)
}(tpl, target, sg)
}
wp.Wait()
}
type ChildExecuter struct {
e *Engine
results *atomic.Bool
}
// Close closes the executer returning bool results
func (e *ChildExecuter) Close() *atomic.Bool {
e.e.workPool.Wait()
return e.results
}
// Execute executes a template and URLs
func (e *ChildExecuter) Execute(template *templates.Template, value *contextargs.MetaInput) {
templateType := template.Type()
var wg *sizedwaitgroup.SizedWaitGroup
if templateType == types.HeadlessProtocol {
wg = e.e.workPool.Headless
} else {
wg = e.e.workPool.Default
}
wg.Add()
go func(tpl *templates.Template) {
defer wg.Done()
ctxArgs := contextargs.New()
ctxArgs.MetaInput = value
match, err := template.Executer.Execute(ctxArgs)
if err != nil {
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.e.executerOpts.Colorizer.BrightBlue(template.ID), err)
}
e.results.CompareAndSwap(false, match)
}(template)
}
// ExecuteWithOpts executes with the full options
func (e *Engine) ChildExecuter() *ChildExecuter {
return &ChildExecuter{
e: e,
results: &atomic.Bool{},
}
}

View File

@ -171,8 +171,8 @@ func (i *Input) Set(value string) {
return
}
// parse hostname if url is given
urlx, err := urlutil.ParseWithScheme(URL)
if err != nil || urlx != nil && urlx.Host == "" {
urlx, err := urlutil.Parse(URL)
if err != nil || (urlx != nil && urlx.Host == "") {
gologger.Debug().Label("url").MsgFunc(func() string {
if err != nil {
return fmt.Sprintf("failed to parse url %v got %v skipping ip selection", URL, err)
@ -185,7 +185,7 @@ func (i *Input) Set(value string) {
}
// Check if input is ip or hostname
if iputil.IsIP(urlx.Host) {
if iputil.IsIP(urlx.Hostname()) {
metaInput := &contextargs.MetaInput{Input: URL}
i.setItem(metaInput)
return
@ -193,7 +193,7 @@ func (i *Input) Set(value string) {
if i.ipOptions.ScanAllIPs {
// scan all ips
dnsData, err := protocolstate.Dialer.GetDNSData(urlx.Host)
dnsData, err := protocolstate.Dialer.GetDNSData(urlx.Hostname())
if err == nil {
if (len(dnsData.A) + len(dnsData.AAAA)) > 0 {
var ips []string
@ -223,7 +223,7 @@ func (i *Input) Set(value string) {
ips := []string{}
// only scan the target but ipv6 if it has one
if i.ipOptions.IPV6 {
dnsData, err := protocolstate.Dialer.GetDNSData(urlx.Host)
dnsData, err := protocolstate.Dialer.GetDNSData(urlx.Hostname())
if err == nil && len(dnsData.AAAA) > 0 {
// pick/ prefer 1st
ips = append(ips, dnsData.AAAA[0])

View File

@ -3,9 +3,9 @@ package core
import (
"fmt"
"net/http/cookiejar"
"sync/atomic"
"github.com/remeh/sizedwaitgroup"
"go.uber.org/atomic"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v2/pkg/output"

View File

@ -89,9 +89,6 @@ func (c *Cache) normalizeCacheValue(value string) string {
// - host type
func (c *Cache) Check(value string) bool {
finalValue := c.normalizeCacheValue(value)
if !c.failedTargets.Has(finalValue) {
return false
}
existingCacheItem, err := c.failedTargets.GetIFPresent(finalValue)
if err != nil {

View File

@ -0,0 +1,27 @@
package dns
// CanCluster returns true if the request can be clustered.
//
// This used by the clustering engine to decide whether two requests
// are similar enough to be considered one and can be checked by
// just adding the matcher/extractors for the request and the correct IDs.
func (request *Request) CanCluster(other *Request) bool {
if len(request.Resolvers) > 0 || request.Trace || request.ID != "" {
return false
}
if request.Name != other.Name ||
request.class != other.class ||
request.Retries != other.Retries ||
request.question != other.question {
return false
}
if request.Recursion != nil {
if other.Recursion == nil {
return false
}
if *request.Recursion != *other.Recursion {
return false
}
}
return true
}

View File

@ -106,6 +106,11 @@ func (request *Request) GetID() string {
return request.ID
}
// Options returns executer options for http request
func (r *Request) Options() *protocols.ExecuterOptions {
return r.options
}
// Compile compiles the protocol request for further execution.
func (request *Request) Compile(options *protocols.ExecuterOptions) error {
if request.Retries == 0 {

View File

@ -499,7 +499,8 @@ func TestActionWaitVisible(t *testing.T) {
})
t.Run("timeout because of element not visible", func(t *testing.T) {
testHeadlessSimpleResponse(t, response, actions, time.Second/2, func(page *Page, err error, out map[string]string) {
// increased timeout from time.Second/2 to time.Second due to random fails (probably due to overhead and system)
testHeadlessSimpleResponse(t, response, actions, time.Second, func(page *Page, err error, out map[string]string) {
require.Error(t, err)
require.Contains(t, err.Error(), "Element did not appear in the given amount of time")
})

View File

@ -5,9 +5,6 @@ import (
"context"
"fmt"
"io"
"net"
"net/http"
"net/url"
"regexp"
"strings"
"time"
@ -27,6 +24,7 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/types"
"github.com/projectdiscovery/rawhttp"
"github.com/projectdiscovery/retryablehttp-go"
readerutil "github.com/projectdiscovery/utils/reader"
stringsutil "github.com/projectdiscovery/utils/strings"
urlutil "github.com/projectdiscovery/utils/url"
)
@ -75,14 +73,19 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context,
payloads[payloadName] = types.ToString(payloadValue)
}
}
parsed, err := url.Parse(input.MetaInput.Input)
parsed, err := urlutil.Parse(input.MetaInput.Input)
if err != nil {
return nil, err
}
isRawRequest := len(r.request.Raw) > 0
data, parsed = baseURLWithTemplatePrefs(data, parsed, isRawRequest)
// if path contains port ex: {{BaseURL}}:8080 use port
parsed, data = UsePortFromPayload(parsed, data)
// If not raw request process input values
if !isRawRequest {
data, parsed = addParamsToBaseURL(data, parsed)
}
// If the request is not a raw request, and the URL input path is suffixed with
// a trailing slash, and our Input URL is also suffixed with a trailing slash,
@ -138,11 +141,6 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st
generators.BuildPayloadFromOptions(r.request.options.Options),
)
// in case cases (eg requests signing, some variables uses default values if missing)
if defaultList := GetVariablesDefault(r.request.Signature.Value); defaultList != nil {
values = generators.MergeMaps(defaultList, values)
}
parts[1] = replacer.Replace(parts[1], values)
if len(dynamicValues) > 0 {
parts[1] = replacer.Replace(parts[1], dynamicValues)
@ -157,7 +155,7 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st
return nil, err
}
parsed, err := url.Parse(parts[1])
parsed, err := urlutil.ParseURL(parts[1], true)
if err != nil {
return nil, fmt.Errorf("could not parse request URL: %w", err)
}
@ -183,65 +181,6 @@ func (r *requestGenerator) Total() int {
return len(r.request.Path)
}
// baseURLWithTemplatePrefs returns the url for BaseURL keeping
// the template port along with any query parameters over the user provided one.
func baseURLWithTemplatePrefs(data string, parsed *url.URL, isRaw bool) (string, *url.URL) {
// template port preference over input URL port if template has a port
matches := urlWithPortRegex.FindAllStringSubmatch(data, -1)
if len(matches) > 0 {
port := matches[0][1]
parsed.Host = net.JoinHostPort(parsed.Hostname(), port)
data = strings.ReplaceAll(data, ":"+port, "")
if parsed.Path == "" {
parsed.Path = "/"
}
}
if isRaw {
// do not swap parameters from parsedURL to base
return data, parsed
}
// transfer any parmas from URL to data( i.e {{BaseURL}} )
params := urlutil.GetParams(parsed.Query())
if len(params) == 0 {
return data, parsed
}
// remove any existing params from parsedInput (tracked using params)
// parsed.RawQuery = ""
// ex: {{BaseURL}}/metrics?user=xxx
dataURLrelpath := strings.TrimLeft(data, "{{BaseURL}}") //nolint:all
if dataURLrelpath == "" || dataURLrelpath == "/" {
// just attach raw query to data
dataURLrelpath += "?" + params.Encode()
} else {
// /?action=x or /metrics/ parse it
payloadpath, err := url.Parse(dataURLrelpath)
if err != nil {
// payload not possible to parse (edgecase)
dataURLrelpath += "?" + params.Encode()
} else {
payloadparams := urlutil.GetParams(payloadpath.Query())
if len(payloadparams) != 0 {
// ex: /?action=x
for k := range payloadparams {
params.Add(k, payloadparams.Get(k))
}
}
//ex: /?admin=user&action=x
payloadpath.RawQuery = params.Encode()
dataURLrelpath = payloadpath.String()
}
}
data = "{{BaseURL}}" + dataURLrelpath
parsed.RawQuery = ""
return data, parsed
}
// MakeHTTPRequestFromModel creates a *http.Request from a request template
func (r *requestGenerator) makeHTTPRequestFromModel(ctx context.Context, data string, values, generatorValues map[string]interface{}) (*generatedRequest, error) {
if r.options.Interactsh != nil {
@ -265,7 +204,7 @@ func (r *requestGenerator) makeHTTPRequestFromModel(ctx context.Context, data st
}
// Build a request on the specified URL
req, err := http.NewRequestWithContext(ctx, method, data, nil)
req, err := retryablehttp.NewRequestWithContext(ctx, method, data, nil)
if err != nil {
return nil, err
}
@ -311,7 +250,6 @@ func (r *requestGenerator) handleRawWithPayloads(ctx context.Context, rawRequest
return unsafeReq, nil
}
// retryablehttp
var body io.ReadCloser
body = io.NopCloser(strings.NewReader(rawRequestData.Data))
if r.request.Race {
@ -320,7 +258,7 @@ func (r *requestGenerator) handleRawWithPayloads(ctx context.Context, rawRequest
body = race.NewOpenGateWithTimeout(body, time.Duration(2)*time.Second)
}
req, err := http.NewRequestWithContext(ctx, rawRequestData.Method, rawRequestData.FullURL, body)
req, err := retryablehttp.NewRequestWithContext(ctx, rawRequestData.Method, rawRequestData.FullURL, body)
if err != nil {
return nil, err
}
@ -347,7 +285,7 @@ func (r *requestGenerator) handleRawWithPayloads(ctx context.Context, rawRequest
}
if reqWithAnnotations, cancelFunc, hasAnnotations := r.request.parseAnnotations(rawRequest, req); hasAnnotations {
generatedRequest.request.Request = reqWithAnnotations
generatedRequest.request = reqWithAnnotations
generatedRequest.customCancelFunction = cancelFunc
}
@ -355,7 +293,7 @@ func (r *requestGenerator) handleRawWithPayloads(ctx context.Context, rawRequest
}
// fillRequest fills various headers in the request with values
func (r *requestGenerator) fillRequest(req *http.Request, values map[string]interface{}) (*retryablehttp.Request, error) {
func (r *requestGenerator) fillRequest(req *retryablehttp.Request, values map[string]interface{}) (*retryablehttp.Request, error) {
// Set the header values requested
for header, value := range r.request.Headers {
if r.options.Interactsh != nil {
@ -386,7 +324,11 @@ func (r *requestGenerator) fillRequest(req *http.Request, values map[string]inte
if err != nil {
return nil, errors.Wrap(err, evaluateHelperExpressionErrorMessage)
}
req.Body = io.NopCloser(strings.NewReader(body))
bodyreader, err := readerutil.NewReusableReadCloser([]byte(body))
if err != nil {
return nil, errors.Wrap(err, "failed to create reusable reader for request body")
}
req.Body = bodyreader
}
if !r.request.Unsafe {
setHeader(req, "User-Agent", uarand.GetRandom())
@ -407,24 +349,19 @@ func (r *requestGenerator) fillRequest(req *http.Request, values map[string]inte
}
}
filledRequest, err := retryablehttp.FromRequest(req)
if err != nil {
return nil, err
}
if r.request.DigestAuthUsername != "" {
filledRequest.Auth = &retryablehttp.Auth{
req.Auth = &retryablehttp.Auth{
Type: retryablehttp.DigestAuth,
Username: r.request.DigestAuthUsername,
Password: r.request.DigestAuthPassword,
}
}
return filledRequest, nil
return req, nil
}
// setHeader sets some headers only if the header wasn't supplied by the user
func setHeader(req *http.Request, name, value string) {
func setHeader(req *retryablehttp.Request, name, value string) {
if _, ok := req.Header[name]; !ok {
req.Header.Set(name, value)
}
@ -432,3 +369,47 @@ func setHeader(req *http.Request, name, value string) {
req.Host = value
}
}
// UsePortFromPayload overrides input port if specified in payload(ex: {{BaseURL}}:8080)
func UsePortFromPayload(parsed *urlutil.URL, data string) (*urlutil.URL, string) {
matches := urlWithPortRegex.FindAllStringSubmatch(data, -1)
if len(matches) > 0 {
port := matches[0][1]
parsed.UpdatePort(port)
// remove it from dsl
data = strings.Replace(data, ":"+port, "", 1)
}
return parsed, data
}
// If input/target contains any parameters add them to payload preserving the order
func addParamsToBaseURL(data string, parsed *urlutil.URL) (string, *urlutil.URL) {
// preprocess
payloadPath := strings.TrimPrefix(data, "{{BaseURL}}")
if strings.HasSuffix(parsed.Path, "/") && strings.HasPrefix(payloadPath, "/") {
// keeping payload intact trim extra slash from input
parsed.Path = strings.TrimSuffix(parsed.Path, "/")
}
if payloadPath == "" {
return data, parsed
}
if strings.HasPrefix(payloadPath, "?") {
// does not contain path only params
payloadPath = strings.TrimPrefix(payloadPath, "?")
payloadParams := make(urlutil.Params)
payloadParams.Decode(payloadPath)
payloadParams.Merge(parsed.Params)
return "{{BaseURL}}?" + payloadParams.Encode(), parsed
}
// If payload has path parse it add automerge parameters with proper preference
payloadURL, err := urlutil.ParseURL(payloadPath, true)
if err != nil {
gologger.Debug().Msgf("failed to parse payload %v and %v.skipping param merge", data, parsed.String())
return data, parsed
}
payloadURL.Params.Merge(parsed.Params)
payloadPath = "{{BaseURL}}" + payloadURL.GetRelativePath()
return payloadPath, parsed
}

View File

@ -2,7 +2,6 @@ package http
import (
"context"
"net/url"
"testing"
"time"
@ -14,14 +13,15 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
urlutil "github.com/projectdiscovery/utils/url"
)
func TestBaseURLWithTemplatePrefs(t *testing.T) {
baseURL := "http://localhost:53/test"
parsed, _ := url.Parse(baseURL)
parsed, _ := urlutil.Parse(baseURL)
data := "{{BaseURL}}:8000/newpath"
data, parsed = baseURLWithTemplatePrefs(data, parsed, false)
parsed, data = UsePortFromPayload(parsed, data)
require.Equal(t, "http://localhost:8000/test", parsed.String(), "could not get correct value")
require.Equal(t, "{{BaseURL}}/newpath", data, "could not get correct data")
}
@ -53,7 +53,9 @@ func TestMakeRequestFromModal(t *testing.T) {
inputData, payloads, _ := generator.nextValue()
req, err := generator.Make(context.Background(), contextargs.NewWithInput("https://example.com"), inputData, payloads, map[string]interface{}{})
require.Nil(t, err, "could not make http request")
if req.request.URL == nil {
t.Fatalf("url is nil in generator make")
}
bodyBytes, _ := req.request.BodyBytes()
require.Equal(t, "/login.php", req.request.URL.Path, "could not get correct request path")
require.Equal(t, "username=test&password=pass", string(bodyBytes), "could not get correct request body")

View File

@ -1,7 +1,6 @@
package fuzz
import (
"net/url"
"regexp"
"strings"
@ -9,12 +8,13 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
"github.com/projectdiscovery/retryablehttp-go"
urlutil "github.com/projectdiscovery/utils/url"
)
// ExecuteRuleInput is the input for rule Execute function
type ExecuteRuleInput struct {
// URL is the URL for the request
URL *url.URL
URL *urlutil.URL
// Callback is the callback for generated rule requests
Callback func(GeneratedRequest) bool
// InteractURLs contains interact urls for execute call
@ -69,7 +69,7 @@ func (rule *Rule) Execute(input *ExecuteRuleInput) error {
}
// isExecutable returns true if the rule can be executed based on provided input
func (rule *Rule) isExecutable(parsed *url.URL) bool {
func (rule *Rule) isExecutable(parsed *urlutil.URL) bool {
if len(parsed.Query()) > 0 && rule.partType == queryPartType {
return true
}

View File

@ -1,9 +1,9 @@
package fuzz
import (
"net/url"
"testing"
urlutil "github.com/projectdiscovery/utils/url"
"github.com/stretchr/testify/require"
)
@ -12,11 +12,11 @@ func TestRuleIsExecutable(t *testing.T) {
err := rule.Compile(nil, nil)
require.NoError(t, err, "could not compile rule")
parsed, _ := url.Parse("https://example.com/?url=localhost")
parsed, _ := urlutil.Parse("https://example.com/?url=localhost")
result := rule.isExecutable(parsed)
require.True(t, result, "could not get correct result")
parsed, _ = url.Parse("https://example.com/")
parsed, _ = urlutil.Parse("https://example.com/")
result = rule.isExecutable(parsed)
require.False(t, result, "could not get correct result")
}

View File

@ -4,7 +4,6 @@ import (
"context"
"io"
"net/http"
"net/url"
"strings"
"github.com/corpix/uarand"
@ -25,10 +24,13 @@ func (rule *Rule) executePartRule(input *ExecuteRuleInput, payload string) error
// executeQueryPartRule executes query part rules
func (rule *Rule) executeQueryPartRule(input *ExecuteRuleInput, payload string) error {
requestURL := *input.URL
temp := urlutil.NewParams()
requestURL := input.URL.Clone()
temp := urlutil.Params{}
for k, v := range input.URL.Query() {
temp[k] = v
// this has to be a deep copy
x := []string{}
x = append(x, v...)
temp[k] = x
}
for key, values := range input.URL.Query() {
@ -41,7 +43,7 @@ func (rule *Rule) executeQueryPartRule(input *ExecuteRuleInput, payload string)
temp[key][i] = evaluated
if rule.modeType == singleModeType {
requestURL.RawQuery = temp.Encode()
requestURL.Params = temp
if err := rule.buildQueryInput(input, requestURL, input.InteractURLs); err != nil {
return err
}
@ -51,7 +53,7 @@ func (rule *Rule) executeQueryPartRule(input *ExecuteRuleInput, payload string)
}
if rule.modeType == multipleModeType {
requestURL.RawQuery = temp.Encode()
requestURL.Params = temp
if err := rule.buildQueryInput(input, requestURL, input.InteractURLs); err != nil {
return err
}
@ -60,25 +62,24 @@ func (rule *Rule) executeQueryPartRule(input *ExecuteRuleInput, payload string)
}
// buildQueryInput returns created request for a Query Input
func (rule *Rule) buildQueryInput(input *ExecuteRuleInput, parsed url.URL, interactURLs []string) error {
var req *http.Request
func (rule *Rule) buildQueryInput(input *ExecuteRuleInput, parsed *urlutil.URL, interactURLs []string) error {
var req *retryablehttp.Request
var err error
if input.BaseRequest == nil {
req, err = http.NewRequest(http.MethodGet, parsed.String(), nil)
req, err = retryablehttp.NewRequest(http.MethodGet, parsed.String(), nil)
if err != nil {
return err
}
req.Header.Set("User-Agent", uarand.GetRandom())
} else {
req = input.BaseRequest.Clone(context.Background())
req.URL = &parsed
}
httpreq, err := retryablehttp.FromRequest(req)
if err != nil {
return err
req = input.BaseRequest.Clone(context.TODO())
//TODO: abstract below 3 lines with `req.UpdateURL(xx *urlutil.URL)`
req.URL = parsed
req.Request.URL = parsed.URL
req.Update()
}
request := GeneratedRequest{
Request: httpreq,
Request: req,
InteractURLs: interactURLs,
DynamicValues: input.Values,
}

View File

@ -1,16 +1,16 @@
package fuzz
import (
"net/url"
"testing"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
urlutil "github.com/projectdiscovery/utils/url"
"github.com/stretchr/testify/require"
)
func TestExecuteQueryPartRule(t *testing.T) {
parsed, _ := url.Parse("http://localhost:8080/?url=localhost&mode=multiple&file=passwdfile")
parsed, _ := urlutil.Parse("http://localhost:8080/?url=localhost&mode=multiple&file=passwdfile")
options := &protocols.ExecuterOptions{
Interactsh: &interactsh.Client{},
}

View File

@ -15,7 +15,7 @@ type HTTPMethodType int
const (
// name:GET
HTTPGet HTTPMethodType = iota + 1
// name:GET
// name:HEAD
HTTPHead
// name:POST
HTTPPost

View File

@ -6,11 +6,10 @@ import (
"errors"
"fmt"
"io"
"net/url"
"strings"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/utils"
"github.com/projectdiscovery/rawhttp/client"
errorutil "github.com/projectdiscovery/utils/errors"
stringsutil "github.com/projectdiscovery/utils/strings"
urlutil "github.com/projectdiscovery/utils/url"
)
@ -29,35 +28,10 @@ type Request struct {
// Parse parses the raw request as supplied by the user
func Parse(request, baseURL string, unsafe bool) (*Request, error) {
// parse Input URL
inputURL, err := url.Parse(baseURL)
inputURL, err := urlutil.ParseURL(baseURL, unsafe)
if err != nil {
return nil, fmt.Errorf("could not parse request URL: %w", err)
return nil, errorutil.NewWithErr(err).Msgf("could not parse request URL").WithTag("raw")
}
inputParams := urlutil.GetParams(inputURL.Query())
// Joins input url and new url preserving query parameters
joinPath := func(relpath string) (string, error) {
newpath := ""
// Join path with input along with parameters
relUrl, relerr := url.Parse(relpath)
if relUrl == nil {
// special case when url.Parse fails
newpath = utils.JoinURLPath(inputURL.Path, relpath)
} else {
newpath = utils.JoinURLPath(inputURL.Path, relUrl.Path)
if len(relUrl.Query()) > 0 {
relParam := urlutil.GetParams(relUrl.Query())
for k := range relParam {
inputParams.Add(k, relParam.Get(k))
}
}
}
if len(inputParams) > 0 {
newpath += "?" + inputParams.Encode()
}
return newpath, relerr
}
rawrequest, err := readRawRequest(request, unsafe)
if err != nil {
return nil, err
@ -67,25 +41,39 @@ func Parse(request, baseURL string, unsafe bool) (*Request, error) {
// If path is empty do not tamper input url (see doc)
// can be omitted but makes things clear
case rawrequest.Path == "":
rawrequest.Path, _ = joinPath("")
rawrequest.Path = inputURL.GetRelativePath()
// full url provided instead of rel path
case strings.HasPrefix(rawrequest.Path, "http") && !unsafe:
var parseErr error
rawrequest.Path, parseErr = joinPath(rawrequest.Path)
if parseErr != nil {
return nil, fmt.Errorf("could not parse url:%w", parseErr)
urlx, err := urlutil.ParseURL(rawrequest.Path, true)
if err != nil {
return nil, errorutil.NewWithErr(err).WithTag("raw").Msgf("failed to parse url %v from template", rawrequest.Path)
}
cloned := inputURL.Clone()
parseErr := cloned.MergePath(urlx.GetRelativePath(), true)
if parseErr != nil {
return nil, errorutil.NewWithTag("raw", "could not automergepath for template path %v", urlx.GetRelativePath()).Wrap(parseErr)
}
rawrequest.Path = cloned.GetRelativePath()
// If unsafe changes must be made in raw request string iteself
case unsafe:
prevPath := rawrequest.Path
unsafeRelativePath, _ := joinPath(rawrequest.Path)
cloned := inputURL.Clone()
err := cloned.MergePath(rawrequest.Path, true)
unsafeRelativePath := cloned.GetRelativePath()
if err != nil {
return nil, errorutil.NewWithErr(err).WithTag("raw").Msgf("failed to automerge %v from unsafe template", rawrequest.Path)
}
// replace itself
rawrequest.UnsafeRawBytes = bytes.Replace(rawrequest.UnsafeRawBytes, []byte(prevPath), []byte(unsafeRelativePath), 1)
default:
rawrequest.Path, _ = joinPath(rawrequest.Path)
cloned := inputURL.Clone()
parseErr := cloned.MergePath(rawrequest.Path, true)
if parseErr != nil {
return nil, errorutil.NewWithTag("raw", "could not automergepath for template path %v", rawrequest.Path).Wrap(parseErr)
}
rawrequest.Path = cloned.GetRelativePath()
}
if !unsafe {
@ -96,7 +84,6 @@ func Parse(request, baseURL string, unsafe bool) (*Request, error) {
}
return rawrequest, nil
}
// reads raw request line by line following convention

View File

@ -8,7 +8,6 @@ import (
"io"
"net/http"
"net/http/httputil"
"net/url"
"path"
"strings"
"sync"
@ -38,6 +37,7 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/types"
"github.com/projectdiscovery/rawhttp"
stringsutil "github.com/projectdiscovery/utils/strings"
urlutil "github.com/projectdiscovery/utils/url"
)
const defaultMaxWorkers = 150
@ -121,7 +121,6 @@ func (request *Request) executeRaceRequest(input *contextargs.Context, previous
// executeRaceRequest executes parallel requests for a template
func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicValues output.InternalEvent, callback protocols.OutputEventCallback) error {
generator := request.newGenerator(false)
// Workers that keeps enqueuing new requests
maxWorkers := request.Threads
swg := sizedwaitgroup.New(maxWorkers)
@ -170,7 +169,7 @@ func (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValu
generator := request.newGenerator(false)
// need to extract the target from the url
URL, err := url.Parse(input.MetaInput.Input)
URL, err := urlutil.Parse(input.MetaInput.Input)
if err != nil {
return err
}
@ -230,7 +229,7 @@ func (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValu
// executeFuzzingRule executes fuzzing request for a URL
func (request *Request) executeFuzzingRule(input *contextargs.Context, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
parsed, err := url.Parse(input.MetaInput.Input)
parsed, err := urlutil.Parse(input.MetaInput.Input)
if err != nil {
return errors.Wrap(err, "could not parse url")
}
@ -508,7 +507,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
if generatedRequest.original.Pipeline {
if generatedRequest.rawRequest != nil {
formedURL = generatedRequest.rawRequest.FullURL
if parsed, parseErr := url.Parse(formedURL); parseErr == nil {
if parsed, parseErr := urlutil.ParseURL(formedURL, true); parseErr == nil {
hostname = parsed.Host
}
resp, err = generatedRequest.pipelinedClient.DoRaw(generatedRequest.rawRequest.Method, input.MetaInput.Input, generatedRequest.rawRequest.Path, generators.ExpandMapValues(generatedRequest.rawRequest.Headers), io.NopCloser(strings.NewReader(generatedRequest.rawRequest.Data)))
@ -519,7 +518,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
formedURL = generatedRequest.rawRequest.FullURL
// use request url as matched url if empty
if formedURL == "" {
urlx, err := url.Parse(input.MetaInput.Input)
urlx, err := urlutil.Parse(input.MetaInput.Input)
if err != nil {
formedURL = fmt.Sprintf("%s%s", formedURL, generatedRequest.rawRequest.Path)
} else {
@ -527,7 +526,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
formedURL = fmt.Sprintf("%v://%v", urlx.Scheme, path.Join(urlx.Host, generatedRequest.rawRequest.Path))
}
}
if parsed, parseErr := url.Parse(formedURL); parseErr == nil {
if parsed, parseErr := urlutil.ParseURL(formedURL, true); parseErr == nil {
hostname = parsed.Host
}
options := *generatedRequest.original.rawhttpClient.Options
@ -768,19 +767,16 @@ func (request *Request) handleSignature(generatedRequest *generatedRequest) erro
switch request.Signature.Value {
case AWSSignature:
var awsSigner signer.Signer
vars := request.options.Options.Vars.AsMap()
allvars := generators.MergeMaps(request.options.Options.Vars.AsMap(), generatedRequest.dynamicValues)
awsopts := signer.AWSOptions{
AwsID: types.ToString(vars["aws-id"]),
AwsSecretToken: types.ToString(vars["aws-secret"]),
AwsID: types.ToString(allvars["aws-id"]),
AwsSecretToken: types.ToString(allvars["aws-secret"]),
}
// type ctxkey string
ctx := context.WithValue(context.Background(), signer.SignerArg("service"), generatedRequest.dynamicValues["service"])
ctx = context.WithValue(ctx, signer.SignerArg("region"), generatedRequest.dynamicValues["region"])
awsSigner, err := signerpool.Get(request.options.Options, &signerpool.Configuration{SignerArgs: &awsopts})
if err != nil {
return err
}
ctx := signer.GetCtxWithArgs(allvars, signer.AwsDefaultVars)
err = awsSigner.SignHTTP(ctx, generatedRequest.request.Request)
if err != nil {
return err

View File

@ -3,15 +3,14 @@ package http
import (
"context"
"net"
"net/http"
"regexp"
"strings"
"time"
"github.com/projectdiscovery/fastdialer/fastdialer"
"github.com/projectdiscovery/retryablehttp-go"
iputil "github.com/projectdiscovery/utils/ip"
stringsutil "github.com/projectdiscovery/utils/strings"
urlutil "github.com/projectdiscovery/utils/url"
)
var (
@ -49,7 +48,7 @@ func parseFlowAnnotations(rawRequest string) (flowMark, bool) {
}
// parseAnnotations and override requests settings
func (r *Request) parseAnnotations(rawRequest string, request *http.Request) (*http.Request, context.CancelFunc, bool) {
func (r *Request) parseAnnotations(rawRequest string, request *retryablehttp.Request) (*retryablehttp.Request, context.CancelFunc, bool) {
var (
modified bool
cancelFunc context.CancelFunc
@ -62,9 +61,9 @@ func (r *Request) parseAnnotations(rawRequest string, request *http.Request) (*h
// handle scheme
switch {
case stringsutil.HasPrefixI(value, "http://"):
request.URL.Scheme = urlutil.HTTP
request.URL.Scheme = "http"
case stringsutil.HasPrefixI(value, "https://"):
request.URL.Scheme = urlutil.HTTPS
request.URL.Scheme = "https"
}
value = stringsutil.TrimPrefixAny(value, "http://", "https://")
@ -116,7 +115,6 @@ func (r *Request) parseAnnotations(rawRequest string, request *http.Request) (*h
request = request.Clone(ctx)
}
}
return request, cancelFunc, modified
}

View File

@ -6,6 +6,7 @@ import (
"testing"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/httpclientpool"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/stretchr/testify/require"
)
@ -18,7 +19,7 @@ func TestRequestParseAnnotationsTimeout(t *testing.T) {
GET / HTTP/1.1
Host: {{Hostname}}`
httpReq, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
httpReq, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com", nil)
require.Nil(t, err, "could not create http request")
newRequest, cancelFunc, modified := request.parseAnnotations(rawRequest, httpReq)
@ -35,7 +36,7 @@ func TestRequestParseAnnotationsTimeout(t *testing.T) {
rawRequest := `GET / HTTP/1.1
Host: {{Hostname}}`
httpReq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://example.com", nil)
httpReq, err := retryablehttp.NewRequestWithContext(context.Background(), http.MethodGet, "https://example.com", nil)
require.Nil(t, err, "could not create http request")
newRequest, cancelFunc, modified := request.parseAnnotations(rawRequest, httpReq)

View File

@ -96,13 +96,3 @@ func GetVariablesNamesSkipList(signature SignatureType) map[string]interface{} {
return nil
}
}
// GetVariablesNamesSkipList depending on the signature type
func GetVariablesDefault(signature SignatureType) map[string]interface{} {
switch signature {
case AWSSignature:
return signer.AwsDefaultVars
default:
return nil
}
}

View File

@ -114,7 +114,8 @@ var AwsSkipList = map[string]interface{}{
}
var AwsDefaultVars = map[string]interface{}{
"region": "us-east-2",
"region": "us-east-2",
"service": "sts",
}
var AwsInternalOnlyVars = map[string]interface{}{

View File

@ -4,6 +4,8 @@ import (
"context"
"errors"
"net/http"
"github.com/projectdiscovery/nuclei/v2/pkg/types"
)
// An Argument that can be passed to Signer
@ -32,3 +34,21 @@ func NewSigner(args SignerArgs) (signer Signer, err error) {
return nil, errors.New("unknown signature arguments type")
}
}
// GetCtxWithArgs creates and returns context with signature args
func GetCtxWithArgs(maps ...map[string]interface{}) context.Context {
var region, service string
for _, v := range maps {
for key, val := range v {
if key == "region" && region == "" {
region = types.ToString(val)
}
if key == "service" && service == "" {
service = types.ToString(val)
}
}
}
// type ctxkey string
ctx := context.WithValue(context.Background(), SignerArg("service"), service)
return context.WithValue(ctx, SignerArg("region"), region)
}

View File

@ -4,7 +4,6 @@ import (
"bytes"
"compress/gzip"
"compress/zlib"
"context"
"io"
"net/http"
"net/http/httputil"
@ -115,26 +114,7 @@ func normalizeResponseBody(resp *http.Response, response *redirectedResponse) er
// dump creates a dump of the http request in form of a byte slice
func dump(req *generatedRequest, reqURL string) ([]byte, error) {
if req.request != nil {
cloned := req.request.Clone(context.Background())
// Create a copy on the fly of the request body - ignore errors
bodyBytes, _ := req.request.BodyBytes()
var dumpBody bool
if len(bodyBytes) > 0 {
dumpBody = true
cloned.ContentLength = int64(len(bodyBytes))
cloned.Body = io.NopCloser(bytes.NewReader(bodyBytes))
} else {
cloned.ContentLength = 0
cloned.Body = nil
delete(cloned.Header, "Content-length")
}
dumpBytes, err := httputil.DumpRequestOut(cloned, dumpBody)
if err != nil {
return nil, err
}
return dumpBytes, nil
return req.request.Dump()
}
rawHttpOptions := &rawhttp.Options{CustomHeaders: req.rawRequest.UnsafeHeaders, CustomRawBytes: req.rawRequest.UnsafeRawBytes}
return rawhttp.DumpRequestRaw(req.rawRequest.Method, reqURL, req.rawRequest.Path, generators.ExpandMapValues(req.rawRequest.Headers), io.NopCloser(strings.NewReader(req.rawRequest.Data)), rawHttpOptions)

View File

@ -1,39 +0,0 @@
package utils
import (
"fmt"
"path"
"strings"
)
// Joins two relative paths and handles trailing slash edgecase
func JoinURLPath(elem1 string, elem2 string) string {
/*
Trailing Slash EdgeCase
Path.Join converts /test/ to /test
this should be handled manually
*/
// if any one of path is empty path.Join removes trailing slash
if elem2 == "" {
return elem1
} else if elem1 == "" {
return elem2
}
if elem2 == "/" || elem2 == "/?" {
// check for extra slash
if strings.HasSuffix(elem1, "/") && strings.HasPrefix(elem2, "/") {
elem1 = strings.TrimRight(elem1, "/")
}
// merge and return
return fmt.Sprintf("%v%v", elem1, elem2)
} else {
if strings.HasPrefix(elem2, "?") {
// path2 is parameter and not a url append and return
return fmt.Sprintf("%v%v", elem1, elem2)
}
// Note:
// path.Join implicitly calls path.Clean so any relative paths are filtered
// if not encoded properly
return path.Join(elem1, elem2)
}
}

View File

@ -1,29 +0,0 @@
package utils_test
import (
"fmt"
"path"
"testing"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/utils"
)
func TestURLJoin(t *testing.T) {
fmt.Println(path.Join("/wp-content", "/wp-content/admin.php"))
testcases := []struct {
URL1 string
URL2 string
ExpectedJoin string
}{
{"/test/", "", "/test/"},
{"/test", "/", "/test/"},
{"/test", "?param=true", "/test?param=true"},
{"/test/", "/", "/test/"},
}
for _, v := range testcases {
res := utils.JoinURLPath(v.URL1, v.URL2)
if res != v.ExpectedJoin {
t.Errorf("failed to join urls expected %v but got %v", v.ExpectedJoin, res)
}
}
}

View File

@ -2,18 +2,18 @@ package utils
import (
"fmt"
"net/url"
"path"
"strings"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/dns"
urlutil "github.com/projectdiscovery/utils/url"
)
// GenerateVariables will create default variables with context args
func GenerateVariablesWithContextArgs(input *contextargs.Context, trailingSlash bool) map[string]interface{} {
parsed, err := url.Parse(input.MetaInput.Input)
parsed, err := urlutil.Parse(input.MetaInput.Input)
if err != nil {
return nil
}
@ -21,7 +21,7 @@ func GenerateVariablesWithContextArgs(input *contextargs.Context, trailingSlash
}
// GenerateVariables will create default variables after parsing a url with additional variables
func GenerateVariablesWithURL(parsed *url.URL, trailingSlash bool, additionalVars map[string]interface{}) map[string]interface{} {
func GenerateVariablesWithURL(parsed *urlutil.URL, trailingSlash bool, additionalVars map[string]interface{}) map[string]interface{} {
domain := parsed.Host
if strings.Contains(parsed.Host, ":") {
domain = strings.Split(parsed.Host, ":")[0]

View File

@ -1,16 +1,16 @@
package utils
import (
"net/url"
"testing"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
urlutil "github.com/projectdiscovery/utils/url"
"github.com/stretchr/testify/require"
)
func TestVariables(t *testing.T) {
baseURL := "http://localhost:9001/test/123"
parsed, _ := url.Parse(baseURL)
parsed, _ := urlutil.Parse(baseURL)
values := GenerateVariablesWithURL(parsed, true, nil)
require.Equal(t, values["BaseURL"], parsed.String(), "incorrect baseurl")
@ -23,7 +23,7 @@ func TestVariables(t *testing.T) {
require.Equal(t, values["Hostname"], "localhost:9001", "incorrect hostname")
baseURL = "https://example.com"
parsed, _ = url.Parse(baseURL)
parsed, _ = urlutil.Parse(baseURL)
values = GenerateVariablesWithURL(parsed, false, nil)
require.Equal(t, values["BaseURL"], parsed.String(), "incorrect baseurl")
@ -35,7 +35,7 @@ func TestVariables(t *testing.T) {
require.Equal(t, values["Hostname"], "example.com", "incorrect hostname")
baseURL = "ftp://foobar.com/"
parsed, _ = url.Parse(baseURL)
parsed, _ = urlutil.Parse(baseURL)
values = GenerateVariablesWithURL(parsed, true, nil)
require.Equal(t, values["BaseURL"], parsed.String(), "incorrect baseurl")

View File

@ -13,6 +13,7 @@ import (
"github.com/projectdiscovery/fastdialer/fastdialer"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v2/pkg/model"
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
@ -77,6 +78,17 @@ type Request struct {
options *protocols.ExecuterOptions
}
// CanCluster returns true if the request can be clustered.
func (request *Request) CanCluster(other *Request) bool {
if len(request.CiperSuites) > 0 || request.MinVersion != "" || request.MaxVersion != "" {
return false
}
if request.Address != other.Address || request.ScanMode != other.ScanMode {
return false
}
return true
}
// Compile compiles the request generators preparing any requests possible.
func (request *Request) Compile(options *protocols.ExecuterOptions) error {
request.options = options
@ -126,6 +138,11 @@ func (request *Request) Compile(options *protocols.ExecuterOptions) error {
return nil
}
// Options returns executer options for http request
func (r *Request) Options() *protocols.ExecuterOptions {
return r.options
}
// Requests returns the total number of requests the rule will perform
func (request *Request) Requests() int {
return 1
@ -315,9 +332,9 @@ func (request *Request) Type() templateTypes.ProtocolType {
func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {
data := &output.ResultEvent{
TemplateID: types.ToString(request.options.TemplateID),
TemplatePath: types.ToString(request.options.TemplatePath),
Info: request.options.TemplateInfo,
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
Info: wrapped.InternalEvent["template-info"].(model.Info),
Type: types.ToString(wrapped.InternalEvent["type"]),
Host: types.ToString(wrapped.InternalEvent["host"]),
Matched: types.ToString(wrapped.InternalEvent["host"]),

View File

@ -12,7 +12,6 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/writer"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http"
"github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
cryptoutil "github.com/projectdiscovery/utils/crypto"
)
@ -24,10 +23,10 @@ import (
// request which saves time and network resources during execution.
//
// The clusterer goes through all the templates, looking for templates with a single
// HTTP request to an endpoint (multiple requests aren't clustered as of now).
// HTTP/DNS/TLS request to an endpoint (multiple requests aren't clustered as of now).
//
// All the templates are iterated and any templates with request that is identical
// to the first individual HTTP request is compared for equality.
// to the first individual request is compared for equality.
// The equality check is performed as described below -
//
// Cases where clustering is not perfomed (request is considered different)
@ -35,6 +34,8 @@ import (
// - If request methods,max-redirects,cookie-reuse,redirects are not equal
// - If request paths aren't identical.
// - If request headers aren't identical
// - Similarly for DNS, only identical DNS requests are clustered to a target.
// - Similarly for TLS, only identical TLS requests are clustered to a target.
//
// If multiple requests are identified as identical, they are appended to a slice.
// Finally, the engine creates a single executer with a clusteredexecuter for all templates
@ -44,34 +45,57 @@ func Cluster(list map[string]*Template) [][]*Template {
// Each protocol that can be clustered should be handled here.
for key, template := range list {
// We only cluster http requests as of now.
// We only cluster http and dns requests as of now.
// Take care of requests that can't be clustered first.
if len(template.RequestsHTTP) == 0 {
if len(template.RequestsHTTP) == 0 && len(template.RequestsDNS) == 0 && len(template.RequestsSSL) == 0 {
delete(list, key)
final = append(final, []*Template{template})
continue
}
delete(list, key) // delete element first so it's not found later.
var templateType types.ProtocolType
switch {
case len(template.RequestsDNS) == 1:
templateType = types.DNSProtocol
case len(template.RequestsHTTP) == 1:
templateType = types.HTTPProtocol
case len(template.RequestsSSL) == 1:
templateType = types.SSLProtocol
}
// Find any/all similar matching request that is identical to
// this one and cluster them together for http protocol only.
if len(template.RequestsHTTP) == 1 {
cluster := []*Template{}
for otherKey, other := range list {
if len(other.RequestsHTTP) == 0 {
cluster := []*Template{}
for otherKey, other := range list {
switch templateType {
case types.DNSProtocol:
if len(other.RequestsDNS) == 0 || len(other.RequestsDNS) > 1 {
continue
} else if template.RequestsDNS[0].CanCluster(other.RequestsDNS[0]) {
delete(list, otherKey)
cluster = append(cluster, other)
}
if template.RequestsHTTP[0].CanCluster(other.RequestsHTTP[0]) {
case types.HTTPProtocol:
if len(other.RequestsHTTP) == 0 || len(other.RequestsHTTP) > 1 {
continue
} else if template.RequestsHTTP[0].CanCluster(other.RequestsHTTP[0]) {
delete(list, otherKey)
cluster = append(cluster, other)
}
case types.SSLProtocol:
if len(other.RequestsSSL) == 0 || len(other.RequestsSSL) > 1 {
continue
} else if template.RequestsSSL[0].CanCluster(other.RequestsSSL[0]) {
delete(list, otherKey)
cluster = append(cluster, other)
}
}
if len(cluster) > 0 {
cluster = append(cluster, template)
final = append(final, cluster)
continue
}
}
if len(cluster) > 0 {
cluster = append(cluster, template)
final = append(final, cluster)
continue
}
final = append(final, []*Template{template})
}
@ -107,15 +131,23 @@ func ClusterTemplates(templatesList []*Template, options protocols.ExecuterOptio
executerOpts := options
clusterID := fmt.Sprintf("cluster-%s", ClusterID(cluster))
for _, req := range cluster[0].RequestsDNS {
req.Options().TemplateID = clusterID
}
for _, req := range cluster[0].RequestsHTTP {
req.Options().TemplateID = clusterID
}
for _, req := range cluster[0].RequestsSSL {
req.Options().TemplateID = clusterID
}
executerOpts.TemplateID = clusterID
finalTemplatesList = append(finalTemplatesList, &Template{
ID: clusterID,
RequestsDNS: cluster[0].RequestsDNS,
RequestsHTTP: cluster[0].RequestsHTTP,
RequestsSSL: cluster[0].RequestsSSL,
Executer: NewClusterExecuter(cluster, &executerOpts),
TotalRequests: len(cluster[0].RequestsHTTP),
TotalRequests: len(cluster[0].RequestsHTTP) + len(cluster[0].RequestsDNS),
})
clusterCount += len(cluster)
} else {
@ -128,12 +160,11 @@ func ClusterTemplates(templatesList []*Template, options protocols.ExecuterOptio
// ClusterExecuter executes a group of requests for a protocol for a clustered
// request. It is different from normal executers since the original
// operators are all combined and post processed after making the request.
//
// TODO: We only cluster http requests as of now.
type ClusterExecuter struct {
requests *http.Request
operators []*clusteredOperator
options *protocols.ExecuterOptions
requests protocols.Request
operators []*clusteredOperator
templateType types.ProtocolType
options *protocols.ExecuterOptions
}
type clusteredOperator struct {
@ -147,22 +178,41 @@ var _ protocols.Executer = &ClusterExecuter{}
// NewClusterExecuter creates a new request executer for list of requests
func NewClusterExecuter(requests []*Template, options *protocols.ExecuterOptions) *ClusterExecuter {
executer := &ClusterExecuter{
options: options,
requests: requests[0].RequestsHTTP[0],
executer := &ClusterExecuter{options: options}
if len(requests[0].RequestsDNS) == 1 {
executer.templateType = types.DNSProtocol
executer.requests = requests[0].RequestsDNS[0]
} else if len(requests[0].RequestsHTTP) == 1 {
executer.templateType = types.HTTPProtocol
executer.requests = requests[0].RequestsHTTP[0]
} else if len(requests[0].RequestsSSL) == 1 {
executer.templateType = types.SSLProtocol
executer.requests = requests[0].RequestsSSL[0]
}
appendOperator := func(req *Template, operator *operators.Operators) {
operator.TemplateID = req.ID
operator.ExcludeMatchers = options.ExcludeMatchers
executer.operators = append(executer.operators, &clusteredOperator{
operator: operator,
templateID: req.ID,
templateInfo: req.Info,
templatePath: req.Path,
})
}
for _, req := range requests {
if req.RequestsHTTP[0].CompiledOperators != nil {
operator := req.RequestsHTTP[0].CompiledOperators
operator.TemplateID = req.ID
operator.ExcludeMatchers = options.ExcludeMatchers
executer.operators = append(executer.operators, &clusteredOperator{
operator: operator,
templateID: req.ID,
templateInfo: req.Info,
templatePath: req.Path,
})
if executer.templateType == types.DNSProtocol {
if req.RequestsDNS[0].CompiledOperators != nil {
appendOperator(req, req.RequestsDNS[0].CompiledOperators)
}
} else if executer.templateType == types.HTTPProtocol {
if req.RequestsHTTP[0].CompiledOperators != nil {
appendOperator(req, req.RequestsHTTP[0].CompiledOperators)
}
} else if executer.templateType == types.SSLProtocol {
if req.RequestsSSL[0].CompiledOperators != nil {
appendOperator(req, req.RequestsSSL[0].CompiledOperators)
}
}
}
return executer
@ -186,7 +236,7 @@ func (e *ClusterExecuter) Execute(input *contextargs.Context) (bool, error) {
inputItem := input.Clone()
if e.options.InputHelper != nil && input.MetaInput.Input != "" {
if inputItem.MetaInput.Input = e.options.InputHelper.Transform(input.MetaInput.Input, types.HTTPProtocol); input.MetaInput.Input == "" {
if inputItem.MetaInput.Input = e.options.InputHelper.Transform(input.MetaInput.Input, e.templateType); input.MetaInput.Input == "" {
return false, nil
}
}
@ -226,7 +276,7 @@ func (e *ClusterExecuter) ExecuteWithResults(input *contextargs.Context, callbac
inputItem := input.Clone()
if e.options.InputHelper != nil && input.MetaInput.Input != "" {
if inputItem.MetaInput.Input = e.options.InputHelper.Transform(input.MetaInput.Input, types.HTTPProtocol); input.MetaInput.Input == "" {
if inputItem.MetaInput.Input = e.options.InputHelper.Transform(input.MetaInput.Input, e.templateType); input.MetaInput.Input == "" {
return nil
}
}

View File

@ -0,0 +1,58 @@
package templates
import (
"testing"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/dns"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http"
"github.com/stretchr/testify/require"
)
func TestClusterTemplates(t *testing.T) {
tests := []struct {
name string
templates map[string]*Template
expected [][]*Template
}{
{
name: "http-cluster-get",
templates: map[string]*Template{
"first.yaml": {RequestsHTTP: []*http.Request{{Path: []string{"{{BaseURL}}"}}}},
"second.yaml": {RequestsHTTP: []*http.Request{{Path: []string{"{{BaseURL}}"}}}},
},
expected: [][]*Template{{
{RequestsHTTP: []*http.Request{{Path: []string{"{{BaseURL}}"}}}},
{RequestsHTTP: []*http.Request{{Path: []string{"{{BaseURL}}"}}}},
}},
},
{
name: "no-http-cluster",
templates: map[string]*Template{
"first.yaml": {RequestsHTTP: []*http.Request{{Path: []string{"{{BaseURL}}/random"}}}},
"second.yaml": {RequestsHTTP: []*http.Request{{Path: []string{"{{BaseURL}}/another"}}}},
},
expected: [][]*Template{
{{RequestsHTTP: []*http.Request{{Path: []string{"{{BaseURL}}/random"}}}}},
{{RequestsHTTP: []*http.Request{{Path: []string{"{{BaseURL}}/another"}}}}},
},
},
{
name: "dns-cluster",
templates: map[string]*Template{
"first.yaml": {RequestsDNS: []*dns.Request{{Name: "{{Hostname}}"}}},
"second.yaml": {RequestsDNS: []*dns.Request{{Name: "{{Hostname}}"}}},
},
expected: [][]*Template{{
{RequestsDNS: []*dns.Request{{Name: "{{Hostname}}"}}},
{RequestsDNS: []*dns.Request{{Name: "{{Hostname}}"}}},
}},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
returned := Cluster(test.templates)
require.ElementsMatch(t, returned, test.expected, "could not get cluster results")
})
}
}

View File

@ -141,6 +141,8 @@ type Options struct {
MetricsPort int
// MaxHostError is the maximum number of errors allowed for a host
MaxHostError int
// NoHostErrors disables host skipping after maximum number of errors
NoHostErrors bool
// BulkSize is the of targets analyzed in parallel for each template
BulkSize int
// TemplateThreads is the number of templates executed in parallel
@ -389,5 +391,21 @@ func DefaultOptions() *Options {
// HasCloudOptions returns true if cloud options have been specified
func (options *Options) HasCloudOptions() bool {
return options.ScanList || options.DeleteScan != "" || options.ScanOutput != "" || options.ListDatasources || options.ListTargets || options.ListTemplates || options.RemoveDatasource != "" || options.AddTarget != "" || options.AddTemplate != "" || options.RemoveTarget != "" || options.RemoveTemplate != "" || options.GetTarget != "" || options.GetTemplate != ""
return options.ScanList ||
options.DeleteScan != "" ||
options.ScanOutput != "" ||
options.ListDatasources ||
options.ListTargets ||
options.ListTemplates ||
options.RemoveDatasource != "" ||
options.AddTarget != "" ||
options.AddTemplate != "" ||
options.RemoveTarget != "" ||
options.RemoveTemplate != "" ||
options.GetTarget != "" ||
options.GetTemplate != ""
}
func (options *Options) ShouldUseHostError() bool {
return options.MaxHostError > 0 && !options.NoHostErrors
}