mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-11-08 10:10:18 -10:00
Compare commits
105
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8c88feb4a | ||
|
|
5bbca25bea | ||
|
|
64bc83e004 | ||
|
|
37bba07662 | ||
|
|
9de96e4dda | ||
|
|
4a6f7c5a86 | ||
|
|
890147c20e | ||
|
|
89c2054bc1 | ||
|
|
938b19d470 | ||
|
|
ba05210f16 | ||
|
|
e557207b41 | ||
|
|
4c7e1687ad | ||
|
|
479c8187a5 | ||
|
|
7936ec36d8 | ||
|
|
e6e558dc23 | ||
|
|
9d9012ce29 | ||
|
|
671b875d23 | ||
|
|
367dfdb436 | ||
|
|
ad1447b329 | ||
|
|
1f39f73e93 | ||
|
|
437897d054 | ||
|
|
31636218ee | ||
|
|
eb1bb88ce5 | ||
|
|
6f5a8ec5d4 | ||
|
|
dd78cd5959 | ||
|
|
4d4b88f6ca | ||
|
|
e2e3574f59 | ||
|
|
250c2a5fd7 | ||
|
|
06ff996374 | ||
|
|
62d6adde14 | ||
|
|
16e4a7acc9 | ||
|
|
c6ef995875 | ||
|
|
89d43000cf | ||
|
|
63bfe19f40 | ||
|
|
f239d6e7c6 | ||
|
|
6e990151b4 | ||
|
|
6b66f91147 | ||
|
|
60f73840e5 | ||
|
|
6ecdb94740 | ||
|
|
7cab28d225 | ||
|
|
da2e6910d2 | ||
|
|
a12f7fb110 | ||
|
|
35a87ba411 | ||
|
|
bcf20899ec | ||
|
|
44e6f5445a | ||
|
|
6d81cc3b62 | ||
|
|
2710ba232b | ||
|
|
ce470c32f8 | ||
|
|
17092671a8 | ||
|
|
679ee5ef3a | ||
|
|
be21943ee4 | ||
|
|
f447c23a66 | ||
|
|
6d7a0fa435 | ||
|
|
4e422a9308 | ||
|
|
a360efc658 | ||
|
|
435ae4c042 | ||
|
|
0888e6244d | ||
|
|
c928dcc652 | ||
|
|
8171606055 | ||
|
|
a1291fa1ab | ||
|
|
bcd580703d | ||
|
|
9c47e6c122 | ||
|
|
23e7624f04 | ||
|
|
85723be139 | ||
|
|
25587f94b1 | ||
|
|
57db383502 | ||
|
|
e4a83ad38d | ||
|
|
fac0b7196d | ||
|
|
1427b35cdb | ||
|
|
ffd6e0b18d | ||
|
|
f9cf234c12 | ||
|
|
ccbfb12bd0 | ||
|
|
1e294aa7b9 | ||
|
|
d63d0228d0 | ||
|
|
77d8b42ee1 | ||
|
|
f82673434c | ||
|
|
2d840752c6 | ||
|
|
1512979e81 | ||
|
|
14b89f8714 | ||
|
|
8a02819c3b | ||
|
|
3a8b65cb45 | ||
|
|
4c03c29e45 | ||
|
|
7ed3c9ec15 | ||
|
|
9def9af7e3 | ||
|
|
638be4481d | ||
|
|
68daef44a7 | ||
|
|
323df47351 | ||
|
|
69c8d02cba | ||
|
|
a86c4faa82 | ||
|
|
0547616fa8 | ||
|
|
4cabca06f6 | ||
|
|
1fe6025b96 | ||
|
|
85abc4f90f | ||
|
|
9611926a9a | ||
|
|
1c440e755a | ||
|
|
c041bf2922 | ||
|
|
c0b1f4b3bf | ||
|
|
0d7855874f | ||
|
|
63124eff2c | ||
|
|
acff3e3c3a | ||
|
|
7f6096ee66 | ||
|
|
ffbb05b254 | ||
|
|
aa52d85991 | ||
|
|
d7dea30cd5 | ||
|
|
4a8b83f977 |
No files matched your search
@@ -13,7 +13,7 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go/compat-checks@v1
|
||||
with:
|
||||
go-version: "stable"
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
TARGET_URL: "http://honey.scanme.sh/-/?foo=bar"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- run: make build
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
outputs:
|
||||
matrix: ${{ steps.discover.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
with:
|
||||
go-version-file: go.tool.mod
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
GOFUZZ_PACKAGE: ${{ matrix.pkg }}
|
||||
FUZZ_DURATION: 15m
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
with:
|
||||
go-version-file: go.tool.mod
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
if: ${{ !endsWith(github.actor, '[bot]') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/actions/setup/git@v1
|
||||
- run: make syntax-docs
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
TARGET_LIST: "/tmp/targets.txt"
|
||||
PROFILE_MEM: "/tmp/nuclei-profile"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- run: |
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
env:
|
||||
OUTPUT: "/tmp/results.sarif"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
with:
|
||||
go-version-file: go.tool.mod
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
if: ${{ github.repository == 'projectdiscovery/nuclei' && !endsWith(github.actor, '[bot]') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/actions/setup/git@v1
|
||||
- run: make memogen
|
||||
|
||||
@@ -11,14 +11,14 @@ jobs:
|
||||
env:
|
||||
BENCH_OUT: "/tmp/bench.out"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- run: make build-test
|
||||
- run: ./bin/nuclei.test -test.run - -test.bench=. -test.benchmem ./cmd/nuclei/ | tee $BENCH_OUT
|
||||
env:
|
||||
DISABLE_STDOUT: "1"
|
||||
- uses: actions/cache/restore@v5
|
||||
- uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: ./cache
|
||||
key: ${{ runner.os }}-benchmark
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
comment-on-alert: true
|
||||
summary-always: true
|
||||
- uses: actions/cache/save@v5
|
||||
- uses: actions/cache/save@v6
|
||||
if: github.event_name == 'push'
|
||||
with:
|
||||
path: ./cache
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
needs: ["pgo"]
|
||||
runs-on: ubuntu-latest-16-cores
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/download-artifact@v8
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@v11
|
||||
with:
|
||||
days-before-stale: 90
|
||||
days-before-close: 7
|
||||
|
||||
@@ -22,9 +22,14 @@ jobs:
|
||||
if: ${{ !endsWith(github.actor, '[bot]') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- uses: projectdiscovery/actions/golangci-lint/v2@v1
|
||||
# go vet runs once here (not per-OS in the tests matrix): it is platform
|
||||
# independent and folding it into the lint gate fails fast on a cheap
|
||||
# runner before the heavy test/integration jobs start.
|
||||
- run: make vet
|
||||
|
||||
tests:
|
||||
name: "Tests"
|
||||
@@ -37,7 +42,7 @@ jobs:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
runs-on: "${{ matrix.os }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- uses: projectdiscovery/actions/free-disk-space@v1
|
||||
@@ -49,14 +54,34 @@ jobs:
|
||||
misc-packages: 'false'
|
||||
docker-images: 'false'
|
||||
tools-cache: 'false'
|
||||
- run: make vet
|
||||
- run: make build
|
||||
- run: make test
|
||||
# The data-race detector is OS-independent, so we only pay its ~2-3x cost
|
||||
# on ubuntu; windows/macOS run the same suite without instrumentation.
|
||||
- name: "Unit tests (race)"
|
||||
run: make test
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
env:
|
||||
PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}"
|
||||
- name: "Unit tests"
|
||||
run: make test RACE=
|
||||
if: ${{ matrix.os != 'ubuntu-latest' }}
|
||||
env:
|
||||
PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}"
|
||||
- run: go run -race . -l ${{ github.workspace }}/internal/tests/functional/testdata/targets.txt -id tech-detect,tls-version
|
||||
if: ${{ matrix.os != 'windows-latest' }} # known issue: https://github.com/golang/go/issues/46099
|
||||
working-directory: cmd/nuclei/
|
||||
# Hermetic HTTP-engine scale regression: stands up many loopback hosts and
|
||||
# asserts finding parity across a diverse template set (per-host pool,
|
||||
# connection-reuse and http->https tracker + cookie-reuse). Race
|
||||
# build runs on ubuntu only (same rationale as the unit tests above);
|
||||
# other OSes run it plain. Large runs stay opt-in locally via
|
||||
# NUCLEI_SCALE_HOSTS.
|
||||
- name: "Scale regression (race)"
|
||||
run: go test -tags=regression -race -timeout 15m ./lib/tests -run TestScaleRegression
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
- name: "Scale regression"
|
||||
run: make regression
|
||||
if: ${{ matrix.os != 'ubuntu-latest' }}
|
||||
|
||||
sdk:
|
||||
name: "Run example SDK"
|
||||
@@ -65,7 +90,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ github.token }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- name: "Simple"
|
||||
@@ -90,9 +115,10 @@ jobs:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/python@v1
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- run: make integration
|
||||
env:
|
||||
PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}"
|
||||
@@ -109,9 +135,10 @@ jobs:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/python@v1
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- uses: projectdiscovery/nuclei-action@v3
|
||||
with:
|
||||
version: latest
|
||||
@@ -125,7 +152,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ github.token }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/nuclei-action/cache@v3
|
||||
- run: make template-validate
|
||||
@@ -139,19 +166,45 @@ jobs:
|
||||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: 'go'
|
||||
- uses: github/codeql-action/autobuild@v4
|
||||
- uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
output: sarif-results
|
||||
upload: never
|
||||
category: "/language:go"
|
||||
# internal/fuzzplayground is a deliberately vulnerable mock server used to
|
||||
# exercise nuclei's fuzzing templates (CMDI/SQLi/SSRF/etc. are by design),
|
||||
# so its CodeQL alerts are expected false positives. paths-ignore has no
|
||||
# effect for compiled languages, so drop those results from the SARIF
|
||||
# before upload instead.
|
||||
- name: "Filter fuzzplayground alerts from SARIF"
|
||||
run: |
|
||||
for sarif in sarif-results/*.sarif; do
|
||||
[ -e "$sarif" ] || continue
|
||||
jq '
|
||||
.runs[].results |= map(
|
||||
select(
|
||||
[ .locations[]?.physicalLocation.artifactLocation.uri // "" ]
|
||||
| any(startswith("internal/fuzzplayground/")) | not
|
||||
)
|
||||
)
|
||||
' "$sarif" > "$sarif.filtered" && mv "$sarif.filtered" "$sarif"
|
||||
done
|
||||
- uses: github/codeql-action/upload-sarif@v4
|
||||
with:
|
||||
sarif_file: sarif-results
|
||||
category: "/language:go"
|
||||
|
||||
release:
|
||||
name: "Release test"
|
||||
needs: ["tests"]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
with:
|
||||
go-version: "stable"
|
||||
|
||||
@@ -16,5 +16,5 @@ jobs:
|
||||
if: ${{ !endsWith(github.actor, '[bot]') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: crate-ci/[email protected]7.2
|
||||
- uses: actions/checkout@v7
|
||||
- uses: crate-ci/[email protected]8.0
|
||||
@@ -23,7 +23,7 @@ endif
|
||||
|
||||
.PHONY: all build build-stats clean devtools-all devtools-bindgen devtools-scrapefuncs fuzz fuzz-ci fuzz-tools
|
||||
.PHONY: devtools-tsgen docs docgen dsl-docs functional go-build lint lint-strict fuzzplayground syntax-docs
|
||||
.PHONY: integration integration-debug jsupdate-all jsupdate-bindgen jsupdate-tsgen memogen scan-charts test test-with-lint
|
||||
.PHONY: integration integration-debug regression jsupdate-all jsupdate-bindgen jsupdate-tsgen memogen scan-charts test test-with-lint
|
||||
.PHONY: tidy ts verify download vet template-validate build-fuzz discover-fuzz-packages
|
||||
|
||||
all: build
|
||||
@@ -82,7 +82,11 @@ syntax-docs: docgen
|
||||
syntax-docs:
|
||||
./bin/docgen SYNTAX-REFERENCE.md nuclei-jsonschema.json
|
||||
|
||||
test: GOFLAGS = -race -v -timeout 1h -count 1
|
||||
# RACE controls the data-race detector (on by default for local runs). CI builds
|
||||
# the race variant on a single OS and passes RACE= elsewhere, since data races are
|
||||
# OS-independent and the detector costs ~2-3x build+run time on every runner.
|
||||
RACE ?= -race
|
||||
test: GOFLAGS = $(RACE) -v -timeout 1h -count 1
|
||||
test:
|
||||
$(GOTEST) $(GOFLAGS) ./...
|
||||
|
||||
@@ -92,6 +96,12 @@ integration:
|
||||
integration-debug:
|
||||
$(GOTEST) -tags=integration ./internal/tests/integration -v $(GO_TEST_ARGS) -args $(INTEGRATION_ARGS)
|
||||
|
||||
# Opt-in HTTP engine scale regression harness (not part of CI). Stands up many
|
||||
# loopback hosts and asserts finding parity across a diverse template set.
|
||||
# Override host count with NUCLEI_SCALE_HOSTS, e.g. NUCLEI_SCALE_HOSTS=500 make regression
|
||||
regression:
|
||||
$(GOTEST) -tags=regression -timeout 30m ./lib/tests -run TestScaleRegression -v
|
||||
|
||||
functional: build
|
||||
@release_binary="$$(command -v nuclei.exe 2>/dev/null || command -v nuclei 2>/dev/null)"; \
|
||||
if [ -z "$$release_binary" ]; then \
|
||||
|
||||
@@ -140,6 +140,7 @@ Flags:
|
||||
TARGET:
|
||||
-u, -target string[] target URLs/hosts to scan
|
||||
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
|
||||
-targets-inline string inline multiline target list (for use in template profiles)
|
||||
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
|
||||
-resume string resume scan from and save to specified file (clustering will be disabled)
|
||||
-sa, -scan-all-ips scan all the IP's associated with dns record
|
||||
@@ -149,6 +150,8 @@ TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
|
||||
-ro, -required-only use only required fields in input format when generating requests
|
||||
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
|
||||
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
|
||||
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
|
||||
|
||||
TEMPLATES:
|
||||
-nt, -new-templates run only new templates added in latest nuclei-templates release
|
||||
@@ -205,6 +208,7 @@ OUTPUT:
|
||||
-se, -sarif-export string file to export results in SARIF format
|
||||
-je, -json-export string file to export results in JSON format
|
||||
-jle, -jsonl-export string file to export results in JSONL(ine) format
|
||||
-pe, -pdf-export string file to export results in PDF format
|
||||
-rd, -redact string[] redact given list of keys from query parameter, request header and body
|
||||
|
||||
CONFIGURATIONS:
|
||||
@@ -277,6 +281,7 @@ UNCOVER:
|
||||
RATE-LIMIT:
|
||||
-rl, -rate-limit int maximum number of requests to send per second (default 150)
|
||||
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
|
||||
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
|
||||
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
|
||||
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
|
||||
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
|
||||
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
|
||||
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
|
||||
-irt, -input-read-timeout value timeout on input read (default 3m0s)
|
||||
-nh, -no-httpx disable httpx probing for non-url input
|
||||
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
|
||||
-no-stdin disable stdin processing
|
||||
|
||||
HEADLESS:
|
||||
|
||||
@@ -140,6 +140,7 @@ Flags:
|
||||
TARGET:
|
||||
-u, -target string[] target URLs/hosts to scan
|
||||
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
|
||||
-targets-inline string inline multiline target list (for use in template profiles)
|
||||
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
|
||||
-resume string resume scan from and save to specified file (clustering will be disabled)
|
||||
-sa, -scan-all-ips scan all the IP's associated with dns record
|
||||
@@ -149,6 +150,8 @@ TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
|
||||
-ro, -required-only use only required fields in input format when generating requests
|
||||
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
|
||||
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
|
||||
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
|
||||
|
||||
TEMPLATES:
|
||||
-nt, -new-templates run only new templates added in latest nuclei-templates release
|
||||
@@ -205,6 +208,7 @@ OUTPUT:
|
||||
-se, -sarif-export string file to export results in SARIF format
|
||||
-je, -json-export string file to export results in JSON format
|
||||
-jle, -jsonl-export string file to export results in JSONL(ine) format
|
||||
-pe, -pdf-export string file to export results in PDF format
|
||||
-rd, -redact string[] redact given list of keys from query parameter, request header and body
|
||||
|
||||
CONFIGURATIONS:
|
||||
@@ -277,6 +281,7 @@ UNCOVER:
|
||||
RATE-LIMIT:
|
||||
-rl, -rate-limit int maximum number of requests to send per second (default 150)
|
||||
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
|
||||
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
|
||||
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
|
||||
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
|
||||
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
|
||||
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
|
||||
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
|
||||
-irt, -input-read-timeout value timeout on input read (default 3m0s)
|
||||
-nh, -no-httpx disable httpx probing for non-url input
|
||||
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
|
||||
-no-stdin disable stdin processing
|
||||
|
||||
HEADLESS:
|
||||
|
||||
@@ -140,6 +140,7 @@ Flags:
|
||||
TARGET:
|
||||
-u, -target string[] target URLs/hosts to scan
|
||||
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
|
||||
-targets-inline string inline multiline target list (for use in template profiles)
|
||||
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
|
||||
-resume string resume scan from and save to specified file (clustering will be disabled)
|
||||
-sa, -scan-all-ips scan all the IP's associated with dns record
|
||||
@@ -149,6 +150,8 @@ TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
|
||||
-ro, -required-only use only required fields in input format when generating requests
|
||||
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
|
||||
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
|
||||
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
|
||||
|
||||
TEMPLATES:
|
||||
-nt, -new-templates run only new templates added in latest nuclei-templates release
|
||||
@@ -205,6 +208,7 @@ OUTPUT:
|
||||
-se, -sarif-export string file to export results in SARIF format
|
||||
-je, -json-export string file to export results in JSON format
|
||||
-jle, -jsonl-export string file to export results in JSONL(ine) format
|
||||
-pe, -pdf-export string file to export results in PDF format
|
||||
-rd, -redact string[] redact given list of keys from query parameter, request header and body
|
||||
|
||||
CONFIGURATIONS:
|
||||
@@ -277,6 +281,7 @@ UNCOVER:
|
||||
RATE-LIMIT:
|
||||
-rl, -rate-limit int maximum number of requests to send per second (default 150)
|
||||
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
|
||||
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
|
||||
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
|
||||
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
|
||||
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
|
||||
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
|
||||
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
|
||||
-irt, -input-read-timeout value timeout on input read (default 3m0s)
|
||||
-nh, -no-httpx disable httpx probing for non-url input
|
||||
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
|
||||
-no-stdin disable stdin processing
|
||||
|
||||
HEADLESS:
|
||||
|
||||
@@ -140,6 +140,7 @@ Flags:
|
||||
TARGET:
|
||||
-u, -target string[] target URLs/hosts to scan
|
||||
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
|
||||
-targets-inline string inline multiline target list (for use in template profiles)
|
||||
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
|
||||
-resume string resume scan from and save to specified file (clustering will be disabled)
|
||||
-sa, -scan-all-ips scan all the IP's associated with dns record
|
||||
@@ -149,6 +150,8 @@ TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
|
||||
-ro, -required-only use only required fields in input format when generating requests
|
||||
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
|
||||
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
|
||||
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
|
||||
|
||||
TEMPLATES:
|
||||
-nt, -new-templates run only new templates added in latest nuclei-templates release
|
||||
@@ -205,6 +208,7 @@ OUTPUT:
|
||||
-se, -sarif-export string file to export results in SARIF format
|
||||
-je, -json-export string file to export results in JSON format
|
||||
-jle, -jsonl-export string file to export results in JSONL(ine) format
|
||||
-pe, -pdf-export string file to export results in PDF format
|
||||
-rd, -redact string[] redact given list of keys from query parameter, request header and body
|
||||
|
||||
CONFIGURATIONS:
|
||||
@@ -277,6 +281,7 @@ UNCOVER:
|
||||
RATE-LIMIT:
|
||||
-rl, -rate-limit int maximum number of requests to send per second (default 150)
|
||||
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
|
||||
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
|
||||
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
|
||||
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
|
||||
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
|
||||
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
|
||||
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
|
||||
-irt, -input-read-timeout value timeout on input read (default 3m0s)
|
||||
-nh, -no-httpx disable httpx probing for non-url input
|
||||
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
|
||||
-no-stdin disable stdin processing
|
||||
|
||||
HEADLESS:
|
||||
|
||||
@@ -140,6 +140,7 @@ Flags:
|
||||
TARGET:
|
||||
-u, -target string[] target URLs/hosts to scan
|
||||
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
|
||||
-targets-inline string inline multiline target list (for use in template profiles)
|
||||
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
|
||||
-resume string resume scan from and save to specified file (clustering will be disabled)
|
||||
-sa, -scan-all-ips scan all the IP's associated with dns record
|
||||
@@ -149,6 +150,8 @@ TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
|
||||
-ro, -required-only use only required fields in input format when generating requests
|
||||
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
|
||||
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
|
||||
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
|
||||
|
||||
TEMPLATES:
|
||||
-nt, -new-templates run only new templates added in latest nuclei-templates release
|
||||
@@ -205,6 +208,7 @@ OUTPUT:
|
||||
-se, -sarif-export string file to export results in SARIF format
|
||||
-je, -json-export string file to export results in JSON format
|
||||
-jle, -jsonl-export string file to export results in JSONL(ine) format
|
||||
-pe, -pdf-export string file to export results in PDF format
|
||||
-rd, -redact string[] redact given list of keys from query parameter, request header and body
|
||||
|
||||
CONFIGURATIONS:
|
||||
@@ -277,6 +281,7 @@ UNCOVER:
|
||||
RATE-LIMIT:
|
||||
-rl, -rate-limit int maximum number of requests to send per second (default 150)
|
||||
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
|
||||
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
|
||||
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
|
||||
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
|
||||
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
|
||||
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
|
||||
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
|
||||
-irt, -input-read-timeout value timeout on input read (default 3m0s)
|
||||
-nh, -no-httpx disable httpx probing for non-url input
|
||||
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
|
||||
-no-stdin disable stdin processing
|
||||
|
||||
HEADLESS:
|
||||
|
||||
@@ -140,6 +140,7 @@ Flags:
|
||||
TARGET:
|
||||
-u, -target string[] target URLs/hosts to scan
|
||||
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
|
||||
-targets-inline string inline multiline target list (for use in template profiles)
|
||||
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
|
||||
-resume string resume scan from and save to specified file (clustering will be disabled)
|
||||
-sa, -scan-all-ips scan all the IP's associated with dns record
|
||||
@@ -149,6 +150,8 @@ TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
|
||||
-ro, -required-only use only required fields in input format when generating requests
|
||||
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
|
||||
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
|
||||
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
|
||||
|
||||
TEMPLATES:
|
||||
-nt, -new-templates run only new templates added in latest nuclei-templates release
|
||||
@@ -205,6 +208,7 @@ OUTPUT:
|
||||
-se, -sarif-export string file to export results in SARIF format
|
||||
-je, -json-export string file to export results in JSON format
|
||||
-jle, -jsonl-export string file to export results in JSONL(ine) format
|
||||
-pe, -pdf-export string file to export results in PDF format
|
||||
-rd, -redact string[] redact given list of keys from query parameter, request header and body
|
||||
|
||||
CONFIGURATIONS:
|
||||
@@ -277,6 +281,7 @@ UNCOVER:
|
||||
RATE-LIMIT:
|
||||
-rl, -rate-limit int maximum number of requests to send per second (default 150)
|
||||
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
|
||||
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
|
||||
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
|
||||
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
|
||||
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
|
||||
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
|
||||
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
|
||||
-irt, -input-read-timeout value timeout on input read (default 3m0s)
|
||||
-nh, -no-httpx disable httpx probing for non-url input
|
||||
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
|
||||
-no-stdin disable stdin processing
|
||||
|
||||
HEADLESS:
|
||||
|
||||
@@ -140,6 +140,7 @@ Flags:
|
||||
TARGET:
|
||||
-u, -target string[] target URLs/hosts to scan
|
||||
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
|
||||
-targets-inline string inline multiline target list (for use in template profiles)
|
||||
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
|
||||
-resume string resume scan from and save to specified file (clustering will be disabled)
|
||||
-sa, -scan-all-ips scan all the IP's associated with dns record
|
||||
@@ -149,6 +150,8 @@ TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
|
||||
-ro, -required-only use only required fields in input format when generating requests
|
||||
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
|
||||
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
|
||||
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
|
||||
|
||||
TEMPLATES:
|
||||
-nt, -new-templates run only new templates added in latest nuclei-templates release
|
||||
@@ -205,6 +208,7 @@ OUTPUT:
|
||||
-se, -sarif-export string file to export results in SARIF format
|
||||
-je, -json-export string file to export results in JSON format
|
||||
-jle, -jsonl-export string file to export results in JSONL(ine) format
|
||||
-pe, -pdf-export string file to export results in PDF format
|
||||
-rd, -redact string[] redact given list of keys from query parameter, request header and body
|
||||
|
||||
CONFIGURATIONS:
|
||||
@@ -277,6 +281,7 @@ UNCOVER:
|
||||
RATE-LIMIT:
|
||||
-rl, -rate-limit int maximum number of requests to send per second (default 150)
|
||||
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
|
||||
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
|
||||
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
|
||||
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
|
||||
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
|
||||
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
|
||||
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
|
||||
-irt, -input-read-timeout value timeout on input read (default 3m0s)
|
||||
-nh, -no-httpx disable httpx probing for non-url input
|
||||
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
|
||||
-no-stdin disable stdin processing
|
||||
|
||||
HEADLESS:
|
||||
|
||||
@@ -140,6 +140,7 @@ Flags:
|
||||
TARGET:
|
||||
-u, -target string[] target URLs/hosts to scan
|
||||
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
|
||||
-targets-inline string inline multiline target list (for use in template profiles)
|
||||
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
|
||||
-resume string resume scan from and save to specified file (clustering will be disabled)
|
||||
-sa, -scan-all-ips scan all the IP's associated with dns record
|
||||
@@ -149,6 +150,8 @@ TARGET-FORMAT:
|
||||
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
|
||||
-ro, -required-only use only required fields in input format when generating requests
|
||||
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
|
||||
-vtt, -vars-text-templating enable text templating for vars in input file (only for yaml input mode)
|
||||
-vfp, -var-file-paths string[] list of yaml file contained vars to inject into yaml input
|
||||
|
||||
TEMPLATES:
|
||||
-nt, -new-templates run only new templates added in latest nuclei-templates release
|
||||
@@ -205,6 +208,7 @@ OUTPUT:
|
||||
-se, -sarif-export string file to export results in SARIF format
|
||||
-je, -json-export string file to export results in JSON format
|
||||
-jle, -jsonl-export string file to export results in JSONL(ine) format
|
||||
-pe, -pdf-export string file to export results in PDF format
|
||||
-rd, -redact string[] redact given list of keys from query parameter, request header and body
|
||||
|
||||
CONFIGURATIONS:
|
||||
@@ -277,6 +281,7 @@ UNCOVER:
|
||||
RATE-LIMIT:
|
||||
-rl, -rate-limit int maximum number of requests to send per second (default 150)
|
||||
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
|
||||
-per-host-rate-limit enable per-host rate limiting (global rate limit becomes unlimited when enabled)
|
||||
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
|
||||
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
|
||||
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
|
||||
@@ -301,6 +306,7 @@ OPTIMIZATIONS:
|
||||
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
|
||||
-irt, -input-read-timeout value timeout on input read (default 3m0s)
|
||||
-nh, -no-httpx disable httpx probing for non-url input
|
||||
-preflight-portscan run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)
|
||||
-no-stdin disable stdin processing
|
||||
|
||||
HEADLESS:
|
||||
|
||||
@@ -1009,6 +1009,26 @@ Part Definitions:
|
||||
- <code>all</code> - HTTP response body + headers
|
||||
- <code>cookies_from_response</code> - HTTP response cookies in name:value format
|
||||
- <code>headers_from_response</code> - HTTP response headers in name:value format
|
||||
- <code>tls_version</code> - TLS version negotiated for the HTTP connection
|
||||
- <code>cipher</code> - TLS cipher suite negotiated for the HTTP connection
|
||||
- <code>sni</code> - SNI value used in the TLS handshake
|
||||
- <code>subject_cn</code> - Leaf certificate subject common name
|
||||
- <code>subject_dn</code> - Leaf certificate subject distinguished name
|
||||
- <code>subject_an</code> - Leaf certificate subject alternative names
|
||||
- <code>subject_org</code> - Leaf certificate subject organization
|
||||
- <code>issuer_cn</code> - Leaf certificate issuer common name
|
||||
- <code>issuer_dn</code> - Leaf certificate issuer distinguished name
|
||||
- <code>issuer_org</code> - Leaf certificate issuer organization
|
||||
- <code>serial</code> - Leaf certificate serial number
|
||||
- <code>fingerprint_hash</code> - Leaf certificate fingerprint hashes (md5/sha1/sha256)
|
||||
- <code>not_before</code> - Leaf certificate not-before timestamp
|
||||
- <code>not_after</code> - Leaf certificate not-after timestamp
|
||||
- <code>expired</code> - Whether the leaf certificate has expired
|
||||
- <code>self_signed</code> - Whether the leaf certificate is self-signed
|
||||
- <code>mismatched</code> - Whether the leaf certificate does not match the SNI hostname
|
||||
- <code>domains</code> - Deduplicated domains from subject CN and SANs
|
||||
- <code>wildcard_certificate</code> - Whether the leaf certificate is a wildcard certificate
|
||||
- <code>emails</code> - Email addresses embedded in the leaf certificate
|
||||
|
||||
<hr />
|
||||
|
||||
@@ -2606,6 +2626,7 @@ Part Definitions:
|
||||
- <code>host</code> - Host is the input to the template
|
||||
- <code>matched</code> - Matched is the input which was matched upon
|
||||
- <code>request</code> - Request contains the DNS request in text format
|
||||
- <code>duration</code> - Protocol operation duration in seconds
|
||||
- <code>type</code> - Type is the type of request made
|
||||
- <code>rcode</code> - Rcode field returned for the DNS request
|
||||
- <code>question</code> - Question contains the DNS question field
|
||||
@@ -2898,6 +2919,16 @@ Enum Values:
|
||||
- <code>ANY</code>
|
||||
|
||||
- <code>SRV</code>
|
||||
|
||||
- <code>RRSIG</code>
|
||||
|
||||
- <code>NSEC</code>
|
||||
|
||||
- <code>DNSKEY</code>
|
||||
|
||||
- <code>NSEC3</code>
|
||||
|
||||
- <code>NSEC3PARAM</code>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
@@ -3071,6 +3102,82 @@ NoRecursive specifies whether to not do recursive checks if folders are provided
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>smb-user</code> <i>string</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
SMBUser authenticates to remote SMB shares when the file input is a UNC
|
||||
or smb:// path (issue #6142). Guest/anon: empty password.
|
||||
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
|
||||
```yaml
|
||||
smb-user: auditor
|
||||
```
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>smb-password</code> <i>string</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
SMBPassword is the password for SMB file targets.
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>smb-domain</code> <i>string</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
SMBDomain is the optional NTLM domain / workgroup.
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>smb-hash</code> <i>string</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
SMBHash enables pass-the-hash (overrides smb-password when set).
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
<code>smb-port</code> <i>int</i>
|
||||
|
||||
</div>
|
||||
<div class="dt">
|
||||
|
||||
SMBPort overrides the default SMB port (445) for UNC targets.
|
||||
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3109,6 +3216,7 @@ Part Definitions:
|
||||
- <code>matched</code> - Matched is the input which was matched upon
|
||||
- <code>type</code> - Type is the type of request made
|
||||
- <code>request</code> - Network request made from the client
|
||||
- <code>duration</code> - Latest measured operation duration in seconds
|
||||
- <code>body,all,data</code> - Network response received from server (default)
|
||||
- <code>raw</code> - Full Network protocol data
|
||||
|
||||
@@ -3501,6 +3609,7 @@ Part Definitions:
|
||||
- <code>matched</code> - Matched is the input which was matched upon
|
||||
- <code>type</code> - Type is the type of request made
|
||||
- <code>req</code> - Headless request made from the client
|
||||
- <code>duration</code> - Latest measured operation duration in seconds
|
||||
- <code>resp,body,data</code> - Headless response received from client (default)
|
||||
|
||||
<hr />
|
||||
@@ -3881,6 +3990,7 @@ Part Definitions:
|
||||
- <code>matched</code> - Matched is the input which was matched upon
|
||||
- <code>type</code> - Type is the type of request made
|
||||
- <code>timestamp</code> - Timestamp is the time when the request was made
|
||||
- <code>duration</code> - Protocol operation duration in seconds
|
||||
- <code>response</code> - JSON SSL protocol handshake details
|
||||
- <code>cipher</code> - Cipher is the encryption algorithm used
|
||||
- <code>domains</code> - Domains are the list of domain names in the certificate
|
||||
@@ -4080,6 +4190,7 @@ Part Definitions:
|
||||
- <code>type</code> - Type is the type of request made
|
||||
- <code>success</code> - Success specifies whether websocket connection was successful
|
||||
- <code>request</code> - Websocket request made to the server
|
||||
- <code>duration</code> - Latest measured operation duration in seconds
|
||||
- <code>response</code> - Websocket response received from the server
|
||||
- <code>host</code> - Host is the input to the template
|
||||
- <code>matched</code> - Matched is the input which was matched upon
|
||||
@@ -4254,8 +4365,14 @@ Appears in:
|
||||
|
||||
|
||||
|
||||
Part Definitions:
|
||||
|
||||
|
||||
- <code>type</code> - Type is the type of request made
|
||||
- <code>host</code> - Host is the input to the template
|
||||
- <code>response</code> - WHOIS response data
|
||||
- <code>duration</code> - Protocol operation duration in seconds
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="dd">
|
||||
|
||||
@@ -25,6 +25,7 @@ extend-exclude = [
|
||||
[default.extend-identifiers]
|
||||
# Go identifiers that cannot be renamed without breaking API
|
||||
MisMatched = "MisMatched"
|
||||
IsMisMatchedCert = "IsMisMatchedCert" # tlsx clients API
|
||||
NoopWriter = "NoopWriter"
|
||||
AllowdTypes = "AllowdTypes"
|
||||
# Deprecated alias kept for backward compatibility
|
||||
|
||||
+7
-4
@@ -15,12 +15,12 @@ import (
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
_pdcp "github.com/projectdiscovery/nuclei/v3/internal/pdcp"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
|
||||
"github.com/projectdiscovery/utils/auth/pdcp"
|
||||
"github.com/projectdiscovery/utils/env"
|
||||
_ "github.com/projectdiscovery/utils/pprof"
|
||||
stringsutil "github.com/projectdiscovery/utils/strings"
|
||||
"github.com/rs/xid"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/projectdiscovery/goflags"
|
||||
"github.com/projectdiscovery/gologger/levels"
|
||||
@@ -260,6 +260,9 @@ func readConfig() *goflags.FlagSet {
|
||||
flagSet.SetDescription(`Nuclei is a fast, template based vulnerability scanner focusing
|
||||
on extensive configurability, massive extensibility and ease of use.`)
|
||||
|
||||
// Shared PD flags (e.g. -max-time soft-kill via interrupt).
|
||||
flagSet.AddCommonFlags()
|
||||
|
||||
/* TODO Important: The defined default values, especially for slice/array types are NOT DEFAULT VALUES, but rather implicit values to which the user input is appended.
|
||||
This can be very confusing and should be addressed
|
||||
*/
|
||||
@@ -418,6 +421,7 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
flagSet.CreateGroup("rate-limit", "Rate-Limit",
|
||||
flagSet.IntVarP(&options.RateLimit, "rate-limit", "rl", 150, "maximum number of requests to send per second"),
|
||||
flagSet.DurationVarP(&options.RateLimitDuration, "rate-limit-duration", "rld", time.Second, "maximum number of requests to send per second"),
|
||||
flagSet.BoolVar(&options.PerHostRateLimit, "per-host-rate-limit", false, "enable per-host rate limiting (global rate limit becomes unlimited when enabled)"),
|
||||
flagSet.IntVarP(&options.RateLimitMinute, "rate-limit-minute", "rlm", 0, "maximum number of requests to send per minute (DEPRECATED)"),
|
||||
flagSet.IntVarP(&options.BulkSize, "bulk-size", "bs", 25, "maximum number of hosts to be analyzed in parallel per template"),
|
||||
flagSet.IntVarP(&options.TemplateThreads, "concurrency", "c", 25, "maximum number of templates to be executed in parallel"),
|
||||
@@ -446,6 +450,7 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
}),
|
||||
flagSet.DurationVarP(&options.InputReadTimeout, "input-read-timeout", "irt", time.Duration(3*time.Minute), "timeout on input read"),
|
||||
flagSet.BoolVarP(&options.DisableHTTPProbe, "no-httpx", "nh", false, "disable httpx probing for non-url input"),
|
||||
flagSet.BoolVar(&options.PreflightPortScan, "preflight-portscan", false, "run preflight resolve + TCP portscan and filter targets before scanning (disabled by default)"),
|
||||
flagSet.BoolVar(&options.DisableStdin, "no-stdin", false, "disable stdin processing"),
|
||||
)
|
||||
|
||||
@@ -771,9 +776,7 @@ func disableUpdatesCallback() {
|
||||
// printVersion prints the nuclei version and exits.
|
||||
func printVersion() {
|
||||
options.Logger.Info().Msgf("Nuclei Engine Version: %s", config.Version)
|
||||
options.Logger.Info().Msgf("Nuclei Config Directory: %s", config.DefaultConfig.GetConfigDir())
|
||||
options.Logger.Info().Msgf("Nuclei Cache Directory: %s", config.DefaultConfig.GetCacheDir()) // cache dir contains resume files
|
||||
options.Logger.Info().Msgf("PDCP Directory: %s", pdcp.PDCPDir)
|
||||
runner.LogDirectoryInfo(options.Logger)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,15 @@ func TestMain(m *testing.M) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
dummyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
targetURL = dummyServer.URL
|
||||
// Only start the shared httptest for benchmarks so unit tests (e.g. goleak)
|
||||
// are not polluted by a long-lived accept loop.
|
||||
var dummyServer *httptest.Server
|
||||
if isBenchmarkRun() {
|
||||
dummyServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
targetURL = dummyServer.URL
|
||||
}
|
||||
|
||||
// Execute tests
|
||||
|
||||
@@ -45,13 +50,24 @@ func TestMain(m *testing.M) {
|
||||
|
||||
// Tear down
|
||||
|
||||
dummyServer.Close()
|
||||
if dummyServer != nil {
|
||||
dummyServer.Close()
|
||||
}
|
||||
_ = os.RemoveAll(projectPath)
|
||||
_ = os.Unsetenv("DISABLE_STDOUT")
|
||||
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func isBenchmarkRun() bool {
|
||||
for _, arg := range os.Args[1:] {
|
||||
if strings.HasPrefix(arg, "-test.bench") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getUniqFilename generates a unique filename by appending .N if file exists
|
||||
// Similar to wget's behavior: file.cpu.prof, file.cpu.1.prof, file.cpu.2.prof, etc.
|
||||
func getUniqFilename(basePath string) string {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/gologger/levels"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/runner"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/rs/xid"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tarunKoyalwar/goleak"
|
||||
)
|
||||
|
||||
const cliLeakTestChildEnv = "NUCLEI_CLI_LEAKTEST_CHILD"
|
||||
|
||||
var cliKnownLeaks = []goleak.Option{
|
||||
goleak.Pretty(),
|
||||
goleak.IgnoreAnyFunction("net/http.(*http2ClientConn).readLoop"),
|
||||
goleak.IgnoreAnyFunction("net/http.(*persistConn).readLoop"),
|
||||
goleak.IgnoreAnyFunction("net/http.(*persistConn).writeLoop"),
|
||||
goleak.IgnoreAnyContainingPkg("github.com/hashicorp/golang-lru/v2/expirable"),
|
||||
goleak.IgnoreAnyContainingPkg("github.com/syndtr/goleveldb"),
|
||||
goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/ratelimit"),
|
||||
goleak.IgnoreAnyFunction("github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate.StartActiveMemGuardian.func1"),
|
||||
}
|
||||
|
||||
// TestCLIRunnerGoroutineLeak runs a minimal CLI-path nuclei scan via internal/runner
|
||||
// and asserts no unexpected goroutine leaks remain after Close.
|
||||
//
|
||||
// The body runs in a child process so shared package state from other tests
|
||||
// does not pollute goleak (same approach as lib/tests SDK leak coverage).
|
||||
func TestCLIRunnerGoroutineLeak(t *testing.T) {
|
||||
if os.Getenv(cliLeakTestChildEnv) == "1" {
|
||||
runCLIRunnerLeakTest(t)
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.Command(os.Args[0], "-test.run=^TestCLIRunnerGoroutineLeak$", "-test.count=1", "-test.v")
|
||||
cmd.Env = append(os.Environ(), cliLeakTestChildEnv+"=1")
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, "CLI leak test child failed:\n%s", string(out))
|
||||
}
|
||||
|
||||
func runCLIRunnerLeakTest(t *testing.T) {
|
||||
gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)
|
||||
_ = os.Setenv("DISABLE_STDOUT", "true")
|
||||
t.Cleanup(func() { _ = os.Unsetenv("DISABLE_STDOUT") })
|
||||
config.DefaultConfig.DisableUpdateCheck()
|
||||
|
||||
defer func() {
|
||||
time.Sleep(2 * time.Second)
|
||||
goleak.VerifyNone(t, cliKnownLeaks...)
|
||||
}()
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
_, thisFile, _, ok := runtime.Caller(0)
|
||||
require.True(t, ok)
|
||||
templatePath := filepath.Join(filepath.Dir(thisFile), "testdata", "leaktest", "basic-http.yaml")
|
||||
|
||||
options := getDefaultOptions()
|
||||
options.Targets = []string{server.URL}
|
||||
options.Templates = []string{templatePath}
|
||||
options.NoInteractsh = true
|
||||
options.DisableStdin = true
|
||||
options.BulkSize = 1
|
||||
options.TemplateThreads = 1
|
||||
options.PayloadConcurrency = 1
|
||||
options.ExecutionId = xid.New().String()
|
||||
|
||||
runner.ParseOptions(options)
|
||||
|
||||
nucleiRunner, err := runner.New(options)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, nucleiRunner)
|
||||
defer nucleiRunner.Close()
|
||||
|
||||
require.NoError(t, nucleiRunner.RunEnumeration())
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/projectdiscovery/goflags"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Ensures nuclei's goflags dependency still exposes AddCommonFlags / -max-time,
|
||||
// which is what cmd/nuclei wires via flagSet.AddCommonFlags().
|
||||
func TestMaxTimeCommonFlagAvailable(t *testing.T) {
|
||||
flagSet := goflags.NewFlagSet()
|
||||
common := flagSet.AddCommonFlags()
|
||||
require.NotNil(t, common)
|
||||
|
||||
require.NoError(t, flagSet.Parse("-max-time", "1h30m"))
|
||||
require.Equal(t, 90*time.Minute, common.MaxTime)
|
||||
|
||||
flagSet = goflags.NewFlagSet()
|
||||
common = flagSet.AddCommonFlags()
|
||||
require.NoError(t, flagSet.Parse("-mt", "45s"))
|
||||
require.Equal(t, 45*time.Second, common.MaxTime)
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
id: leaktest-basic-http
|
||||
|
||||
info:
|
||||
name: CLI Leak Test Basic HTTP
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
http:
|
||||
- method: GET
|
||||
path:
|
||||
- "{{BaseURL}}/"
|
||||
|
||||
matchers:
|
||||
- type: status
|
||||
status:
|
||||
- 204
|
||||
@@ -1,30 +1,28 @@
|
||||
module github.com/projectdiscovery/nuclei/v3
|
||||
|
||||
go 1.25.7
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/andygrunwald/go-jira v1.16.1
|
||||
github.com/antchfx/htmlquery v1.3.5
|
||||
github.com/bluele/gcache v0.0.2
|
||||
github.com/go-playground/validator/v10 v10.26.0
|
||||
github.com/go-rod/rod v0.116.2
|
||||
github.com/gobwas/ws v1.4.0
|
||||
github.com/google/go-github v17.0.0+incompatible
|
||||
github.com/invopop/jsonschema v0.13.0
|
||||
github.com/itchyny/gojq v0.12.17
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
||||
github.com/miekg/dns v1.1.68
|
||||
github.com/miekg/dns v1.1.72
|
||||
github.com/olekukonko/tablewriter v1.0.8
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/projectdiscovery/clistats v0.1.4
|
||||
github.com/projectdiscovery/fastdialer v0.5.10
|
||||
github.com/projectdiscovery/fastdialer v0.5.14
|
||||
github.com/projectdiscovery/hmap v0.0.101
|
||||
github.com/projectdiscovery/interactsh v1.3.1
|
||||
github.com/projectdiscovery/rawhttp v0.1.90
|
||||
github.com/projectdiscovery/rawhttp v0.1.91
|
||||
github.com/projectdiscovery/retryabledns v1.0.115
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.14
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.21
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.6
|
||||
github.com/remeh/sizedwaitgroup v1.0.0
|
||||
github.com/rs/xid v1.6.0
|
||||
@@ -32,13 +30,11 @@ require (
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/spf13/cast v1.10.0
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/valyala/fasttemplate v1.2.2
|
||||
github.com/weppos/publicsuffix-go v0.50.3
|
||||
github.com/weppos/publicsuffix-go v0.50.3 // indirect
|
||||
go.uber.org/multierr v1.11.0
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/oauth2 v0.34.0
|
||||
golang.org/x/text v0.37.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/text v0.39.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -49,18 +45,14 @@ require (
|
||||
github.com/Azure/go-ntlmssp v0.1.1
|
||||
github.com/DataDog/gostackparse v0.7.0
|
||||
github.com/FalconOpsLLC/goexec v0.3.0
|
||||
github.com/Masterminds/semver/v3 v3.4.0
|
||||
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057
|
||||
github.com/Masterminds/semver/v3 v3.5.0
|
||||
github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d
|
||||
github.com/Mzack9999/goimpacket v0.0.0-20260422121140-7085336a0415
|
||||
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697
|
||||
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883
|
||||
github.com/RedTeamPentesting/adauth v0.5.4-0.20260511073005-3d18e8a5a687
|
||||
github.com/alexsnet/go-vnc v0.1.0
|
||||
github.com/alitto/pond v1.9.2
|
||||
github.com/antchfx/xmlquery v1.4.4
|
||||
github.com/antchfx/xpath v1.3.6
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.17
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.70
|
||||
@@ -73,22 +65,24 @@ require (
|
||||
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/getkin/kin-openapi v0.132.0
|
||||
github.com/fullstorydev/grpcurl v1.9.3
|
||||
github.com/getkin/kin-openapi v0.144.0
|
||||
github.com/go-git/go-git/v5 v5.19.1
|
||||
github.com/go-ldap/ldap/v3 v3.4.12
|
||||
github.com/go-pdf/fpdf v0.9.0
|
||||
github.com/go-pg/pg/v10 v10.15.0
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/go-sql-driver/mysql v1.10.0
|
||||
github.com/goccy/go-json v0.10.5
|
||||
github.com/google/go-github/v30 v30.1.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/invopop/yaml v0.3.1
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4
|
||||
github.com/jhump/protoreflect v1.17.0
|
||||
github.com/kitabisa/go-ci v1.0.3
|
||||
github.com/labstack/echo/v4 v4.13.4
|
||||
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa
|
||||
github.com/lib/pq v1.11.2
|
||||
github.com/lib/pq v1.12.3
|
||||
github.com/logrusorgru/aurora/v4 v4.0.0
|
||||
github.com/mattn/go-sqlite3 v1.14.28
|
||||
github.com/maypok86/otter/v2 v2.2.1
|
||||
@@ -97,19 +91,20 @@ require (
|
||||
github.com/oiweiwei/go-msrpc v1.2.12
|
||||
github.com/ory/dockertest/v3 v3.12.0
|
||||
github.com/praetorian-inc/fingerprintx v1.1.15
|
||||
github.com/projectdiscovery/dsl v0.8.19
|
||||
github.com/projectdiscovery/dsl v0.8.20
|
||||
github.com/projectdiscovery/fasttemplate v0.0.2
|
||||
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c
|
||||
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb
|
||||
github.com/projectdiscovery/goflags v0.1.74
|
||||
github.com/projectdiscovery/gologger v1.1.70
|
||||
github.com/projectdiscovery/goflags v0.1.75
|
||||
github.com/projectdiscovery/goja v0.0.0-20260618133720-acb73e419534
|
||||
github.com/projectdiscovery/goja_nodejs v0.0.0-20260618132410-8519f75f703d
|
||||
github.com/projectdiscovery/gologger v1.1.71
|
||||
github.com/projectdiscovery/gostruct v0.0.2
|
||||
github.com/projectdiscovery/govaluate v0.0.0-20260504230327-80320480bb6e
|
||||
github.com/projectdiscovery/govaluate v0.0.0-20260615100919-5ee2581bbf7e
|
||||
github.com/projectdiscovery/gozero v0.1.1-0.20260530071156-fa1dad563d76
|
||||
github.com/projectdiscovery/httpx v1.9.0
|
||||
github.com/projectdiscovery/httpx v1.10.0
|
||||
github.com/projectdiscovery/mapcidr v1.1.97
|
||||
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
|
||||
github.com/projectdiscovery/networkpolicy v0.1.40
|
||||
github.com/projectdiscovery/networkpolicy v0.1.44
|
||||
github.com/projectdiscovery/ratelimit v0.0.88
|
||||
github.com/projectdiscovery/rdap v0.9.0
|
||||
github.com/projectdiscovery/sarif v0.1.0
|
||||
@@ -117,20 +112,20 @@ require (
|
||||
github.com/projectdiscovery/uncover v1.2.1
|
||||
github.com/projectdiscovery/useragent v0.0.108
|
||||
github.com/projectdiscovery/utils v0.11.1
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.84
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.91
|
||||
github.com/redis/go-redis/v9 v9.11.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466
|
||||
github.com/sijms/go-ora/v2 v2.9.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9
|
||||
github.com/testcontainers/testcontainers-go v0.42.0
|
||||
github.com/testcontainers/testcontainers-go/modules/mongodb v0.42.0
|
||||
github.com/yassinebenaid/godump v0.11.1
|
||||
github.com/zmap/zgrab2 v0.1.8
|
||||
gitlab.com/gitlab-org/api/client-go v1.9.1
|
||||
go.mongodb.org/mongo-driver v1.17.9
|
||||
golang.org/x/term v0.43.0
|
||||
golang.org/x/sync v0.21.0
|
||||
golang.org/x/term v0.44.0
|
||||
google.golang.org/grpc v1.82.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
moul.io/http2curl v1.0.0
|
||||
)
|
||||
@@ -138,18 +133,19 @@ require (
|
||||
require (
|
||||
aead.dev/minisign v0.3.0 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.1 // indirect
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 // indirect
|
||||
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.11.0 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.12.0 // indirect
|
||||
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/akrylysov/pogreb v0.10.2 // indirect
|
||||
@@ -159,6 +155,7 @@ require (
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
||||
@@ -182,6 +179,7 @@ require (
|
||||
github.com/bodgit/sevenzip v1.6.1 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
github.com/brianvoe/gofakeit/v7 v7.2.1 // indirect
|
||||
github.com/bufbuild/protocompile v0.14.1 // indirect
|
||||
github.com/buger/jsonparser v1.1.2 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
@@ -200,13 +198,11 @@ require (
|
||||
github.com/cloudflare/cfssl v1.6.4 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
|
||||
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a // indirect
|
||||
github.com/containerd/continuity v0.4.5 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
@@ -214,19 +210,22 @@ require (
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/djherbis/times v1.6.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/dlclark/regexp2/v2 v2.2.1 // indirect
|
||||
github.com/docker/cli v29.2.0+incompatible // indirect
|
||||
github.com/docker/go-connections v0.7.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
|
||||
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/fgprof v0.9.5 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gaissmai/bart v0.28.0 // indirect
|
||||
github.com/gaissmai/bart v0.29.0 // indirect
|
||||
github.com/geoffgarside/ber v1.2.0 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
@@ -234,11 +233,12 @@ require (
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.9.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.5 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
|
||||
github.com/go-pg/zerochecker v0.2.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
@@ -248,9 +248,9 @@ require (
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/certificate-transparency-go v1.3.2 // indirect
|
||||
github.com/google/go-github/v30 v30.1.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/gosimple/slug v1.15.0 // indirect
|
||||
@@ -259,9 +259,8 @@ require (
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/go-version v1.8.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf // indirect
|
||||
github.com/hdm/jarm-go v0.0.7 // indirect
|
||||
github.com/hdm/jarm-go v0.0.8 // indirect
|
||||
github.com/iangcarroll/cookiemonster v1.6.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/indece-official/go-ebcdic v1.2.0 // indirect
|
||||
@@ -273,23 +272,20 @@ require (
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 // indirect
|
||||
github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166 // indirect
|
||||
github.com/kataras/jwt v0.1.10 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/compress v1.18.7 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/libdns/libdns v1.1.1 // indirect
|
||||
github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
|
||||
github.com/mackerelio/go-osstat v0.2.6 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||
@@ -301,45 +297,41 @@ require (
|
||||
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.2.0 // indirect
|
||||
github.com/moby/moby/api v1.54.2 // indirect
|
||||
github.com/moby/moby/client v0.4.1 // indirect
|
||||
github.com/moby/patternmatcher v0.6.1 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||
github.com/oasdiff/yaml v0.1.1 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.14 // indirect
|
||||
github.com/oiweiwei/go-smb2.fork v1.0.0 // indirect
|
||||
github.com/oiweiwei/gokrb5.fork/v9 v9.0.6 // indirect
|
||||
github.com/olekukonko/errors v1.1.0 // indirect
|
||||
github.com/olekukonko/ll v0.0.9 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/opencontainers/runc v1.2.8 // indirect
|
||||
github.com/opencontainers/runc v1.3.6 // indirect
|
||||
github.com/openrdap/rdap v0.9.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.23 // indirect
|
||||
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/projectdiscovery/asnmap v1.1.1 // indirect
|
||||
github.com/projectdiscovery/blackrock v0.0.1 // indirect
|
||||
github.com/projectdiscovery/cdncheck v1.2.39 // indirect
|
||||
github.com/projectdiscovery/cdncheck v1.2.46 // indirect
|
||||
github.com/projectdiscovery/freeport v0.0.7 // indirect
|
||||
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb // indirect
|
||||
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 // indirect
|
||||
github.com/refraction-networking/utls v1.8.2 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
||||
github.com/sashabaranov/go-openai v1.37.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
@@ -349,6 +341,8 @@ require (
|
||||
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/tidwall/btree v1.8.1 // indirect
|
||||
github.com/tidwall/buntdb v1.3.2 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
@@ -382,22 +376,22 @@ require (
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/ysmood/fetchup v0.2.3 // indirect
|
||||
github.com/ysmood/got v0.40.0 // indirect
|
||||
github.com/yuin/goldmark v1.7.13 // indirect
|
||||
github.com/yuin/goldmark v1.7.17 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.6 // indirect
|
||||
github.com/zcalusic/sysinfo v1.1.3 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||
mellium.im/sasl v0.3.2 // indirect
|
||||
software.sslmate.com/src/go-pkcs12 v0.7.0 // indirect
|
||||
software.sslmate.com/src/go-pkcs12 v0.7.2 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -419,13 +413,13 @@ require (
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
goftp.io/server/v2 v2.0.1 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/crypto v0.53.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260527015227-08cc5374adb3
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/mod v0.37.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.45.0
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
golang.org/x/tools v0.47.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
|
||||
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
|
||||
)
|
||||
|
||||
@@ -41,12 +41,10 @@ code.gitea.io/sdk/gitea v0.17.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBl
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
|
||||
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a h1:3i+FJ7IpSZHL+VAjtpQeZCRhrpP0odl5XfoLBY4fxJ8=
|
||||
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a/go.mod h1:C7hXLmFmPYPjIDGfQl1clsmQ5TMEQfmzWTrJk475bUs=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
|
||||
@@ -72,15 +70,15 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mo
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4=
|
||||
github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
|
||||
github.com/FalconOpsLLC/goexec v0.3.0 h1:ryLMkrGT6asnkqdc5rFMNOSTYdMH/iCfyEuwu0D6ZhA=
|
||||
github.com/FalconOpsLLC/goexec v0.3.0/go.mod h1:kiyxVbmFCGbbwXRyZmOSKlOy7PiK+JH2gq07Ztag/k8=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
|
||||
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
@@ -92,10 +90,6 @@ github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d h1:DofPB5AcjTnO
|
||||
github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d/go.mod h1:uzdh/m6XQJI7qRvufeBPDa+lj5SVCJO8B9eLxTbtI5U=
|
||||
github.com/Mzack9999/goimpacket v0.0.0-20260422121140-7085336a0415 h1:lpSZPcbowbxvKFaFvE1reLTCStezWXcRVk0zzBtUatg=
|
||||
github.com/Mzack9999/goimpacket v0.0.0-20260422121140-7085336a0415/go.mod h1:Wvb2f1Aq6NVL4Fza/dPNxv6/canpeizpgmiTCBGMD50=
|
||||
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697 h1:54I+OF5vS4a/rxnUrN5J3hi0VEYKcrTlpc8JosDyP+c=
|
||||
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697/go.mod h1:yNqYRqxYkSROY1J+LX+A0tOSA/6soXQs5m8hZSqYBac=
|
||||
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883 h1:+Is1AS20q3naP+qJophNpxuvx1daFOx9C0kLIuI0GVk=
|
||||
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883/go.mod h1:K+FhM7iKGKtalkeXGEviafPPwyVjDv1a/ehomabLF2w=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
@@ -103,8 +97,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
|
||||
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
||||
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
|
||||
github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo=
|
||||
github.com/PuerkitoBio/goquery v1.12.0/go.mod h1:802ej+gV2y7bbIhOIoPY5sT183ZW0YFofScC4q/hIpQ=
|
||||
github.com/RedTeamPentesting/adauth v0.5.4-0.20260511073005-3d18e8a5a687 h1:HHkHNwakgAFX3qlMm88c090vIYNFWiO5+4WyAr0xJuM=
|
||||
github.com/RedTeamPentesting/adauth v0.5.4-0.20260511073005-3d18e8a5a687/go.mod h1:l6FRaC2TliS/JMNWskO3J1tmrcKJyOaMFhWC6hHeWno=
|
||||
github.com/RumbleDiscovery/rumble-tools v0.0.0-20201105153123-f2adbb3244d2/go.mod h1:jD2+mU+E2SZUuAOHZvZj4xP4frlOo+N/YrXDvASFhkE=
|
||||
@@ -213,8 +207,6 @@ github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCk
|
||||
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bits-and-blooms/bloom/v3 v3.5.0 h1:AKDvi1V3xJCmSR6QhcBfHbCN4Vf8FfxeWkMNQfmAGhY=
|
||||
github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
|
||||
@@ -227,6 +219,8 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
|
||||
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
|
||||
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
|
||||
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
@@ -290,6 +284,8 @@ github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJ
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
|
||||
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a h1:Ohw57yVY2dBTt+gsC6aZdteyxwlxfbtgkFEMTEkwgSw=
|
||||
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
|
||||
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
|
||||
@@ -298,13 +294,7 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
@@ -328,6 +318,8 @@ github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYC
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0=
|
||||
github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
|
||||
github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=
|
||||
github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
|
||||
@@ -349,7 +341,11 @@ github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FM
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
|
||||
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 h1:R/ZjJpjQKsZ6L/+Gf9WHbt31GG8NMVcpRqUE+1mMIyo=
|
||||
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
@@ -369,14 +365,16 @@ github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b/go.mod h1:l2Jrml4vo
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fullstorydev/grpcurl v1.9.3 h1:PC1Xi3w+JAvEE2Tg2Gf2RfVgPbf9+tbuQr1ZkyVU3jk=
|
||||
github.com/fullstorydev/grpcurl v1.9.3/go.mod h1:/b4Wxe8bG6ndAjlfSUjwseQReUDUvBJiFEB7UllOlUE=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gaissmai/bart v0.28.0 h1:89yZLo8NmyqD0RYgJ3QO9HhqqGGw+oWhf90cZm69Lko=
|
||||
github.com/gaissmai/bart v0.28.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=
|
||||
github.com/gaissmai/bart v0.29.0 h1:wO6HGE8g9YE0Wm0bCpYxwRzfQ4+fbJKOhL64e5ACGCI=
|
||||
github.com/gaissmai/bart v0.29.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=
|
||||
github.com/geoffgarside/ber v1.2.0 h1:/loowoRcs/MWLYmGX9QtIAbA+V/FrnVLsMMPhwiRm64=
|
||||
github.com/geoffgarside/ber v1.2.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
|
||||
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
|
||||
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
|
||||
github.com/getkin/kin-openapi v0.144.0 h1:hIRcTH+KjLfkLpYU6bSSfdFpi0fZi1fp+hSPi4aQu9Y=
|
||||
github.com/getkin/kin-openapi v0.144.0/go.mod h1:3BH9M9XDe/y9M5DSvEocVYAYq1w0qrhJHjC/vZi0AaY=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
@@ -400,6 +398,8 @@ github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhE
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
@@ -419,10 +419,12 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
|
||||
github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
|
||||
github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
|
||||
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
|
||||
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
|
||||
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
|
||||
github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw=
|
||||
github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y=
|
||||
github.com/go-pg/pg/v10 v10.15.0 h1:6DQwbaxJz/e4wvgzbxBkBLiL/Uuk87MGgHhkURtzx24=
|
||||
@@ -441,11 +443,9 @@ github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
|
||||
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
|
||||
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goburrow/cache v0.1.4 h1:As4KzO3hgmzPlnaMniZU9+VmoNYseUhuELbxy9mRBfw=
|
||||
@@ -459,6 +459,8 @@ github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@@ -502,6 +504,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
@@ -524,7 +528,6 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo=
|
||||
github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=
|
||||
@@ -588,8 +591,9 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf h1:umfGUaWdFP2s6457fz1+xXYIWDxdGc7HdkLS9aJ1skk=
|
||||
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf/go.mod h1:V99KdStnMHZsvVOwIvhfcUzYgYkRZeQWUtumtL+SKxA=
|
||||
github.com/hdm/jarm-go v0.0.7 h1:Eq0geenHrBSYuKrdVhrBdMMzOmA+CAMLzN2WrF3eL6A=
|
||||
github.com/hdm/jarm-go v0.0.7/go.mod h1:kinGoS0+Sdn1Rr54OtanET5E5n7AlD6T6CrJAKDjJSQ=
|
||||
github.com/hdm/jarm-go v0.0.8 h1:8yDIGrmhEJs+QW1voR3JU0Zaz+AUqs3CJzMAQKzGFIc=
|
||||
github.com/hdm/jarm-go v0.0.8/go.mod h1:memdt/YMMW3811nvANDJsrtc8Fy2e1685aDWdI7Gwto=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
@@ -604,8 +608,6 @@ github.com/indece-official/go-ebcdic v1.2.0 h1:nKCubkNoXrGvBp3MSYuplOQnhANCDEY51
|
||||
github.com/indece-official/go-ebcdic v1.2.0/go.mod h1:RBddVJt0Ks0eDLRG5dhPwBDRiTNA7n+yv0dVFpSs46Q=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
|
||||
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
|
||||
github.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg=
|
||||
github.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY=
|
||||
github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
|
||||
@@ -626,10 +628,11 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
|
||||
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
@@ -660,8 +663,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
||||
github.com/kitabisa/go-ci v1.0.3 h1:JmIUIvcercRQc/9x/v02ydCCqU4MadSHaNaOF8T2pGA=
|
||||
github.com/kitabisa/go-ci v1.0.3/go.mod h1:e3wBSzaJbcifXrr/Gw2ZBLn44MmeqP5WySwXyHlCK/U=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=
|
||||
github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
@@ -680,17 +683,13 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
|
||||
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa h1:KQKuQDgA3DZX6C396lt3WDYB9Um1gLITLbvficVbqXk=
|
||||
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa/go.mod h1:HbwNE4XGwjgtUELkvQaAOjWrpianHYZdQVNqSdYW3UM=
|
||||
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
|
||||
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
|
||||
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
@@ -706,8 +705,6 @@ github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDh
|
||||
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/mackerelio/go-osstat v0.2.6 h1:gs4U8BZeS1tjrL08tt5VUliVvSWP26Ai2Ob8Lr7f2i0=
|
||||
github.com/mackerelio/go-osstat v0.2.6/go.mod h1:lRy8V9ZuHpuRVZh+vyTkODeDPl3/d5MgXHtLSaqG8bA=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
@@ -735,8 +732,8 @@ github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB
|
||||
github.com/microsoft/go-mssqldb v1.9.2 h1:nY8TmFMQOHpm2qVWo6y4I2mAmVdZqlGiMGAYt64Ibbs=
|
||||
github.com/microsoft/go-mssqldb v1.9.2/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=
|
||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
|
||||
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
||||
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
||||
github.com/minio/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
|
||||
@@ -749,20 +746,12 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
|
||||
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
|
||||
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
|
||||
github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||
github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY=
|
||||
github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ=
|
||||
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
|
||||
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -772,8 +761,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||
@@ -788,10 +775,10 @@ github.com/nwaples/rardecode/v2 v2.2.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3
|
||||
github.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
|
||||
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
|
||||
github.com/oasdiff/yaml v0.1.1 h1:6nHx+pn9gBRM6YpBlFZFQGCCd1nuvqOBtTD3KKTgGxY=
|
||||
github.com/oasdiff/yaml v0.1.1/go.mod h1:EYJNoyktvWMJ0Hmhx+6qTaqMOsalUaRGT8Sj1hNcegU=
|
||||
github.com/oasdiff/yaml3 v0.0.14 h1:aLJee3hxBK2H5wdXd9iPcIXb93Nty1Ge0pT171eHtkw=
|
||||
github.com/oasdiff/yaml3 v0.0.14/go.mod h1:csto2xfDjYccdUn/yw/bPjj/cYTdp6HtFA0J4TWG+gg=
|
||||
github.com/oiweiwei/go-msrpc v1.2.12 h1:gaxnv1cyX3v9l+NNxyr4ONyvh/mnmh8Egel9r8zxNxE=
|
||||
github.com/oiweiwei/go-msrpc v1.2.12/go.mod h1:T6/ENmAoD1nYCr3NW8PS8AjIX0tZEAL7yO0tsejtK18=
|
||||
github.com/oiweiwei/go-smb2.fork v1.0.0 h1:xHq/eYPM8hQEO/nwCez8YwHWHC8mlcsgw/Neu52fPN4=
|
||||
@@ -816,8 +803,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q=
|
||||
github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI=
|
||||
github.com/opencontainers/runc v1.3.6 h1:SLGIymCtsk80iNPWgbc8dtjI30r+5mTVV+4dN8/17Sk=
|
||||
github.com/opencontainers/runc v1.3.6/go.mod h1:o1wyv76EDlTkcf0KTFgN8bMWLPvgF/HfX709lDv+rr4=
|
||||
github.com/openrdap/rdap v0.9.1 h1:Rv6YbanbiVPsKRvOLdUmlU1AL5+2OFuEFLjFN+mQsCM=
|
||||
github.com/openrdap/rdap v0.9.1/go.mod h1:vKSiotbsENrjM/vaHXLddXbW8iQkBfa+ldEuYEjyLTQ=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
@@ -825,8 +812,6 @@ github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCy
|
||||
github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU=
|
||||
github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
|
||||
@@ -837,6 +822,8 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -848,36 +835,38 @@ github.com/projectdiscovery/asnmap v1.1.1 h1:ImJiKIaACOT7HPx4Pabb5dksolzaFYsD1kI
|
||||
github.com/projectdiscovery/asnmap v1.1.1/go.mod h1:QT7jt9nQanj+Ucjr9BqGr1Q2veCCKSAVyUzLXfEcQ60=
|
||||
github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ=
|
||||
github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=
|
||||
github.com/projectdiscovery/cdncheck v1.2.39 h1:gNE2dyaK+ZqEdEWyVUFlq8PvromEhSxamhsmFZR2Voc=
|
||||
github.com/projectdiscovery/cdncheck v1.2.39/go.mod h1:9oE9KKxCSHNvUf0UaMeqqUwWpC38FkNaTll0ScIBT3w=
|
||||
github.com/projectdiscovery/cdncheck v1.2.46 h1:ELtTCaT5dLigd0FGmw6z+JuIpbFAr422hbgAFXMmKvY=
|
||||
github.com/projectdiscovery/cdncheck v1.2.46/go.mod h1:9oE9KKxCSHNvUf0UaMeqqUwWpC38FkNaTll0ScIBT3w=
|
||||
github.com/projectdiscovery/clistats v0.1.4 h1:kDnXoNxIdOvQElOF7k2Mt6XosGa5GbMKPtRXdPHMVzU=
|
||||
github.com/projectdiscovery/clistats v0.1.4/go.mod h1:hjJYNcUubk9T3cuFvA+JkLhZGjzYW50fkC48dqUAtbU=
|
||||
github.com/projectdiscovery/dsl v0.8.19 h1:qA5OFJMfghSCjKqS4AdsEtnur/SoriXDw3geE7+mReU=
|
||||
github.com/projectdiscovery/dsl v0.8.19/go.mod h1:Twk93q7fxQ43v/8nR+0TJV8/eFTdBAC5tIXe3qzua9Y=
|
||||
github.com/projectdiscovery/fastdialer v0.5.10 h1:dB9MSu4cSo22qne4pHiK9iYSxfOgpwlKB6zfOHvz3RI=
|
||||
github.com/projectdiscovery/fastdialer v0.5.10/go.mod h1:W1ZkULr9mMR6i0oRFTztANnpVyEEzPUovK8sUM4eAw8=
|
||||
github.com/projectdiscovery/dsl v0.8.20 h1:CxWcKuoHFpOSS1kzqnbJuK5No/6qoRG8IzNDMnZ6c/M=
|
||||
github.com/projectdiscovery/dsl v0.8.20/go.mod h1:e1oHi7mxAxF+UhBhD5gOk90Ga6LQqvFea2voMO1E5D0=
|
||||
github.com/projectdiscovery/fastdialer v0.5.14 h1:83hkWNnbWq8IxDM65h1CCmHf/GoVj1fpwbR+wACiBhA=
|
||||
github.com/projectdiscovery/fastdialer v0.5.14/go.mod h1:+QaDuVAsvPk4VPSk9aVQCdZzHVbtI9+2BeeFhQUyebo=
|
||||
github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA=
|
||||
github.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw=
|
||||
github.com/projectdiscovery/freeport v0.0.7 h1:Q6uXo/j8SaV/GlAHkEYQi8WQoPXyJWxyspx+aFmz9Qk=
|
||||
github.com/projectdiscovery/freeport v0.0.7/go.mod h1:cOhWKvNBe9xM6dFJ3RrrLvJ5vXx2NQ36SecuwjenV2k=
|
||||
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c h1:s+lLAlrOrgwlPZQ9DFqNw+kia2nteKnJZ2Ek313yoUc=
|
||||
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c/go.mod h1:rN35/D3lVx2YDeENFFz06uj8j3XIqK1Ym9XcISF5fzg=
|
||||
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb h1:rutG906Drtbpz4DwU5mhGIeOhRcktDH4cGQitGUMAsg=
|
||||
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb/go.mod h1:FLjF1DmZ+POoGEiIQdWuYVwS++C/GwpX8YaCsTSm1RY=
|
||||
github.com/projectdiscovery/goflags v0.1.74 h1:n85uTRj5qMosm0PFBfsvOL24I7TdWRcWq/1GynhXS7c=
|
||||
github.com/projectdiscovery/goflags v0.1.74/go.mod h1:UMc9/7dFz2oln+10tv6cy+7WZKTHf9UGhaNkF95emh4=
|
||||
github.com/projectdiscovery/gologger v1.1.70 h1:A1ZAsUJfRUXO6qqwTwyTWXLVlBrVu/Gpi1zzL1hg5LY=
|
||||
github.com/projectdiscovery/gologger v1.1.70/go.mod h1:kpLKNafZWRN9P7WpJYtIOY/XvY/v41GDdU9NzICdKmo=
|
||||
github.com/projectdiscovery/goflags v0.1.75 h1:njEBnyueQaFa2ptWxbyl9zX0OClNdlN2AzZveNHiBOs=
|
||||
github.com/projectdiscovery/goflags v0.1.75/go.mod h1:7nAP1r2Dqgn/rwmOE3EWbZWUCEJKNIhVSBGpuzJAIns=
|
||||
github.com/projectdiscovery/goja v0.0.0-20260618133720-acb73e419534 h1:hYd1zQA/dxO2ASyQ6Re73TcJkW1LjLQvt4+86Hxefz8=
|
||||
github.com/projectdiscovery/goja v0.0.0-20260618133720-acb73e419534/go.mod h1:SO0AP+uKfYeeoR6jyVH/PKRYJE/f5gJrPMAM00iGEMc=
|
||||
github.com/projectdiscovery/goja_nodejs v0.0.0-20260618132410-8519f75f703d h1:fqqH9LHpN2WDz9QuxFrhKNxXSRtzk+Sa6jAhbB7tXcQ=
|
||||
github.com/projectdiscovery/goja_nodejs v0.0.0-20260618132410-8519f75f703d/go.mod h1:Ezmbgdaw4EunGGBU4MQViLoGMJc37LA3ip55YV3KeRI=
|
||||
github.com/projectdiscovery/gologger v1.1.71 h1:IYU4mw9viKdSzMTIGVpYuw1Gtg7QIHIStqAQgeNXcBQ=
|
||||
github.com/projectdiscovery/gologger v1.1.71/go.mod h1:mJwODZcFDg70ihINpOvZevmBtgvpP8H9/l8Y+OPhZPY=
|
||||
github.com/projectdiscovery/gostruct v0.0.2 h1:s8gP8ApugGM4go1pA+sVlPDXaWqNP5BBDDSv7VEdG1M=
|
||||
github.com/projectdiscovery/gostruct v0.0.2/go.mod h1:H86peL4HKwMXcQQtEa6lmC8FuD9XFt6gkNR0B/Mu5PE=
|
||||
github.com/projectdiscovery/govaluate v0.0.0-20260504230327-80320480bb6e h1:o+ulEIaC2+9V2Ezr6mI5xEhKWsf0V/+FUQIS723Aj6U=
|
||||
github.com/projectdiscovery/govaluate v0.0.0-20260504230327-80320480bb6e/go.mod h1:xH7bPwHxUlz1yx9UlVeTF+UVCUaKhTnZgaxHb5z362E=
|
||||
github.com/projectdiscovery/govaluate v0.0.0-20260615100919-5ee2581bbf7e h1:vxzgQlz2Cy/YvizYDQx9OhucBcmBotfDhbQ4yCY2vfA=
|
||||
github.com/projectdiscovery/govaluate v0.0.0-20260615100919-5ee2581bbf7e/go.mod h1:xH7bPwHxUlz1yx9UlVeTF+UVCUaKhTnZgaxHb5z362E=
|
||||
github.com/projectdiscovery/gozero v0.1.1-0.20260530071156-fa1dad563d76 h1:AN70bbi6BBs7KpIM9w0LxygUN7uzT/oH+owDIQ+Fz/k=
|
||||
github.com/projectdiscovery/gozero v0.1.1-0.20260530071156-fa1dad563d76/go.mod h1:cWHYnRXoYWHtTpOYyAp5laGYX8GH8ITUhgQaP8G/8FA=
|
||||
github.com/projectdiscovery/hmap v0.0.101 h1:zXM6YtLmsn8Q0CUUw8QavhqWmiQYwaw+/U679Rr00pc=
|
||||
github.com/projectdiscovery/hmap v0.0.101/go.mod h1:w6N9/a5H8kvyx53AhtPDUWe5Qq3D6NBDPA23glHpa/Q=
|
||||
github.com/projectdiscovery/httpx v1.9.0 h1:5yn4ik/LqZ+v3MLgU7+CZJQyND9osW9NmZ3squylxsc=
|
||||
github.com/projectdiscovery/httpx v1.9.0/go.mod h1:jGTRyUHddo2WyK4klWIwQXgGF1Lu39XVyzlue4H3pX8=
|
||||
github.com/projectdiscovery/httpx v1.10.0 h1:sZvfeJDeX4eJfB5Oh29My4gOHmLfvpCt2wCqhargJrE=
|
||||
github.com/projectdiscovery/httpx v1.10.0/go.mod h1:gfdNnFznLLlAgAgSQlYjUeEVvYzKrNV0jO5Qyo5w+FI=
|
||||
github.com/projectdiscovery/interactsh v1.3.1 h1:5HzeVGVCAX/cjTguJ+7ClOmML5r97Ty7op9s+/F7BiM=
|
||||
github.com/projectdiscovery/interactsh v1.3.1/go.mod h1:MXQ11EoBPROb4bEw+WP9e4DX4fMhrpS6EwfMfZomBsw=
|
||||
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb h1:MGtI4oE12ruWv11ZlPXXd7hl/uAaQZrFvrIDYDeVMd8=
|
||||
@@ -888,18 +877,18 @@ github.com/projectdiscovery/mapcidr v1.1.97 h1:7FkxNNVXp+m1rIu5Nv/2SrF9k4+LwP8Qu
|
||||
github.com/projectdiscovery/mapcidr v1.1.97/go.mod h1:9dgTJh1SP02gYZdpzMjm6vtYFkEHQHoTyaVNvaeJ7lA=
|
||||
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 h1:L/e8z8yw1pfT6bg35NiN7yd1XKtJap5Nk6lMwQ0RNi8=
|
||||
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5/go.mod h1:pGW2ncnTxTxHtP9wzcIJAB+3/NMp6IiuQWd2NK7K+oc=
|
||||
github.com/projectdiscovery/networkpolicy v0.1.40 h1:kYin4u1/dgb0nuz5fE1bz4Q0Zh66mOKIdkSHJ00bjGY=
|
||||
github.com/projectdiscovery/networkpolicy v0.1.40/go.mod h1:9ULLaMbdv9UnT0C5rmuK4nIwYs0o776xMnkPUb8TtaE=
|
||||
github.com/projectdiscovery/networkpolicy v0.1.44 h1:+fnpYNQVH9mYZY7VrsTqdL4YJGVZSA8XLAlRHBMl5QE=
|
||||
github.com/projectdiscovery/networkpolicy v0.1.44/go.mod h1:q1KeQiHchXdElScEMWc5mShWNRNoJXI5koRnVaX6Qh8=
|
||||
github.com/projectdiscovery/ratelimit v0.0.88 h1:AcurW9aLRzlEyPe9kSjnOpr3XzLMWTpiWAlW/w73ALU=
|
||||
github.com/projectdiscovery/ratelimit v0.0.88/go.mod h1:CU1s+68UUG2mctSl2wi32/DHLJA6TMg+4rxgP59LfVk=
|
||||
github.com/projectdiscovery/rawhttp v0.1.90 h1:LOSZ6PUH08tnKmWsIwvwv1Z/4zkiYKYOSZ6n+8RFKtw=
|
||||
github.com/projectdiscovery/rawhttp v0.1.90/go.mod h1:VZYAM25UI/wVB3URZ95ZaftgOnsbphxyAw/XnQRRz4Y=
|
||||
github.com/projectdiscovery/rawhttp v0.1.91 h1:6EIhZxCkBn27kJAl47FieCFCe4H2ZdbhoBVj0J6ZCVY=
|
||||
github.com/projectdiscovery/rawhttp v0.1.91/go.mod h1:VZYAM25UI/wVB3URZ95ZaftgOnsbphxyAw/XnQRRz4Y=
|
||||
github.com/projectdiscovery/rdap v0.9.0 h1:wPhHx5pQ2QI+WGhyNb2PjhTl0NtB39Nk7YFZ9cp8ZGA=
|
||||
github.com/projectdiscovery/rdap v0.9.0/go.mod h1:zk4yrJFQ2Hy36Aqk+DvotYQxYAeALaCJ5ORySkff36Q=
|
||||
github.com/projectdiscovery/retryabledns v1.0.115 h1:RKV63FNIznFHUoawg/1hs53pVH3wqPtFhwstCuxVSoA=
|
||||
github.com/projectdiscovery/retryabledns v1.0.115/go.mod h1:+fEMWoPigw+M0lGNKY7AZ+g8FIgj+4sONjsinMmeL3k=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.14 h1:vCBLwK8iIuua3i97jEac5/+TWkYTLhTkGblHu9ETPVc=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.14/go.mod h1:reVhQ+DzMAPYEQHdawCQ6h0tX3CpFyMH4XjcAyq9+U8=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.21 h1:HytR9e8AfhIF/4xUo6szqg213a7pOq5tE3kpNZCyYwI=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.21/go.mod h1:0SCELxSKpkqHTy9pcZDZ2ry+5djp4geKpEURJ1uSB24=
|
||||
github.com/projectdiscovery/sarif v0.1.0 h1:O541T+a448nSJsmIMnXXSOeDQEzpnCAYvRfe0eG5h74=
|
||||
github.com/projectdiscovery/sarif v0.1.0/go.mod h1:LBC+reM3bkI3qIIhE0rZaINaYX6VG+En6u2hHa5mA7E=
|
||||
github.com/projectdiscovery/stringsutil v0.0.2 h1:uzmw3IVLJSMW1kEg8eCStG/cGbYYZAja8BH3LqqJXMA=
|
||||
@@ -912,8 +901,8 @@ github.com/projectdiscovery/useragent v0.0.108 h1:fb+uLuFJvC+MHZjCtxQJxtvp1X6A8n
|
||||
github.com/projectdiscovery/useragent v0.0.108/go.mod h1:XdNRrlvtDmYfVL1Oybat4uMe+W6cLwsK9S18ond17CI=
|
||||
github.com/projectdiscovery/utils v0.11.1 h1:PWj1KjIASxt8icxommH72C0TQqNOvGkcSODRkiq0SQw=
|
||||
github.com/projectdiscovery/utils v0.11.1/go.mod h1:yktGrHGk2CTjNiccXovnvGrLHX9sV2bqz9nSnbA3V8M=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.84 h1:19c+ea8KZCnZIuZPztafFKK2uczDXxcZ/z6/l6DEEEs=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.84/go.mod h1:gMH0o5lBp65sKMwHx/tuUdOtW2RjodC6Ti+9QDsYMkY=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.91 h1:pjbEOCJKmxfEG5xK3WnUNY4SuYjPoYUuAUxa3F1QN1Y=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.91/go.mod h1:gMH0o5lBp65sKMwHx/tuUdOtW2RjodC6Ti+9QDsYMkY=
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.6 h1:GCEdIRlQjDux28xTXKszM7n3jlMf152d5nqVpVoetas=
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.6/go.mod h1:R5lWrNzP+7Oyn77NDVPnBsxx2/FyQZBBkIAaSaCQFxw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
@@ -960,6 +949,8 @@ github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
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/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/sashabaranov/go-openai v1.37.0 h1:hQQowgYm4OXJ1Z/wTrE+XZaO20BYsL0R3uRPSpfNZkY=
|
||||
github.com/sashabaranov/go-openai v1.37.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
|
||||
@@ -1001,6 +992,8 @@ github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
@@ -1026,10 +1019,6 @@ github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFd
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9 h1:GXIyLuIJ5Qk46lI8WJ83qHBZKUI3zhmMmuoY9HICUIQ=
|
||||
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9/go.mod h1:uQdBQGrE1fZ2EyOs0pLcCDd1bBV4rSThieuIIGhXZ50=
|
||||
github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY=
|
||||
github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30=
|
||||
github.com/testcontainers/testcontainers-go/modules/mongodb v0.42.0 h1:jX10Aprgf1L+Ov+KxcheZ/1JXdiJ/3wdevfWFSkxm6s=
|
||||
github.com/testcontainers/testcontainers-go/modules/mongodb v0.42.0/go.mod h1:Ph+xH0hAC6djPFTjPgLa3VmSfE4h82kzVIKxTj3n2o4=
|
||||
github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
|
||||
github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
|
||||
github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=
|
||||
@@ -1074,8 +1063,6 @@ github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
|
||||
github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc=
|
||||
@@ -1142,8 +1129,8 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.7.17 h1:p36OVWwRb246iHxA/U4p8OPEpOTESm4n+g+8t0EE5uA=
|
||||
github.com/yuin/goldmark v1.7.17/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
|
||||
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
@@ -1176,8 +1163,6 @@ go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
|
||||
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
|
||||
go.mongodb.org/mongo-driver/v2 v2.3.0 h1:sh55yOXA2vUjW1QYw/2tRlHSQViwDyPnW61AwpZ4rtU=
|
||||
go.mongodb.org/mongo-driver/v2 v2.3.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@@ -1187,16 +1172,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
@@ -1237,8 +1222,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -1276,8 +1261,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1327,8 +1312,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1338,8 +1323,8 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
|
||||
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -1356,8 +1341,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1424,8 +1409,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@@ -1439,8 +1424,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1458,8 +1443,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
|
||||
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1511,12 +1496,14 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
||||
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
@@ -1569,6 +1556,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -1581,6 +1570,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
|
||||
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -1621,7 +1612,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
@@ -1644,5 +1634,5 @@ pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
software.sslmate.com/src/go-pkcs12 v0.7.0 h1:Db8W44cB54TWD7stUFFSWxdfpdn6fZVcDl0w3R4RVM0=
|
||||
software.sslmate.com/src/go-pkcs12 v0.7.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
software.sslmate.com/src/go-pkcs12 v0.7.2 h1:Rh9FoMaI5k7Oo6EOS+2/BnoZ+JFIS+XHjM0VGkSPXLM=
|
||||
software.sslmate.com/src/go-pkcs12 v0.7.2/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
@@ -2,39 +2,67 @@
|
||||
package fuzzplayground
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"github.com/projectdiscovery/retryablehttp-go"
|
||||
)
|
||||
|
||||
func GetPlaygroundServer() *echo.Echo {
|
||||
e := echo.New()
|
||||
e.Use(middleware.Recover())
|
||||
e.Use(middleware.Logger())
|
||||
// PlaygroundServer wraps the fuzz playground handler with the lifecycle methods
|
||||
// used by the integration tests and the standalone playground command.
|
||||
type PlaygroundServer struct {
|
||||
handler http.Handler
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
e.GET("/", indexHandler)
|
||||
e.GET("/info", infoHandler)
|
||||
e.GET("/redirect", redirectHandler)
|
||||
e.GET("/request", requestHandler)
|
||||
e.GET("/email", emailHandler)
|
||||
e.GET("/permissions", permissionsHandler)
|
||||
func GetPlaygroundServer() *PlaygroundServer {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /{$}", indexHandler)
|
||||
mux.HandleFunc("GET /info", infoHandler)
|
||||
mux.HandleFunc("GET /redirect", redirectHandler)
|
||||
mux.HandleFunc("GET /request", requestHandler)
|
||||
mux.HandleFunc("GET /email", emailHandler)
|
||||
mux.HandleFunc("GET /permissions", permissionsHandler)
|
||||
|
||||
e.GET("/blog/post", numIdorHandler) // for num based idors like ?id=44
|
||||
e.POST("/reset-password", resetPasswordHandler)
|
||||
e.GET("/host-header-lab", hostHeaderLabHandler)
|
||||
e.GET("/user/:id/profile", userProfileHandler)
|
||||
e.POST("/user", patchUnsanitizedUserHandler)
|
||||
e.GET("/blog/posts", getPostsHandler)
|
||||
return e
|
||||
mux.HandleFunc("GET /blog/post", numIdorHandler) // for num based idors like ?id=44
|
||||
mux.HandleFunc("POST /reset-password", resetPasswordHandler)
|
||||
mux.HandleFunc("GET /host-header-lab", hostHeaderLabHandler)
|
||||
mux.HandleFunc("GET /user/{id}/profile", userProfileHandler)
|
||||
mux.HandleFunc("POST /user", patchUnsanitizedUserHandler)
|
||||
mux.HandleFunc("GET /blog/posts", getPostsHandler)
|
||||
|
||||
handler := recoverPlaygroundRequest(logPlaygroundRequest(mux))
|
||||
return &PlaygroundServer{handler: handler}
|
||||
}
|
||||
|
||||
func (s *PlaygroundServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (s *PlaygroundServer) Start(addr string) error {
|
||||
s.server = &http.Server{
|
||||
Addr: addr,
|
||||
Handler: s.handler,
|
||||
}
|
||||
if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PlaygroundServer) Close() error {
|
||||
if s.server == nil {
|
||||
return nil
|
||||
}
|
||||
return s.server.Close()
|
||||
}
|
||||
|
||||
var bodyTemplate = `<html>
|
||||
@@ -46,8 +74,8 @@ var bodyTemplate = `<html>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
func indexHandler(ctx echo.Context) error {
|
||||
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, `<h1>Fuzzing Playground</h1><hr>
|
||||
func indexHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
writeHTML(w, http.StatusOK, fmt.Sprintf(bodyTemplate, `<h1>Fuzzing Playground</h1><hr>
|
||||
<ul>
|
||||
|
||||
<li><a href="/info?name=test&another=value&random=data">Info Page XSS</a></li>
|
||||
@@ -65,154 +93,172 @@ func indexHandler(ctx echo.Context) error {
|
||||
`))
|
||||
}
|
||||
|
||||
func infoHandler(ctx echo.Context) error {
|
||||
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Name of user: %s%s%s", ctx.QueryParam("name"), ctx.QueryParam("another"), ctx.QueryParam("random"))))
|
||||
func infoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query()
|
||||
writeHTML(w, http.StatusOK, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Name of user: %s%s%s", query.Get("name"), query.Get("another"), query.Get("random"))))
|
||||
}
|
||||
|
||||
func redirectHandler(ctx echo.Context) error {
|
||||
url := ctx.QueryParam("redirect_url")
|
||||
return ctx.Redirect(302, url)
|
||||
func redirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, r.URL.Query().Get("redirect_url"), http.StatusFound)
|
||||
}
|
||||
|
||||
func requestHandler(ctx echo.Context) error {
|
||||
url := ctx.QueryParam("url")
|
||||
data, err := retryablehttp.DefaultClient().Get(url)
|
||||
func requestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
requestURL := r.URL.Query().Get("url")
|
||||
data, err := retryablehttp.DefaultClient().Get(requestURL)
|
||||
if err != nil {
|
||||
return ctx.HTML(500, err.Error())
|
||||
writeHTML(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = data.Body.Close()
|
||||
}()
|
||||
|
||||
body, _ := io.ReadAll(data.Body)
|
||||
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(body)))
|
||||
writeHTML(w, http.StatusOK, fmt.Sprintf(bodyTemplate, string(body)))
|
||||
}
|
||||
|
||||
func emailHandler(ctx echo.Context) error {
|
||||
text := ctx.QueryParam("text")
|
||||
func emailHandler(w http.ResponseWriter, r *http.Request) {
|
||||
text := r.URL.Query().Get("text")
|
||||
if strings.Contains(text, "{{") {
|
||||
trimmed := strings.SplitN(strings.Trim(text[strings.Index(text, "{"):], "{}"), "*", 2)
|
||||
if len(trimmed) < 2 {
|
||||
return ctx.HTML(500, "invalid template")
|
||||
writeHTML(w, http.StatusInternalServerError, "invalid template")
|
||||
return
|
||||
}
|
||||
first, _ := strconv.Atoi(trimmed[0])
|
||||
second, _ := strconv.Atoi(trimmed[1])
|
||||
text = strconv.Itoa(first * second)
|
||||
}
|
||||
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Text: %s", text)))
|
||||
writeHTML(w, http.StatusOK, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Text: %s", text)))
|
||||
}
|
||||
|
||||
func permissionsHandler(ctx echo.Context) error {
|
||||
command := ctx.QueryParam("cmd")
|
||||
func permissionsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
command := r.URL.Query().Get("cmd")
|
||||
fields := strings.Fields(command)
|
||||
cmd := exec.Command(fields[0], fields[1:]...)
|
||||
data, _ := cmd.CombinedOutput()
|
||||
|
||||
return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(data)))
|
||||
writeHTML(w, http.StatusOK, fmt.Sprintf(bodyTemplate, string(data)))
|
||||
}
|
||||
|
||||
func numIdorHandler(ctx echo.Context) error {
|
||||
func numIdorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// validate if any numerical query param is present
|
||||
// if not, return 400 if so, return 200
|
||||
for k := range ctx.QueryParams() {
|
||||
if _, err := strconv.Atoi(ctx.QueryParam(k)); err == nil {
|
||||
return ctx.JSON(200, "Profile Info for user with id "+ctx.QueryParam(k))
|
||||
for k := range r.URL.Query() {
|
||||
value := r.URL.Query().Get(k)
|
||||
if _, err := strconv.Atoi(value); err == nil {
|
||||
writeJSON(w, http.StatusOK, "Profile Info for user with id "+value)
|
||||
return
|
||||
}
|
||||
}
|
||||
return ctx.JSON(400, "No numerical query param found")
|
||||
writeJSON(w, http.StatusBadRequest, "No numerical query param found")
|
||||
}
|
||||
|
||||
func patchUnsanitizedUserHandler(ctx echo.Context) error {
|
||||
func patchUnsanitizedUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var user User
|
||||
|
||||
contentType := ctx.Request().Header.Get("Content-Type")
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
// manually handle unmarshalling data
|
||||
if strings.Contains(contentType, "application/json") {
|
||||
err := ctx.Bind(&user)
|
||||
if err != nil {
|
||||
return ctx.JSON(500, "Invalid JSON data")
|
||||
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, "Invalid JSON data")
|
||||
return
|
||||
}
|
||||
} else if strings.Contains(contentType, "application/x-www-form-urlencoded") {
|
||||
user.Name = ctx.FormValue("name")
|
||||
user.Age, _ = strconv.Atoi(ctx.FormValue("age"))
|
||||
user.Role = ctx.FormValue("role")
|
||||
user.ID, _ = strconv.Atoi(ctx.FormValue("id"))
|
||||
user.Name = r.FormValue("name")
|
||||
user.Age, _ = strconv.Atoi(r.FormValue("age"))
|
||||
user.Role = r.FormValue("role")
|
||||
user.ID, _ = strconv.Atoi(r.FormValue("id"))
|
||||
} else if strings.Contains(contentType, "application/xml") {
|
||||
bin, _ := io.ReadAll(ctx.Request().Body)
|
||||
bin, _ := io.ReadAll(r.Body)
|
||||
err := xml.Unmarshal(bin, &user)
|
||||
if err != nil {
|
||||
return ctx.JSON(500, "Invalid XML data")
|
||||
writeJSON(w, http.StatusInternalServerError, "Invalid XML data")
|
||||
return
|
||||
}
|
||||
} else if strings.Contains(contentType, "multipart/form-data") {
|
||||
user.Name = ctx.FormValue("name")
|
||||
user.Age, _ = strconv.Atoi(ctx.FormValue("age"))
|
||||
user.Role = ctx.FormValue("role")
|
||||
user.ID, _ = strconv.Atoi(ctx.FormValue("id"))
|
||||
user.Name = r.FormValue("name")
|
||||
user.Age, _ = strconv.Atoi(r.FormValue("age"))
|
||||
user.Role = r.FormValue("role")
|
||||
user.ID, _ = strconv.Atoi(r.FormValue("id"))
|
||||
} else {
|
||||
return ctx.JSON(500, "Invalid Content-Type")
|
||||
writeJSON(w, http.StatusInternalServerError, "Invalid Content-Type")
|
||||
return
|
||||
}
|
||||
|
||||
err := patchUnsanitizedUser(db, user)
|
||||
if err != nil {
|
||||
return ctx.JSON(500, err.Error())
|
||||
writeJSON(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
return ctx.JSON(200, "User updated successfully")
|
||||
writeJSON(w, http.StatusOK, "User updated successfully")
|
||||
}
|
||||
|
||||
// resetPassword mock
|
||||
func resetPasswordHandler(c echo.Context) error {
|
||||
func resetPasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var m map[string]interface{}
|
||||
if err := c.Bind(&m); err != nil {
|
||||
return c.JSON(500, "Something went wrong")
|
||||
if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, "Something went wrong")
|
||||
return
|
||||
}
|
||||
|
||||
host := c.Request().Header.Get("X-Forwarded-For")
|
||||
host := r.Header.Get("X-Forwarded-For")
|
||||
if host == "" {
|
||||
return c.JSON(500, "Something went wrong")
|
||||
writeJSON(w, http.StatusInternalServerError, "Something went wrong")
|
||||
return
|
||||
}
|
||||
resp, err := http.Get("http://internal." + host + "/update?user=1337&pass=" + m["password"].(string))
|
||||
password, ok := m["password"].(string)
|
||||
if !ok {
|
||||
writeJSON(w, http.StatusInternalServerError, "Something went wrong")
|
||||
return
|
||||
}
|
||||
resp, err := http.Get("http://internal." + host + "/update?user=1337&pass=" + password)
|
||||
if err != nil {
|
||||
return c.JSON(500, "Something went wrong")
|
||||
writeJSON(w, http.StatusInternalServerError, "Something went wrong")
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
return c.JSON(200, "Password reset successfully")
|
||||
writeJSON(w, http.StatusOK, "Password reset successfully")
|
||||
}
|
||||
|
||||
func hostHeaderLabHandler(c echo.Context) error {
|
||||
func hostHeaderLabHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// vulnerable app has custom routing and trusts x-forwarded-host
|
||||
// to route to internal services
|
||||
if c.Request().Header.Get("X-Forwarded-Host") != "" {
|
||||
resp, err := http.Get("http://" + c.Request().Header.Get("X-Forwarded-Host"))
|
||||
if r.Header.Get("X-Forwarded-Host") != "" {
|
||||
resp, err := http.Get("http://" + r.Header.Get("X-Forwarded-Host"))
|
||||
if err != nil {
|
||||
return c.JSON(500, "Something went wrong")
|
||||
writeJSON(w, http.StatusInternalServerError, "Something went wrong")
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
c.Response().Header().Set("Content-Type", resp.Header.Get("Content-Type"))
|
||||
c.Response().WriteHeader(resp.StatusCode)
|
||||
_, err = io.Copy(c.Response().Writer, resp.Body)
|
||||
w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
|
||||
w.WriteHeader(resp.StatusCode)
|
||||
_, err = io.Copy(w, resp.Body)
|
||||
if err != nil {
|
||||
return c.JSON(500, "Something went wrong")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
return c.JSON(200, "Not a Teapot")
|
||||
writeJSON(w, http.StatusOK, "Not a Teapot")
|
||||
}
|
||||
|
||||
func userProfileHandler(ctx echo.Context) error {
|
||||
val, _ := url.PathUnescape(ctx.Param("id"))
|
||||
func userProfileHandler(w http.ResponseWriter, r *http.Request) {
|
||||
val, _ := url.PathUnescape(r.PathValue("id"))
|
||||
fmt.Printf("Unescaped: %s\n", val)
|
||||
user, err := getUnsanitizedUser(db, val)
|
||||
if err != nil {
|
||||
return ctx.JSON(500, err.Error())
|
||||
writeJSON(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
return ctx.JSON(200, user)
|
||||
writeJSON(w, http.StatusOK, user)
|
||||
}
|
||||
|
||||
func getPostsHandler(c echo.Context) error {
|
||||
lang, err := c.Cookie("lang")
|
||||
func getPostsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
lang, err := r.Cookie("lang")
|
||||
if err != nil {
|
||||
// If the language cookie is missing, default to English
|
||||
lang = new(http.Cookie)
|
||||
@@ -220,7 +266,38 @@ func getPostsHandler(c echo.Context) error {
|
||||
}
|
||||
posts, err := getUnsanitizedPostsByLang(db, lang.Value)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, err.Error())
|
||||
writeJSON(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
return c.JSON(http.StatusOK, posts)
|
||||
writeJSON(w, http.StatusOK, posts)
|
||||
}
|
||||
|
||||
func writeHTML(w http.ResponseWriter, statusCode int, value string) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
|
||||
w.WriteHeader(statusCode)
|
||||
_, _ = io.WriteString(w, value)
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, statusCode int, value interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
w.WriteHeader(statusCode)
|
||||
_ = json.NewEncoder(w).Encode(value)
|
||||
}
|
||||
|
||||
func recoverPlaygroundRequest(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func logPlaygroundRequest(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("%s %s", r.Method, r.URL.RequestURI())
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
pdcpauth "github.com/projectdiscovery/utils/auth/pdcp"
|
||||
)
|
||||
|
||||
// AppendDirectoryInfo writes nuclei config/cache/PDCP directory paths.
|
||||
func AppendDirectoryInfo(w io.Writer) {
|
||||
_, _ = fmt.Fprintf(w, "Nuclei Config Directory: %s\n", config.DefaultConfig.GetConfigDir())
|
||||
_, _ = fmt.Fprintf(w, "Nuclei Cache Directory: %s\n", config.DefaultConfig.GetCacheDir())
|
||||
_, _ = fmt.Fprintf(w, "PDCP Directory: %s\n", pdcpauth.PDCPDir)
|
||||
}
|
||||
|
||||
// DirectoryInfo returns nuclei config/cache/PDCP directory paths as a string.
|
||||
func DirectoryInfo() string {
|
||||
var b strings.Builder
|
||||
AppendDirectoryInfo(&b)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// LogDirectoryInfo logs nuclei config/cache/PDCP directory paths at info level.
|
||||
func LogDirectoryInfo(logger *gologger.Logger) {
|
||||
if logger == nil {
|
||||
return
|
||||
}
|
||||
logger.Info().Msgf("Nuclei Config Directory: %s", config.DefaultConfig.GetConfigDir())
|
||||
logger.Info().Msgf("Nuclei Cache Directory: %s", config.DefaultConfig.GetCacheDir())
|
||||
logger.Info().Msgf("PDCP Directory: %s", pdcpauth.PDCPDir)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
pdcpauth "github.com/projectdiscovery/utils/auth/pdcp"
|
||||
)
|
||||
|
||||
func TestDirectoryInfoContainsKnownLabels(t *testing.T) {
|
||||
info := DirectoryInfo()
|
||||
cfg := config.DefaultConfig
|
||||
|
||||
require.Equal(t, strings.Join([]string{
|
||||
fmt.Sprintf("Nuclei Config Directory: %s", cfg.GetConfigDir()),
|
||||
fmt.Sprintf("Nuclei Cache Directory: %s", cfg.GetCacheDir()),
|
||||
fmt.Sprintf("PDCP Directory: %s", pdcpauth.PDCPDir),
|
||||
"",
|
||||
}, "\n"), info)
|
||||
require.Equal(t, 3, strings.Count(info, "\n"))
|
||||
}
|
||||
@@ -20,6 +20,7 @@ func DoHealthCheck(options *types.Options) string {
|
||||
fmt.Fprintf(&test, "Architecture: %s\n", runtime.GOARCH)
|
||||
fmt.Fprintf(&test, "Go Version: %s\n", runtime.Version())
|
||||
fmt.Fprintf(&test, "Compiler: %s\n", runtime.Compiler)
|
||||
AppendDirectoryInfo(&test)
|
||||
|
||||
var testResult string
|
||||
cfg := config.DefaultConfig
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestParseHeadlessOptionalArguments(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
strsl := goflags.StringSlice{}
|
||||
for _, v := range strings.Split(tt.input, ",") {
|
||||
for v := range strings.SplitSeq(tt.input, ",") {
|
||||
//nolint
|
||||
strsl.Set(v)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,654 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
|
||||
inputtypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
||||
"github.com/projectdiscovery/utils/errkit"
|
||||
iputil "github.com/projectdiscovery/utils/ip"
|
||||
mapsutil "github.com/projectdiscovery/utils/maps"
|
||||
sliceutil "github.com/projectdiscovery/utils/slice"
|
||||
stringsutil "github.com/projectdiscovery/utils/strings"
|
||||
syncutil "github.com/projectdiscovery/utils/sync"
|
||||
urlutil "github.com/projectdiscovery/utils/url"
|
||||
)
|
||||
|
||||
const preflightWorkers = 100
|
||||
|
||||
// preflightDialTimeout is intentionally short: this is a coarse filter to skip
|
||||
// obviously-dead targets before the real scan (which will use full timeouts).
|
||||
const preflightDialTimeout = 750 * time.Millisecond
|
||||
|
||||
type filteringInputProvider struct {
|
||||
base provider.InputProvider
|
||||
allowed *mapsutil.SyncLockMap[string, struct{}]
|
||||
allowCnt int64
|
||||
execID string
|
||||
}
|
||||
|
||||
func (f *filteringInputProvider) Count() int64 { return f.allowCnt }
|
||||
func (f *filteringInputProvider) InputType() string { return f.base.InputType() }
|
||||
func (f *filteringInputProvider) Close() { f.base.Close() }
|
||||
func (f *filteringInputProvider) Set(executionId string, value string) {
|
||||
f.base.Set(executionId, value)
|
||||
}
|
||||
func (f *filteringInputProvider) SetWithProbe(executionId string, value string, probe inputtypes.InputLivenessProbe) error {
|
||||
return f.base.SetWithProbe(executionId, value, probe)
|
||||
}
|
||||
func (f *filteringInputProvider) SetWithExclusions(executionId string, value string) error {
|
||||
return f.base.SetWithExclusions(executionId, value)
|
||||
}
|
||||
func (f *filteringInputProvider) Iterate(callback func(value *contextargs.MetaInput) bool) {
|
||||
f.base.Iterate(func(mi *contextargs.MetaInput) bool {
|
||||
key, err := mi.MarshalString()
|
||||
if err != nil {
|
||||
return callback(mi)
|
||||
}
|
||||
if _, ok := f.allowed.Get(key); !ok {
|
||||
return true
|
||||
}
|
||||
return callback(mi)
|
||||
})
|
||||
}
|
||||
|
||||
// preflightResolveAndPortScan resolves hostname targets and performs a TCP connect scan for ports
|
||||
// required by loaded templates. Targets that are non-resolvable hostnames or have no relevant open
|
||||
// ports are filtered out from the input provider.
|
||||
func (r *Runner) preflightResolveAndPortScan(store *loader.Store) error {
|
||||
if r.inputProvider == nil {
|
||||
return nil
|
||||
}
|
||||
// MultiFormat inputs may represent complete requests; skip preflight for now.
|
||||
if r.inputProvider.InputType() == provider.MultiFormatInputProvider {
|
||||
return nil
|
||||
}
|
||||
|
||||
finalTemplates := []*templates.Template{}
|
||||
finalTemplates = append(finalTemplates, store.Templates()...)
|
||||
finalTemplates = append(finalTemplates, store.Workflows()...)
|
||||
if len(finalTemplates) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
dialers := protocolstate.GetDialersWithId(r.options.ExecutionId)
|
||||
if dialers == nil || dialers.Fastdialer == nil {
|
||||
return fmt.Errorf("dialers not initialized for %s", r.options.ExecutionId)
|
||||
}
|
||||
|
||||
portsPopularity := portsPopularityFromTemplates(finalTemplates)
|
||||
// Also include ports explicitly present in input list (ip:port or URL with port),
|
||||
// so that a user-provided port isn't dropped even if templates didn't specify it.
|
||||
var inputs []preflightTarget
|
||||
portsFromInputs := map[string]struct{}{}
|
||||
var totalTargets atomic.Int64
|
||||
r.inputProvider.Iterate(func(mi *contextargs.MetaInput) bool {
|
||||
totalTargets.Add(1)
|
||||
key, err := mi.MarshalString()
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
// Use Target()/CustomIP so preflight resolves the same address the real
|
||||
// scan would connect to (honors ReqResp-backed targets and CustomIP),
|
||||
// instead of only the raw Input which can mismatch routing state.
|
||||
inputs = append(inputs, preflightTarget{key: key, target: mi.Target(), customIP: mi.CustomIP})
|
||||
extractPortsFromInput(portsFromInputs, mi.Target())
|
||||
return true
|
||||
})
|
||||
|
||||
portsToScan := sliceutil.Dedupe(append(keysOfPopularity(portsPopularity), keysOf(portsFromInputs)...))
|
||||
portsToScan = filterValidPorts(portsToScan)
|
||||
// Sort by "likely-open" order (nmap-ish/common ports first), then numeric for determinism.
|
||||
likelyRank := likelyOpenPortRank()
|
||||
sort.Slice(portsToScan, func(i, j int) bool {
|
||||
pi, pj := portsToScan[i], portsToScan[j]
|
||||
ri, okRi := likelyRank[pi]
|
||||
rj, okRj := likelyRank[pj]
|
||||
// ranked ports first
|
||||
if okRi != okRj {
|
||||
return okRi
|
||||
}
|
||||
// among ranked ports, lower rank wins
|
||||
if okRi && okRj && ri != rj {
|
||||
return ri < rj
|
||||
}
|
||||
// numeric asc
|
||||
ni, _ := strconv.Atoi(pi)
|
||||
nj, _ := strconv.Atoi(pj)
|
||||
return ni < nj
|
||||
})
|
||||
|
||||
// If no ports were found, nothing to scan -> keep all.
|
||||
if len(portsToScan) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !r.options.Silent {
|
||||
r.Logger.Info().Msgf("Running preflight portscan (workers=%d, ports=%d, targets=%d)", preflightWorkers, len(portsToScan), totalTargets.Load())
|
||||
}
|
||||
|
||||
swg, err := syncutil.New(syncutil.WithSize(preflightWorkers))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Resolve all targets (once) up-front so we can optionally run a single batched TCP dial scan.
|
||||
// Map original input key -> resolved IPs (deduped).
|
||||
// SyncLockMap requires comparable values; store resolved IPs as a comma-separated string.
|
||||
resolvedIPsByKey := mapsutil.NewSyncLockMap[string, string]()
|
||||
allIPsSet := mapsutil.NewSyncLockMap[string, struct{}]()
|
||||
var resolveProcessed atomic.Int64
|
||||
var resolveDNSFail atomic.Int64
|
||||
|
||||
for _, t := range inputs {
|
||||
swg.Add()
|
||||
go func(t preflightTarget) {
|
||||
defer swg.Done()
|
||||
host, _, _, _ := hostForResolveAndScan(t.target)
|
||||
if t.customIP != "" {
|
||||
host = t.customIP
|
||||
}
|
||||
if host == "" {
|
||||
resolveDNSFail.Add(1)
|
||||
resolveProcessed.Add(1)
|
||||
return
|
||||
}
|
||||
ips := []string{}
|
||||
if iputil.IsIP(host) {
|
||||
ips = append(ips, host)
|
||||
} else {
|
||||
dns, err := dialers.Fastdialer.GetDNSData(host)
|
||||
if err != nil || (len(dns.A) == 0 && len(dns.AAAA) == 0) {
|
||||
resolveDNSFail.Add(1)
|
||||
resolveProcessed.Add(1)
|
||||
return
|
||||
}
|
||||
ips = append(ips, dns.A...)
|
||||
ips = append(ips, dns.AAAA...)
|
||||
}
|
||||
ips = sliceutil.Dedupe(ips)
|
||||
if len(ips) == 0 {
|
||||
resolveDNSFail.Add(1)
|
||||
resolveProcessed.Add(1)
|
||||
return
|
||||
}
|
||||
|
||||
// store
|
||||
// (small contention; acceptable at preflight scale)
|
||||
_ = resolvedIPsByKey.Set(t.key, strings.Join(ips, ","))
|
||||
for _, ip := range ips {
|
||||
_ = allIPsSet.Set(ip, struct{}{})
|
||||
}
|
||||
resolveProcessed.Add(1)
|
||||
}(t)
|
||||
}
|
||||
swg.Wait()
|
||||
|
||||
// Prepare list of all IPs for scan.
|
||||
allIPsMap := allIPsSet.GetAll()
|
||||
allIPs := make([]string, 0, len(allIPsMap))
|
||||
for ip := range allIPsMap {
|
||||
allIPs = append(allIPs, ip)
|
||||
}
|
||||
sort.Strings(allIPs)
|
||||
|
||||
// we do fast TCP dial scanning against resolved IPs.
|
||||
if !r.options.Silent {
|
||||
r.Logger.Info().Msgf("Preflight resolution: total=%d resolvable=%d unresolvable=%d", totalTargets.Load(), int64(len(resolvedIPsByKey.GetAll())), resolveDNSFail.Load())
|
||||
}
|
||||
|
||||
allowed := mapsutil.NewSyncLockMap[string, struct{}]()
|
||||
|
||||
var dnsFail atomic.Int64
|
||||
var portFail atomic.Int64
|
||||
var kept atomic.Int64
|
||||
var processed atomic.Int64
|
||||
|
||||
perPortOpen := mapsutil.NewSyncLockMap[string, *atomic.Int64]()
|
||||
|
||||
// Periodic progress logging
|
||||
// Always enabled unless running in silent mode.
|
||||
debugProgress := true
|
||||
stopProgress := make(chan struct{})
|
||||
if debugProgress && !r.options.Silent {
|
||||
start := time.Now()
|
||||
go func() {
|
||||
t := time.NewTicker(1 * time.Second)
|
||||
defer t.Stop()
|
||||
var lastProcessed int64
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
p := processed.Load()
|
||||
if p == lastProcessed {
|
||||
continue
|
||||
}
|
||||
lastProcessed = p
|
||||
total := totalTargets.Load()
|
||||
k := kept.Load()
|
||||
df := dnsFail.Load()
|
||||
pf := portFail.Load()
|
||||
dropped := p - k
|
||||
r.Logger.Info().Msgf("Preflight progress: %d/%d processed (kept=%d dropped=%d dns_fail=%d port_fail=%d elapsed=%s)",
|
||||
p, total, k, dropped, df, pf, time.Since(start).Truncate(time.Second))
|
||||
case <-stopProgress:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for _, t := range inputs {
|
||||
swg.Add()
|
||||
go func(t preflightTarget) {
|
||||
defer swg.Done()
|
||||
ok, openPort, reason := r.preflightOneResolved(t.key, t.target, portsToScan, resolvedIPsByKey)
|
||||
processed.Add(1)
|
||||
if ok {
|
||||
_ = allowed.Set(t.key, struct{}{})
|
||||
kept.Add(1)
|
||||
if openPort != "" {
|
||||
counter, _ := perPortOpen.Get(openPort)
|
||||
if counter == nil {
|
||||
counter = &atomic.Int64{}
|
||||
_ = perPortOpen.Set(openPort, counter)
|
||||
}
|
||||
counter.Add(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
switch reason {
|
||||
case preflightReasonDNS:
|
||||
dnsFail.Add(1)
|
||||
case preflightReasonPorts:
|
||||
portFail.Add(1)
|
||||
}
|
||||
}(t)
|
||||
}
|
||||
swg.Wait()
|
||||
close(stopProgress)
|
||||
|
||||
// Apply filtering wrapper
|
||||
allowedAll := allowed.GetAll()
|
||||
r.inputProvider = &filteringInputProvider{
|
||||
base: r.inputProvider,
|
||||
allowed: allowed,
|
||||
allowCnt: int64(len(allowedAll)),
|
||||
execID: r.options.ExecutionId,
|
||||
}
|
||||
|
||||
// Summary
|
||||
if !r.options.Silent {
|
||||
dropped := totalTargets.Load() - kept.Load()
|
||||
r.Logger.Info().Msgf("Preflight summary: total=%d kept=%d filtered_dns=%d filtered_ports=%d",
|
||||
totalTargets.Load(), kept.Load(), dnsFail.Load(), portFail.Load())
|
||||
r.Logger.Info().Msgf("Preflight targets: dropped=%d left=%d", dropped, kept.Load())
|
||||
perPortOpenAll := perPortOpen.GetAll()
|
||||
if len(perPortOpenAll) > 0 {
|
||||
type kv struct {
|
||||
port string
|
||||
count int64
|
||||
}
|
||||
kvs := make([]kv, 0, len(perPortOpenAll))
|
||||
for p, c := range perPortOpenAll {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
kvs = append(kvs, kv{port: p, count: c.Load()})
|
||||
}
|
||||
sort.Slice(kvs, func(i, j int) bool {
|
||||
if kvs[i].count == kvs[j].count {
|
||||
return kvs[i].port < kvs[j].port
|
||||
}
|
||||
return kvs[i].count > kvs[j].count
|
||||
})
|
||||
parts := make([]string, 0, len(kvs))
|
||||
for _, item := range kvs {
|
||||
parts = append(parts, fmt.Sprintf("%s=%d", item.port, item.count))
|
||||
}
|
||||
r.Logger.Info().Msgf("Preflight open-port distribution: %s", strings.Join(parts, " "))
|
||||
}
|
||||
}
|
||||
|
||||
_ = gologger.DefaultLogger // ensure logger imported even when silent builds vary
|
||||
return nil
|
||||
}
|
||||
|
||||
type preflightTarget struct {
|
||||
key string
|
||||
target string
|
||||
customIP string
|
||||
}
|
||||
|
||||
type preflightReason int
|
||||
|
||||
const (
|
||||
preflightReasonNone preflightReason = iota
|
||||
preflightReasonDNS
|
||||
preflightReasonPorts
|
||||
)
|
||||
|
||||
func (r *Runner) preflightOneResolved(key string, raw string, ports []string, resolved *mapsutil.SyncLockMap[string, string]) (ok bool, openPort string, reason preflightReason) {
|
||||
resolvedIPsCSV, _ := resolved.Get(key)
|
||||
if resolvedIPsCSV == "" {
|
||||
return false, "", preflightReasonDNS
|
||||
}
|
||||
ips := strings.Split(resolvedIPsCSV, ",")
|
||||
|
||||
// TCP dial scan using resolved IPs
|
||||
host, schemePort, hasSchemePort, _ := hostForResolveAndScan(raw)
|
||||
ordered := ports
|
||||
if hasSchemePort && schemePort != "" {
|
||||
ordered = append([]string{schemePort}, ports...)
|
||||
ordered = sliceutil.Dedupe(ordered)
|
||||
}
|
||||
|
||||
timeout := preflightDialTimeout
|
||||
if r.options.Timeout > 0 {
|
||||
t := time.Duration(r.options.Timeout) * time.Second
|
||||
if t > 0 && t < timeout {
|
||||
timeout = t
|
||||
}
|
||||
}
|
||||
// Use net.Dialer directly for strict timeout enforcement.
|
||||
// We already resolved IPs, so we don't need fastdialer DNS behavior here.
|
||||
// This avoids rare cases where proxy dialers / custom dial stacks may not respect ctx cancellation promptly.
|
||||
d := &net.Dialer{Timeout: timeout}
|
||||
|
||||
// Per-host parallelism: probe up to 3 ports concurrently, stop on first success.
|
||||
ctx, cancelAll := context.WithCancel(context.Background())
|
||||
defer cancelAll()
|
||||
|
||||
type hit struct {
|
||||
port string
|
||||
}
|
||||
resultCh := make(chan hit, 1)
|
||||
portsCh := make(chan string)
|
||||
|
||||
worker := func() {
|
||||
for p := range portsCh {
|
||||
// Stop quickly if someone already found an open port.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
_ = host // keep for debugging parity
|
||||
dctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
conn, err := d.DialContext(dctx, "tcp", net.JoinHostPort(ip, p))
|
||||
cancel()
|
||||
if err == nil {
|
||||
_ = conn.Close()
|
||||
// Best-effort: first hit wins.
|
||||
select {
|
||||
case resultCh <- hit{port: p}:
|
||||
cancelAll()
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
// If ctx cancelled (other worker won), stop early.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
workers := 3
|
||||
if len(ordered) < workers {
|
||||
workers = len(ordered)
|
||||
}
|
||||
for i := 0; i < workers; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
worker()
|
||||
}()
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(portsCh)
|
||||
for _, p := range ordered {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case portsCh <- p:
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for either a hit or all workers to finish.
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case h := <-resultCh:
|
||||
<-done
|
||||
return true, h.port, preflightReasonNone
|
||||
case <-done:
|
||||
return false, "", preflightReasonPorts
|
||||
}
|
||||
}
|
||||
|
||||
func portsPopularityFromTemplates(tpls []*templates.Template) map[string]int {
|
||||
out := map[string]int{}
|
||||
for _, tpl := range tpls {
|
||||
// HTTP templates imply 80/443 for preflight.
|
||||
if len(tpl.RequestsHTTP) > 0 || len(tpl.RequestsWithHTTP) > 0 || len(tpl.RequestsHeadless) > 0 {
|
||||
out["80"]++
|
||||
out["443"]++
|
||||
}
|
||||
// Network templates declare ports directly.
|
||||
for _, req := range tpl.RequestsNetwork {
|
||||
for _, p := range splitPorts(req.Port) {
|
||||
out[p]++
|
||||
}
|
||||
}
|
||||
for _, req := range tpl.RequestsWithTCP {
|
||||
for _, p := range splitPorts(req.Port) {
|
||||
out[p]++
|
||||
}
|
||||
}
|
||||
// Javascript templates may include args.Port (comma-separated).
|
||||
for _, req := range tpl.RequestsJavascript {
|
||||
for _, p := range extractPortsFromJSArgs(req.Args) {
|
||||
out[p]++
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func keysOfPopularity(m map[string]int) []string {
|
||||
out := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
out = append(out, k)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// likelyOpenPortRank returns a heuristic "most likely to be open" ranking for common TCP ports.
|
||||
// This is intentionally static (fast + deterministic) and loosely aligns with what scanners like nmap
|
||||
// tend to prioritize (common services first).
|
||||
func likelyOpenPortRank() map[string]int {
|
||||
// Lower index = higher priority.
|
||||
// Keep this list small-ish but useful; anything not in here falls back to template popularity + numeric.
|
||||
common := []string{
|
||||
"80", "443",
|
||||
"22", "21", "23",
|
||||
"25", "110", "143", "465", "587", "993", "995",
|
||||
"53",
|
||||
"3389",
|
||||
"445", "139",
|
||||
"135",
|
||||
"3306", "5432", "1433", "1521",
|
||||
"6379", "27017",
|
||||
"9200", "9300",
|
||||
"8080", "8443", "8000", "8008", "8081", "8888",
|
||||
"9201",
|
||||
"161", "162",
|
||||
"389", "636",
|
||||
"5900",
|
||||
"11211",
|
||||
"69", "123",
|
||||
"1194",
|
||||
"500", "4500",
|
||||
}
|
||||
rank := make(map[string]int, len(common))
|
||||
for i, p := range common {
|
||||
// do not overwrite if duplicates
|
||||
if _, ok := rank[p]; !ok {
|
||||
rank[p] = i
|
||||
}
|
||||
}
|
||||
return rank
|
||||
}
|
||||
|
||||
func extractPortsFromJSArgs(args map[string]interface{}) []string {
|
||||
if args == nil {
|
||||
return nil
|
||||
}
|
||||
for k, v := range args {
|
||||
if strings.EqualFold(k, "Port") {
|
||||
s := fmt.Sprint(v)
|
||||
return splitPorts(s)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitPorts(s string) []string {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
var out []string
|
||||
for p := range strings.SplitSeq(s, ",") {
|
||||
p = strings.TrimSpace(p)
|
||||
if p != "" {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func extractPortsFromInput(dst map[string]struct{}, input string) {
|
||||
if dst == nil {
|
||||
return
|
||||
}
|
||||
in := strings.TrimSpace(input)
|
||||
if in == "" {
|
||||
return
|
||||
}
|
||||
low := strings.ToLower(in)
|
||||
if strings.HasPrefix(low, "http://") {
|
||||
dst["80"] = struct{}{}
|
||||
}
|
||||
if strings.HasPrefix(low, "https://") {
|
||||
dst["443"] = struct{}{}
|
||||
}
|
||||
// URL parsing (best effort)
|
||||
if u, err := urlutil.Parse(in); err == nil && u != nil {
|
||||
if p := u.Port(); p != "" {
|
||||
dst[p] = struct{}{}
|
||||
} else if u.Scheme == "http" {
|
||||
dst["80"] = struct{}{}
|
||||
} else if u.Scheme == "https" {
|
||||
dst["443"] = struct{}{}
|
||||
}
|
||||
return
|
||||
}
|
||||
// host:port
|
||||
_, p, err := net.SplitHostPort(in)
|
||||
if err == nil && p != "" {
|
||||
dst[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func hostForResolveAndScan(raw string) (host string, schemeDefaultPort string, hasSchemePort bool, err error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return "", "", false, errkit.New("empty input")
|
||||
}
|
||||
// If it looks like URL, parse and extract hostname.
|
||||
if stringsutil.ContainsAny(raw, "://") {
|
||||
u, perr := urlutil.ParseAbsoluteURL(raw, false)
|
||||
if perr == nil && u != nil {
|
||||
host = u.Hostname()
|
||||
if u.Port() != "" {
|
||||
return host, "", false, nil
|
||||
}
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "http":
|
||||
return host, "80", true, nil
|
||||
case "https":
|
||||
return host, "443", true, nil
|
||||
}
|
||||
return host, "", false, nil
|
||||
}
|
||||
}
|
||||
// Try host:port form
|
||||
h, _, serr := net.SplitHostPort(raw)
|
||||
if serr == nil && h != "" {
|
||||
return h, "", false, nil
|
||||
}
|
||||
// Bare host/ip
|
||||
return raw, "", false, nil
|
||||
}
|
||||
|
||||
func keysOf(m map[string]struct{}) []string {
|
||||
out := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
out = append(out, k)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func filterValidPorts(ports []string) []string {
|
||||
out := make([]string, 0, len(ports))
|
||||
for _, p := range ports {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
// allow numeric only
|
||||
if !isNumeric(p) {
|
||||
continue
|
||||
}
|
||||
i, err := strconv.Atoi(p)
|
||||
if err != nil || i < 1 || i > 65535 {
|
||||
continue
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return sliceutil.Dedupe(out)
|
||||
}
|
||||
|
||||
func isNumeric(s string) bool {
|
||||
for _, r := range s {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+93
-21
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -55,6 +56,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/uncover"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
|
||||
@@ -120,6 +122,10 @@ func New(options *types.Options) (*Runner, error) {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if options.Verbose || options.VerboseVerbose {
|
||||
LogDirectoryInfo(runner.Logger)
|
||||
}
|
||||
|
||||
// Version check by default
|
||||
if config.DefaultConfig.CanCheckForUpdates() {
|
||||
if err := installer.NucleiVersionCheck(); err != nil {
|
||||
@@ -181,6 +187,7 @@ func New(options *types.Options) (*Runner, error) {
|
||||
}
|
||||
|
||||
yaml.StrictSyntax = !options.NoStrictSyntax
|
||||
yaml.AllowLocalFileAccess = options.AllowLocalFileAccess
|
||||
|
||||
if options.Headless {
|
||||
if engine.MustDisableSandbox() {
|
||||
@@ -198,7 +205,7 @@ func New(options *types.Options) (*Runner, error) {
|
||||
var httpclient *retryablehttp.Client
|
||||
if options.ProxyInternal && options.AliveHttpProxy != "" || options.AliveSocksProxy != "" {
|
||||
var err error
|
||||
httpclient, err = httpclientpool.Get(options, &httpclientpool.Configuration{})
|
||||
httpclient, err = httpclientpool.Get(options, &httpclientpool.Configuration{}, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -404,7 +411,12 @@ func New(options *types.Options) (*Runner, error) {
|
||||
if options.RateLimit > 0 && options.RateLimitDuration == 0 {
|
||||
options.RateLimitDuration = time.Second
|
||||
}
|
||||
runner.rateLimiter = utils.GetRateLimiter(context.Background(), options.RateLimit, options.RateLimitDuration)
|
||||
// If per-host rate limiting is enabled, make global rate limiter unlimited
|
||||
if options.PerHostRateLimit {
|
||||
runner.rateLimiter = utils.GetRateLimiter(context.Background(), 0, 0)
|
||||
} else {
|
||||
runner.rateLimiter = utils.GetRateLimiter(context.Background(), options.RateLimit, options.RateLimitDuration)
|
||||
}
|
||||
|
||||
// Initialization successful, disable cleanup on error
|
||||
cleanupOnError = false
|
||||
@@ -427,6 +439,29 @@ func (r *Runner) Close() {
|
||||
if r.httpStats != nil {
|
||||
r.httpStats.DisplayTopStats(r.options.NoColor)
|
||||
}
|
||||
if newConns, reusedConns := httpclientpool.GetConnectionStats(); newConns+reusedConns > 0 {
|
||||
total := newConns + reusedConns
|
||||
ratio := float64(reusedConns) / float64(total) * 100
|
||||
gologger.Info().Msgf("HTTP connections: %d total, %d new, %d reused (%.1f%%)", total, newConns, reusedConns, ratio)
|
||||
|
||||
// Per-host breakdown is opt-in (verbose) since large scans touch many hosts.
|
||||
if r.options.Verbose {
|
||||
perHost := httpclientpool.GetPerHostConnectionStats()
|
||||
sort.Slice(perHost, func(i, j int) bool {
|
||||
return (perHost[i].New + perHost[i].Reused) > (perHost[j].New + perHost[j].Reused)
|
||||
})
|
||||
const maxPerHostLines = 20
|
||||
for i, s := range perHost {
|
||||
if i >= maxPerHostLines {
|
||||
gologger.Info().Msgf("HTTP connections: ... and %d more host(s)", len(perHost)-maxPerHostLines)
|
||||
break
|
||||
}
|
||||
hostTotal := s.New + s.Reused
|
||||
hostRatio := float64(s.Reused) / float64(hostTotal) * 100
|
||||
gologger.Info().Msgf("HTTP connections [%s]: %d total, %d new, %d reused (%.1f%%)", s.Host, hostTotal, s.New, s.Reused, hostRatio)
|
||||
}
|
||||
}
|
||||
}
|
||||
// dump hosterrors cache
|
||||
if r.hostErrors != nil {
|
||||
r.hostErrors.Close()
|
||||
@@ -507,6 +542,11 @@ func (r *Runner) setupPDCPUpload(writer output.Writer) output.Writer {
|
||||
// RunEnumeration sets up the input layer for giving input nuclei.
|
||||
// binary and runs the actual enumeration
|
||||
func (r *Runner) RunEnumeration() error {
|
||||
// Reset connection-reuse counters so the summary logged on Close()
|
||||
// reflects only this run, not totals accumulated across multiple
|
||||
// in-process executions (e.g. SDK / embedded usage).
|
||||
httpclientpool.ResetConnectionStats()
|
||||
|
||||
// If the user has asked for DAST server mode, run the live
|
||||
// DAST fuzzing server.
|
||||
if r.options.DASTServer {
|
||||
@@ -674,7 +714,7 @@ func (r *Runner) RunEnumeration() error {
|
||||
if err := store.ValidateTemplates(); err != nil {
|
||||
return err
|
||||
}
|
||||
if stats.GetValue(templates.SyntaxErrorStats) == 0 && stats.GetValue(templates.SyntaxWarningStats) == 0 && stats.GetValue(templates.RuntimeWarningsStats) == 0 {
|
||||
if stats.GetValue(templates.TemplateSyntaxErrorStats) == 0 && stats.GetValue(templates.TemplateSyntaxWarningStats) == 0 && stats.GetValue(templates.TemplateRuntimeWarningStats) == 0 {
|
||||
r.Logger.Info().Msgf("All templates validated successfully")
|
||||
} else {
|
||||
return errors.New("encountered errors while performing template validation")
|
||||
@@ -701,6 +741,15 @@ func (r *Runner) RunEnumeration() error {
|
||||
_ = r.inputProvider.SetWithExclusions(r.options.ExecutionId, host)
|
||||
}
|
||||
}
|
||||
|
||||
// Preflight: resolve hosts + portscan for ports required by loaded templates, then filter inputs.
|
||||
// This reduces time spent on non-resolvable targets or targets with no relevant open ports.
|
||||
// Preflight is a best-effort optimization: on failure we log and continue with the full input set.
|
||||
if r.options.PreflightPortScan {
|
||||
if err := r.preflightResolveAndPortScan(store); err != nil {
|
||||
gologger.Warning().Msgf("preflight resolve/portscan failed, continuing without input filtering: %s", err)
|
||||
}
|
||||
}
|
||||
// display execution info like version , templates used etc
|
||||
r.displayExecutionInfo(store)
|
||||
|
||||
@@ -723,13 +772,15 @@ func (r *Runner) RunEnumeration() error {
|
||||
executorOpts.InputHelper.InputsHTTP = inputHelpers
|
||||
}
|
||||
|
||||
inputCount := int(r.inputProvider.Count())
|
||||
|
||||
// initialize stats worker ( this is no-op unless nuclei is built with stats build tag)
|
||||
// during execution a directory with 2 files will be created in the current directory
|
||||
// config.json - containing below info
|
||||
// events.jsonl - containing all start and end times of all templates
|
||||
events.InitWithConfig(&events.ScanConfig{
|
||||
Name: "nuclei-stats", // make this configurable
|
||||
TargetCount: int(r.inputProvider.Count()),
|
||||
TargetCount: inputCount,
|
||||
TemplatesCount: len(store.Templates()) + len(store.Workflows()),
|
||||
TemplateConcurrency: r.options.TemplateThreads,
|
||||
PayloadConcurrency: r.options.PayloadConcurrency,
|
||||
@@ -771,6 +822,25 @@ func (r *Runner) RunEnumeration() error {
|
||||
|
||||
r.progress.Stop()
|
||||
timeTaken := time.Since(now)
|
||||
|
||||
// Print pool/tracker stats if available (single dialers lookup, reads under lock)
|
||||
if dialers := protocolstate.GetDialersWithId(r.options.ExecutionId); dialers != nil {
|
||||
dialers.Lock()
|
||||
perHostRateLimitPool := dialers.PerHostRateLimitPool
|
||||
httpToHTTPSPortTracker := dialers.HTTPToHTTPSPortTracker
|
||||
dialers.Unlock()
|
||||
|
||||
if pool, ok := perHostRateLimitPool.(interface{ PrintStats() }); ok {
|
||||
pool.PrintStats()
|
||||
}
|
||||
if pool, ok := perHostRateLimitPool.(interface{ PrintPerHostPPSStats() }); ok {
|
||||
pool.PrintPerHostPPSStats()
|
||||
}
|
||||
if tracker, ok := httpToHTTPSPortTracker.(interface{ PrintStats() }); ok {
|
||||
tracker.PrintStats()
|
||||
}
|
||||
}
|
||||
|
||||
// todo: error propagation without canonical straight error check is required by cloud?
|
||||
// use safe dereferencing to avoid potential panics in case of previous unchecked errors
|
||||
if v := ptrutil.Safe(results); !v.Load() {
|
||||
@@ -865,36 +935,38 @@ func (r *Runner) executeTemplatesInput(store *loader.Store, engine *core.Engine)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// displayExecutionInfo displays misc info about the nuclei engine execution
|
||||
// displayExecutionInfo prints parser stats, version info, and scan counts.
|
||||
func (r *Runner) displayExecutionInfo(store *loader.Store) {
|
||||
// Display stats for any loaded templates' syntax warnings or errors
|
||||
stats.Display(templates.SyntaxWarningStats)
|
||||
stats.Display(templates.SyntaxErrorStats)
|
||||
stats.Display(templates.RuntimeWarningsStats)
|
||||
// Display parser stats for templates loaded into the store.
|
||||
stats.Display(templates.TemplateSyntaxWarningStats)
|
||||
stats.Display(templates.TemplateSyntaxErrorStats)
|
||||
stats.Display(templates.TemplateRuntimeWarningStats)
|
||||
|
||||
tmplCount := len(store.Templates())
|
||||
workflowCount := len(store.Workflows())
|
||||
if r.options.Verbose || (tmplCount == 0 && workflowCount == 0) {
|
||||
// only print these stats in verbose mode
|
||||
stats.ForceDisplayWarning(templates.ExcludedHeadlessTmplStats)
|
||||
stats.ForceDisplayWarning(templates.ExcludedCodeTmplStats)
|
||||
stats.ForceDisplayWarning(templates.ExcludedDastTmplStats)
|
||||
stats.ForceDisplayWarning(templates.TemplatesExcludedStats)
|
||||
stats.ForceDisplayWarning(templates.ExcludedFileStats)
|
||||
stats.ForceDisplayWarning(templates.ExcludedSelfContainedStats)
|
||||
// Excluded-template stats are noisy during normal scans, but useful in verbose mode
|
||||
// and when no runnable templates remain.
|
||||
for _, capability := range templates.AllCapabilities() {
|
||||
stats.ForceDisplayWarning(capability.Stat())
|
||||
}
|
||||
stats.ForceDisplayWarning(templates.ExcludedWeakMatcherTemplateStats)
|
||||
}
|
||||
|
||||
if tmplCount == 0 && workflowCount == 0 {
|
||||
// if dast flag is used print explicit warning
|
||||
if r.options.DAST {
|
||||
r.Logger.Warning().Msg("No DAST templates found")
|
||||
}
|
||||
stats.ForceDisplayWarning(templates.SkippedCodeTmplTamperedStats)
|
||||
stats.ForceDisplayWarning(templates.SkippedUnverifiedCodeTemplateStats)
|
||||
stats.ForceDisplayWarning(templates.SkippedUnverifiedJavascriptTemplateStats)
|
||||
} else {
|
||||
stats.DisplayAsWarning(templates.SkippedCodeTmplTamperedStats)
|
||||
stats.DisplayAsWarning(templates.SkippedUnverifiedCodeTemplateStats)
|
||||
stats.DisplayAsWarning(templates.SkippedUnverifiedJavascriptTemplateStats)
|
||||
}
|
||||
|
||||
stats.DisplayAsWarning(httpProtocol.SetThreadToCountZero)
|
||||
stats.ForceDisplayWarning(templates.SkippedUnsignedStats)
|
||||
stats.ForceDisplayWarning(templates.SkippedRequestSignatureStats)
|
||||
stats.ForceDisplayWarning(templates.SkippedUnverifiedTemplateStats)
|
||||
stats.ForceDisplayWarning(templates.SkippedRequestSignatureTemplateStats)
|
||||
|
||||
cfg := config.DefaultConfig
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/chroma/quick"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/logrusorgru/aurora/v4"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
|
||||
)
|
||||
|
||||
// log available templates for verbose (-vv)
|
||||
@@ -99,7 +99,7 @@ func (r *Runner) listAvailableTags(tagsMap map[string]int) {
|
||||
|
||||
for _, tag := range tagsList {
|
||||
if r.options.JSONL {
|
||||
marshalled, _ := jsoniter.Marshal(tag)
|
||||
marshalled, _ := json.Marshal(tag)
|
||||
r.Logger.Print().Msgf("%s", string(marshalled))
|
||||
} else {
|
||||
r.Logger.Print().Msgf("%s (%d)", tag.Key, tag.Value)
|
||||
|
||||
@@ -190,10 +190,13 @@ func (n *nucleiExecutor) ExecuteScan(target PostRequestsHandlerRequest) error {
|
||||
}
|
||||
|
||||
func (n *nucleiExecutor) Close() {
|
||||
if n == nil || n.executorOpts == nil {
|
||||
return
|
||||
}
|
||||
if n.executorOpts.FuzzStatsDB != nil {
|
||||
n.executorOpts.FuzzStatsDB.Close()
|
||||
}
|
||||
if n.options.Interactsh != nil {
|
||||
if n.options != nil && n.options.Interactsh != nil {
|
||||
_ = n.options.Interactsh.Close()
|
||||
}
|
||||
if n.executorOpts.InputHelper != nil {
|
||||
|
||||
+151
-48
@@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
@@ -11,8 +12,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/alitto/pond"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/server/scope"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
@@ -25,7 +24,7 @@ import (
|
||||
// DASTServer is a server that performs execution of fuzzing templates
|
||||
// on user input passed to the API.
|
||||
type DASTServer struct {
|
||||
echo *echo.Echo
|
||||
httpServer *http.Server
|
||||
options *Options
|
||||
tasksPool *pond.WorkerPool
|
||||
deduplicator *requestDeduplicator
|
||||
@@ -111,6 +110,7 @@ func New(options *Options) (*DASTServer, error) {
|
||||
|
||||
func NewStatsServer(fuzzStatsDB *stats.Tracker) (*DASTServer, error) {
|
||||
server := &DASTServer{
|
||||
options: &Options{},
|
||||
nucleiExecutor: &nucleiExecutor{
|
||||
executorOpts: &protocols.ExecutorOptions{
|
||||
FuzzStatsDB: fuzzStatsDB,
|
||||
@@ -124,62 +124,70 @@ func NewStatsServer(fuzzStatsDB *stats.Tracker) (*DASTServer, error) {
|
||||
}
|
||||
|
||||
func (s *DASTServer) Close() {
|
||||
s.nucleiExecutor.Close()
|
||||
_ = s.echo.Close()
|
||||
s.tasksPool.StopAndWaitFor(1 * time.Minute)
|
||||
if s.nucleiExecutor != nil {
|
||||
s.nucleiExecutor.Close()
|
||||
}
|
||||
if s.httpServer != nil {
|
||||
_ = s.httpServer.Close()
|
||||
}
|
||||
if s.tasksPool != nil {
|
||||
s.tasksPool.StopAndWaitFor(1 * time.Minute)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DASTServer) buildURL(endpoint string) string {
|
||||
values := make(url.Values)
|
||||
if s.options.Token != "" {
|
||||
values.Set("token", s.options.Token)
|
||||
opts := s.optionsOrDefault()
|
||||
if opts.Token != "" {
|
||||
values.Set("token", opts.Token)
|
||||
}
|
||||
|
||||
// Use url.URL struct to safely construct the URL
|
||||
u := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: s.options.Address,
|
||||
Host: opts.Address,
|
||||
Path: endpoint,
|
||||
RawQuery: values.Encode(),
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
func (s *DASTServer) optionsOrDefault() *Options {
|
||||
if s.options != nil {
|
||||
return s.options
|
||||
}
|
||||
return &Options{}
|
||||
}
|
||||
|
||||
func (s *DASTServer) setupHandlers(onlyStats bool) {
|
||||
e := echo.New()
|
||||
e.Use(middleware.Recover())
|
||||
if s.options.Verbose {
|
||||
cfg := middleware.DefaultLoggerConfig
|
||||
cfg.Skipper = func(c echo.Context) bool {
|
||||
// Skip /stats and /stats.json
|
||||
return c.Request().URL.Path == "/stats" || c.Request().URL.Path == "/stats.json"
|
||||
}
|
||||
e.Use(middleware.LoggerWithConfig(cfg))
|
||||
}
|
||||
e.Use(middleware.CORS())
|
||||
|
||||
if s.options.Token != "" {
|
||||
e.Use(middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{
|
||||
KeyLookup: "query:token",
|
||||
Validator: func(key string, c echo.Context) (bool, error) {
|
||||
return key == s.options.Token, nil
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
e.HideBanner = true
|
||||
mux := http.NewServeMux()
|
||||
// POST /fuzz - Queue a request for fuzzing
|
||||
if !onlyStats {
|
||||
e.POST("/fuzz", s.handleRequest)
|
||||
mux.HandleFunc("POST /fuzz", s.handleRequest)
|
||||
}
|
||||
e.GET("/stats", s.handleStats)
|
||||
e.GET("/stats.json", s.handleStatsJSON)
|
||||
mux.HandleFunc("GET /stats", s.handleStats)
|
||||
mux.HandleFunc("GET /stats.json", s.handleStatsJSON)
|
||||
|
||||
s.echo = e
|
||||
handler := http.Handler(mux)
|
||||
opts := s.optionsOrDefault()
|
||||
if opts.Token != "" {
|
||||
handler = s.tokenAuthMiddleware(handler)
|
||||
}
|
||||
handler = corsMiddleware(handler)
|
||||
if opts.Verbose {
|
||||
handler = requestLoggerMiddleware(handler)
|
||||
}
|
||||
handler = recoverMiddleware(handler)
|
||||
|
||||
s.httpServer = &http.Server{Handler: handler}
|
||||
}
|
||||
|
||||
func (s *DASTServer) Start() error {
|
||||
if err := s.echo.Start(s.options.Address); err != nil && err != http.ErrServerClosed {
|
||||
if s.httpServer == nil {
|
||||
s.setupHandlers(false)
|
||||
}
|
||||
s.httpServer.Addr = s.optionsOrDefault().Address
|
||||
if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -191,24 +199,26 @@ type PostRequestsHandlerRequest struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func (s *DASTServer) handleRequest(c echo.Context) error {
|
||||
func (s *DASTServer) handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
var req PostRequestsHandlerRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
fmt.Printf("Error binding request: %s\n", err)
|
||||
return err
|
||||
writeServerJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate the request
|
||||
if req.RawHTTP == "" || req.URL == "" {
|
||||
fmt.Printf("Missing required fields\n")
|
||||
return c.JSON(400, map[string]string{"error": "missing required fields"})
|
||||
writeServerJSON(w, http.StatusBadRequest, map[string]string{"error": "missing required fields"})
|
||||
return
|
||||
}
|
||||
|
||||
s.endpointsInQueue.Add(1)
|
||||
s.tasksPool.Submit(func() {
|
||||
s.consumeTaskRequest(req)
|
||||
})
|
||||
return c.NoContent(200)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
type StatsResponse struct {
|
||||
@@ -274,23 +284,116 @@ func (s *DASTServer) getStats() (StatsResponse, error) {
|
||||
//go:embed templates/index.html
|
||||
var indexTemplate string
|
||||
|
||||
func (s *DASTServer) handleStats(c echo.Context) error {
|
||||
func (s *DASTServer) handleStats(w http.ResponseWriter, _ *http.Request) {
|
||||
stats, err := s.getStats()
|
||||
if err != nil {
|
||||
return c.JSON(500, map[string]string{"error": err.Error()})
|
||||
writeServerJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
tmpl, err := template.New("index").Parse(indexTemplate)
|
||||
if err != nil {
|
||||
return c.JSON(500, map[string]string{"error": err.Error()})
|
||||
writeServerJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if err := tmpl.Execute(w, stats); err != nil {
|
||||
writeServerJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
return tmpl.Execute(c.Response().Writer, stats)
|
||||
}
|
||||
|
||||
func (s *DASTServer) handleStatsJSON(c echo.Context) error {
|
||||
func (s *DASTServer) handleStatsJSON(w http.ResponseWriter, _ *http.Request) {
|
||||
resp, err := s.getStats()
|
||||
if err != nil {
|
||||
return c.JSON(500, map[string]string{"error": err.Error()})
|
||||
writeServerJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
return c.JSONPretty(200, resp, " ")
|
||||
writeServerJSONPretty(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func (s *DASTServer) tokenAuthMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.URL.Query().Get("token")
|
||||
if token == "" {
|
||||
writeServerJSON(w, http.StatusBadRequest, map[string]string{"message": "missing key in the query string"})
|
||||
return
|
||||
}
|
||||
if token != s.optionsOrDefault().Token {
|
||||
writeServerJSON(w, http.StatusUnauthorized, map[string]string{"message": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func corsMiddleware(next http.Handler) http.Handler {
|
||||
const allowMethods = "GET,HEAD,PUT,PATCH,POST,DELETE"
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
origin := r.Header.Get("Origin")
|
||||
w.Header().Add("Vary", "Origin")
|
||||
if origin != "" {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
}
|
||||
if r.Method == http.MethodOptions {
|
||||
if origin != "" {
|
||||
w.Header().Add("Vary", "Access-Control-Request-Method")
|
||||
w.Header().Add("Vary", "Access-Control-Request-Headers")
|
||||
w.Header().Set("Access-Control-Allow-Methods", allowMethods)
|
||||
if headers := r.Header.Get("Access-Control-Request-Headers"); headers != "" {
|
||||
w.Header().Set("Access-Control-Allow-Headers", headers)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func requestLoggerMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/stats" || r.URL.Path == "/stats.json" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
recorder := &statusRecorder{ResponseWriter: w, statusCode: http.StatusOK}
|
||||
start := time.Now()
|
||||
next.ServeHTTP(recorder, r)
|
||||
fmt.Printf("%s %s %d %s\n", r.Method, r.URL.RequestURI(), recorder.statusCode, time.Since(start))
|
||||
})
|
||||
}
|
||||
|
||||
func recoverMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
type statusRecorder struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (r *statusRecorder) WriteHeader(statusCode int) {
|
||||
r.statusCode = statusCode
|
||||
r.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func writeServerJSON(w http.ResponseWriter, statusCode int, value interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
w.WriteHeader(statusCode)
|
||||
_ = json.NewEncoder(w).Encode(value)
|
||||
}
|
||||
|
||||
func writeServerJSONPretty(w http.ResponseWriter, statusCode int, value interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
w.WriteHeader(statusCode)
|
||||
encoder := json.NewEncoder(w)
|
||||
encoder.SetIndent("", " ")
|
||||
_ = encoder.Encode(value)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDASTServerTokenAuthRejectsMissingAndInvalidTokens(t *testing.T) {
|
||||
server := &DASTServer{options: &Options{Token: "secret"}}
|
||||
server.setupHandlers(false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
target string
|
||||
statusCode int
|
||||
}{
|
||||
{
|
||||
name: "missing token",
|
||||
target: "/stats",
|
||||
statusCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "invalid token",
|
||||
target: "/stats?token=wrong",
|
||||
statusCode: http.StatusUnauthorized,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodGet, tt.target, nil)
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
server.httpServer.Handler.ServeHTTP(response, request)
|
||||
|
||||
require.Equal(t, tt.statusCode, response.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDASTServerCORSPreflightBypassesTokenAuth(t *testing.T) {
|
||||
server := &DASTServer{options: &Options{Token: "secret"}}
|
||||
server.setupHandlers(false)
|
||||
|
||||
request := httptest.NewRequest(http.MethodOptions, "/stats", nil)
|
||||
request.Header.Set("Origin", "https://example.com")
|
||||
request.Header.Set("Access-Control-Request-Headers", "X-Test")
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
server.httpServer.Handler.ServeHTTP(response, request)
|
||||
|
||||
require.Equal(t, http.StatusNoContent, response.Code)
|
||||
require.Equal(t, "*", response.Header().Get("Access-Control-Allow-Origin"))
|
||||
require.Equal(t, "GET,HEAD,PUT,PATCH,POST,DELETE", response.Header().Get("Access-Control-Allow-Methods"))
|
||||
require.Equal(t, "X-Test", response.Header().Get("Access-Control-Allow-Headers"))
|
||||
}
|
||||
|
||||
func TestStatsServerCloseDoesNotRequireFullExecutorOptions(t *testing.T) {
|
||||
server, err := NewStatsServer(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotPanics(t, server.Close)
|
||||
}
|
||||
+4
-1
@@ -24,7 +24,10 @@
|
||||
{{binary}} -tags cve -exclude-templates http/cves/2020/
|
||||
{{binary}} -tags cve -exclude-templates http/cves/2020/CVE-2020-9757.yaml
|
||||
{{binary}} -tags cve -exclude-templates http/cves/2020/CVE-2020-9757.yaml -exclude-templates http/cves/2021/
|
||||
{{binary}} -tags cve -include-tags dos,fuzz
|
||||
# Skipped until the next nuclei release ships #7536: release still force-includes
|
||||
# via -include-tags (bypassing -tags), so release/current load counts diverge by design.
|
||||
# Covered by pkg/catalog/index filter unit tests in the meantime.
|
||||
# {{binary}} -tags cve -include-tags dos,fuzz
|
||||
{{binary}} -tags cve -ntv 8.8.8,8.8.9
|
||||
{{binary}} -tags cve -severity high
|
||||
{{binary}} -tags cve,dos,fuzz
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
)
|
||||
|
||||
var isCodeDisabled = func() bool { return osutils.IsWindows() && os.Getenv("CI") == "true" }
|
||||
var signedCodeTemplates sync.Once
|
||||
var signedCodeTemplatesErr error
|
||||
var signedIntegrationTemplates sync.Once
|
||||
var signedIntegrationTemplatesErr error
|
||||
|
||||
func hasAnyExecutable(names ...string) bool {
|
||||
for _, name := range names {
|
||||
@@ -34,6 +34,7 @@ var codeTestCases = []integrationCase{
|
||||
{Path: "protocols/code/py-file.yaml", TestCase: &codeFile{}, DisableOn: isCodeDisabled},
|
||||
{Path: "protocols/code/py-env-var.yaml", TestCase: &codeEnvVar{}, DisableOn: isCodeDisabled},
|
||||
{Path: "protocols/code/unsigned.yaml", TestCase: &unsignedCode{}, DisableOn: isCodeDisabled},
|
||||
{Path: "protocols/code/dast-unsigned.yaml", TestCase: &dastUnsignedCode{}, DisableOn: isCodeDisabled},
|
||||
{Path: "protocols/code/py-nosig.yaml", TestCase: &codePyNoSig{}, DisableOn: isCodeDisabled},
|
||||
{Path: "protocols/code/py-interactsh.yaml", TestCase: &codeSnippet{}, DisableOn: isCodeDisabled},
|
||||
{Path: "protocols/code/ps1-snippet.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsWindows() || isCodeDisabled() }},
|
||||
@@ -50,23 +51,20 @@ const (
|
||||
|
||||
var testcertpath = ""
|
||||
|
||||
func ensureSignedCodeTemplates() error {
|
||||
if isCodeDisabled() {
|
||||
return nil
|
||||
}
|
||||
signedCodeTemplates.Do(func() {
|
||||
func ensureSignedIntegrationTemplates() error {
|
||||
signedIntegrationTemplates.Do(func() {
|
||||
previousWD, err := os.Getwd()
|
||||
if err != nil {
|
||||
signedCodeTemplatesErr = err
|
||||
signedIntegrationTemplatesErr = err
|
||||
return
|
||||
}
|
||||
if err := os.Chdir(suite.fixturesDir); err != nil {
|
||||
signedCodeTemplatesErr = err
|
||||
signedIntegrationTemplatesErr = err
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if chdirErr := os.Chdir(previousWD); chdirErr != nil && signedCodeTemplatesErr == nil {
|
||||
signedCodeTemplatesErr = chdirErr
|
||||
if chdirErr := os.Chdir(previousWD); chdirErr != nil && signedIntegrationTemplatesErr == nil {
|
||||
signedIntegrationTemplatesErr = chdirErr
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -78,13 +76,16 @@ func ensureSignedCodeTemplates() error {
|
||||
|
||||
tsigner, err := signer.NewTemplateSignerFromFiles(certPath, keyPath)
|
||||
if err != nil {
|
||||
signedCodeTemplatesErr = err
|
||||
signedIntegrationTemplatesErr = err
|
||||
return
|
||||
}
|
||||
|
||||
templatesToSign := []string{
|
||||
"workflow/code-template-1.yaml",
|
||||
"workflow/code-template-2.yaml",
|
||||
var templatesToSign []string
|
||||
if !isCodeDisabled() {
|
||||
templatesToSign = append(templatesToSign,
|
||||
"workflow/code-template-1.yaml",
|
||||
"workflow/code-template-2.yaml",
|
||||
)
|
||||
}
|
||||
for _, v := range codeTestCases {
|
||||
if v.DisableOn != nil && v.DisableOn() {
|
||||
@@ -96,16 +97,25 @@ func ensureSignedCodeTemplates() error {
|
||||
if _, ok := v.TestCase.(*codePyNoSig); ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := v.TestCase.(*dastUnsignedCode); ok {
|
||||
continue
|
||||
}
|
||||
templatesToSign = append(templatesToSign, v.Path)
|
||||
}
|
||||
for _, v := range jsTestcases {
|
||||
if v.DisableOn != nil && v.DisableOn() {
|
||||
continue
|
||||
}
|
||||
templatesToSign = append(templatesToSign, v.Path)
|
||||
}
|
||||
for _, templatePath := range templatesToSign {
|
||||
if err := templates.SignTemplate(tsigner, fixturePath(templatePath)); err != nil {
|
||||
signedCodeTemplatesErr = err
|
||||
signedIntegrationTemplatesErr = err
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
return signedCodeTemplatesErr
|
||||
return signedIntegrationTemplatesErr
|
||||
}
|
||||
|
||||
func getEnvValues() []string {
|
||||
@@ -114,6 +124,15 @@ func getEnvValues() []string {
|
||||
}
|
||||
}
|
||||
|
||||
func runSignedNucleiTemplateAndGetResults(template, url string, debug bool, extra ...string) ([]string, error) {
|
||||
if err := ensureSignedIntegrationTemplates(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args := []string{"-t", template, "-target", url}
|
||||
args = append(args, extra...)
|
||||
return testutils.RunNucleiBareArgsAndGetResults(debug, getEnvValues(), args...)
|
||||
}
|
||||
|
||||
type codeSnippet struct{}
|
||||
|
||||
// Execute executes a test case and returns an error if occurred
|
||||
@@ -178,6 +197,22 @@ func (h *unsignedCode) Execute(filePath string) error {
|
||||
return errors.Join(expectResultsCount(results, 1), errors.New("unsigned template was executed"))
|
||||
}
|
||||
|
||||
type dastUnsignedCode struct{}
|
||||
|
||||
// Execute runs an unsigned template that pairs a fuzzable request with a code
|
||||
// block under -dast, which must not bypass the code-signing check.
|
||||
func (h *dastUnsignedCode) Execute(filePath string) error {
|
||||
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-dast")
|
||||
|
||||
// should error out
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// this point should never be reached
|
||||
return errors.Join(expectResultsCount(results, 1), errors.New("unsigned template was executed"))
|
||||
}
|
||||
|
||||
type codePyNoSig struct{}
|
||||
|
||||
// Execute executes a test case and returns an error if occurred
|
||||
|
||||
@@ -9,12 +9,26 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
|
||||
)
|
||||
|
||||
func expectPublicDNSResultsCount(results []string, expectedNumbers ...int) error {
|
||||
return expectResultsCount(results, append([]int{0}, expectedNumbers...)...)
|
||||
}
|
||||
|
||||
func TestDNS(t *testing.T) {
|
||||
t.Run("A", func(t *testing.T) {
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/dns/a.yaml", "one.one.one.one", suite.debug)
|
||||
if err != nil {
|
||||
t.Fatalf("dns A request failed: %v", err)
|
||||
}
|
||||
if err := expectPublicDNSResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Duration", func(t *testing.T) {
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/dns/duration.yaml", "one.one.one.one", suite.debug)
|
||||
if err != nil {
|
||||
t.Fatalf("dns duration request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -25,7 +39,7 @@ func TestDNS(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dns AAAA request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
if err := expectPublicDNSResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
@@ -35,7 +49,7 @@ func TestDNS(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dns CNAME request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
if err := expectPublicDNSResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
@@ -45,7 +59,7 @@ func TestDNS(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dns SRV request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
if err := expectPublicDNSResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
@@ -55,7 +69,7 @@ func TestDNS(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dns NS request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
if err := expectPublicDNSResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
@@ -65,7 +79,7 @@ func TestDNS(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dns TXT request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
if err := expectPublicDNSResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
@@ -75,7 +89,7 @@ func TestDNS(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dns PTR request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
if err := expectPublicDNSResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
@@ -85,7 +99,7 @@ func TestDNS(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dns CAA request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
if err := expectPublicDNSResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
@@ -105,7 +119,7 @@ func TestDNS(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dns variables request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
if err := expectPublicDNSResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
@@ -115,7 +129,7 @@ func TestDNS(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dns payload request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 3); err != nil {
|
||||
if err := expectPublicDNSResultsCount(results, 1, 2, 3); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -123,7 +137,7 @@ func TestDNS(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dns payload request with CLI override failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 4); err != nil {
|
||||
if err := expectPublicDNSResultsCount(results, 1, 2, 3, 4); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
@@ -133,7 +147,7 @@ func TestDNS(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dns DSL matcher variable request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
if err := expectPublicDNSResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
package integration_test
|
||||
|
||||
import "time"
|
||||
|
||||
// Keep duration assertions above the timer granularity of fast local sockets on Windows.
|
||||
const integrationDurationObservationDelay = 10 * time.Millisecond
|
||||
@@ -6,22 +6,24 @@ package integration_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/ory/dockertest/v3"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/mongo"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
mongocontainer "github.com/testcontainers/testcontainers-go/modules/mongodb"
|
||||
|
||||
osutil "github.com/projectdiscovery/utils/os"
|
||||
mongoclient "go.mongodb.org/mongo-driver/mongo"
|
||||
mongooptions "go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
const (
|
||||
dbName = "test"
|
||||
dbImage = "mongo:8"
|
||||
dbName = "test"
|
||||
dbRepository = "mongo"
|
||||
dbTag = "8"
|
||||
dbPort = "27017/tcp"
|
||||
mongoDatabaseReadyTimeout = 3 * time.Minute
|
||||
mongoServerSelectionDelay = time.Second
|
||||
)
|
||||
|
||||
var exportersTestCases = []integrationCase{
|
||||
@@ -36,22 +38,43 @@ func (m *mongoExporter) Execute(filepath string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// Start a MongoDB container
|
||||
mongodbContainer, err := mongocontainer.Run(ctx, dbImage)
|
||||
defer func() {
|
||||
if err := testcontainers.TerminateContainer(mongodbContainer); err != nil {
|
||||
log.Printf("failed to terminate container: %s", err)
|
||||
}
|
||||
}()
|
||||
pool, err := dockertest.NewPool("")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create docker pool: %w", err)
|
||||
}
|
||||
if err := pool.Client.Ping(); err != nil {
|
||||
return fmt.Errorf("could not connect to Docker: %w", err)
|
||||
}
|
||||
pool.MaxWait = mongoDatabaseReadyTimeout
|
||||
|
||||
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
|
||||
Repository: dbRepository,
|
||||
Tag: dbTag,
|
||||
ExposedPorts: []string{dbPort},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start container: %w", err)
|
||||
}
|
||||
defer purge(pool, resource)
|
||||
|
||||
connString, err := mongodbContainer.ConnectionString(ctx)
|
||||
mappedPort := resource.GetPort(dbPort)
|
||||
if mappedPort == "" {
|
||||
return fmt.Errorf("missing mapped port for %s", dbPort)
|
||||
}
|
||||
connString := fmt.Sprintf("mongodb://%s/%s", net.JoinHostPort("127.0.0.1", mappedPort), dbName)
|
||||
|
||||
err = pool.Retry(func() error {
|
||||
clientOptions := mongooptions.Client().ApplyURI(connString).SetServerSelectionTimeout(mongoServerSelectionDelay)
|
||||
client, err := mongoclient.Connect(ctx, clientOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Disconnect(ctx)
|
||||
return client.Ping(ctx, nil)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get connection string for MongoDB container: %s", err)
|
||||
return fmt.Errorf("failed to wait for MongoDB container: %w", err)
|
||||
}
|
||||
connString = connString + dbName
|
||||
|
||||
// Create a MongoDB exporter and write a test result to the database
|
||||
opts := mongo.Options{
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestFlow(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("conditional flow request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
if err := expectPublicDNSResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
@@ -77,7 +77,7 @@ func TestFlow(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dns ns probe flow request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 2); err != nil {
|
||||
if err := expectPublicDNSResultsCount(results, 1, 2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
var headlessTestcases = []integrationCase{
|
||||
{Path: "protocols/headless/headless-basic.yaml", TestCase: &headlessBasic{}},
|
||||
{Path: "protocols/headless/headless-duration.yaml", TestCase: &headlessBasic{}},
|
||||
{Path: "protocols/headless/headless-waitevent.yaml", TestCase: &headlessBasic{}},
|
||||
{Path: "protocols/headless/headless-dsl.yaml", TestCase: &headlessBasic{}},
|
||||
{Path: "protocols/headless/headless-self-contained.yaml", TestCase: &headlessSelfContained{}},
|
||||
|
||||
@@ -19,10 +19,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
|
||||
"github.com/projectdiscovery/retryablehttp-go"
|
||||
"github.com/projectdiscovery/utils/errkit"
|
||||
logutil "github.com/projectdiscovery/utils/log"
|
||||
@@ -76,6 +75,7 @@ var httpTestcases = []integrationCase{
|
||||
{Path: "protocols/http/variable-dsl-function.yaml", TestCase: &httpVariableDSLFunction{}},
|
||||
{Path: "protocols/http/get-override-sni.yaml", TestCase: &httpSniAnnotation{}},
|
||||
{Path: "protocols/http/get-sni.yaml", TestCase: &customCLISNI{}},
|
||||
{Path: "protocols/http/tls-metadata.yaml", TestCase: &httpTLSMetadata{}},
|
||||
{Path: "protocols/http/redirect-match-url.yaml", TestCase: &httpRedirectMatchURL{}},
|
||||
{Path: "protocols/http/get-sni-unsafe.yaml", TestCase: &customCLISNIUnsafe{}},
|
||||
{Path: "protocols/http/annotation-timeout.yaml", TestCase: &annotationTimeout{}},
|
||||
@@ -830,7 +830,7 @@ func (h *httpRawUnsafePath) Execute(filepath string) error {
|
||||
}
|
||||
|
||||
actual := []string{}
|
||||
for _, v := range strings.Split(results, "\n") {
|
||||
for v := range strings.SplitSeq(results, "\n") {
|
||||
if strings.Contains(v, "GET") {
|
||||
parts := strings.Fields(v)
|
||||
if len(parts) == 3 {
|
||||
@@ -878,7 +878,7 @@ func (h *httpPaths) Execute(filepath string) error {
|
||||
}
|
||||
|
||||
actual := []string{}
|
||||
for _, v := range strings.Split(results, "\n") {
|
||||
for v := range strings.SplitSeq(results, "\n") {
|
||||
if strings.Contains(v, "GET") {
|
||||
parts := strings.Fields(v)
|
||||
if len(parts) == 3 {
|
||||
@@ -1395,7 +1395,7 @@ func (h *httpVariableDSLFunction) Execute(filePath string) error {
|
||||
}
|
||||
|
||||
actual := []string{}
|
||||
for _, v := range strings.Split(results, "\n") {
|
||||
for v := range strings.SplitSeq(results, "\n") {
|
||||
if strings.Contains(v, "GET") {
|
||||
parts := strings.Fields(v)
|
||||
if len(parts) == 3 {
|
||||
@@ -1432,6 +1432,28 @@ func (h *customCLISNI) Execute(filePath string) error {
|
||||
return expectResultsCount(results, 1)
|
||||
}
|
||||
|
||||
type httpTLSMetadata struct{}
|
||||
|
||||
// Execute verifies TLS handshake metadata from the same HTTP connection is matchable.
|
||||
func (h *httpTLSMetadata) Execute(filePath string) error {
|
||||
router := httprouter.New()
|
||||
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
if r.TLS != nil && r.TLS.ServerName == "test" {
|
||||
_, _ = w.Write([]byte("test-ok"))
|
||||
return
|
||||
}
|
||||
_, _ = w.Write([]byte("test-ko"))
|
||||
})
|
||||
ts := httptest.NewTLSServer(router)
|
||||
defer ts.Close()
|
||||
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-sni", "test")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
}
|
||||
|
||||
type httpSniAnnotation struct{}
|
||||
|
||||
// Execute executes a test case and returns an error if occurred
|
||||
@@ -1764,7 +1786,7 @@ func (h *httpRawPathSingleSlash) Execute(filepath string) error {
|
||||
}
|
||||
|
||||
var actual string
|
||||
for _, v := range strings.Split(results, "\n") {
|
||||
for v := range strings.SplitSeq(results, "\n") {
|
||||
if strings.Contains(v, "GET") {
|
||||
parts := strings.Fields(v)
|
||||
if len(parts) == 3 {
|
||||
@@ -1789,7 +1811,7 @@ func (h *httpRawUnsafePathSingleSlash) Execute(filepath string) error {
|
||||
}
|
||||
|
||||
var actual string
|
||||
for _, v := range strings.Split(results, "\n") {
|
||||
for v := range strings.SplitSeq(results, "\n") {
|
||||
if strings.Contains(v, "GET") {
|
||||
parts := strings.Fields(v)
|
||||
if len(parts) == 3 {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/health"
|
||||
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
||||
"google.golang.org/grpc/reflection"
|
||||
)
|
||||
|
||||
// startGRPCHealthServer starts an in-process gRPC server exposing the standard
|
||||
// health service (status SERVING) and server reflection on a random loopback
|
||||
// port. It returns the "host:port" address and a stop func.
|
||||
func startGRPCHealthServer() (string, func(), error) {
|
||||
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("grpc health listen: %w", err)
|
||||
}
|
||||
srv := grpc.NewServer()
|
||||
hs := health.NewServer()
|
||||
hs.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
|
||||
healthpb.RegisterHealthServer(srv, hs)
|
||||
reflection.Register(srv)
|
||||
go func() { _ = srv.Serve(lis) }()
|
||||
return lis.Addr().String(), srv.Stop, nil
|
||||
}
|
||||
|
||||
// javascriptGRPCHealth exercises the nuclei/grpc library end to end against a
|
||||
// real gRPC server via server reflection.
|
||||
type javascriptGRPCHealth struct{}
|
||||
|
||||
func (j *javascriptGRPCHealth) Execute(filePath string) error {
|
||||
address, stop, err := startGRPCHealthServer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stop()
|
||||
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, address, debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
}
|
||||
|
||||
// javascriptGRPCDenied asserts the nuclei/grpc library refuses to dial a host
|
||||
// on the exclude list (network-policy enforcement) before connecting.
|
||||
type javascriptGRPCDenied struct{}
|
||||
|
||||
func (j *javascriptGRPCDenied) Execute(filePath string) error {
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// startJSHTTPServer starts a local HTTP server exercising the generic paths
|
||||
// used by the nuclei/http javascript integration templates.
|
||||
func startJSHTTPServer() *httptest.Server {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Test", "ok")
|
||||
_, _ = io.WriteString(w, "hello")
|
||||
})
|
||||
mux.HandleFunc("/echo-header", func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = io.WriteString(w, r.Header.Get("X-Token"))
|
||||
})
|
||||
mux.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
|
||||
b, _ := io.ReadAll(r.Body)
|
||||
_, _ = w.Write(b)
|
||||
})
|
||||
mux.HandleFunc("/set-cookie", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.SetCookie(w, &http.Cookie{Name: "sid", Value: "abc"})
|
||||
_, _ = io.WriteString(w, "set")
|
||||
})
|
||||
mux.HandleFunc("/echo-cookie", func(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := r.Cookie("sid")
|
||||
if err != nil || c.Value != "abc" {
|
||||
_, _ = io.WriteString(w, "missing")
|
||||
return
|
||||
}
|
||||
_, _ = io.WriteString(w, "sid-ok")
|
||||
})
|
||||
mux.HandleFunc("/redir", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/land", http.StatusFound)
|
||||
})
|
||||
mux.HandleFunc("/land", func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = io.WriteString(w, "landed")
|
||||
})
|
||||
return httptest.NewServer(mux)
|
||||
}
|
||||
|
||||
type javascriptHTTPGet struct{}
|
||||
|
||||
func (j *javascriptHTTPGet) Execute(filePath string) error {
|
||||
ts := startJSHTTPServer()
|
||||
defer ts.Close()
|
||||
|
||||
// Target as host:port so javascript Host/Port args populate cleanly.
|
||||
hostPort := strings.TrimPrefix(ts.URL, "http://")
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, hostPort, debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
}
|
||||
|
||||
type javascriptHTTPClientFlow struct{}
|
||||
|
||||
func (j *javascriptHTTPClientFlow) Execute(filePath string) error {
|
||||
ts := startJSHTTPServer()
|
||||
defer ts.Close()
|
||||
|
||||
hostPort := strings.TrimPrefix(ts.URL, "http://")
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, hostPort, debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
}
|
||||
|
||||
type javascriptHTTPDenied struct{}
|
||||
|
||||
func (j *javascriptHTTPDenied) Execute(filePath string) error {
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
}
|
||||
@@ -19,8 +19,6 @@ import (
|
||||
"github.com/jcmturner/gokrb5/v8/iana/nametype"
|
||||
"github.com/jcmturner/gokrb5/v8/messages"
|
||||
"github.com/jcmturner/gokrb5/v8/types"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
|
||||
)
|
||||
|
||||
// javascriptASRepRoast exercises templates/ad/asrep-roast.yaml end-to-end
|
||||
@@ -46,7 +44,7 @@ func (j *javascriptASRepRoast) Execute(filePath string) error {
|
||||
}
|
||||
defer kdc.Close()
|
||||
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, kdc.Address(), debug)
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, kdc.Address(), debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type javascriptMSSQLFingerprint struct{}
|
||||
|
||||
func (j *javascriptMSSQLFingerprint) Execute(filePath string) error {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = ln.Close() }()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go handleMSSQLPreloginConn(conn)
|
||||
}
|
||||
}()
|
||||
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, ln.Addr().String(), debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
}
|
||||
|
||||
func handleMSSQLPreloginConn(conn net.Conn) {
|
||||
defer func() { _ = conn.Close() }()
|
||||
_ = conn.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
buf := make([]byte, 256)
|
||||
if _, err := io.ReadAtLeast(conn, buf, 8); err != nil {
|
||||
return
|
||||
}
|
||||
_, _ = conn.Write(sampleMSSQLPreloginResponse())
|
||||
}
|
||||
|
||||
func sampleMSSQLPreloginResponse() []byte {
|
||||
body := []byte{
|
||||
0x00, 0x00, 0x1f, 0x00, 0x06,
|
||||
0x01, 0x00, 0x25, 0x00, 0x01,
|
||||
0x02, 0x00, 0x26, 0x00, 0x01,
|
||||
0x03, 0x00, 0x27, 0x00, 0x00,
|
||||
0x04, 0x00, 0x27, 0x00, 0x01,
|
||||
0x05, 0x00, 0x28, 0x00, 0x00,
|
||||
0xff,
|
||||
0x0f, 0x00, 0x07, 0xd0, 0x00, 0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
0x01,
|
||||
}
|
||||
header := []byte{0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}
|
||||
binary.BigEndian.PutUint16(header[2:4], uint16(8+len(body)))
|
||||
return append(header, body...)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-pg/pg/v10"
|
||||
@@ -24,15 +25,27 @@ func javascriptDockerDisabled() bool {
|
||||
return !osutils.IsLinux() || !hasAnyExecutable("docker", "podman")
|
||||
}
|
||||
|
||||
func javascriptGoExecSambaDisabled() bool {
|
||||
return javascriptDockerDisabled() || os.Getenv("RUN_GOEXEC_SAMBA_LOCAL") != "1"
|
||||
}
|
||||
|
||||
var jsTestcases = []integrationCase{
|
||||
{Path: "protocols/javascript/redis-pass-brute.yaml", TestCase: &javascriptRedisPassBrute{}, DisableOn: javascriptDockerDisabled, Serial: true},
|
||||
{Path: "protocols/javascript/redis-lua-script.yaml", TestCase: &javascriptRedisLuaScript{}, DisableOn: javascriptDockerDisabled, Serial: true},
|
||||
{Path: "protocols/javascript/ssh-server-fingerprint.yaml", TestCase: &javascriptSSHServerFingerprint{}, DisableOn: javascriptDockerDisabled, Serial: true},
|
||||
{Path: "protocols/javascript/net-multi-step.yaml", TestCase: &networkMultiStep{}},
|
||||
{Path: "protocols/javascript/net-https.yaml", TestCase: &javascriptNetHttps{}},
|
||||
{Path: "protocols/javascript/grpc-health.yaml", TestCase: &javascriptGRPCHealth{}},
|
||||
{Path: "protocols/javascript/grpc-denied.yaml", TestCase: &javascriptGRPCDenied{}},
|
||||
{Path: "protocols/javascript/http-get.yaml", TestCase: &javascriptHTTPGet{}},
|
||||
{Path: "protocols/javascript/http-client-flow.yaml", TestCase: &javascriptHTTPClientFlow{}},
|
||||
{Path: "protocols/javascript/http-denied.yaml", TestCase: &javascriptHTTPDenied{}},
|
||||
{Path: "protocols/javascript/rsync-test.yaml", TestCase: &javascriptRsyncTest{}, DisableOn: javascriptDockerDisabled, Serial: true},
|
||||
{Path: "protocols/javascript/vnc-pass-brute.yaml", TestCase: &javascriptVncPassBrute{}, DisableOn: javascriptDockerDisabled, Serial: true},
|
||||
{Path: "protocols/javascript/postgres-pass-brute.yaml", TestCase: &javascriptPostgresPassBrute{}, DisableOn: javascriptDockerDisabled, Serial: true},
|
||||
{Path: "protocols/javascript/mysql-connect.yaml", TestCase: &javascriptMySQLConnect{}, DisableOn: javascriptDockerDisabled, Serial: true},
|
||||
{Path: "protocols/javascript/mysql-fingerprint.yaml", TestCase: &javascriptMySQLFingerprint{}, DisableOn: javascriptDockerDisabled, Serial: true},
|
||||
{Path: "protocols/javascript/mssql-fingerprint.yaml", TestCase: &javascriptMSSQLFingerprint{}},
|
||||
{Path: "protocols/javascript/multi-ports.yaml", TestCase: &javascriptMultiPortsSSH{}},
|
||||
{Path: "protocols/javascript/no-port-args.yaml", TestCase: &javascriptNoPortArgs{}},
|
||||
{Path: "protocols/javascript/telnet-auth-test.yaml", TestCase: &javascriptTelnetAuthTest{}, DisableOn: javascriptDockerDisabled, Serial: true},
|
||||
@@ -40,7 +53,7 @@ var jsTestcases = []integrationCase{
|
||||
{Path: "protocols/javascript/wmi-command.yaml", TestCase: &javascriptWMICommand{}},
|
||||
{Path: "protocols/javascript/goexec-redaction.yaml", TestCase: &javascriptGoExecRedaction{}},
|
||||
{Path: "protocols/javascript/goexec-modules.yaml", TestCase: &javascriptGoExecModules{}},
|
||||
{Path: "protocols/javascript/goexec-samba-ntlm.yaml", TestCase: &javascriptGoExecSambaNTLM{}, DisableOn: javascriptDockerDisabled, Serial: true},
|
||||
{Path: "protocols/javascript/goexec-samba-ntlm.yaml", TestCase: &javascriptGoExecSambaNTLM{}, DisableOn: javascriptGoExecSambaDisabled, Serial: true},
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -48,7 +61,6 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
javascriptContainerTTLSeconds = 300
|
||||
javascriptDatabaseReadyTimeout = 3 * time.Minute
|
||||
javascriptServiceReadyTimeout = 45 * time.Second
|
||||
javascriptRetryDelay = 500 * time.Millisecond
|
||||
@@ -90,7 +102,7 @@ func newJavascriptDockerSpec(port string, options *dockertest.RunOptions, readyT
|
||||
type javascriptNetHttps struct{}
|
||||
|
||||
func (j *javascriptNetHttps) Execute(filePath string) error {
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -107,6 +119,16 @@ func (j *javascriptRedisPassBrute) Execute(filePath string) error {
|
||||
}, javascriptServiceReadyTimeout, 0, nil))
|
||||
}
|
||||
|
||||
type javascriptRedisLuaScript struct{}
|
||||
|
||||
func (j *javascriptRedisLuaScript) Execute(filePath string) error {
|
||||
return runJavascriptDockerCase(filePath, newJavascriptDockerSpec("6379/tcp", &dockertest.RunOptions{
|
||||
Repository: "redis",
|
||||
Tag: "latest",
|
||||
Cmd: []string{"redis-server", "--requirepass", "iamadmin"},
|
||||
}, javascriptServiceReadyTimeout, 0, nil))
|
||||
}
|
||||
|
||||
type javascriptSSHServerFingerprint struct{}
|
||||
|
||||
func (j *javascriptSSHServerFingerprint) Execute(filePath string) error {
|
||||
@@ -159,11 +181,62 @@ func (j *javascriptMySQLConnect) Execute(filePath string) error {
|
||||
}, javascriptDatabaseReadyTimeout, 0, mysqlReadyCheck("root", "secret")))
|
||||
}
|
||||
|
||||
type javascriptMySQLFingerprint struct{}
|
||||
|
||||
// Execute fingerprints multiple MySQL/MariaDB server versions and asserts the
|
||||
// extended handshake fields (protocol, version, salt, capabilities, auth plugin).
|
||||
func (j *javascriptMySQLFingerprint) Execute(filePath string) error {
|
||||
cases := []struct {
|
||||
name string
|
||||
repository string
|
||||
tag string
|
||||
env []string
|
||||
}{
|
||||
{
|
||||
name: "mysql-5.7",
|
||||
repository: "mysql",
|
||||
tag: "5.7",
|
||||
env: []string{"MYSQL_ROOT_PASSWORD=secret"},
|
||||
},
|
||||
{
|
||||
name: "mysql-8.0",
|
||||
repository: "mysql",
|
||||
tag: "8.0",
|
||||
env: []string{"MYSQL_ROOT_PASSWORD=secret"},
|
||||
},
|
||||
{
|
||||
name: "mysql-8.4",
|
||||
repository: "mysql",
|
||||
tag: "8.4",
|
||||
env: []string{"MYSQL_ROOT_PASSWORD=secret"},
|
||||
},
|
||||
{
|
||||
name: "mariadb-11.4",
|
||||
repository: "mariadb",
|
||||
tag: "11.4",
|
||||
env: []string{"MARIADB_ROOT_PASSWORD=secret"},
|
||||
},
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for _, tc := range cases {
|
||||
err := runJavascriptDockerCase(filePath, newJavascriptDockerSpec("3306/tcp", &dockertest.RunOptions{
|
||||
Repository: tc.repository,
|
||||
Tag: tc.tag,
|
||||
Env: tc.env,
|
||||
}, javascriptDatabaseReadyTimeout, 0, mysqlReadyCheck("root", "secret")))
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("%s: %w", tc.name, err))
|
||||
}
|
||||
}
|
||||
return multierr.Combine(errs...)
|
||||
}
|
||||
|
||||
type javascriptMultiPortsSSH struct{}
|
||||
|
||||
func (j *javascriptMultiPortsSSH) Execute(filePath string) error {
|
||||
// use scanme.sh as target to ensure we match on the 2nd default port 22
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -173,7 +246,7 @@ func (j *javascriptMultiPortsSSH) Execute(filePath string) error {
|
||||
type javascriptNoPortArgs struct{}
|
||||
|
||||
func (j *javascriptNoPortArgs) Execute(filePath string) error {
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "yo.dawg", debug)
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, "yo.dawg", debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -188,7 +261,7 @@ func (j *javascriptWMICommand) Execute(filePath string) error {
|
||||
// the exclude list and short-circuits before any dial, so the JSON result
|
||||
// must contain "ok":false plus "network policy" in the error while not
|
||||
// leaking the password.
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -200,7 +273,7 @@ type javascriptGoExecRedaction struct{}
|
||||
func (j *javascriptGoExecRedaction) Execute(filePath string) error {
|
||||
listener := newGoExecCloseListener()
|
||||
defer listener.Close()
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -212,7 +285,7 @@ type javascriptGoExecModules struct{}
|
||||
func (j *javascriptGoExecModules) Execute(filePath string) error {
|
||||
listener := newGoExecCloseListener()
|
||||
defer listener.Close()
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -257,10 +330,6 @@ func (j *javascriptGoExecSambaNTLM) Execute(filePath string) error {
|
||||
}
|
||||
defer purge(pool, resource)
|
||||
|
||||
if err := resource.Expire(javascriptContainerTTLSeconds); err != nil {
|
||||
return fmt.Errorf("could not expire samba: %w", err)
|
||||
}
|
||||
|
||||
targetAddress := "127.0.0.1:445"
|
||||
if err := waitForTCPService(targetAddress, javascriptServiceReadyTimeout); err != nil {
|
||||
return err
|
||||
@@ -275,7 +344,7 @@ func (j *javascriptGoExecSambaNTLM) Execute(filePath string) error {
|
||||
|
||||
errS := make([]error, 0, defaultRetry)
|
||||
for attempt := 1; attempt <= defaultRetry; attempt++ {
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug)
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug)
|
||||
if err == nil {
|
||||
if countErr := expectResultsCount(results, 1); countErr == nil {
|
||||
return nil
|
||||
@@ -383,7 +452,7 @@ func (j *networkMultiStep) Execute(filePath string) error {
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, server.URL, debug)
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, server.URL, debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -419,10 +488,6 @@ func runJavascriptDockerCase(filePath string, spec javascriptDockerSpec, expecte
|
||||
}
|
||||
defer purge(pool, resource)
|
||||
|
||||
if err := resource.Expire(javascriptContainerTTLSeconds); err != nil {
|
||||
return fmt.Errorf("could not expire resource for %s: %w", filePath, err)
|
||||
}
|
||||
|
||||
mappedPort := resource.GetPort(spec.port)
|
||||
if mappedPort == "" {
|
||||
return fmt.Errorf("missing mapped port for %s", spec.port)
|
||||
@@ -443,7 +508,7 @@ func runJavascriptDockerCase(filePath string, spec javascriptDockerSpec, expecte
|
||||
|
||||
errS := make([]error, 0, defaultRetry)
|
||||
for attempt := 1; attempt <= defaultRetry; attempt++ {
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, targetAddress, debug)
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, targetAddress, debug)
|
||||
if err == nil {
|
||||
if countErr := expectResultsCount(results, expectedNumbers...); countErr == nil {
|
||||
return nil
|
||||
@@ -586,7 +651,6 @@ func purge(pool *dockertest.Pool, resource *dockertest.Resource) {
|
||||
return
|
||||
}
|
||||
containerName := resource.Container.Name
|
||||
_ = pool.Client.StopContainer(resource.Container.ID, 0)
|
||||
_ = pool.Purge(resource)
|
||||
_ = pool.RemoveContainerByName(containerName)
|
||||
}
|
||||
@@ -79,7 +79,7 @@ type javascriptNoAccess struct{}
|
||||
|
||||
// Execute executes a test case and returns an error if occurred
|
||||
func (h *javascriptNoAccess) Execute(filePath string) error {
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
|
||||
results, err := runSignedNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ package integration_test
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -117,6 +116,46 @@ func TestNetwork(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Duration", func(t *testing.T) {
|
||||
errState := &capturedError{}
|
||||
server := testutils.NewTCPServer(nil, 0, func(conn net.Conn) {
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
data, err := reader.ConnReadNWithTimeout(conn, 5, 5*time.Second)
|
||||
if err != nil {
|
||||
errState.Set(err)
|
||||
return
|
||||
}
|
||||
if string(data) == "FIRST" {
|
||||
time.Sleep(integrationDurationObservationDelay)
|
||||
_, _ = conn.Write([]byte("PING"))
|
||||
}
|
||||
|
||||
data, err = reader.ConnReadNWithTimeout(conn, 6, 5*time.Second)
|
||||
if err != nil {
|
||||
errState.Set(err)
|
||||
return
|
||||
}
|
||||
if string(data) == "SECOND" {
|
||||
time.Sleep(integrationDurationObservationDelay)
|
||||
_, _ = conn.Write([]byte("PONG"))
|
||||
}
|
||||
_, _ = conn.Write([]byte("NUCLEI"))
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/network/duration.yaml", server.URL, suite.debug)
|
||||
if err != nil {
|
||||
t.Fatalf("duration network request failed: %v", err)
|
||||
}
|
||||
if err := errState.Err(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SelfContained", func(t *testing.T) {
|
||||
server := testutils.NewTCPServer(nil, 0, func(conn net.Conn) {
|
||||
defer func() { _ = conn.Close() }()
|
||||
@@ -213,9 +252,24 @@ func TestNetwork(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
results, err = testutils.RunNucleiTemplateAndGetResults("protocols/network/network-port.yaml", strings.ReplaceAll(server.URL, "23846", "443"), suite.debug)
|
||||
// An explicitly specified reserved port (e.g. host:8081) must be preserved
|
||||
// and not overridden by the template port (regression test for #7323).
|
||||
// 8081 is a reserved port but is used here on an unprivileged port so the
|
||||
// listener binds without root. The server is started on 8081 and the
|
||||
// template (port 23846) must dial the operator-specified 8081 to match.
|
||||
serverReserved := testutils.NewTCPServer(nil, 8081, func(conn net.Conn) {
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
data, err := reader.ConnReadNWithTimeout(conn, 4, 5*time.Second)
|
||||
if err == nil && string(data) == "PING" {
|
||||
_, _ = conn.Write([]byte("PONG"))
|
||||
}
|
||||
})
|
||||
defer serverReserved.Close()
|
||||
|
||||
results, err = testutils.RunNucleiTemplateAndGetResults("protocols/network/network-port.yaml", serverReserved.URL, suite.debug)
|
||||
if err != nil {
|
||||
t.Fatalf("network-port template failed with overridden input port: %v", err)
|
||||
t.Fatalf("network-port template failed with explicit reserved input port: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -159,9 +159,9 @@ func executeIntegrationCase(testCase integrationCase) error {
|
||||
if testCase.DisableOn != nil && testCase.DisableOn() {
|
||||
return nil
|
||||
}
|
||||
if needsSignedCodeTemplates(testCase.Path) {
|
||||
if err := ensureSignedCodeTemplates(); err != nil {
|
||||
return fmt.Errorf("failed to sign code templates: %w", err)
|
||||
if needsSignedIntegrationTemplates(testCase.Path) {
|
||||
if err := ensureSignedIntegrationTemplates(); err != nil {
|
||||
return fmt.Errorf("failed to sign integration templates: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,8 +191,10 @@ func parallelism() int {
|
||||
return parallelism
|
||||
}
|
||||
|
||||
func needsSignedCodeTemplates(path string) bool {
|
||||
return strings.HasPrefix(path, "protocols/code/") || strings.Contains(path, "workflow/code-")
|
||||
func needsSignedIntegrationTemplates(path string) bool {
|
||||
return strings.HasPrefix(path, "protocols/code/") ||
|
||||
strings.Contains(path, "workflow/code-") ||
|
||||
strings.HasPrefix(path, "protocols/javascript/")
|
||||
}
|
||||
|
||||
func testNameForPath(path string) string {
|
||||
|
||||
@@ -23,6 +23,17 @@ func TestSSL(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Duration", func(t *testing.T) {
|
||||
server := newTLSServer(t, &tls.Config{})
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/ssl/duration.yaml", server.URL, suite.debug)
|
||||
if err != nil {
|
||||
t.Fatalf("duration ssl request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("BasicZTLS", func(t *testing.T) {
|
||||
server := newTLSServer(t, &tls.Config{})
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/ssl/basic-ztls.yaml", server.URL, suite.debug, "-ztls")
|
||||
|
||||
@@ -34,7 +34,7 @@ func (h *cwdTemplateTest) Execute(filePath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
return expectPublicDNSResultsCount(results, 1)
|
||||
}
|
||||
|
||||
type relativePathTemplateTest struct{}
|
||||
@@ -45,7 +45,7 @@ func (h *relativePathTemplateTest) Execute(filePath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
return expectPublicDNSResultsCount(results, 1)
|
||||
}
|
||||
|
||||
type absolutePathTemplateTest struct{}
|
||||
@@ -56,7 +56,7 @@ func (h *absolutePathTemplateTest) Execute(filePath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
return expectPublicDNSResultsCount(results, 1)
|
||||
}
|
||||
|
||||
type folderPathTemplateTest struct{}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
id: dast-unsigned-code
|
||||
|
||||
info:
|
||||
name: dast-unsigned-code
|
||||
author: pdteam
|
||||
severity: info
|
||||
tags: dast,code
|
||||
description: |
|
||||
unsigned code paired with a fuzzable request must not execute under -dast
|
||||
|
||||
http:
|
||||
- method: GET
|
||||
path:
|
||||
- "{{BaseURL}}/?x=1"
|
||||
fuzzing:
|
||||
- part: query
|
||||
type: replace
|
||||
mode: single
|
||||
keys: ["x"]
|
||||
fuzz:
|
||||
- "test"
|
||||
|
||||
code:
|
||||
- engine:
|
||||
- py
|
||||
- python3
|
||||
source: |
|
||||
print("unsigned code")
|
||||
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- "unsigned code"
|
||||
@@ -0,0 +1,23 @@
|
||||
id: dns-duration
|
||||
|
||||
info:
|
||||
name: DNS Duration
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
flow: dns("duration_dns")
|
||||
|
||||
dns:
|
||||
- id: duration_dns
|
||||
name: "{{FQDN}}"
|
||||
type: A
|
||||
class: inet
|
||||
recursion: true
|
||||
retries: 3
|
||||
matchers:
|
||||
- type: dsl
|
||||
condition: and
|
||||
dsl:
|
||||
- "duration > 0 && duration < 60"
|
||||
- "duration_dns_duration > 0 && duration_dns_duration < 60"
|
||||
- "duration == duration_dns_duration"
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
id: headless-duration
|
||||
|
||||
info:
|
||||
name: Headless Duration
|
||||
author: pdteam
|
||||
severity: info
|
||||
tags: headless
|
||||
|
||||
flow: headless("duration_headless")
|
||||
|
||||
headless:
|
||||
- id: duration_headless
|
||||
steps:
|
||||
- action: navigate
|
||||
args:
|
||||
url: "{{BaseURL}}/"
|
||||
- action: waitload
|
||||
matchers:
|
||||
- type: dsl
|
||||
condition: and
|
||||
dsl:
|
||||
- "duration > 0 && duration < 60"
|
||||
- "duration_1 > 0 && duration_1 < 60"
|
||||
- "duration_2 > 0 && duration_2 < 60"
|
||||
- "duration == duration_2"
|
||||
- "duration_headless_duration > 0 && duration_headless_duration < 60"
|
||||
- "duration_headless_duration_1 > 0 && duration_headless_duration_1 < 60"
|
||||
- "duration_headless_duration_2 > 0 && duration_headless_duration_2 < 60"
|
||||
- "duration_headless_duration == duration"
|
||||
- "duration_headless_duration_1 == duration_1"
|
||||
- "duration_headless_duration_2 == duration_2"
|
||||
- type: word
|
||||
words:
|
||||
- "<html>"
|
||||
matchers-condition: and
|
||||
@@ -0,0 +1,22 @@
|
||||
id: http-tls-metadata
|
||||
|
||||
info:
|
||||
name: HTTP TLS metadata from same connection
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
http:
|
||||
- method: GET
|
||||
path:
|
||||
- "{{BaseURL}}"
|
||||
matchers-condition: and
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- "test-ok"
|
||||
- type: dsl
|
||||
dsl:
|
||||
- sni == "test"
|
||||
- tls_version != ""
|
||||
- cipher != ""
|
||||
- len(to_string(domains)) > 0 || subject_cn != "" || len(to_string(subject_an)) > 0
|
||||
@@ -0,0 +1,37 @@
|
||||
id: grpc-network-policy-denied
|
||||
|
||||
info:
|
||||
name: gRPC Network Policy Denial
|
||||
author: pdteam
|
||||
severity: info
|
||||
description: |
|
||||
Hardening check for the nuclei/grpc javascript library: dialing a host on
|
||||
the exclude list (RFC 5737 TEST-NET-3) must be rejected by the network
|
||||
policy before any connection is made. The error is captured and asserted.
|
||||
|
||||
javascript:
|
||||
- code: |
|
||||
const grpc = require('nuclei/grpc');
|
||||
let out = "";
|
||||
try {
|
||||
const opts = new grpc.Options();
|
||||
opts.Plaintext = true;
|
||||
const client = new grpc.Client("203.0.113.10:50051", opts);
|
||||
client.Invoke('grpc.health.v1.Health/Check', '{}');
|
||||
} catch (e) {
|
||||
out = "" + e;
|
||||
}
|
||||
out;
|
||||
args:
|
||||
Host: "{{Host}}"
|
||||
matchers-condition: and
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- "success == true"
|
||||
- type: word
|
||||
part: response
|
||||
words:
|
||||
- "network policy"
|
||||
- "203.0.113.10"
|
||||
condition: and
|
||||
@@ -0,0 +1,31 @@
|
||||
id: grpc-health-check
|
||||
|
||||
info:
|
||||
name: gRPC Health Check
|
||||
author: pdteam
|
||||
severity: info
|
||||
description: |
|
||||
Exercises the nuclei/grpc javascript library end to end: dial through the
|
||||
network policy, resolve the schema via server reflection and invoke the
|
||||
standard health check, expecting a SERVING response.
|
||||
|
||||
javascript:
|
||||
- code: |
|
||||
const grpc = require('nuclei/grpc');
|
||||
const opts = new grpc.Options();
|
||||
opts.Plaintext = true;
|
||||
const client = new grpc.Client(address, opts);
|
||||
const resp = client.Invoke('grpc.health.v1.Health/Check', '{"service":""}');
|
||||
client.Close();
|
||||
resp;
|
||||
args:
|
||||
address: "{{Host}}:{{Port}}"
|
||||
matchers-condition: and
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- "success == true"
|
||||
- type: word
|
||||
part: response
|
||||
words:
|
||||
- "SERVING"
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
id: js-http-client-flow
|
||||
|
||||
info:
|
||||
name: nuclei/http client flow
|
||||
author: pdteam
|
||||
severity: info
|
||||
description: |
|
||||
Exercises nuclei/http Client for Post, cookie jar reuse, and redirect
|
||||
following against a local HTTP server.
|
||||
|
||||
javascript:
|
||||
- code: |
|
||||
const http = require('nuclei/http');
|
||||
const client = new http.Client();
|
||||
const post = client.Post(BaseURL + '/echo', 'payload');
|
||||
client.Get(BaseURL + '/set-cookie');
|
||||
const cookie = client.Get(BaseURL + '/echo-cookie');
|
||||
const redir = client.Get(BaseURL + '/redir');
|
||||
post.Body + '|' + cookie.Body + '|' + redir.StatusCode + '|' + redir.Body;
|
||||
args:
|
||||
BaseURL: "http://{{Host}}:{{Port}}"
|
||||
matchers-condition: and
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- "success == true"
|
||||
- type: word
|
||||
part: response
|
||||
words:
|
||||
- "payload|sid-ok|200|landed"
|
||||
@@ -0,0 +1,33 @@
|
||||
id: js-http-network-policy-denied
|
||||
|
||||
info:
|
||||
name: nuclei/http network policy denial
|
||||
author: pdteam
|
||||
severity: info
|
||||
description: |
|
||||
Hardening check for nuclei/http: requesting a host on the exclude list
|
||||
(RFC 5737 TEST-NET-3) must be rejected by network policy before dialing.
|
||||
|
||||
javascript:
|
||||
- code: |
|
||||
const http = require('nuclei/http');
|
||||
let out = "";
|
||||
try {
|
||||
http.Get('http://203.0.113.10/');
|
||||
} catch (e) {
|
||||
out = "" + e;
|
||||
}
|
||||
out;
|
||||
args:
|
||||
Host: "{{Host}}"
|
||||
matchers-condition: and
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- "success == true"
|
||||
- type: word
|
||||
part: response
|
||||
words:
|
||||
- "network policy"
|
||||
- "203.0.113.10"
|
||||
condition: and
|
||||
@@ -0,0 +1,29 @@
|
||||
id: js-http-get
|
||||
|
||||
info:
|
||||
name: nuclei/http GET
|
||||
author: pdteam
|
||||
severity: info
|
||||
description: |
|
||||
Exercises nuclei/http package Get and Client.Get/SetHeader against a local
|
||||
HTTP server (status, body, response headers, default request header).
|
||||
|
||||
javascript:
|
||||
- code: |
|
||||
const http = require('nuclei/http');
|
||||
const a = http.Get(BaseURL + '/hello');
|
||||
const client = new http.Client();
|
||||
client.SetHeader('X-Token', 'nuclei');
|
||||
const b = client.Get(BaseURL + '/echo-header');
|
||||
a.StatusCode + '|' + a.Body + '|' + a.GetHeader('X-Test') + '|' + b.Body;
|
||||
args:
|
||||
BaseURL: "http://{{Host}}:{{Port}}"
|
||||
matchers-condition: and
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- "success == true"
|
||||
- type: word
|
||||
part: response
|
||||
words:
|
||||
- "200|hello|ok|nuclei"
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
id: mssql-fingerprint
|
||||
|
||||
info:
|
||||
name: MSSQL Fingerprint Test
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
javascript:
|
||||
- pre-condition: |
|
||||
isPortOpen(Host, Port)
|
||||
code: |
|
||||
const mssql = require('nuclei/mssql');
|
||||
const client = new mssql.MSSQLClient();
|
||||
const isMssql = client.IsMssql(Host, Port);
|
||||
const info = client.FingerprintMssql(Host, Port);
|
||||
const ok = isMssql && info && info.Version && info.Version.length > 0 && info.MajorVersion > 0 && info.EncryptionMode && info.EncryptionMode.length > 0 && info.Protocol === "mssql";
|
||||
Export({
|
||||
ok: ok,
|
||||
isMssql: isMssql,
|
||||
version: info.Version,
|
||||
majorVersion: info.MajorVersion,
|
||||
minorVersion: info.MinorVersion,
|
||||
buildNumber: info.BuildNumber,
|
||||
encryptionMode: info.EncryptionMode,
|
||||
protocol: info.Protocol,
|
||||
transport: info.Transport,
|
||||
});
|
||||
args:
|
||||
Host: "{{Host}}"
|
||||
Port: "1433"
|
||||
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- "success == true"
|
||||
- "ok == true"
|
||||
- "isMssql == true"
|
||||
- "len(version) > 0"
|
||||
- "majorVersion > 0"
|
||||
- 'protocol == "mssql"'
|
||||
- 'transport == "tcp"'
|
||||
- "len(encryptionMode) > 0"
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
id: mysql-fingerprint
|
||||
|
||||
info:
|
||||
name: MySQL Fingerprint Test
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
javascript:
|
||||
- pre-condition: |
|
||||
isPortOpen(Host, Port)
|
||||
code: |
|
||||
const mysql = require('nuclei/mysql');
|
||||
const client = new mysql.MySQLClient();
|
||||
const isMySQL = client.IsMySQL(Host, Port);
|
||||
const info = client.FingerprintMySQL(Host, Port);
|
||||
const ok = isMySQL && info && info.ProtocolVersion === 10 && info.Version && info.Version.length > 0 && info.Salt && info.Salt.length > 0 && info.CapabilityFlags > 0 && info.Debug && info.Debug.PacketType === 'handshake';
|
||||
Export({
|
||||
ok: ok,
|
||||
isMySQL: isMySQL,
|
||||
packetType: info.Debug.PacketType,
|
||||
protocolVersion: info.ProtocolVersion,
|
||||
version: info.Version,
|
||||
threadId: info.ThreadID,
|
||||
authPluginName: info.AuthPluginName,
|
||||
capabilityFlags: info.CapabilityFlags,
|
||||
capabilities: info.Capabilities,
|
||||
status: info.Status,
|
||||
salt: info.Salt,
|
||||
});
|
||||
args:
|
||||
Host: "{{Host}}"
|
||||
Port: "3306"
|
||||
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- "success == true"
|
||||
- "ok == true"
|
||||
- "isMySQL == true"
|
||||
- 'packetType == "handshake"'
|
||||
- "protocolVersion == 10"
|
||||
- "len(version) > 0"
|
||||
- "capabilityFlags > 0"
|
||||
- "len(salt) > 0"
|
||||
- "threadId > 0"
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
id: redis-lua-script
|
||||
|
||||
info:
|
||||
name: Redis RunLuaScript - Detect
|
||||
author: DhiyaneshDK
|
||||
severity: info
|
||||
|
||||
javascript:
|
||||
- code: |
|
||||
const redis = require('nuclei/redis');
|
||||
// Set a key via KEYS with a string and a numeric arg via ARGV: validates
|
||||
// that keys are passed through and non-string args are not dropped
|
||||
redis.RunLuaScript(Host, Port, Password, 'return redis.call("set", KEYS[1], ARGV[1] .. ":" .. ARGV[2])', ['testkey'], ['testvalue', 123]);
|
||||
|
||||
// Read the value back via KEYS and return it as the response so the
|
||||
// matcher can assert the combined string was stored correctly
|
||||
redis.RunLuaScript(Host, Port, Password, 'return redis.call("get", KEYS[1])', ['testkey'], []);
|
||||
|
||||
args:
|
||||
Host: "{{Host}}"
|
||||
Port: "6379"
|
||||
# must match the --requirepass value of the dockerized redis instance
|
||||
# started by internal/tests/integration/javascript_test.go
|
||||
Password: "iamadmin"
|
||||
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- 'success == true && contains(response, "testvalue:123")'
|
||||
@@ -0,0 +1,48 @@
|
||||
id: network-duration
|
||||
|
||||
info:
|
||||
name: Network Duration
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
flow: tcp("duration_network")
|
||||
|
||||
network:
|
||||
- id: duration_network
|
||||
inputs:
|
||||
- data: "FIRST"
|
||||
read: 4
|
||||
name: first
|
||||
- data: "SECOND"
|
||||
read: 4
|
||||
name: second
|
||||
host:
|
||||
- "{{Hostname}}"
|
||||
read-size: 6
|
||||
matchers:
|
||||
- type: dsl
|
||||
condition: and
|
||||
dsl:
|
||||
- "duration > 0 && duration < 60"
|
||||
- "duration_1 > 0 && duration_1 < 60"
|
||||
- "duration_2 > 0 && duration_2 < 60"
|
||||
- "duration == duration_2"
|
||||
- "duration_network_duration > 0 && duration_network_duration < 60"
|
||||
- "duration_network_duration_1 > 0 && duration_network_duration_1 < 60"
|
||||
- "duration_network_duration_2 > 0 && duration_network_duration_2 < 60"
|
||||
- "duration_network_duration == duration"
|
||||
- "duration_network_duration_1 == duration_1"
|
||||
- "duration_network_duration_2 == duration_2"
|
||||
- type: word
|
||||
part: first
|
||||
words:
|
||||
- "PING"
|
||||
- type: word
|
||||
part: second
|
||||
words:
|
||||
- "PONG"
|
||||
- type: word
|
||||
part: data
|
||||
words:
|
||||
- "NUCLEI"
|
||||
matchers-condition: and
|
||||
@@ -0,0 +1,20 @@
|
||||
id: ssl-duration
|
||||
|
||||
info:
|
||||
name: SSL Duration
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
flow: ssl("duration_ssl")
|
||||
|
||||
ssl:
|
||||
- id: duration_ssl
|
||||
address: "{{Host}}:{{Port}}"
|
||||
matchers:
|
||||
- type: dsl
|
||||
condition: and
|
||||
dsl:
|
||||
- "probe_status == true"
|
||||
- "duration > 0 && duration < 60"
|
||||
- "duration_ssl_duration > 0 && duration_ssl_duration < 60"
|
||||
- "duration == duration_ssl_duration"
|
||||
@@ -0,0 +1,36 @@
|
||||
id: websocket-duration
|
||||
|
||||
info:
|
||||
name: WebSocket Duration
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
flow: websocket("duration_ws")
|
||||
|
||||
websocket:
|
||||
- id: duration_ws
|
||||
address: "{{Scheme}}://{{Hostname}}"
|
||||
inputs:
|
||||
- data: hello
|
||||
- data: status
|
||||
matchers:
|
||||
- type: dsl
|
||||
condition: and
|
||||
dsl:
|
||||
- "duration > 0 && duration < 60"
|
||||
- "duration_1 > 0 && duration_1 < 60"
|
||||
- "duration_2 > 0 && duration_2 < 60"
|
||||
- "duration == duration_2"
|
||||
- "duration_ws_duration > 0 && duration_ws_duration < 60"
|
||||
- "duration_ws_duration_1 > 0 && duration_ws_duration_1 < 60"
|
||||
- "duration_ws_duration_2 > 0 && duration_ws_duration_2 < 60"
|
||||
- "duration_ws_duration == duration"
|
||||
- "duration_ws_duration_1 == duration_1"
|
||||
- "duration_ws_duration_2 == duration_2"
|
||||
- type: word
|
||||
part: response
|
||||
words:
|
||||
- world
|
||||
- ready
|
||||
condition: and
|
||||
matchers-condition: and
|
||||
@@ -0,0 +1,19 @@
|
||||
id: whois-duration
|
||||
|
||||
info:
|
||||
name: WHOIS Duration
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
flow: whois("duration_whois")
|
||||
|
||||
whois:
|
||||
- id: duration_whois
|
||||
query: "{{Host}}"
|
||||
matchers:
|
||||
- type: dsl
|
||||
condition: and
|
||||
dsl:
|
||||
- "duration > 0 && duration < 60"
|
||||
- "duration_whois_duration > 0 && duration_whois_duration < 60"
|
||||
- "duration == duration_whois_duration"
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
one
|
||||
docs
|
||||
drive
|
||||
play
|
||||
|
||||
www
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
|
||||
@@ -38,6 +39,37 @@ func TestWebSocket(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Duration", func(t *testing.T) {
|
||||
connHandler := func(conn net.Conn) {
|
||||
for {
|
||||
msg, op, err := wsutil.ReadClientData(conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch string(msg) {
|
||||
case "hello":
|
||||
time.Sleep(integrationDurationObservationDelay)
|
||||
_ = wsutil.WriteServerMessage(conn, op, []byte("world"))
|
||||
case "status":
|
||||
time.Sleep(integrationDurationObservationDelay)
|
||||
_ = wsutil.WriteServerMessage(conn, op, []byte("ready"))
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
server := testutils.NewWebsocketServer("", connHandler, func(origin string) bool { return true })
|
||||
defer server.Close()
|
||||
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults("protocols/websocket/duration.yaml", strings.ReplaceAll(server.URL, "http", "ws"), suite.debug)
|
||||
if err != nil {
|
||||
t.Fatalf("duration websocket request failed: %v", err)
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CSWSH", func(t *testing.T) {
|
||||
server := testutils.NewWebsocketServer("", func(conn net.Conn) {}, func(origin string) bool { return true })
|
||||
defer server.Close()
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
var whoisTestCases = []integrationCase{
|
||||
{Path: "protocols/whois/basic.yaml", TestCase: &whoisBasic{}},
|
||||
{Path: "protocols/whois/duration.yaml", TestCase: &whoisBasic{}},
|
||||
}
|
||||
|
||||
type whoisBasic struct{}
|
||||
|
||||
+18
-4
@@ -10,10 +10,6 @@ import (
|
||||
"github.com/projectdiscovery/goflags"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/runner"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
|
||||
"github.com/projectdiscovery/utils/errkit"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
|
||||
@@ -24,6 +20,9 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
|
||||
pkgtypes "github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
|
||||
"github.com/projectdiscovery/utils/errkit"
|
||||
)
|
||||
|
||||
// TemplateSources contains template sources
|
||||
@@ -340,6 +339,21 @@ func WithScanStrategy(strategy string) NucleiSDKOptions {
|
||||
// OutputWriter
|
||||
type OutputWriter output.Writer
|
||||
|
||||
// WithResultCallback sets a result callback invoked for each finding.
|
||||
//
|
||||
// When used with ThreadSafeNucleiEngine.ExecuteNucleiWithOpts / ExecuteNucleiWithOptsCtx,
|
||||
// the callback is scoped to that execution only (combined with any GlobalResultCallback).
|
||||
// When used at engine construction time, it behaves like the default result callback list.
|
||||
func WithResultCallback(callback func(event *output.ResultEvent)) NucleiSDKOptions {
|
||||
return func(e *NucleiEngine) error {
|
||||
if callback == nil {
|
||||
return nil
|
||||
}
|
||||
e.resultCallbacks = append(e.resultCallbacks, callback)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UseOutputWriter allows setting custom output writer
|
||||
// by default a mock writer is used with user defined callback
|
||||
// if outputWriter is used callback will be ignored
|
||||
|
||||
+46
-12
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/logrusorgru/aurora/v4"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/core"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
|
||||
@@ -22,14 +23,17 @@ import (
|
||||
// in ThreadSafeNucleiEngine
|
||||
type unsafeOptions struct {
|
||||
executerOpts *protocols.ExecutorOptions
|
||||
engine *core.Engine
|
||||
}
|
||||
|
||||
// createEphemeralObjects creates ephemeral nuclei objects/instances/types
|
||||
func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types.Options) (*unsafeOptions, error) {
|
||||
// createEphemeralObjects creates ephemeral nuclei objects/instances/types.
|
||||
// outputWriter overrides the base engine writer when non-nil (used for per-call callbacks).
|
||||
func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types.Options, outputWriter output.Writer) (*unsafeOptions, error) {
|
||||
if outputWriter == nil {
|
||||
outputWriter = base.customWriter
|
||||
}
|
||||
u := &unsafeOptions{}
|
||||
u.executerOpts = &protocols.ExecutorOptions{
|
||||
Output: base.customWriter,
|
||||
Output: outputWriter,
|
||||
Options: opts,
|
||||
Progress: base.customProgress,
|
||||
Catalog: base.catalog,
|
||||
@@ -40,6 +44,11 @@ func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types
|
||||
ResumeCfg: types.NewResumeCfg(),
|
||||
Parser: base.parser,
|
||||
Browser: base.browserInstance,
|
||||
// Thread-safe executes can run concurrently with different Output writers.
|
||||
// The compiled-template cache shallow-copies requests and mutates shared
|
||||
// ExecutorOptions.Output via UpdateOptions/ApplyNewEngineOptions, which
|
||||
// would otherwise route all findings to whichever call last won the race.
|
||||
DoNotCache: true,
|
||||
}
|
||||
if opts.ShouldUseHostError() && base.hostErrCache != nil {
|
||||
u.executerOpts.HostErrorsCache = base.hostErrCache
|
||||
@@ -52,11 +61,33 @@ func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types
|
||||
opts.RateLimitDuration = time.Second
|
||||
}
|
||||
u.executerOpts.RateLimiter = utils.GetRateLimiter(ctx, opts.RateLimit, opts.RateLimitDuration)
|
||||
u.engine = core.New(opts)
|
||||
u.engine.SetExecuterOptions(u.executerOpts)
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// resolveEphemeralOutput combines the base/global writer with any per-call result callbacks.
|
||||
// Global callbacks keep working; per-call callbacks are isolated to this execution.
|
||||
func resolveEphemeralOutput(base, call *NucleiEngine) output.Writer {
|
||||
out := base.customWriter
|
||||
if call == nil || len(call.resultCallbacks) == 0 {
|
||||
return out
|
||||
}
|
||||
|
||||
callbacks := append([]func(event *output.ResultEvent){}, call.resultCallbacks...)
|
||||
callWriter := testutils.NewMockOutputWriter(call.opts.OmitTemplate)
|
||||
callWriter.WriteCallback = func(event *output.ResultEvent) {
|
||||
for _, cb := range callbacks {
|
||||
if cb != nil {
|
||||
cb(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if out == nil {
|
||||
return callWriter
|
||||
}
|
||||
return output.NewMultiWriter(out, callWriter)
|
||||
}
|
||||
|
||||
// closeEphemeralObjects closes all resources used by ephemeral nuclei objects/instances/types
|
||||
func closeEphemeralObjects(u *unsafeOptions) {
|
||||
if u.executerOpts.RateLimiter != nil {
|
||||
@@ -113,14 +144,15 @@ func (e *ThreadSafeNucleiEngine) GlobalLoadAllTemplates() error {
|
||||
}
|
||||
|
||||
// GlobalResultCallback sets a callback function which will be called for each result
|
||||
// across all ExecuteNucleiWithOpts invocations. For a callback scoped to a single
|
||||
// execution, pass WithResultCallback in that call's opts instead.
|
||||
func (e *ThreadSafeNucleiEngine) GlobalResultCallback(callback func(event *output.ResultEvent)) {
|
||||
e.eng.resultCallbacks = []func(*output.ResultEvent){callback}
|
||||
}
|
||||
|
||||
// ExecuteNucleiWithOptsCtx executes templates on targets and calls callback on each result(only if results are found)
|
||||
// This method can be called concurrently and it will use some global resources but can be run parallelly
|
||||
// by invoking this method with different options and targets
|
||||
// Note: Not all options are thread-safe. this method will throw error if you try to use non-thread-safe options
|
||||
// ExecuteNucleiWithOptsCtx executes templates on targets and can be called concurrently.
|
||||
// Pass WithResultCallback in opts for a per-execution result callback (combined with any
|
||||
// GlobalResultCallback). Not all options are thread-safe.
|
||||
func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, targets []string, opts ...NucleiSDKOptions) error {
|
||||
baseOpts := e.eng.opts.Copy()
|
||||
tmpEngine := &NucleiEngine{opts: baseOpts, mode: threadSafe}
|
||||
@@ -131,7 +163,7 @@ func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, t
|
||||
}
|
||||
|
||||
// create ephemeral nuclei objects/instances/types using base nuclei engine
|
||||
unsafeOpts, err := createEphemeralObjects(ctx, e.eng, tmpEngine.opts)
|
||||
unsafeOpts, err := createEphemeralObjects(ctx, e.eng, tmpEngine.opts, resolveEphemeralOutput(e.eng, tmpEngine))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -145,7 +177,9 @@ func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, t
|
||||
}
|
||||
unsafeOpts.executerOpts.WorkflowLoader = workflowLoader
|
||||
|
||||
store, err := loader.New(loader.NewConfig(tmpEngine.opts, e.eng.catalog, unsafeOpts.executerOpts))
|
||||
loaderConfig := loader.NewConfig(tmpEngine.opts, e.eng.catalog, unsafeOpts.executerOpts)
|
||||
loaderConfig.MetadataIndex = e.eng.getMetadataIndex()
|
||||
store, err := loader.New(loaderConfig)
|
||||
if err != nil {
|
||||
return errkit.Wrapf(err, "Could not create loader client: %s", err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package nuclei
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/index"
|
||||
)
|
||||
|
||||
func BenchmarkExecuteNucleiWithOptsCtx(b *testing.B) {
|
||||
templatePath := filepath.Join(b.TempDir(), "benchmark.yaml")
|
||||
err := os.WriteFile(templatePath, []byte(`id: benchmark-execute-nuclei-with-opts-ctx
|
||||
|
||||
info:
|
||||
name: Benchmark ExecuteNucleiWithOptsCtx
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
http:
|
||||
- method: GET
|
||||
path:
|
||||
- "{{BaseURL}}"
|
||||
matchers:
|
||||
- type: status
|
||||
status:
|
||||
- 418
|
||||
`), 0o600)
|
||||
if err != nil {
|
||||
b.Fatalf("could not write benchmark template: %s", err)
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
b.Cleanup(server.Close)
|
||||
|
||||
metadataIndex, err := index.NewIndex(b.TempDir())
|
||||
if err != nil {
|
||||
b.Fatalf("could not create benchmark metadata index: %s", err)
|
||||
}
|
||||
|
||||
engine, err := NewThreadSafeNucleiEngineCtx(
|
||||
b.Context(),
|
||||
DisableUpdateCheck(),
|
||||
WithTemplatesOrWorkflows(TemplateSources{Templates: []string{templatePath}}),
|
||||
)
|
||||
if err != nil {
|
||||
b.Fatalf("could not create thread-safe nuclei engine: %s", err)
|
||||
}
|
||||
b.Cleanup(engine.Close)
|
||||
|
||||
// Keep benchmark metadata isolated from the user's persistent cache.
|
||||
engine.eng.metadataIndexOnce.Do(func() {
|
||||
engine.eng.metadataIndex = metadataIndex
|
||||
})
|
||||
|
||||
targets := []string{server.URL}
|
||||
if err := engine.ExecuteNucleiWithOptsCtx(b.Context(), targets); err != nil {
|
||||
b.Fatalf("could not warm up nuclei execution: %s", err)
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for b.Loop() {
|
||||
if err := engine.ExecuteNucleiWithOptsCtx(b.Context(), targets); err != nil {
|
||||
b.Fatalf("could not execute nuclei: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package nuclei_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestThreadSafeWithResultCallbackIsolation(t *testing.T) {
|
||||
templatePath := writeSDKMatchTemplate(t)
|
||||
|
||||
srvA := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("token-A-unique"))
|
||||
}))
|
||||
t.Cleanup(srvA.Close)
|
||||
|
||||
srvB := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("token-B-unique"))
|
||||
}))
|
||||
t.Cleanup(srvB.Close)
|
||||
|
||||
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.Background())
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(ne.Close)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
callbackA []string
|
||||
callbackB []string
|
||||
globalURLs []string
|
||||
globalCount int
|
||||
errA, errB error
|
||||
)
|
||||
|
||||
ne.GlobalResultCallback(func(event *output.ResultEvent) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
globalCount++
|
||||
globalURLs = append(globalURLs, eventMatchedURL(event))
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errA = ne.ExecuteNucleiWithOptsCtx(context.Background(), []string{srvA.URL},
|
||||
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{templatePath}}),
|
||||
nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"sdk-callback-match"}}),
|
||||
nuclei.WithResultCallback(func(event *output.ResultEvent) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
callbackA = append(callbackA, eventMatchedURL(event))
|
||||
}),
|
||||
)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errB = ne.ExecuteNucleiWithOptsCtx(context.Background(), []string{srvB.URL},
|
||||
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{templatePath}}),
|
||||
nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"sdk-callback-match"}}),
|
||||
nuclei.WithResultCallback(func(event *output.ResultEvent) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
callbackB = append(callbackB, eventMatchedURL(event))
|
||||
}),
|
||||
)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
require.NoError(t, errA)
|
||||
require.NoError(t, errB)
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
require.NotEmpty(t, callbackA, "per-call callback A should receive results")
|
||||
require.NotEmpty(t, callbackB, "per-call callback B should receive results")
|
||||
for _, matched := range callbackA {
|
||||
require.Contains(t, matched, srvA.URL, "callback A must only see server A")
|
||||
require.NotContains(t, matched, srvB.URL, "callback A must not see server B")
|
||||
}
|
||||
for _, matched := range callbackB {
|
||||
require.Contains(t, matched, srvB.URL, "callback B must only see server B")
|
||||
require.NotContains(t, matched, srvA.URL, "callback B must not see server A")
|
||||
}
|
||||
|
||||
// Global callback still sees both executions (no regression).
|
||||
require.GreaterOrEqual(t, globalCount, 2)
|
||||
require.NotEmpty(t, globalURLs)
|
||||
}
|
||||
|
||||
func TestThreadSafeGlobalCallbackWithoutPerCallStillWorks(t *testing.T) {
|
||||
templatePath := writeSDKMatchTemplate(t)
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte("token-A-unique"))
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.Background())
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(ne.Close)
|
||||
|
||||
var got int
|
||||
ne.GlobalResultCallback(func(event *output.ResultEvent) {
|
||||
got++
|
||||
})
|
||||
|
||||
err = ne.ExecuteNucleiWithOptsCtx(context.Background(), []string{srv.URL},
|
||||
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{templatePath}}),
|
||||
nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"sdk-callback-match"}}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.GreaterOrEqual(t, got, 1, "global callback should still receive results without WithResultCallback")
|
||||
}
|
||||
|
||||
func TestWithResultCallbackNilIsNoop(t *testing.T) {
|
||||
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.Background(), nuclei.WithResultCallback(nil))
|
||||
require.NoError(t, err)
|
||||
ne.Close()
|
||||
}
|
||||
|
||||
func writeSDKMatchTemplate(t *testing.T) string {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "sdk-callback-match.yaml")
|
||||
content := `id: sdk-callback-match
|
||||
info:
|
||||
name: SDK callback match
|
||||
author: nuclei-sdk-test
|
||||
severity: info
|
||||
http:
|
||||
- method: GET
|
||||
path:
|
||||
- "{{BaseURL}}/"
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- "token-"
|
||||
`
|
||||
require.NoError(t, os.WriteFile(path, []byte(content), 0o600))
|
||||
return path
|
||||
}
|
||||
|
||||
// eventMatchedURL prefers Matched/URL over Host (Host omits the port).
|
||||
func eventMatchedURL(event *output.ResultEvent) string {
|
||||
if event == nil {
|
||||
return ""
|
||||
}
|
||||
if event.Matched != "" {
|
||||
return event.Matched
|
||||
}
|
||||
if event.URL != "" {
|
||||
return event.URL
|
||||
}
|
||||
return event.Host
|
||||
}
|
||||
+40
-1
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
|
||||
metadataindex "github.com/projectdiscovery/nuclei/v3/pkg/catalog/index"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/core"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
|
||||
@@ -80,8 +81,12 @@ type NucleiEngine struct {
|
||||
browserInstance *engine.Browser
|
||||
httpClient *retryablehttp.Client
|
||||
parser *templates.Parser
|
||||
ownsParser bool // true when the engine created the parser and may purge it on Close
|
||||
authprovider authprovider.AuthProvider
|
||||
|
||||
metadataIndex *metadataindex.Index
|
||||
metadataIndexOnce sync.Once
|
||||
|
||||
// unexported meta options
|
||||
opts *types.Options
|
||||
interactshOpts *interactsh.Options
|
||||
@@ -99,6 +104,24 @@ type NucleiEngine struct {
|
||||
tmpDir string
|
||||
}
|
||||
|
||||
func (e *NucleiEngine) getMetadataIndex() *metadataindex.Index {
|
||||
e.metadataIndexOnce.Do(func() {
|
||||
metadataIndex, err := metadataindex.NewDefaultIndex()
|
||||
if err != nil {
|
||||
e.Logger.Warning().Msgf("Could not create metadata cache: %v", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
e.metadataIndex = metadataIndex
|
||||
if err := e.metadataIndex.Load(); err != nil {
|
||||
e.Logger.Warning().Msgf("Could not load metadata cache: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
return e.metadataIndex
|
||||
}
|
||||
|
||||
// LoadAllTemplates loads all nuclei template based on given options
|
||||
func (e *NucleiEngine) LoadAllTemplates() error {
|
||||
workflowLoader, err := workflow.NewLoader(e.executerOpts)
|
||||
@@ -107,7 +130,11 @@ func (e *NucleiEngine) LoadAllTemplates() error {
|
||||
}
|
||||
e.executerOpts.WorkflowLoader = workflowLoader
|
||||
|
||||
e.store, err = loader.New(loader.NewConfig(e.opts, e.catalog, e.executerOpts))
|
||||
loaderConfig := loader.NewConfig(e.opts, e.catalog, e.executerOpts)
|
||||
if e.mode == threadSafe {
|
||||
loaderConfig.MetadataIndex = e.getMetadataIndex()
|
||||
}
|
||||
e.store, err = loader.New(loaderConfig)
|
||||
if err != nil {
|
||||
return errkit.Wrapf(err, "Could not create loader client: %s", err)
|
||||
}
|
||||
@@ -238,12 +265,24 @@ func (e *NucleiEngine) closeInternal() {
|
||||
if e.httpxClient != nil {
|
||||
_ = e.httpxClient.Close()
|
||||
}
|
||||
if e.metadataIndex != nil {
|
||||
if err := e.metadataIndex.Save(); err != nil {
|
||||
e.Logger.Warning().Msgf("Could not save metadata cache: %v", err)
|
||||
}
|
||||
}
|
||||
if e.tmpDir != "" {
|
||||
_ = os.RemoveAll(e.tmpDir)
|
||||
}
|
||||
if e.opts != nil {
|
||||
generators.ClearOptionsPayloadMap(e.opts)
|
||||
}
|
||||
// Purge the template caches (compiled templates are heap-heavy) so a
|
||||
// long-running embedder does not retain them for the process lifetime.
|
||||
// Only do this when the engine created the parser; a caller-supplied
|
||||
// parser is an opt-in shared cache the caller owns.
|
||||
if e.ownsParser && e.parser != nil {
|
||||
e.parser.Purge()
|
||||
}
|
||||
}
|
||||
|
||||
// Close all resources used by nuclei engine
|
||||
|
||||
+3
-1
@@ -170,6 +170,7 @@ func (e *NucleiEngine) init(ctx context.Context) error {
|
||||
|
||||
if e.parser == nil {
|
||||
e.parser = templates.NewParser()
|
||||
e.ownsParser = true
|
||||
}
|
||||
|
||||
if protocolstate.ShouldInit(e.opts.ExecutionId) {
|
||||
@@ -177,7 +178,7 @@ func (e *NucleiEngine) init(ctx context.Context) error {
|
||||
}
|
||||
|
||||
if e.opts.ProxyInternal && e.opts.AliveHttpProxy != "" || e.opts.AliveSocksProxy != "" {
|
||||
httpclient, err := httpclientpool.Get(e.opts, &httpclientpool.Configuration{})
|
||||
httpclient, err := httpclientpool.Get(e.opts, &httpclientpool.Configuration{}, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -314,6 +315,7 @@ func (e *NucleiEngine) init(ctx context.Context) error {
|
||||
if e.parser == nil {
|
||||
op := templates.NewParser()
|
||||
e.parser = op
|
||||
e.ownsParser = true
|
||||
e.opts.Parser = op
|
||||
e.executerOpts.Parser = op
|
||||
e.executerOpts.Options.Parser = op
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
//go:build regression
|
||||
|
||||
// Package sdk_test contains an opt-in, large-scale regression harness for the
|
||||
// HTTP engine. It is gated behind the "regression" build tag so it never runs
|
||||
// as part of the normal unit/integration suites; run it explicitly with:
|
||||
//
|
||||
// go test -tags regression ./lib/tests/ -run TestScaleRegression -v
|
||||
//
|
||||
// Tune the host count with NUCLEI_SCALE_HOSTS (default 50).
|
||||
//
|
||||
// The harness stands up many independent loopback HTTP hosts and runs a diverse
|
||||
// template set (word/header/regex/status/dsl/extractor matchers, redirects, raw
|
||||
// requests and a multi-step cookie-reuse flow) through the engine, asserting
|
||||
// that every host yields the full expected finding set. It also includes a host
|
||||
// that emits the "plain HTTP request was sent to HTTPS port" 400 body to
|
||||
// exercise the HTTP->HTTPS port tracker: a wrongly detected/false-positive scheme
|
||||
// rewrite must not silently drop findings of unrelated templates hitting the
|
||||
// same host:port (regression guard for the tracker fallback).
|
||||
package sdk_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// scaleTemplates is the diverse template set exercised by the harness. Each one
|
||||
// deterministically matches every host so per-template finding counts must
|
||||
// equal the number of hosts.
|
||||
var scaleTemplates = map[string]string{
|
||||
"01-basic-word.yaml": `id: scale-basic-word
|
||||
info: {name: Scale Basic Word, author: regression, severity: info}
|
||||
http:
|
||||
- method: GET
|
||||
path: ["{{BaseURL}}/"]
|
||||
matchers-condition: and
|
||||
matchers:
|
||||
- {type: word, part: body, words: ["REGRESSION-OK"]}
|
||||
- {type: status, status: [200]}
|
||||
`,
|
||||
"02-header-match.yaml": `id: scale-header-match
|
||||
info: {name: Scale Header Match, author: regression, severity: info}
|
||||
http:
|
||||
- method: GET
|
||||
path: ["{{BaseURL}}/"]
|
||||
matchers:
|
||||
- {type: word, part: header, words: ["nuclei-regression"]}
|
||||
`,
|
||||
"03-regex.yaml": `id: scale-regex
|
||||
info: {name: Scale Regex, author: regression, severity: info}
|
||||
http:
|
||||
- method: GET
|
||||
path: ["{{BaseURL}}/"]
|
||||
matchers:
|
||||
- {type: regex, part: body, regex: ["token=[A-Z0-9]{6}"]}
|
||||
`,
|
||||
"04-multi-cookie.yaml": `id: scale-multi-cookie
|
||||
info: {name: Scale Multi-step Cookie Reuse, author: regression, severity: info}
|
||||
http:
|
||||
- cookie-reuse: true
|
||||
raw:
|
||||
- |
|
||||
GET /login HTTP/1.1
|
||||
Host: {{Hostname}}
|
||||
User-Agent: regression
|
||||
|
||||
- |
|
||||
GET /profile HTTP/1.1
|
||||
Host: {{Hostname}}
|
||||
User-Agent: regression
|
||||
|
||||
matchers:
|
||||
- {type: word, part: body, words: ["welcome-admin"]}
|
||||
`,
|
||||
"05-redirect.yaml": `id: scale-redirect
|
||||
info: {name: Scale Redirect, author: regression, severity: info}
|
||||
http:
|
||||
- method: GET
|
||||
path: ["{{BaseURL}}/redirect"]
|
||||
host-redirects: true
|
||||
max-redirects: 3
|
||||
matchers:
|
||||
- {type: word, part: body, words: ["REGRESSION-OK"]}
|
||||
`,
|
||||
"06-raw.yaml": `id: scale-raw
|
||||
info: {name: Scale Raw Request, author: regression, severity: info}
|
||||
http:
|
||||
- raw:
|
||||
- |
|
||||
GET / HTTP/1.1
|
||||
Host: {{Hostname}}
|
||||
User-Agent: regression-raw
|
||||
|
||||
matchers-condition: and
|
||||
matchers:
|
||||
- {type: status, status: [200]}
|
||||
- {type: word, part: body, words: ["build=stable"]}
|
||||
`,
|
||||
"07-dsl-json.yaml": `id: scale-dsl-json
|
||||
info: {name: Scale DSL JSON, author: regression, severity: info}
|
||||
http:
|
||||
- method: GET
|
||||
path: ["{{BaseURL}}/api"]
|
||||
matchers:
|
||||
- {type: dsl, dsl: ['contains(body, "\"admin\":true") && status_code == 200']}
|
||||
`,
|
||||
"08-extractor.yaml": `id: scale-extractor
|
||||
info: {name: Scale Extractor, author: regression, severity: info}
|
||||
http:
|
||||
- method: GET
|
||||
path: ["{{BaseURL}}/api"]
|
||||
extractors:
|
||||
- {type: regex, part: body, regex: ['"version":"([0-9.]+)"'], group: 1}
|
||||
matchers:
|
||||
- {type: word, part: body, words: ["active"]}
|
||||
`,
|
||||
// This template triggers the HTTP->HTTPS port tracker. With the fallback in
|
||||
// place, the tracker must not cause the other plain-HTTP templates above to
|
||||
// be dropped on the same host:port.
|
||||
"09-httpsport.yaml": `id: scale-httpsport
|
||||
info: {name: Scale HTTP to HTTPS Port, author: regression, severity: info}
|
||||
http:
|
||||
- method: GET
|
||||
path: ["{{BaseURL}}/httpsport"]
|
||||
matchers:
|
||||
- {type: word, part: body, words: ["plain HTTP request was sent to HTTPS port"]}
|
||||
`,
|
||||
}
|
||||
|
||||
// scaleHandler serves deterministic, template-matchable content. The /httpsport
|
||||
// endpoint mimics a server that received plain HTTP on an HTTPS port.
|
||||
func scaleHandler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Reg", "nuclei-regression")
|
||||
w.Header().Set("Server", "regression-test/1.0")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("REGRESSION-OK token=ABC123 build=stable\n"))
|
||||
})
|
||||
mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.SetCookie(w, &http.Cookie{Name: "sess", Value: "valid-token", Path: "/"})
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("logged-in\n"))
|
||||
})
|
||||
mux.HandleFunc("/profile", func(w http.ResponseWriter, r *http.Request) {
|
||||
if c, err := r.Cookie("sess"); err != nil || c.Value != "valid-token" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_, _ = w.Write([]byte("unauthorized\n"))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("welcome-admin profile-data\n"))
|
||||
})
|
||||
mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
})
|
||||
mux.HandleFunc("/httpsport", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte("Client sent an HTTP request to an HTTPS server.\nThe plain HTTP request was sent to HTTPS port\n"))
|
||||
})
|
||||
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"status":"active","version":"2.4.1","admin":true}`))
|
||||
})
|
||||
return mux
|
||||
}
|
||||
|
||||
func TestScaleRegression(t *testing.T) {
|
||||
hosts := 50
|
||||
if v, err := strconv.Atoi(os.Getenv("NUCLEI_SCALE_HOSTS")); err == nil && v > 0 {
|
||||
hosts = v
|
||||
}
|
||||
|
||||
// Stand up N independent loopback hosts (distinct host:port each, so the
|
||||
// per-host pool / connection-reuse / http->https machinery is exercised
|
||||
// across many keys).
|
||||
handler := scaleHandler()
|
||||
targets := make([]string, 0, hosts)
|
||||
servers := make([]*httptest.Server, 0, hosts)
|
||||
for i := 0; i < hosts; i++ {
|
||||
srv := httptest.NewServer(handler)
|
||||
servers = append(servers, srv)
|
||||
targets = append(targets, srv.URL)
|
||||
}
|
||||
defer func() {
|
||||
for _, srv := range servers {
|
||||
srv.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// Write the template set to a temp directory.
|
||||
tplDir := t.TempDir()
|
||||
for name, content := range scaleTemplates {
|
||||
require.NoError(t, os.WriteFile(filepath.Join(tplDir, name), []byte(content), 0o644))
|
||||
}
|
||||
|
||||
ne, err := nuclei.NewNucleiEngineCtx(
|
||||
context.Background(),
|
||||
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{tplDir}}),
|
||||
nuclei.WithSandboxOptions(true, false),
|
||||
nuclei.DisableUpdateCheck(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
defer ne.Close()
|
||||
|
||||
ne.LoadTargets(targets, false)
|
||||
require.NoError(t, ne.LoadAllTemplates())
|
||||
|
||||
var mu sync.Mutex
|
||||
perTemplate := map[string]int{}
|
||||
perTemplateHost := map[string]map[string]struct{}{}
|
||||
require.NoError(t, ne.ExecuteWithCallback(func(event *output.ResultEvent) {
|
||||
if event == nil {
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
perTemplate[event.TemplateID]++
|
||||
if perTemplateHost[event.TemplateID] == nil {
|
||||
perTemplateHost[event.TemplateID] = map[string]struct{}{}
|
||||
}
|
||||
// key on matched-at (includes host:port) since Host alone is 127.0.0.1
|
||||
// for every loopback server
|
||||
perTemplateHost[event.TemplateID][event.Matched] = struct{}{}
|
||||
}))
|
||||
|
||||
// Every template must match on every host. In particular scale-multi-cookie
|
||||
// and the other plain-HTTP templates must reach the full host count even
|
||||
// though scale-httpsport marks each host:port as "requires HTTPS" — proving
|
||||
// the http->https tracker fallback prevents silent finding loss.
|
||||
wantIDs := []string{
|
||||
"scale-basic-word", "scale-header-match", "scale-regex", "scale-multi-cookie",
|
||||
"scale-redirect", "scale-raw", "scale-dsl-json", "scale-extractor", "scale-httpsport",
|
||||
}
|
||||
for _, id := range wantIDs {
|
||||
unique := len(perTemplateHost[id])
|
||||
require.Equalf(t, hosts, unique,
|
||||
"template %q matched on %d/%d hosts (per-template hits=%d)", id, unique, hosts, perTemplate[id])
|
||||
}
|
||||
|
||||
t.Logf("scale regression OK: %d hosts x %d templates = %d findings",
|
||||
hosts, len(wantIDs), hosts*len(wantIDs))
|
||||
}
|
||||
+26
-19
@@ -14,11 +14,20 @@ import (
|
||||
)
|
||||
|
||||
var knownLeaks = []goleak.Option{
|
||||
// prettyify the output and generate dependency graph and more details instead of just stack output
|
||||
goleak.Pretty(),
|
||||
// net/http transport maintains idle connections which are closed with cooldown
|
||||
// hence they don't count as leaks
|
||||
// net/http transport maintains idle keep-alive connections whose goroutines
|
||||
// exit on idle timeout or explicit close - not real leaks.
|
||||
goleak.IgnoreAnyFunction("net/http.(*http2ClientConn).readLoop"),
|
||||
goleak.IgnoreAnyFunction("net/http.(*persistConn).readLoop"),
|
||||
goleak.IgnoreAnyFunction("net/http.(*persistConn).writeLoop"),
|
||||
// expirable LRU cache creates a background goroutine for TTL expiration that persists
|
||||
// see: https://github.com/hashicorp/golang-lru/blob/770151e9c8cdfae1797826b7b74c33d6f103fbd8/expirable/expirable_lru.go#L79
|
||||
goleak.IgnoreAnyContainingPkg("github.com/hashicorp/golang-lru/v2/expirable"),
|
||||
// httpcache leveldb + shared rate limiter + memguardian outlive a single SDK engine.
|
||||
// Sleep alone is not reliable under -race or when NewNucleiEngineCtx fails before Close.
|
||||
goleak.IgnoreAnyContainingPkg("github.com/syndtr/goleveldb"),
|
||||
goleak.IgnoreAnyContainingPkg("github.com/projectdiscovery/ratelimit"),
|
||||
goleak.IgnoreAnyFunction("github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate.StartActiveMemGuardian.func1"),
|
||||
}
|
||||
|
||||
func TestSimpleNuclei(t *testing.T) {
|
||||
@@ -34,12 +43,12 @@ func TestSimpleNuclei(t *testing.T) {
|
||||
nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"}), // filter dns templates
|
||||
nuclei.EnableStatsWithOpts(nuclei.StatsOptions{JSON: true}),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
defer ne.Close()
|
||||
ne.LoadTargets([]string{"scanme.sh"}, false) // probe non http/https target is set to false here
|
||||
// when callback is nil it nuclei will print JSON output to stdout
|
||||
err = ne.ExecuteWithCallback(nil)
|
||||
require.Nil(t, err)
|
||||
defer ne.Close()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// this is shared test so needs to be run as separate process
|
||||
@@ -72,14 +81,14 @@ func TestSimpleNucleiRemote(t *testing.T) {
|
||||
},
|
||||
),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
defer ne.Close()
|
||||
ne.LoadTargets([]string{"scanme.sh"}, false) // probe non http/https target is set to false here
|
||||
err = ne.LoadAllTemplates()
|
||||
require.Nil(t, err, "could not load templates")
|
||||
require.NoError(t, err, "could not load templates")
|
||||
// when callback is nil it nuclei will print JSON output to stdout
|
||||
err = ne.ExecuteWithCallback(nil)
|
||||
require.Nil(t, err)
|
||||
defer ne.Close()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// this is shared test so needs to be run as separate process
|
||||
if env.GetEnvOrDefault("TestSimpleNucleiRemote", false) {
|
||||
@@ -104,22 +113,20 @@ func TestThreadSafeNuclei(t *testing.T) {
|
||||
}()
|
||||
// create nuclei engine with options
|
||||
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.TODO())
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
defer ne.Close()
|
||||
|
||||
// scan 1 = run dns templates on scanme.sh
|
||||
t.Run("scanme.sh", func(t *testing.T) {
|
||||
err = ne.ExecuteNucleiWithOpts([]string{"scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"}))
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// scan 2 = run dns templates on honey.scanme.sh
|
||||
t.Run("honey.scanme.sh", func(t *testing.T) {
|
||||
err = ne.ExecuteNucleiWithOpts([]string{"honey.scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"}))
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// wait for all scans to finish
|
||||
defer ne.Close()
|
||||
}
|
||||
|
||||
if env.GetEnvOrDefault("TestThreadSafeNuclei", false) {
|
||||
@@ -149,11 +156,11 @@ func TestWithVarsNuclei(t *testing.T) {
|
||||
nuclei.WithVars([]string{"token=foobar"}),
|
||||
nuclei.WithVerbosity(nuclei.VerbosityOptions{Debug: true}),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
defer ne.Close()
|
||||
ne.LoadTargets([]string{"scanme.sh"}, true) // probe http/https target is set to true here
|
||||
err = ne.ExecuteWithCallback(nil)
|
||||
require.Nil(t, err)
|
||||
defer ne.Close()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// this is shared test so needs to be run as separate process
|
||||
if env.GetEnvOrDefault("TestWithVarsNuclei", false) {
|
||||
|
||||
+31
-1
@@ -122,7 +122,12 @@
|
||||
"CAA",
|
||||
"TLSA",
|
||||
"ANY",
|
||||
"SRV"
|
||||
"SRV",
|
||||
"RRSIG",
|
||||
"NSEC",
|
||||
"DNSKEY",
|
||||
"NSEC3",
|
||||
"NSEC3PARAM"
|
||||
],
|
||||
"title": "type of DNS request to make",
|
||||
"description": "Type is the type of DNS request to make"
|
||||
@@ -468,6 +473,31 @@
|
||||
"type": "boolean",
|
||||
"title": "do not perform recursion",
|
||||
"description": "Specifies whether to not do recursive checks if folders are provided"
|
||||
},
|
||||
"smb-user": {
|
||||
"type": "string",
|
||||
"title": "SMB username",
|
||||
"description": "Username for SMB file targets"
|
||||
},
|
||||
"smb-password": {
|
||||
"type": "string",
|
||||
"title": "SMB password",
|
||||
"description": "Password for SMB file targets"
|
||||
},
|
||||
"smb-domain": {
|
||||
"type": "string",
|
||||
"title": "SMB domain",
|
||||
"description": "Domain for SMB authentication"
|
||||
},
|
||||
"smb-hash": {
|
||||
"type": "string",
|
||||
"title": "SMB NT hash",
|
||||
"description": "NT hash for SMB pass-the-hash"
|
||||
},
|
||||
"smb-port": {
|
||||
"type": "integer",
|
||||
"title": "SMB port",
|
||||
"description": "TCP port for SMB (default 445)"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -31,7 +31,7 @@ const (
|
||||
CLIConfigFileName = "config.yaml"
|
||||
ReportingConfigFilename = "reporting-config.yaml"
|
||||
// Version is the current version of nuclei
|
||||
Version = `v3.9.0`
|
||||
Version = `v3.11.1`
|
||||
// Directory Names of custom templates
|
||||
CustomS3TemplatesDirName = "s3"
|
||||
CustomGitHubTemplatesDirName = "github"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"gopkg.in/yaml.v2"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
|
||||
)
|
||||
|
||||
// IgnoreFile is an internal nuclei template blocking configuration file
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/projectdiscovery/gologger"
|
||||
filepathutil "github.com/projectdiscovery/nuclei/v3/pkg/utils/filepath"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
|
||||
"github.com/projectdiscovery/utils/env"
|
||||
"github.com/projectdiscovery/utils/errkit"
|
||||
fileutil "github.com/projectdiscovery/utils/file"
|
||||
@@ -355,17 +356,12 @@ func (c *Config) IsDebugArgEnabled(arg string) bool {
|
||||
// parseDebugArgs from string
|
||||
func (c *Config) parseDebugArgs(data string) {
|
||||
// use space as separator instead of commas
|
||||
tmp := strings.Fields(data)
|
||||
for _, v := range tmp {
|
||||
for v := range strings.FieldsSeq(data) {
|
||||
key := v
|
||||
value := ""
|
||||
// if it is key value pair then split it
|
||||
if strings.Contains(v, "=") {
|
||||
parts := strings.SplitN(v, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
key, value = strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
|
||||
if k, val, ok := strings.Cut(v, "="); ok {
|
||||
key, value = strings.TrimSpace(k), strings.TrimSpace(val)
|
||||
}
|
||||
if value == "false" || value == "0" {
|
||||
// if false or disabled then skip
|
||||
@@ -425,6 +421,13 @@ func init() {
|
||||
// If custom templates are in default locations only then they are loaded while running nuclei
|
||||
DefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory)
|
||||
DefaultConfig.parseDebugArgs(env.GetEnvOrDefault("NUCLEI_ARGS", ""))
|
||||
|
||||
// expose the templates directory to the yaml preprocessor so include
|
||||
// directives can be confined to it without yaml importing this package
|
||||
// (which would create an import cycle).
|
||||
yaml.TemplateBaseDirProvider = func() string {
|
||||
return DefaultConfig.GetTemplateDir()
|
||||
}
|
||||
}
|
||||
|
||||
// Add Default Config adds default when .templates-config.json file is not present
|
||||
|
||||
@@ -101,8 +101,7 @@ func IsTemplateWithRoot(fpath, rootDir string) bool {
|
||||
// Only check components if pathToCheck is NOT absolute
|
||||
// This avoids false positives on parent directories for absolute paths
|
||||
if !filepath.IsAbs(pathToCheck) {
|
||||
parts := strings.Split(pathToCheck, string(os.PathSeparator))
|
||||
for _, p := range parts {
|
||||
for p := range strings.SplitSeq(pathToCheck, string(os.PathSeparator)) {
|
||||
for _, excluded := range knownMiscDirectories {
|
||||
if strings.EqualFold(p, excluded) {
|
||||
return false
|
||||
|
||||
@@ -193,6 +193,9 @@ func (c *DiskCatalog) findFileMatches(absPath string, processed map[string]struc
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
defer func() {
|
||||
_ = info.Close()
|
||||
}()
|
||||
stat, err := info.Stat()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
|
||||
+12
-15
@@ -15,9 +15,10 @@ import (
|
||||
// Inclusion fields (e.g., Authors, Tags, IDs, Severities, ProtocolTypes) use
|
||||
// AND logic across different filter types and OR logic within each type.
|
||||
// Exclusion fields (e.g., ExcludeTags, ExcludeIDs, ExcludeSeverities,
|
||||
// ExcludeProtocolTypes) take precedence over inclusion fields. Additionally,
|
||||
// IncludeTemplates and IncludeTags can force inclusion of templates even if
|
||||
// they match exclusion criteria.
|
||||
// ExcludeProtocolTypes) take precedence over normal inclusion fields.
|
||||
// IncludeTags can force inclusion only for the same excluded tag; other
|
||||
// exclusion criteria and unrelated excluded tags still apply. IncludeTemplates
|
||||
// remains an explicit template override.
|
||||
type Filter struct {
|
||||
// once ensures that IDs and ExcludedIDs are compiled the first time Matches is called.
|
||||
once sync.Once
|
||||
@@ -31,7 +32,7 @@ type Filter struct {
|
||||
// ExcludeTags to exclude (takes precedence over Tags).
|
||||
ExcludeTags []string
|
||||
|
||||
// IncludeTags to force include even if excluded.
|
||||
// IncludeTags to force include matching tag exclusions.
|
||||
IncludeTags []string
|
||||
|
||||
// IDs to include (supports wildcards).
|
||||
@@ -75,7 +76,7 @@ type Filter struct {
|
||||
func (f *Filter) Matches(m *Metadata) bool {
|
||||
f.once.Do(f.Compile)
|
||||
|
||||
if f.isForcedInclude(m) {
|
||||
if f.matchesIncludeTemplate(m) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -90,8 +91,8 @@ func (f *Filter) Matches(m *Metadata) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// isForcedInclude checks if template is forced to be included.
|
||||
func (f *Filter) isForcedInclude(m *Metadata) bool {
|
||||
// matchesIncludeTemplate checks if template is explicitly included by path.
|
||||
func (f *Filter) matchesIncludeTemplate(m *Metadata) bool {
|
||||
if len(f.IncludeTemplates) > 0 {
|
||||
for _, includePath := range f.IncludeTemplates {
|
||||
if matchesPath(m.FilePath, includePath) {
|
||||
@@ -100,12 +101,6 @@ func (f *Filter) isForcedInclude(m *Metadata) bool {
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.IncludeTags) > 0 {
|
||||
if slices.ContainsFunc(f.IncludeTags, m.HasTag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -120,8 +115,10 @@ func (f *Filter) isExcluded(m *Metadata) bool {
|
||||
}
|
||||
|
||||
if len(f.ExcludeTags) > 0 {
|
||||
if slices.ContainsFunc(f.ExcludeTags, m.HasTag) {
|
||||
return true
|
||||
for _, excludeTag := range f.ExcludeTags {
|
||||
if m.HasTag(excludeTag) && !slices.Contains(f.IncludeTags, excludeTag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,12 +58,111 @@ func TestFilterMatches(t *testing.T) {
|
||||
require.False(t, filter.Matches(metadata))
|
||||
})
|
||||
|
||||
t.Run("Include tags overrides exclude", func(t *testing.T) {
|
||||
t.Run("Include tags overrides same exclude tag", func(t *testing.T) {
|
||||
filter := &Filter{
|
||||
ExcludeTags: []string{"cve"},
|
||||
IncludeTags: []string{"cve"},
|
||||
}
|
||||
require.True(t, filter.Matches(metadata))
|
||||
})
|
||||
|
||||
t.Run("Include tags does not override unrelated exclude tags", func(t *testing.T) {
|
||||
filter := &Filter{
|
||||
ExcludeTags: []string{"rce"},
|
||||
IncludeTags: []string{"cve"},
|
||||
}
|
||||
require.True(t, filter.Matches(metadata))
|
||||
require.False(t, filter.Matches(metadata))
|
||||
})
|
||||
|
||||
t.Run("Include tags does not override other exclusions", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
filter *Filter
|
||||
}{
|
||||
{
|
||||
name: "exclude id",
|
||||
filter: &Filter{
|
||||
ExcludeIDs: []string{"test-template-1"},
|
||||
IncludeTags: []string{"cve"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "exclude template",
|
||||
filter: &Filter{
|
||||
ExcludeTemplates: []string{"/templates/cves/"},
|
||||
IncludeTags: []string{"cve"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "exclude severity",
|
||||
filter: &Filter{
|
||||
ExcludeSeverities: []severity.Severity{severity.Critical},
|
||||
IncludeTags: []string{"cve"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "exclude protocol",
|
||||
filter: &Filter{
|
||||
ExcludeProtocolTypes: []types.ProtocolType{types.HTTPProtocol},
|
||||
IncludeTags: []string{"cve"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.False(t, tt.filter.Matches(metadata))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Include tags does not override normal includes", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
filter *Filter
|
||||
}{
|
||||
{
|
||||
name: "author",
|
||||
filter: &Filter{
|
||||
Authors: []string{"unknown"},
|
||||
IncludeTags: []string{"cve"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tag",
|
||||
filter: &Filter{
|
||||
Tags: []string{"xss"},
|
||||
IncludeTags: []string{"cve"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "id",
|
||||
filter: &Filter{
|
||||
IDs: []string{"other-template"},
|
||||
IncludeTags: []string{"cve"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "severity",
|
||||
filter: &Filter{
|
||||
Severities: []severity.Severity{severity.High},
|
||||
IncludeTags: []string{"cve"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "protocol",
|
||||
filter: &Filter{
|
||||
ProtocolTypes: []types.ProtocolType{types.DNSProtocol},
|
||||
IncludeTags: []string{"cve"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.False(t, tt.filter.Matches(metadata))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ID filter - exact match", func(t *testing.T) {
|
||||
|
||||
+49
-21
@@ -2,6 +2,7 @@ package index
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -19,7 +20,7 @@ const (
|
||||
|
||||
// IndexVersion is the schema version for cache invalidation on breaking
|
||||
// changes.
|
||||
IndexVersion = 1
|
||||
IndexVersion = 2
|
||||
|
||||
// DefaultMaxSize is the default maximum number of templates to cache.
|
||||
DefaultMaxSize = 50000
|
||||
@@ -34,6 +35,7 @@ type Index struct {
|
||||
cacheFile string
|
||||
mu sync.RWMutex
|
||||
version int
|
||||
dirty bool
|
||||
}
|
||||
|
||||
// cacheSnapshot represents the serialized cache structure.
|
||||
@@ -70,6 +72,8 @@ func NewIndex(cacheDir string) (*Index, error) {
|
||||
weight += len(value.Severity)
|
||||
weight += len(value.ProtocolType)
|
||||
weight += len(value.TemplateVerifier)
|
||||
weight += len(value.VerifierFingerprint)
|
||||
weight += len(value.ContentDigest)
|
||||
|
||||
for _, author := range value.Authors {
|
||||
weight += len(author)
|
||||
@@ -105,20 +109,26 @@ func NewDefaultIndex() (*Index, error) {
|
||||
// Get retrieves metadata for a template path, validating freshness via mtime.
|
||||
func (i *Index) Get(path string) (*Metadata, bool) {
|
||||
i.mu.RLock()
|
||||
defer i.mu.RUnlock()
|
||||
|
||||
metadata, found := i.cache.GetIfPresent(path)
|
||||
i.mu.RUnlock()
|
||||
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if !metadata.IsValid() {
|
||||
go i.Delete(path)
|
||||
|
||||
return nil, false
|
||||
if metadata.IsValid() {
|
||||
return metadata.clone(), true
|
||||
}
|
||||
|
||||
return metadata, true
|
||||
i.mu.Lock()
|
||||
current, found := i.cache.GetIfPresent(path)
|
||||
if found && current == metadata {
|
||||
i.cache.Invalidate(path)
|
||||
i.dirty = true
|
||||
}
|
||||
i.mu.Unlock()
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Set stores metadata for a template path.
|
||||
@@ -132,7 +142,10 @@ func (i *Index) Set(path string, metadata *Metadata) (*Metadata, bool) {
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
|
||||
return i.cache.Set(path, metadata)
|
||||
cached, ok := i.cache.Set(path, metadata.clone())
|
||||
i.dirty = true
|
||||
|
||||
return cached.clone(), ok
|
||||
}
|
||||
|
||||
// SetFromTemplate extracts metadata from a parsed template and stores it.
|
||||
@@ -171,7 +184,12 @@ func (i *Index) Delete(path string) {
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
|
||||
if _, found := i.cache.GetIfPresent(path); !found {
|
||||
return
|
||||
}
|
||||
|
||||
i.cache.Invalidate(path)
|
||||
i.dirty = true
|
||||
}
|
||||
|
||||
// Size returns the number of cached entries.
|
||||
@@ -188,18 +206,22 @@ func (i *Index) Clear() {
|
||||
defer i.mu.Unlock()
|
||||
|
||||
i.cache.InvalidateAll()
|
||||
i.dirty = true
|
||||
}
|
||||
|
||||
// Save persists the cache to disk using gob encoding.
|
||||
func (i *Index) Save() error {
|
||||
i.mu.RLock()
|
||||
defer i.mu.RUnlock()
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
|
||||
if !i.dirty {
|
||||
return nil
|
||||
}
|
||||
|
||||
snapshot := &cacheSnapshot{
|
||||
Version: i.version,
|
||||
Data: make(map[string]*Metadata),
|
||||
Data: make(map[string]*Metadata, i.cache.EstimatedSize()),
|
||||
}
|
||||
|
||||
maps.Insert(snapshot.Data, i.cache.All())
|
||||
|
||||
// NOTE(dwisiswant0): write to temp for atomic op.
|
||||
@@ -229,11 +251,20 @@ func (i *Index) Save() error {
|
||||
return err
|
||||
}
|
||||
|
||||
i.dirty = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load loads the cache from disk using gob decoding.
|
||||
func (i *Index) Load() error {
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
|
||||
if i.dirty {
|
||||
return errors.New("cannot load metadata cache into a modified index")
|
||||
}
|
||||
|
||||
file, err := os.Open(i.cacheFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
@@ -261,9 +292,6 @@ func (i *Index) Load() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
|
||||
for key, value := range snapshot.Data {
|
||||
i.cache.Set(key, value)
|
||||
}
|
||||
@@ -296,11 +324,8 @@ func (i *Index) FilterFunc(fn FilterFunc) []string {
|
||||
return i.All()
|
||||
}
|
||||
|
||||
i.mu.RLock()
|
||||
defer i.mu.RUnlock()
|
||||
|
||||
var matched []string
|
||||
for path, metadata := range i.cache.All() {
|
||||
for path, metadata := range i.GetAll() {
|
||||
if fn(metadata) {
|
||||
matched = append(matched, path)
|
||||
}
|
||||
@@ -327,7 +352,10 @@ func (i *Index) GetAll() map[string]*Metadata {
|
||||
i.mu.RLock()
|
||||
defer i.mu.RUnlock()
|
||||
|
||||
result := maps.Collect(i.cache.All())
|
||||
result := make(map[string]*Metadata, i.cache.EstimatedSize())
|
||||
for path, metadata := range i.cache.All() {
|
||||
result[path] = metadata.clone()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -35,6 +35,13 @@ func TestNewIndex(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestMetadataIsValidatedForRejectsUnknownMode(t *testing.T) {
|
||||
metadata := &Metadata{Validation: ValidationMode(255)}
|
||||
|
||||
require.False(t, metadata.IsValidatedFor(false))
|
||||
require.False(t, metadata.IsValidatedFor(true))
|
||||
}
|
||||
|
||||
func TestCacheBasicOperations(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
cache, err := NewIndex(tmpDir)
|
||||
@@ -164,6 +171,143 @@ func TestCachePersistence(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCacheSaveSkipsUnchangedIndex(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
cache, err := NewIndex(tmpDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
cache.Set("/tmp/unchanged.yaml", &Metadata{
|
||||
ID: "unchanged",
|
||||
FilePath: "/tmp/unchanged.yaml",
|
||||
})
|
||||
require.NoError(t, cache.Save())
|
||||
|
||||
cacheFile := filepath.Join(tmpDir, IndexFileName)
|
||||
oldTime := time.Unix(1, 0)
|
||||
require.NoError(t, os.Chtimes(cacheFile, oldTime, oldTime))
|
||||
|
||||
require.NoError(t, cache.Save())
|
||||
|
||||
stat, err := os.Stat(cacheFile)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, oldTime, stat.ModTime(), "unchanged cache should not be rewritten")
|
||||
}
|
||||
|
||||
func TestCacheLoadRejectsModifiedIndex(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
persisted, err := NewIndex(tmpDir)
|
||||
require.NoError(t, err)
|
||||
persisted.Set("/tmp/persisted.yaml", &Metadata{ID: "persisted", FilePath: "/tmp/persisted.yaml"})
|
||||
require.NoError(t, persisted.Save())
|
||||
|
||||
cache, err := NewIndex(tmpDir)
|
||||
require.NoError(t, err)
|
||||
cache.Set("/tmp/unsaved.yaml", &Metadata{ID: "unsaved", FilePath: "/tmp/unsaved.yaml"})
|
||||
|
||||
require.Error(t, cache.Load())
|
||||
require.True(t, cache.Has("/tmp/unsaved.yaml"), "rejected load must preserve unsaved entries")
|
||||
}
|
||||
|
||||
func TestCacheGetSynchronouslyInvalidatesStaleEntry(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
templatePath := filepath.Join(tmpDir, "stale.yaml")
|
||||
require.NoError(t, os.WriteFile(templatePath, []byte("id: stale"), 0o600))
|
||||
|
||||
cache, err := NewIndex(tmpDir)
|
||||
require.NoError(t, err)
|
||||
cache.Set(templatePath, &Metadata{
|
||||
ID: "stale",
|
||||
FilePath: templatePath,
|
||||
ModTime: time.Unix(1, 0),
|
||||
})
|
||||
|
||||
metadata, found := cache.Get(templatePath)
|
||||
require.False(t, found)
|
||||
require.Nil(t, metadata)
|
||||
require.False(t, cache.Has(templatePath), "Get must finish stale-entry invalidation before returning")
|
||||
}
|
||||
|
||||
func TestCacheMetadataIsDefensivelyCopied(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
templatePath := filepath.Join(tmpDir, "metadata.yaml")
|
||||
require.NoError(t, os.WriteFile(templatePath, []byte("id: metadata"), 0o600))
|
||||
info, err := os.Stat(templatePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
cache, err := NewIndex(tmpDir)
|
||||
require.NoError(t, err)
|
||||
input := &Metadata{
|
||||
ID: "original",
|
||||
FilePath: templatePath,
|
||||
ModTime: info.ModTime(),
|
||||
Authors: []string{"original-author"},
|
||||
Tags: []string{"original-tag"},
|
||||
}
|
||||
cache.Set(templatePath, input)
|
||||
|
||||
input.ID = "mutated-input"
|
||||
input.Authors[0] = "mutated-input-author"
|
||||
input.Tags[0] = "mutated-input-tag"
|
||||
require.NoError(t, cache.Save())
|
||||
|
||||
fromGet, found := cache.Get(templatePath)
|
||||
require.True(t, found)
|
||||
fromGet.ID = "mutated-get"
|
||||
fromGet.Authors[0] = "mutated-get-author"
|
||||
|
||||
fromGetAll := cache.GetAll()
|
||||
fromGetAll[templatePath].ID = "mutated-get-all"
|
||||
fromGetAll[templatePath].Tags[0] = "mutated-get-all-tag"
|
||||
require.NoError(t, cache.Save())
|
||||
|
||||
loaded, err := NewIndex(tmpDir)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, loaded.Load())
|
||||
metadata := loaded.GetAll()[templatePath]
|
||||
require.Equal(t, "original", metadata.ID)
|
||||
require.Equal(t, []string{"original-author"}, metadata.Authors)
|
||||
require.Equal(t, []string{"original-tag"}, metadata.Tags)
|
||||
}
|
||||
|
||||
func TestCacheClearPersistsEmptyIndexWithoutLoad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
persisted, err := NewIndex(tmpDir)
|
||||
require.NoError(t, err)
|
||||
persisted.Set("/tmp/persisted.yaml", &Metadata{ID: "persisted", FilePath: "/tmp/persisted.yaml"})
|
||||
require.NoError(t, persisted.Save())
|
||||
|
||||
cache, err := NewIndex(tmpDir)
|
||||
require.NoError(t, err)
|
||||
cache.Clear()
|
||||
require.NoError(t, cache.Save())
|
||||
|
||||
loaded, err := NewIndex(tmpDir)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, loaded.Load())
|
||||
require.Equal(t, 0, loaded.Size())
|
||||
}
|
||||
|
||||
func TestFilterFuncCanMutateIndex(t *testing.T) {
|
||||
cache, err := NewIndex(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
cache.Set("/tmp/original.yaml", &Metadata{ID: "original", FilePath: "/tmp/original.yaml"})
|
||||
|
||||
done := make(chan []string, 1)
|
||||
go func() {
|
||||
done <- cache.FilterFunc(func(*Metadata) bool {
|
||||
cache.Set("/tmp/from-callback.yaml", &Metadata{ID: "callback", FilePath: "/tmp/from-callback.yaml"})
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
select {
|
||||
case paths := <-done:
|
||||
require.Equal(t, []string{"/tmp/original.yaml"}, paths)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("FilterFunc deadlocked when its callback mutated the index")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexVersionMismatch(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
@@ -182,7 +326,10 @@ func TestIndexVersionMismatch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Manually modify version and save again
|
||||
cache1.mu.Lock()
|
||||
cache1.version = 999
|
||||
cache1.dirty = true
|
||||
cache1.mu.Unlock()
|
||||
err = cache1.Save()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -472,6 +619,19 @@ func TestCacheConcurrency(t *testing.T) {
|
||||
<-done
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Concurrent Save", func(t *testing.T) {
|
||||
errs := make(chan error, 10)
|
||||
for range 10 {
|
||||
go func() {
|
||||
errs <- cache.Save()
|
||||
}()
|
||||
}
|
||||
|
||||
for range 10 {
|
||||
require.NoError(t, <-errs)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCacheSize(t *testing.T) {
|
||||
|
||||
@@ -10,6 +10,18 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
|
||||
)
|
||||
|
||||
// ValidationMode records the syntax policy used to validate cached metadata.
|
||||
type ValidationMode uint8
|
||||
|
||||
const (
|
||||
// ValidationUnknown indicates that parser validation has not been recorded.
|
||||
ValidationUnknown ValidationMode = iota
|
||||
// ValidationLax indicates successful validation with unknown fields allowed.
|
||||
ValidationLax
|
||||
// ValidationStrict indicates successful strict syntax validation.
|
||||
ValidationStrict
|
||||
)
|
||||
|
||||
// Metadata contains lightweight metadata extracted from a template.
|
||||
type Metadata struct {
|
||||
// ID is the unique identifier of the template.
|
||||
@@ -42,6 +54,17 @@ type Metadata struct {
|
||||
// TemplateVerifier is the verifier used for the template.
|
||||
TemplateVerifier string `gob:"verifier,omitempty"`
|
||||
|
||||
// VerifierFingerprint identifies the public key that verified the template.
|
||||
VerifierFingerprint [32]byte `gob:"verifier_fingerprint,omitempty"`
|
||||
|
||||
// ContentDigest binds the cached verification result to the content that
|
||||
// was verified.
|
||||
ContentDigest [32]byte `gob:"content_digest,omitempty"`
|
||||
|
||||
// Validation records how the built-in parser validated this metadata's
|
||||
// source before it was cached.
|
||||
Validation ValidationMode `gob:"validation,omitempty"`
|
||||
|
||||
// NOTE(dwisiswant0): Consider adding more fields here in the future to
|
||||
// enhance filtering caps w/o loading full templates, such as:
|
||||
// `has_{code,headless,file}` to indicate presence of protocol-based
|
||||
@@ -54,6 +77,31 @@ type Metadata struct {
|
||||
// needed.
|
||||
}
|
||||
|
||||
func (m *Metadata) clone() *Metadata {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cloned := *m
|
||||
cloned.Authors = slices.Clone(m.Authors)
|
||||
cloned.Tags = slices.Clone(m.Tags)
|
||||
|
||||
return &cloned
|
||||
}
|
||||
|
||||
// IsValidatedFor reports whether the cached validation satisfies the requested
|
||||
// syntax policy.
|
||||
func (m *Metadata) IsValidatedFor(strictSyntax bool) bool {
|
||||
switch m.Validation {
|
||||
case ValidationLax:
|
||||
return !strictSyntax
|
||||
case ValidationStrict:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// NewMetadataFromTemplate creates a new metadata object from a template.
|
||||
func NewMetadataFromTemplate(path string, tpl *templates.Template) *Metadata {
|
||||
return &Metadata{
|
||||
@@ -67,8 +115,10 @@ func NewMetadataFromTemplate(path string, tpl *templates.Template) *Metadata {
|
||||
|
||||
ProtocolType: tpl.Type().String(),
|
||||
|
||||
Verified: tpl.Verified,
|
||||
TemplateVerifier: tpl.TemplateVerifier,
|
||||
Verified: tpl.Verified,
|
||||
TemplateVerifier: tpl.TemplateVerifier,
|
||||
VerifierFingerprint: tpl.VerifierFingerprint(),
|
||||
ContentDigest: tpl.ContentDigest(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+167
-186
@@ -72,6 +72,10 @@ type Config struct {
|
||||
Catalog catalog.Catalog
|
||||
ExecutorOptions *protocols.ExecutorOptions
|
||||
Logger *gologger.Logger
|
||||
|
||||
// MetadataIndex is an optional shared index borrowed by the store. The
|
||||
// caller remains responsible for persisting it.
|
||||
MetadataIndex *index.Index
|
||||
}
|
||||
|
||||
// Store is a storage for loaded nuclei templates
|
||||
@@ -170,14 +174,21 @@ func New(cfg *Config) (*Store, error) {
|
||||
return nil
|
||||
})
|
||||
|
||||
// Initialize metadata index and filter (load from disk & cache for reuse)
|
||||
store.metadataIndex = store.loadTemplatesIndex()
|
||||
// Initialize metadata index and filter (load from disk & cache for reuse).
|
||||
ownsMetadataIndex := cfg.MetadataIndex == nil
|
||||
store.metadataIndex = cfg.MetadataIndex
|
||||
if store.metadataIndex == nil {
|
||||
store.metadataIndex = store.loadTemplatesIndex()
|
||||
}
|
||||
store.indexFilter = store.buildIndexFilter()
|
||||
if cfg.ExecutorOptions != nil {
|
||||
cfg.ExecutorOptions.TemplateVerificationCallback = store.getTemplateVerification
|
||||
metadataIndex := store.metadataIndex
|
||||
cfg.ExecutorOptions.TemplateVerificationCallback = func(templatePath string) *protocols.TemplateVerification {
|
||||
return getTemplateVerification(metadataIndex, templatePath)
|
||||
}
|
||||
}
|
||||
store.saveMetadataIndexOnce = sync.OnceFunc(func() {
|
||||
if store.metadataIndex == nil {
|
||||
if store.metadataIndex == nil || !ownsMetadataIndex {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -250,19 +261,21 @@ func New(cfg *Config) (*Store, error) {
|
||||
return store, nil
|
||||
}
|
||||
|
||||
func (store *Store) getTemplateVerification(templatePath string) *protocols.TemplateVerification {
|
||||
if store.metadataIndex == nil {
|
||||
func getTemplateVerification(metadataIndex *index.Index, templatePath string) *protocols.TemplateVerification {
|
||||
if metadataIndex == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
metadata, found := store.metadataIndex.Get(templatePath)
|
||||
metadata, found := metadataIndex.Get(templatePath)
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &protocols.TemplateVerification{
|
||||
Verified: metadata.Verified,
|
||||
Verifier: metadata.TemplateVerifier,
|
||||
Verified: metadata.Verified,
|
||||
Verifier: metadata.TemplateVerifier,
|
||||
VerifierFingerprint: metadata.VerifierFingerprint,
|
||||
ContentDigest: metadata.ContentDigest,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,6 +403,31 @@ func (store *Store) loadTemplatesIndex() *index.Index {
|
||||
return metadataIdx
|
||||
}
|
||||
|
||||
func (store *Store) cacheValidatedMetadata(templatePath string, template *templates.Template) *index.Metadata {
|
||||
metadata := index.NewMetadataFromTemplate(templatePath, template)
|
||||
if parser, ok := store.config.ExecutorOptions.Parser.(*templates.Parser); ok {
|
||||
metadata.Validation = index.ValidationStrict
|
||||
if parser.NoStrictSyntax {
|
||||
metadata.Validation = index.ValidationLax
|
||||
}
|
||||
}
|
||||
|
||||
info, err := os.Stat(templatePath)
|
||||
if err != nil {
|
||||
return metadata
|
||||
}
|
||||
metadata.ModTime = info.ModTime()
|
||||
|
||||
store.metadataIndex.Set(templatePath, metadata)
|
||||
return metadata
|
||||
}
|
||||
|
||||
func (store *Store) metadataValidForParser(metadata *index.Metadata) bool {
|
||||
parser, ok := store.config.ExecutorOptions.Parser.(*templates.Parser)
|
||||
|
||||
return ok && metadata.IsValidatedFor(!parser.NoStrictSyntax)
|
||||
}
|
||||
|
||||
// LoadTemplateTags loads template tags count using metadata index when possible.
|
||||
//
|
||||
// This method is optimized for tag listing (`-tgl`) and avoids loading all
|
||||
@@ -419,7 +457,7 @@ func (store *Store) LoadTemplateTags() (map[string]int, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !requiresTemplateParse {
|
||||
if !requiresTemplateParse && store.metadataValidForParser(metadata) {
|
||||
for _, tag := range metadata.Tags {
|
||||
tagsMap[tag]++
|
||||
}
|
||||
@@ -431,7 +469,7 @@ func (store *Store) LoadTemplateTags() (map[string]int, error) {
|
||||
loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), templates.ErrExcluded.Error()) {
|
||||
stats.Increment(templates.TemplatesExcludedStats)
|
||||
stats.Increment(templates.ExcludedWeakMatcherTemplateStats)
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error())
|
||||
}
|
||||
@@ -453,7 +491,7 @@ func (store *Store) LoadTemplateTags() (map[string]int, error) {
|
||||
|
||||
var metadata *index.Metadata
|
||||
if store.metadataIndex != nil {
|
||||
metadata, _ = store.metadataIndex.SetFromTemplate(templatePath, template)
|
||||
metadata = store.cacheValidatedMetadata(templatePath, template)
|
||||
} else {
|
||||
metadata = index.NewMetadataFromTemplate(templatePath, template)
|
||||
}
|
||||
@@ -481,39 +519,46 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
|
||||
validPaths := make(map[string]struct{})
|
||||
|
||||
for _, templatePath := range templatePaths {
|
||||
var cachedMetadata *index.Metadata
|
||||
|
||||
if store.metadataIndex != nil {
|
||||
if metadata, found := store.metadataIndex.Get(templatePath); found {
|
||||
if !indexFilter.Matches(metadata) {
|
||||
continue
|
||||
}
|
||||
|
||||
if store.tagFilter != nil {
|
||||
loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog)
|
||||
if !loaded {
|
||||
if err != nil && strings.Contains(err.Error(), templates.ErrExcluded.Error()) {
|
||||
stats.Increment(templates.TemplatesExcludedStats)
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error())
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
validPaths[templatePath] = struct{}{}
|
||||
continue
|
||||
cachedMetadata = metadata
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata-only loading still applies the configured tag filter.
|
||||
loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog)
|
||||
if loaded {
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), templates.ErrExcluded.Error()) {
|
||||
stats.Increment(templates.ExcludedWeakMatcherTemplateStats)
|
||||
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error())
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
store.logger.Warning().Msg(err.Error())
|
||||
}
|
||||
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
|
||||
if cachedMetadata == nil {
|
||||
templatesCache := store.parserCacheOnce()
|
||||
if templatesCache != nil {
|
||||
if template, _, _ := templatesCache.Has(templatePath); template != nil {
|
||||
var metadata *index.Metadata
|
||||
|
||||
if store.metadataIndex != nil {
|
||||
metadata, _ = store.metadataIndex.SetFromTemplate(templatePath, template)
|
||||
metadata = store.cacheValidatedMetadata(templatePath, template)
|
||||
} else {
|
||||
metadata = index.NewMetadataFromTemplate(templatePath, template)
|
||||
}
|
||||
@@ -521,26 +566,11 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
|
||||
if !indexFilter.Matches(metadata) {
|
||||
continue
|
||||
}
|
||||
|
||||
validPaths[templatePath] = struct{}{}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
validPaths[templatePath] = struct{}{}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), templates.ErrExcluded.Error()) {
|
||||
stats.Increment(templates.TemplatesExcludedStats)
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
store.logger.Warning().Msg(err.Error())
|
||||
}
|
||||
validPaths[templatePath] = struct{}{}
|
||||
}
|
||||
|
||||
templatesCache := store.parserCacheOnce()
|
||||
@@ -549,13 +579,7 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
|
||||
}
|
||||
|
||||
loadedTemplateIDs := mapsutil.NewSyncLockMap[string, struct{}]()
|
||||
caps := templates.Capabilities{
|
||||
Headless: store.config.ExecutorOptions.Options.Headless,
|
||||
Code: store.config.ExecutorOptions.Options.EnableCodeTemplates,
|
||||
DAST: store.config.ExecutorOptions.Options.DAST,
|
||||
SelfContained: store.config.ExecutorOptions.Options.EnableSelfContainedTemplates,
|
||||
File: store.config.ExecutorOptions.Options.EnableFileTemplates,
|
||||
}
|
||||
caps := templates.CapabilitiesFromOptions(store.config.ExecutorOptions.Options)
|
||||
isListOrDisplay := store.config.ExecutorOptions.Options.TemplateList ||
|
||||
store.config.ExecutorOptions.Options.TemplateDisplay
|
||||
|
||||
@@ -565,8 +589,11 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
|
||||
continue
|
||||
}
|
||||
|
||||
if !isListOrDisplay && !template.IsEnabledFor(caps) {
|
||||
continue
|
||||
if !isListOrDisplay {
|
||||
if missingCaps := template.MissingLoadCapabilities(caps); len(missingCaps) > 0 {
|
||||
store.noteMissingCapabilities(templatePath, missingCaps)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if loadedTemplateIDs.Has(template.ID) {
|
||||
@@ -582,6 +609,15 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) noteMissingCapabilities(templatePath string, missingCaps []templates.Capability) {
|
||||
for _, capability := range missingCaps {
|
||||
stats.Increment(capability.Stat())
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Warning().Msg(capability.MissingFlagMessage(templatePath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateTemplates takes a list of templates and validates them
|
||||
// erroring out on discovering any faulty templates.
|
||||
func (store *Store) ValidateTemplates() error {
|
||||
@@ -622,7 +658,6 @@ func (store *Store) areTemplatesValid(filteredTemplatePaths map[string]struct{})
|
||||
|
||||
func (store *Store) areWorkflowOrTemplatesValid(filteredTemplatePaths map[string]struct{}, isWorkflow bool, load func(templatePath string, tagFilter *templates.TagFilter) (bool, error)) bool {
|
||||
areTemplatesValid := true
|
||||
parsedCache := store.parserCacheOnce()
|
||||
|
||||
for templatePath := range filteredTemplatePaths {
|
||||
if _, err := load(templatePath, store.tagFilter); err != nil {
|
||||
@@ -632,22 +667,14 @@ func (store *Store) areWorkflowOrTemplatesValid(filteredTemplatePaths map[string
|
||||
}
|
||||
}
|
||||
|
||||
var template *templates.Template
|
||||
var err error
|
||||
|
||||
if parsedCache != nil {
|
||||
if cachedTemplate, _, cacheErr := parsedCache.Has(templatePath); cacheErr == nil && cachedTemplate != nil {
|
||||
template = cachedTemplate
|
||||
}
|
||||
}
|
||||
|
||||
if template == nil {
|
||||
template, err = templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions)
|
||||
if err != nil {
|
||||
if isParsingError(store, "Error occurred parsing template %s: %s\n", templatePath, err) {
|
||||
areTemplatesValid = false
|
||||
continue
|
||||
}
|
||||
// The load step validates the parsed definition and filters templates.
|
||||
// FYI parse must still run because protocol compilation can surface
|
||||
// additional validation errors.
|
||||
template, err := templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions)
|
||||
if err != nil {
|
||||
if isParsingError(store, "Error occurred parsing template %s: %s\n", templatePath, err) {
|
||||
areTemplatesValid = false
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -727,75 +754,28 @@ func (store *Store) LoadTemplates(templatesList []string) ([]*templates.Template
|
||||
return store.LoadTemplatesWithTags(templatesList, nil)
|
||||
}
|
||||
|
||||
// LoadWorkflows takes a list of workflows and returns paths for them.
|
||||
//
|
||||
// Workflows are loaded concurrently using TemplateLoadingConcurrency (mirrors
|
||||
// the concurrency model of LoadTemplatesWithTags). Order is preserved by
|
||||
// pre-allocating the result slice and writing into the original index, so
|
||||
// downstream consumers see the same order as the input list.
|
||||
// LoadWorkflows takes a list of workflows and returns paths for them
|
||||
func (store *Store) LoadWorkflows(workflowsList []string) []*templates.Template {
|
||||
includedWorkflows, errs := store.config.Catalog.GetTemplatesPath(workflowsList)
|
||||
store.logErroredTemplates(errs)
|
||||
|
||||
if len(includedWorkflows) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
concurrency := store.config.ExecutorOptions.Options.TemplateLoadingConcurrency
|
||||
if concurrency <= 0 {
|
||||
concurrency = types.DefaultTemplateLoadingConcurrency
|
||||
}
|
||||
wg, errWg := syncutil.New(syncutil.WithSize(concurrency))
|
||||
if errWg != nil {
|
||||
// Fall back to sequential on the rare error path.
|
||||
loadedWorkflows := make([]*templates.Template, 0, len(includedWorkflows))
|
||||
for _, workflowPath := range includedWorkflows {
|
||||
loaded, err := store.config.ExecutorOptions.Parser.LoadWorkflow(workflowPath, store.config.Catalog)
|
||||
if err != nil {
|
||||
store.logger.Warning().Msgf("Could not load workflow %s: %s\n", workflowPath, err)
|
||||
}
|
||||
if loaded {
|
||||
parsed, err := templates.Parse(workflowPath, store.preprocessor, store.config.ExecutorOptions)
|
||||
if err != nil {
|
||||
store.logger.Warning().Msgf("Could not parse workflow %s: %s\n", workflowPath, err)
|
||||
} else if parsed != nil {
|
||||
loadedWorkflows = append(loadedWorkflows, parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
return loadedWorkflows
|
||||
}
|
||||
|
||||
parsedSlots := make([]*templates.Template, len(includedWorkflows))
|
||||
for i, workflowPath := range includedWorkflows {
|
||||
wg.Add()
|
||||
go func(idx int, path string) {
|
||||
defer wg.Done()
|
||||
loaded, err := store.config.ExecutorOptions.Parser.LoadWorkflow(path, store.config.Catalog)
|
||||
if err != nil {
|
||||
store.logger.Warning().Msgf("Could not load workflow %s: %s\n", path, err)
|
||||
}
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
parsed, err := templates.Parse(path, store.preprocessor, store.config.ExecutorOptions)
|
||||
if err != nil {
|
||||
store.logger.Warning().Msgf("Could not parse workflow %s: %s\n", path, err)
|
||||
return
|
||||
}
|
||||
if parsed != nil {
|
||||
parsedSlots[idx] = parsed
|
||||
}
|
||||
}(i, workflowPath)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
loadedWorkflows := make([]*templates.Template, 0, len(includedWorkflows))
|
||||
for _, p := range parsedSlots {
|
||||
if p != nil {
|
||||
loadedWorkflows = append(loadedWorkflows, p)
|
||||
for _, workflowPath := range includedWorkflows {
|
||||
loaded, err := store.config.ExecutorOptions.Parser.LoadWorkflow(workflowPath, store.config.Catalog)
|
||||
if err != nil {
|
||||
store.logger.Warning().Msgf("Could not load workflow %s: %s\n", workflowPath, err)
|
||||
}
|
||||
|
||||
if loaded {
|
||||
parsed, err := templates.Parse(workflowPath, store.preprocessor, store.config.ExecutorOptions)
|
||||
if err != nil {
|
||||
store.logger.Warning().Msgf("Could not parse workflow %s: %s\n", workflowPath, err)
|
||||
} else if parsed != nil {
|
||||
loadedWorkflows = append(loadedWorkflows, parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loadedWorkflows
|
||||
}
|
||||
|
||||
@@ -806,6 +786,7 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
|
||||
defer store.saveMetadataIndexOnce()
|
||||
|
||||
indexFilter := store.indexFilter
|
||||
requiresTemplateParse := len(store.config.IncludeConditions) > 0
|
||||
|
||||
includedTemplates, errs := store.config.Catalog.GetTemplatesPath(templatesList)
|
||||
store.logErroredTemplates(errs)
|
||||
@@ -848,13 +829,15 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
|
||||
return
|
||||
}
|
||||
|
||||
stats.Increment(templates.TemplatesExcludedStats)
|
||||
stats.Increment(templates.ExcludedWeakMatcherTemplateStats)
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Print().Msgf("[%v] %v excluded from default run using .nuclei-ignore\n", aurora.Yellow("WRN").String(), templatePath)
|
||||
store.logger.Warning().Msgf("%v excluded from default run using .nuclei-ignore", templatePath)
|
||||
}
|
||||
}
|
||||
|
||||
typesOpts := store.config.ExecutorOptions.Options
|
||||
caps := templates.CapabilitiesFromOptions(typesOpts)
|
||||
|
||||
concurrency := typesOpts.TemplateLoadingConcurrency
|
||||
if concurrency <= 0 {
|
||||
concurrency = types.DefaultTemplateLoadingConcurrency
|
||||
@@ -880,8 +863,8 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
|
||||
defer wgLoadTemplates.Done()
|
||||
|
||||
var (
|
||||
metadata *index.Metadata
|
||||
metadataCached bool
|
||||
metadata *index.Metadata
|
||||
metadataReusable bool
|
||||
)
|
||||
|
||||
if store.metadataIndex != nil {
|
||||
@@ -891,21 +874,33 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
|
||||
noteExcludedByTag(templatePath, metadata)
|
||||
return
|
||||
}
|
||||
// NOTE(dwisiswant0): else, tagFilter probably exists (for
|
||||
// IncludeConditions), which still need to check via
|
||||
// LoadTemplate.
|
||||
|
||||
metadataCached = true
|
||||
if len(tags) > 0 && !slices.ContainsFunc(tags, metadata.HasTag) {
|
||||
return
|
||||
}
|
||||
|
||||
metadataReusable = store.metadataValidForParser(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, tags, store.config.Catalog)
|
||||
var err error
|
||||
|
||||
loaded := metadataReusable && !requiresTemplateParse
|
||||
if !loaded {
|
||||
loaded, err = store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, tags, store.config.Catalog)
|
||||
}
|
||||
|
||||
if loaded {
|
||||
parsed, err := templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions)
|
||||
|
||||
if parsed != nil && !metadataCached {
|
||||
verificationChanged := parsed != nil && (metadata == nil ||
|
||||
metadata.Verified != parsed.Verified ||
|
||||
metadata.TemplateVerifier != parsed.TemplateVerifier ||
|
||||
metadata.VerifierFingerprint != parsed.VerifierFingerprint() ||
|
||||
metadata.ContentDigest != parsed.ContentDigest())
|
||||
if parsed != nil && (!metadataReusable || verificationChanged) {
|
||||
if store.metadataIndex != nil {
|
||||
metadata, _ = store.metadataIndex.SetFromTemplate(templatePath, parsed)
|
||||
metadata = store.cacheValidatedMetadata(templatePath, parsed)
|
||||
} else {
|
||||
metadata = index.NewMetadataFromTemplate(templatePath, parsed)
|
||||
}
|
||||
@@ -919,70 +914,56 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
|
||||
if err != nil {
|
||||
// exclude templates not compatible with offline matching from total runtime warning stats
|
||||
if !errors.Is(err, templates.ErrIncompatibleWithOfflineMatching) {
|
||||
stats.Increment(templates.RuntimeWarningsStats)
|
||||
stats.Increment(templates.TemplateRuntimeWarningStats)
|
||||
}
|
||||
store.logger.Warning().Msgf("Could not parse template %s: %s\n", templatePath, err)
|
||||
} else if parsed != nil {
|
||||
if !parsed.Verified && typesOpts.DisableUnsignedTemplates {
|
||||
// skip unverified templates when prompted to
|
||||
stats.Increment(templates.SkippedUnsignedStats)
|
||||
stats.Increment(templates.SkippedUnverifiedTemplateStats)
|
||||
return
|
||||
}
|
||||
|
||||
if parsed.SelfContained && !typesOpts.EnableSelfContainedTemplates {
|
||||
stats.Increment(templates.ExcludedSelfContainedStats)
|
||||
// code-protocol-based templates run arbitrary commands, so an
|
||||
// unsigned one must be reported as an unverified code template
|
||||
// before the generic missing-capability gate can classify it as
|
||||
// just missing -code.
|
||||
if parsed.HasCodeRequest() && !parsed.Verified && !parsed.HasWorkflows() {
|
||||
stats.Increment(templates.SkippedUnverifiedCodeTemplateStats)
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Warning().Msgf("Unverified code template at %q", templatePath)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if parsed.HasFileRequest() && !typesOpts.EnableFileTemplates {
|
||||
stats.Increment(templates.ExcludedFileStats)
|
||||
// javascript-protocol templates expose Go-backed modules through
|
||||
// the JS runtime, so unsigned ones are rejected before execution.
|
||||
if parsed.IsUnsignedJavascriptTemplate() {
|
||||
stats.Increment(templates.SkippedUnverifiedJavascriptTemplateStats)
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Warning().Msgf("Unverified javascript template at %q", templatePath)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if missingCaps := parsed.MissingLoadCapabilities(caps); len(missingCaps) > 0 {
|
||||
store.noteMissingCapabilities(templatePath, missingCaps)
|
||||
return
|
||||
}
|
||||
|
||||
// if template has request signature like aws then only signed and verified templates are allowed
|
||||
if parsed.UsesRequestSignature() && !parsed.Verified {
|
||||
stats.Increment(templates.SkippedRequestSignatureStats)
|
||||
stats.Increment(templates.SkippedRequestSignatureTemplateStats)
|
||||
return
|
||||
}
|
||||
|
||||
// DAST only templates
|
||||
// Skip DAST filter when loading auth templates
|
||||
if store.ID() != AuthStoreId && typesOpts.DAST {
|
||||
// check if the template is a DAST template
|
||||
// also allow global matchers template to be loaded
|
||||
if parsed.IsFuzzableRequest() || parsed.IsGlobalMatchersTemplate() {
|
||||
if parsed.HasHeadlessRequest() && !typesOpts.Headless {
|
||||
stats.Increment(templates.ExcludedHeadlessTmplStats)
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Print().Msgf("[%v] Headless flag is required for headless template '%s'.\n", aurora.Yellow("WRN").String(), templatePath)
|
||||
}
|
||||
} else {
|
||||
loadTemplate(parsed)
|
||||
}
|
||||
}
|
||||
} else if parsed.HasHeadlessRequest() && !typesOpts.Headless {
|
||||
// donot include headless template in final list if headless flag is not set
|
||||
stats.Increment(templates.ExcludedHeadlessTmplStats)
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Print().Msgf("[%v] Headless flag is required for headless template '%s'.\n", aurora.Yellow("WRN").String(), templatePath)
|
||||
}
|
||||
} else if parsed.HasCodeRequest() && !typesOpts.EnableCodeTemplates {
|
||||
// donot include 'Code' protocol custom template in final list if code flag is not set
|
||||
stats.Increment(templates.ExcludedCodeTmplStats)
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Print().Msgf("[%v] Code flag is required for code protocol template '%s'.\n", aurora.Yellow("WRN").String(), templatePath)
|
||||
}
|
||||
} else if parsed.HasCodeRequest() && !parsed.Verified && !parsed.HasWorkflows() {
|
||||
// donot include unverified 'Code' protocol custom template in final list
|
||||
stats.Increment(templates.SkippedCodeTmplTamperedStats)
|
||||
// these will be skipped so increment skip counter
|
||||
stats.Increment(templates.SkippedUnsignedStats)
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Print().Msgf("[%v] Tampered/Unsigned template at %v.\n", aurora.Yellow("WRN").String(), templatePath)
|
||||
}
|
||||
} else if parsed.IsFuzzableRequest() && !typesOpts.DAST {
|
||||
stats.Increment(templates.ExcludedDastTmplStats)
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Print().Msgf("[%v] -dast flag is required for DAST template '%s'.\n", aurora.Yellow("WRN").String(), templatePath)
|
||||
loadTemplate(parsed)
|
||||
}
|
||||
} else {
|
||||
loadTemplate(parsed)
|
||||
@@ -991,9 +972,9 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
|
||||
}
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), templates.ErrExcluded.Error()) {
|
||||
stats.Increment(templates.TemplatesExcludedStats)
|
||||
stats.Increment(templates.ExcludedWeakMatcherTemplateStats)
|
||||
if config.DefaultConfig.LogAllEvents {
|
||||
store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error())
|
||||
store.logger.Warning().Msg(err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/gologger/formatter"
|
||||
"github.com/projectdiscovery/gologger/levels"
|
||||
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
|
||||
metadataindex "github.com/projectdiscovery/nuclei/v3/pkg/catalog/index"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -46,6 +60,144 @@ func TestLoadTemplates(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewUsesConfiguredMetadataIndex(t *testing.T) {
|
||||
indexDir := t.TempDir()
|
||||
metadataIndex, err := metadataindex.NewIndex(indexDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
store, err := New(&Config{
|
||||
Catalog: disk.NewCatalog(""),
|
||||
Logger: &gologger.Logger{},
|
||||
MetadataIndex: metadataIndex,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Same(t, metadataIndex, store.metadataIndex)
|
||||
|
||||
metadataIndex.Set("/tmp/shared.yaml", &metadataindex.Metadata{ID: "shared"})
|
||||
store.saveMetadataIndexOnce()
|
||||
_, err = os.Stat(filepath.Join(indexDir, metadataindex.IndexFileName))
|
||||
require.ErrorIs(t, err, os.ErrNotExist, "borrowed index persistence belongs to its owner")
|
||||
}
|
||||
|
||||
func TestCacheValidatedMetadataReturnsReplacement(t *testing.T) {
|
||||
templatePath := filepath.Join(t.TempDir(), "replacement.yaml")
|
||||
require.NoError(t, os.WriteFile(templatePath, []byte("id: replacement"), 0o600))
|
||||
info, err := os.Stat(templatePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
metadataIndex.Set(templatePath, &metadataindex.Metadata{
|
||||
ID: "old",
|
||||
FilePath: templatePath,
|
||||
ModTime: info.ModTime(),
|
||||
})
|
||||
store := &Store{
|
||||
config: &Config{ExecutorOptions: &protocols.ExecutorOptions{Parser: templates.NewParser()}},
|
||||
metadataIndex: metadataIndex,
|
||||
}
|
||||
|
||||
metadata := store.cacheValidatedMetadata(templatePath, &templates.Template{ID: "replacement"})
|
||||
require.Equal(t, "replacement", metadata.ID)
|
||||
cached, found := metadataIndex.Get(templatePath)
|
||||
require.True(t, found)
|
||||
require.Equal(t, "replacement", cached.ID)
|
||||
}
|
||||
|
||||
func TestLoadTemplatesOnlyMetadataLogsCachedTemplateParseErrors(t *testing.T) {
|
||||
templatePath := filepath.Join(t.TempDir(), "invalid.yaml")
|
||||
require.NoError(t, os.WriteFile(templatePath, []byte("id: invalid\ninfo: [\n"), 0o600))
|
||||
info, err := os.Stat(templatePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
metadataIndex.Set(templatePath, &metadataindex.Metadata{
|
||||
ID: "invalid",
|
||||
FilePath: templatePath,
|
||||
ModTime: info.ModTime(),
|
||||
Validation: metadataindex.ValidationStrict,
|
||||
})
|
||||
|
||||
var output bytes.Buffer
|
||||
logger := &gologger.Logger{}
|
||||
logger.SetFormatter(formatter.NewCLI(false))
|
||||
logger.SetWriter(&utils.CaptureWriter{Buffer: &output})
|
||||
logger.SetMaxLevel(levels.LevelDebug)
|
||||
|
||||
options := testutils.DefaultOptions.Copy()
|
||||
options.Logger = logger
|
||||
options.Templates = []string{templatePath}
|
||||
catalog := disk.NewCatalog("")
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, nil)
|
||||
executerOpts.Catalog = catalog
|
||||
executerOpts.Parser = templates.NewParser()
|
||||
executerOpts.Logger = logger
|
||||
|
||||
loaderConfig := NewConfig(options, catalog, executerOpts)
|
||||
loaderConfig.MetadataIndex = metadataIndex
|
||||
store, err := New(loaderConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
output.Reset()
|
||||
require.NoError(t, store.LoadTemplatesOnlyMetadata())
|
||||
require.Contains(t, output.String(), "Could not load template")
|
||||
}
|
||||
|
||||
func TestValidateTemplatesRejectsRuntimeCompilationError(t *testing.T) {
|
||||
templatePath := filepath.Join(t.TempDir(), "invalid-regex-group.yaml")
|
||||
require.NoError(t, os.WriteFile(templatePath, []byte(`id: invalid-regex-group
|
||||
|
||||
info:
|
||||
name: Invalid Regex Group
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
http:
|
||||
- method: GET
|
||||
path:
|
||||
- "{{BaseURL}}"
|
||||
extractors:
|
||||
- type: regex
|
||||
group: -1
|
||||
regex:
|
||||
- "(a)(b)"
|
||||
`), 0o600))
|
||||
|
||||
options := testutils.DefaultOptions.Copy()
|
||||
var output bytes.Buffer
|
||||
logger := &gologger.Logger{}
|
||||
logger.SetFormatter(formatter.NewCLI(false))
|
||||
logger.SetWriter(&utils.CaptureWriter{Buffer: &output})
|
||||
logger.SetMaxLevel(levels.LevelDebug)
|
||||
options.Logger = logger
|
||||
options.ExecutionId = "loader-invalid-regex-group"
|
||||
options.Templates = []string{templatePath}
|
||||
options.Validate = true
|
||||
options.TemplateLoadingConcurrency = 1
|
||||
testutils.Init(options)
|
||||
t.Cleanup(func() {
|
||||
testutils.Cleanup(options)
|
||||
})
|
||||
|
||||
catalog := disk.NewCatalog("")
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, nil)
|
||||
executerOpts.Catalog = catalog
|
||||
parser := templates.NewParser()
|
||||
parser.ShouldValidate = true
|
||||
executerOpts.Parser = parser
|
||||
executerOpts.Logger = options.Logger
|
||||
|
||||
workflowLoader, err := workflow.NewLoader(executerOpts)
|
||||
require.NoError(t, err)
|
||||
executerOpts.WorkflowLoader = workflowLoader
|
||||
|
||||
store, err := New(NewConfig(options, catalog, executerOpts))
|
||||
require.NoError(t, err)
|
||||
require.Error(t, store.ValidateTemplates())
|
||||
require.Contains(t, output.String(), "regex extractor group must be >= 0")
|
||||
}
|
||||
|
||||
func TestRemoteTemplates(t *testing.T) {
|
||||
catalog := disk.NewCatalog("")
|
||||
|
||||
@@ -101,3 +253,429 @@ func TestRemoteTemplates(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadTemplatesRecordsUnsignedCodeTemplateOnlyAsCodeSkip(t *testing.T) {
|
||||
templatePath := filepath.Join(t.TempDir(), "unsigned-code.yaml")
|
||||
err := os.WriteFile(templatePath, []byte(`id: unsigned-code-template
|
||||
|
||||
info:
|
||||
name: Unsigned Code Template
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
code:
|
||||
- engine:
|
||||
- sh
|
||||
source: |
|
||||
echo unsigned-code-template
|
||||
`), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
options := testutils.DefaultOptions.Copy()
|
||||
options.Logger = &gologger.Logger{}
|
||||
options.ExecutionId = "loader-unsigned-code-template"
|
||||
options.EnableCodeTemplates = false
|
||||
options.DisableUnsignedTemplates = false
|
||||
options.TemplateLoadingConcurrency = 1
|
||||
testutils.Init(options)
|
||||
t.Cleanup(func() {
|
||||
testutils.Cleanup(options)
|
||||
})
|
||||
|
||||
catalog := disk.NewCatalog("")
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, nil)
|
||||
executerOpts.Catalog = catalog
|
||||
executerOpts.Parser = templates.NewParser()
|
||||
executerOpts.Logger = options.Logger
|
||||
|
||||
workflowLoader, err := workflow.NewLoader(executerOpts)
|
||||
require.NoError(t, err)
|
||||
executerOpts.WorkflowLoader = workflowLoader
|
||||
|
||||
store, err := New(NewConfig(options, catalog, executerOpts))
|
||||
require.NoError(t, err)
|
||||
|
||||
initialUnverifiedCode := stats.GetValue(templates.SkippedUnverifiedCodeTemplateStats)
|
||||
initialUnverified := stats.GetValue(templates.SkippedUnverifiedTemplateStats)
|
||||
|
||||
loaded, err := store.LoadTemplates([]string{templatePath})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, loaded)
|
||||
require.Equal(t, initialUnverifiedCode+1, stats.GetValue(templates.SkippedUnverifiedCodeTemplateStats))
|
||||
require.Equal(t, initialUnverified, stats.GetValue(templates.SkippedUnverifiedTemplateStats))
|
||||
}
|
||||
|
||||
func TestLoadTemplatesSkipsMetadataParseForCachedIndexEntry(t *testing.T) {
|
||||
templatePath := filepath.Join(t.TempDir(), "cached-index.yaml")
|
||||
err := os.WriteFile(templatePath, []byte(`id: cached-index-template
|
||||
|
||||
info:
|
||||
name: Cached Index Template
|
||||
author: pdteam
|
||||
severity: info
|
||||
tags: cached
|
||||
|
||||
http:
|
||||
- method: GET
|
||||
path:
|
||||
- "{{BaseURL}}"
|
||||
`), 0o600)
|
||||
require.NoError(t, err)
|
||||
fileInfo, err := os.Stat(templatePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
metadataIndex.Set(templatePath, &metadataindex.Metadata{
|
||||
ID: "cached-index-template",
|
||||
FilePath: templatePath,
|
||||
ModTime: fileInfo.ModTime(),
|
||||
Name: "Cached Index Template",
|
||||
Authors: []string{"pdteam"},
|
||||
Tags: []string{"cached"},
|
||||
Severity: "info",
|
||||
ProtocolType: "http",
|
||||
Validation: metadataindex.ValidationStrict,
|
||||
})
|
||||
|
||||
options := testutils.DefaultOptions.Copy()
|
||||
options.Logger = &gologger.Logger{}
|
||||
options.ExecutionId = "loader-cached-index-template"
|
||||
options.DisableUnsignedTemplates = false
|
||||
options.TemplateLoadingConcurrency = 1
|
||||
testutils.Init(options)
|
||||
t.Cleanup(func() {
|
||||
testutils.Cleanup(options)
|
||||
})
|
||||
|
||||
catalog := disk.NewCatalog("")
|
||||
parser := templates.NewParser()
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, nil)
|
||||
executerOpts.Catalog = catalog
|
||||
executerOpts.Parser = parser
|
||||
executerOpts.Logger = options.Logger
|
||||
|
||||
workflowLoader, err := workflow.NewLoader(executerOpts)
|
||||
require.NoError(t, err)
|
||||
executerOpts.WorkflowLoader = workflowLoader
|
||||
|
||||
loaderConfig := NewConfig(options, catalog, executerOpts)
|
||||
loaderConfig.MetadataIndex = metadataIndex
|
||||
store, err := New(loaderConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
loaded, err := store.LoadTemplates([]string{templatePath})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, loaded, 1)
|
||||
require.Equal(t, 0, parser.ParsedCount(), "valid index metadata should avoid the filtering parse")
|
||||
require.Equal(t, 1, parser.CompiledCount())
|
||||
|
||||
options.IncludeConditions = []string{`id == "does-not-match"`}
|
||||
loaderConfig = NewConfig(options, catalog, executerOpts)
|
||||
loaderConfig.MetadataIndex = metadataIndex
|
||||
store, err = New(loaderConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
loaded, err = store.LoadTemplates([]string{templatePath})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, loaded)
|
||||
require.Equal(t, 1, parser.ParsedCount(), "advanced conditions still require the parsed template")
|
||||
}
|
||||
|
||||
func TestLoadTemplatesRevalidatesLaxMetadataInStrictMode(t *testing.T) {
|
||||
templatePath := filepath.Join(t.TempDir(), "lax-metadata.yaml")
|
||||
err := os.WriteFile(templatePath, []byte(`id: lax-metadata-template
|
||||
|
||||
info:
|
||||
name: Lax Metadata Template
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
unknown-field: accepted-only-in-lax-mode
|
||||
|
||||
http:
|
||||
- method: GET
|
||||
path:
|
||||
- "{{BaseURL}}"
|
||||
`), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
catalog := disk.NewCatalog("")
|
||||
|
||||
newStore := func(executionID string, noStrictSyntax bool) *Store {
|
||||
options := testutils.DefaultOptions.Copy()
|
||||
options.Logger = &gologger.Logger{}
|
||||
options.ExecutionId = executionID
|
||||
options.DisableUnsignedTemplates = false
|
||||
options.TemplateLoadingConcurrency = 1
|
||||
options.NoStrictSyntax = noStrictSyntax
|
||||
testutils.Init(options)
|
||||
t.Cleanup(func() {
|
||||
testutils.Cleanup(options)
|
||||
})
|
||||
|
||||
parser := templates.NewParser()
|
||||
parser.NoStrictSyntax = noStrictSyntax
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, nil)
|
||||
executerOpts.Catalog = catalog
|
||||
executerOpts.Parser = parser
|
||||
executerOpts.Logger = options.Logger
|
||||
|
||||
workflowLoader, err := workflow.NewLoader(executerOpts)
|
||||
require.NoError(t, err)
|
||||
executerOpts.WorkflowLoader = workflowLoader
|
||||
|
||||
loaderConfig := NewConfig(options, catalog, executerOpts)
|
||||
loaderConfig.MetadataIndex = metadataIndex
|
||||
store, err := New(loaderConfig)
|
||||
require.NoError(t, err)
|
||||
return store
|
||||
}
|
||||
|
||||
laxStore := newStore("loader-lax-metadata", true)
|
||||
loaded, err := laxStore.LoadTemplates([]string{templatePath})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, loaded, 1)
|
||||
require.True(t, metadataIndex.Has(templatePath))
|
||||
|
||||
strictStore := newStore("loader-strict-metadata", false)
|
||||
loaded, err = strictStore.LoadTemplates([]string{templatePath})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, loaded, "strict loading must not trust metadata validated only in lax mode")
|
||||
}
|
||||
|
||||
func loadSingleTemplateForTest(t *testing.T, templatePath, executionID string) []*templates.Template {
|
||||
t.Helper()
|
||||
|
||||
options := testutils.DefaultOptions.Copy()
|
||||
options.Logger = &gologger.Logger{}
|
||||
options.ExecutionId = executionID
|
||||
options.DisableUnsignedTemplates = false
|
||||
options.TemplateLoadingConcurrency = 1
|
||||
testutils.Init(options)
|
||||
t.Cleanup(func() {
|
||||
testutils.Cleanup(options)
|
||||
})
|
||||
|
||||
catalog := disk.NewCatalog("")
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, nil)
|
||||
executerOpts.Catalog = catalog
|
||||
executerOpts.Parser = templates.NewParser()
|
||||
executerOpts.Logger = options.Logger
|
||||
|
||||
workflowLoader, err := workflow.NewLoader(executerOpts)
|
||||
require.NoError(t, err)
|
||||
executerOpts.WorkflowLoader = workflowLoader
|
||||
|
||||
store, err := New(NewConfig(options, catalog, executerOpts))
|
||||
require.NoError(t, err)
|
||||
|
||||
loaded, err := store.LoadTemplates([]string{templatePath})
|
||||
require.NoError(t, err)
|
||||
return loaded
|
||||
}
|
||||
|
||||
func TestLoadTemplatesRecordsUnsignedJavascriptTemplateOnlyAsJavascriptSkip(t *testing.T) {
|
||||
templatePath := filepath.Join(t.TempDir(), "unsigned-javascript.yaml")
|
||||
err := os.WriteFile(templatePath, []byte(`id: unsigned-javascript-template
|
||||
|
||||
info:
|
||||
name: Unsigned Javascript Template
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
javascript:
|
||||
- code: |
|
||||
Export("unsigned-javascript-template")
|
||||
`), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
initialUnverifiedJavascript := stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats)
|
||||
initialUnverified := stats.GetValue(templates.SkippedUnverifiedTemplateStats)
|
||||
|
||||
loaded := loadSingleTemplateForTest(t, templatePath, "loader-unsigned-javascript-template")
|
||||
require.Empty(t, loaded)
|
||||
require.Equal(t, initialUnverifiedJavascript+1, stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats))
|
||||
require.Equal(t, initialUnverified, stats.GetValue(templates.SkippedUnverifiedTemplateStats))
|
||||
}
|
||||
|
||||
func TestLoadTemplatesReverifiesCachedJavascriptTemplate(t *testing.T) {
|
||||
templatePath := filepath.Join(t.TempDir(), "cached-javascript.yaml")
|
||||
verifiedModTime := time.Now().Add(-time.Hour)
|
||||
require.NoError(t, os.WriteFile(templatePath, []byte(`id: cached-javascript
|
||||
|
||||
info:
|
||||
name: Cached Javascript
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
javascript:
|
||||
- init: |
|
||||
set("init-status", "executed")
|
||||
code: |
|
||||
Export("cached-javascript")
|
||||
`), 0o600))
|
||||
require.NoError(t, os.Chtimes(templatePath, verifiedModTime, verifiedModTime))
|
||||
fileInfo, err := os.Stat(templatePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
metadataIndex, err := metadataindex.NewIndex(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
metadataIndex.Set(templatePath, &metadataindex.Metadata{
|
||||
ID: "cached-javascript",
|
||||
FilePath: templatePath,
|
||||
ModTime: fileInfo.ModTime(),
|
||||
Name: "Cached Javascript",
|
||||
Authors: []string{"pdteam"},
|
||||
Severity: "info",
|
||||
ProtocolType: "javascript",
|
||||
Verified: true,
|
||||
TemplateVerifier: "projectdiscovery/nuclei-templates",
|
||||
ContentDigest: [32]byte{1},
|
||||
Validation: metadataindex.ValidationStrict,
|
||||
})
|
||||
|
||||
options := testutils.DefaultOptions.Copy()
|
||||
options.Logger = &gologger.Logger{}
|
||||
options.ExecutionId = "loader-cached-javascript"
|
||||
options.DisableUnsignedTemplates = false
|
||||
options.TemplateLoadingConcurrency = 1
|
||||
testutils.Init(options)
|
||||
t.Cleanup(func() {
|
||||
testutils.Cleanup(options)
|
||||
})
|
||||
|
||||
catalog := disk.NewCatalog("")
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, nil)
|
||||
executerOpts.Catalog = catalog
|
||||
executerOpts.Parser = templates.NewParser()
|
||||
executerOpts.Logger = options.Logger
|
||||
|
||||
workflowLoader, err := workflow.NewLoader(executerOpts)
|
||||
require.NoError(t, err)
|
||||
executerOpts.WorkflowLoader = workflowLoader
|
||||
|
||||
loaderConfig := NewConfig(options, catalog, executerOpts)
|
||||
loaderConfig.MetadataIndex = metadataIndex
|
||||
store, err := New(loaderConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
loaded, err := store.LoadTemplates([]string{templatePath})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, loaded, "mtime-only metadata must not authorize javascript execution")
|
||||
|
||||
refreshedMetadata, found := metadataIndex.Get(templatePath)
|
||||
require.True(t, found)
|
||||
require.False(t, refreshedMetadata.Verified)
|
||||
require.NotEqual(t, [32]byte{1}, refreshedMetadata.ContentDigest)
|
||||
}
|
||||
|
||||
func TestLoadTemplatesTreatsMixedTemplateWithJavascriptAsJavascriptSensitive(t *testing.T) {
|
||||
templatePath := filepath.Join(t.TempDir(), "mixed-javascript.yaml")
|
||||
err := os.WriteFile(templatePath, []byte(`id: mixed-javascript-template
|
||||
|
||||
info:
|
||||
name: Mixed Javascript Template
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
http:
|
||||
- method: GET
|
||||
path:
|
||||
- "{{BaseURL}}"
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- mixed-javascript-template
|
||||
|
||||
javascript:
|
||||
- code: |
|
||||
Export("mixed-javascript-template")
|
||||
`), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
initialUnverifiedJavascript := stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats)
|
||||
|
||||
loaded := loadSingleTemplateForTest(t, templatePath, "loader-mixed-javascript-template")
|
||||
require.Empty(t, loaded)
|
||||
require.Equal(t, initialUnverifiedJavascript+1, stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats))
|
||||
}
|
||||
|
||||
func TestLoadTemplatesAllowsUnsignedFlowTemplateWithoutJavascriptProtocol(t *testing.T) {
|
||||
templatePath := filepath.Join(t.TempDir(), "flow-only.yaml")
|
||||
err := os.WriteFile(templatePath, []byte(`id: unsigned-flow-template
|
||||
|
||||
info:
|
||||
name: Unsigned Flow Template
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
flow: http(1)
|
||||
|
||||
http:
|
||||
- method: GET
|
||||
path:
|
||||
- "{{BaseURL}}"
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- unsigned-flow-template
|
||||
`), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
initialUnverifiedJavascript := stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats)
|
||||
|
||||
loaded := loadSingleTemplateForTest(t, templatePath, "loader-unsigned-flow-template")
|
||||
require.Len(t, loaded, 1)
|
||||
require.Equal(t, "unsigned-flow-template", loaded[0].ID)
|
||||
require.Equal(t, initialUnverifiedJavascript, stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats))
|
||||
}
|
||||
|
||||
func TestLoadTemplatesDoesNotRequireGlobalMatchersFlagToLoadTemplate(t *testing.T) {
|
||||
templatePath := filepath.Join(t.TempDir(), "global-matchers.yaml")
|
||||
err := os.WriteFile(templatePath, []byte(`id: global-matchers-template
|
||||
|
||||
info:
|
||||
name: Global Matchers Template
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
http:
|
||||
- global-matchers: true
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- global-matchers-template
|
||||
`), 0o600)
|
||||
require.NoError(t, err)
|
||||
|
||||
options := testutils.DefaultOptions.Copy()
|
||||
options.Logger = &gologger.Logger{}
|
||||
options.ExecutionId = "loader-global-matchers-template"
|
||||
options.EnableGlobalMatchersTemplates = false
|
||||
options.TemplateLoadingConcurrency = 1
|
||||
testutils.Init(options)
|
||||
t.Cleanup(func() {
|
||||
testutils.Cleanup(options)
|
||||
})
|
||||
|
||||
catalog := disk.NewCatalog("")
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, nil)
|
||||
executerOpts.Catalog = catalog
|
||||
executerOpts.Parser = templates.NewParser()
|
||||
executerOpts.Logger = options.Logger
|
||||
|
||||
workflowLoader, err := workflow.NewLoader(executerOpts)
|
||||
require.NoError(t, err)
|
||||
executerOpts.WorkflowLoader = workflowLoader
|
||||
|
||||
store, err := New(NewConfig(options, catalog, executerOpts))
|
||||
require.NoError(t, err)
|
||||
|
||||
loaded, err := store.LoadTemplates([]string{templatePath})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, loaded, 1)
|
||||
require.Equal(t, "global-matchers-template", loaded[0].ID)
|
||||
}
|
||||
@@ -90,14 +90,7 @@ func (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templ
|
||||
value *contextargs.MetaInput
|
||||
}
|
||||
|
||||
// Buffered to workerCount so the producer (target.Iterate) does not block
|
||||
// on every send when workers are momentarily busy. This keeps the input
|
||||
// iterator advancing and reduces tail latency on slow targets.
|
||||
taskBuf := workerCount
|
||||
if taskBuf < 1 {
|
||||
taskBuf = 1
|
||||
}
|
||||
tasks := make(chan task, taskBuf)
|
||||
tasks := make(chan task)
|
||||
var workersWg sync.WaitGroup
|
||||
workersWg.Add(workerCount)
|
||||
for i := 0; i < workerCount; i++ {
|
||||
|
||||
Loaded 100 of 421 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user