mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-01-31 15:53:10 +08:00
Merge pull request #6745 from projectdiscovery/release/v3.6.2
Release v3.6.2
This commit is contained in:
18
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
18
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -36,7 +36,10 @@ body:
|
||||
description: |
|
||||
Steps to reproduce the behavior, for example, commands to run Nuclei.
|
||||
|
||||
📝 For a more detailed output that could help in troubleshooting, you may want to run Nuclei with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood.
|
||||
💡 Prefer copying text output over using screenshots for easier troubleshooting.
|
||||
|
||||
📝 For a more detailed output that could help in troubleshooting, you may want to run Nuclei with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood.<br>
|
||||
📊 For performance or memory investigations, use **`-profile-mem`**, which generates `*.cpu`, `*.mem`, and `*.trace` files. Since GitHub doesn't support these formats directly, compress them (e.g., .zip or .tar.gz) and attach the archive under the "Anything else" section below.
|
||||
|
||||
:warning: **Please redact any literal target hosts/URLs or other sensitive information.**
|
||||
placeholder: |
|
||||
@@ -49,7 +52,10 @@ body:
|
||||
description: |
|
||||
Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
|
||||
📝 For a more detailed output that could help in troubleshooting, you may want to run Nuclei with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood.
|
||||
💡 Prefer copying text output over using screenshots for easier troubleshooting.
|
||||
|
||||
📝 For a more detailed output that could help in troubleshooting, you may want to run Nuclei with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood.<br>
|
||||
📊 For performance or memory investigations, use **`-profile-mem`**, which generates `*.cpu`, `*.mem`, and `*.trace` files. Since GitHub doesn't support these formats directly, compress them (e.g., .zip or .tar.gz) and attach the archive under the "Anything else" section below.
|
||||
|
||||
:warning: **Please redact any literal target hosts/URLs or other sensitive information.**
|
||||
render: shell
|
||||
@@ -58,9 +64,9 @@ body:
|
||||
label: Environment
|
||||
description: |
|
||||
Examples:
|
||||
- **OS**: Ubuntu 20.04
|
||||
- **Nuclei** (`nuclei -version`): v3.3.1
|
||||
- **Go** (`go version`): go1.22.0 _(only if you've installed it via the `go install` command)_
|
||||
- **OS**: Ubuntu 24.04
|
||||
- **Nuclei** (`nuclei -version`): v3.6.0
|
||||
- **Go** (`go version`): go1.24.0 _(only if you've installed it via the `go install` command)_
|
||||
value: |
|
||||
- OS:
|
||||
- Nuclei:
|
||||
@@ -72,7 +78,7 @@ body:
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Links? References? Templates? Anything that will give us more context about the issue you are encountering!
|
||||
Links? References? Templates? CPU, memory, and trace profiles? Anything that will give us more context about the issue you are encountering!
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
|
||||
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,6 +2,9 @@
|
||||
|
||||
<!-- Describe the overall picture of your modifications to help maintainers understand the pull request. PRs are required to be associated to their related issue tickets or feature request. -->
|
||||
|
||||
### Proof
|
||||
|
||||
<!-- How has this been tested? Please describe the tests that you ran to verify your changes. -->
|
||||
|
||||
## Checklist
|
||||
|
||||
|
||||
30
.github/workflows/auto-merge.yaml
vendored
30
.github/workflows/auto-merge.yaml
vendored
@@ -1,28 +1,24 @@
|
||||
name: 🤖 Auto Merge
|
||||
name: 🔀 Auto merge PR
|
||||
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
# pull_request:
|
||||
# types: [opened, synchronize, reopened, ready_for_review]
|
||||
# pull_request_review:
|
||||
# types: [submitted]
|
||||
workflow_run:
|
||||
workflows: ["♾️ Compatibility Check"]
|
||||
types:
|
||||
- completed
|
||||
workflows: ["♾️ Compatibility Checks"]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
repository-projects: write
|
||||
|
||||
jobs:
|
||||
auto-merge:
|
||||
auto-merge-dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: projectdiscovery/actions/pr/approve@v1
|
||||
- uses: projectdiscovery/actions/pr/merge@v1
|
||||
with:
|
||||
token: ${{ secrets.DEPENDABOT_PAT }}
|
||||
|
||||
- uses: ahmadnassri/action-dependabot-auto-merge@v2
|
||||
with:
|
||||
github-token: ${{ secrets.DEPENDABOT_PAT }}
|
||||
target: all
|
||||
auto: "true"
|
||||
5
.github/workflows/compat-checks.yaml
vendored
5
.github/workflows/compat-checks.yaml
vendored
@@ -7,8 +7,8 @@ on:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
check:
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
compat-checks:
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -16,4 +16,5 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: projectdiscovery/actions/setup/go/compat-checks@v1
|
||||
with:
|
||||
go-version: "stable"
|
||||
release-test: true
|
||||
|
||||
61
.github/workflows/flamegraph.yaml
vendored
Normal file
61
.github/workflows/flamegraph.yaml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: 📊 Flamegraph
|
||||
|
||||
on:
|
||||
workflow_call: {}
|
||||
|
||||
jobs:
|
||||
flamegraph:
|
||||
name: "Flamegraph"
|
||||
env:
|
||||
PROFILE_MEM: "/tmp/nuclei-profile"
|
||||
TARGET_URL: "http://honey.scanme.sh/-/?foo=bar"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/actions/cache/go-rod-browser@v1
|
||||
- uses: projectdiscovery/actions/cache/nuclei@v1
|
||||
- run: make build
|
||||
|
||||
- name: "Setup environment (push)"
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: |
|
||||
echo "PROFILE_MEM=${PROFILE_MEM}-${GITHUB_REF_NAME}-${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
echo "FLAMEGRAPH_NAME=nuclei-${GITHUB_REF_NAME} (${GITHUB_SHA})" >> $GITHUB_ENV
|
||||
- name: "Setup environment (pull_request)"
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
echo "PROFILE_MEM=${PROFILE_MEM}-pr-${{ github.event.number }}" >> $GITHUB_ENV
|
||||
echo "FLAMEGRAPH_NAME=nuclei (PR #${{ github.event.number }})" >> $GITHUB_ENV
|
||||
|
||||
- run: ./bin/nuclei -update-templates
|
||||
- run: |
|
||||
./bin/nuclei -silent -target="${TARGET_URL}" \
|
||||
-disable-update-check \
|
||||
-no-mhe -no-httpx \
|
||||
-code -dast -file -headless \
|
||||
-enable-self-contained -enable-global-matchers \
|
||||
-rate-limit=0 \
|
||||
-concurrency=250 \
|
||||
-headless-concurrency=100 \
|
||||
-payload-concurrency=250 \
|
||||
-bulk-size=250 \
|
||||
-headless-bulk-size=100 \
|
||||
-profile-mem="${PROFILE_MEM}"
|
||||
|
||||
- uses: projectdiscovery/actions/flamegraph@v1
|
||||
id: flamegraph-cpu
|
||||
with:
|
||||
profile: "${{ env.PROFILE_MEM }}.cpu"
|
||||
name: "${{ env.FLAMEGRAPH_NAME }} CPU profiles"
|
||||
continue-on-error: true
|
||||
- uses: projectdiscovery/actions/flamegraph@v1
|
||||
id: flamegraph-mem
|
||||
with:
|
||||
profile: "${{ env.PROFILE_MEM }}.mem"
|
||||
name: "${{ env.FLAMEGRAPH_NAME }} memory profiles"
|
||||
continue-on-error: true
|
||||
- if: ${{ steps.flamegraph-mem.outputs.message == '' }}
|
||||
run: |
|
||||
echo "::notice::CPU flamegraph: ${{ steps.flamegraph-cpu.outputs.url }}"
|
||||
echo "::notice::Memory (heap) flamegraph: ${{ steps.flamegraph-mem.outputs.url }}"
|
||||
4
.github/workflows/generate-docs.yaml
vendored
4
.github/workflows/generate-docs.yaml
vendored
@@ -4,11 +4,11 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
publish-docs:
|
||||
if: "${{ !endsWith(github.actor, '[bot]') }}"
|
||||
if: ${{ !endsWith(github.actor, '[bot]') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
66
.github/workflows/generate-pgo.yaml
vendored
66
.github/workflows/generate-pgo.yaml
vendored
@@ -1,56 +1,50 @@
|
||||
name: 👤 Generate PGO
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["dev"]
|
||||
paths:
|
||||
- '**.go'
|
||||
- '**.mod'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# TODO(dwisiswant0): https://go.dev/doc/pgo#merging-profiles
|
||||
workflow_dispatch: {}
|
||||
workflow_call: {}
|
||||
|
||||
jobs:
|
||||
pgo:
|
||||
strategy:
|
||||
matrix:
|
||||
targets: [150]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'projectdiscovery/nuclei'
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
PGO_FILE: "cmd/nuclei/default.pgo"
|
||||
LIST_FILE: "/tmp/targets-${{ matrix.targets }}.txt"
|
||||
PROFILE_MEM: "/tmp/nuclei-profile-${{ matrix.targets }}-targets"
|
||||
TARGET: "https://honey.scanme.sh"
|
||||
TARGET_COUNT: "100"
|
||||
TARGET_LIST: "/tmp/targets.txt"
|
||||
PROFILE_MEM: "/tmp/nuclei-profile"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: projectdiscovery/actions/setup/git@v1
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/actions/cache/go-rod-browser@v1
|
||||
- uses: projectdiscovery/actions/cache/nuclei@v1
|
||||
- name: Generate list
|
||||
run: for i in {1..${{ matrix.targets }}}; do echo "https://honey.scanme.sh/?_=${i}" >> "${LIST_FILE}"; done
|
||||
# NOTE(dwisiswant0): use `-no-mhe` flag to get better samples.
|
||||
- run: go run . -l "${LIST_FILE}" -profile-mem="${PROFILE_MEM}" -no-mhe
|
||||
working-directory: cmd/nuclei/
|
||||
- run: mv "${PROFILE_MEM}.cpu" ${PGO_FILE}
|
||||
# NOTE(dwisiswant0): shall we prune $PGO_FILE git history?
|
||||
# if we prune it, this won't be linear since it requires a force-push.
|
||||
# if we don't, the git objects will just keep growing bigger.
|
||||
#
|
||||
# Ref:
|
||||
# - https://go.dev/blog/pgo#:~:text=We%20recommend%20committing%20default.pgo%20files%20to%20your%20repository
|
||||
# - https://gist.github.com/nottrobin/5758221
|
||||
- uses: projectdiscovery/actions/commit@v1
|
||||
with:
|
||||
files: "${PGO_FILE}"
|
||||
message: "build: update PGO profile :robot:"
|
||||
- run: git push origin $GITHUB_REF
|
||||
- run: |
|
||||
for i in {1..${{ env.TARGET_COUNT }}}; do
|
||||
echo "${{ env.TARGET }}/-/?_=${i}" >> "${{ env.TARGET_LIST }}";
|
||||
done
|
||||
- run: make build
|
||||
- run: ./bin/nuclei -update-templates
|
||||
- run: |
|
||||
./bin/nuclei -silent -list="${TARGET_LIST}" \
|
||||
-disable-update-check \
|
||||
-no-mhe -no-httpx \
|
||||
-code -dast -file -headless \
|
||||
-enable-self-contained -enable-global-matchers \
|
||||
-rate-limit=0 \
|
||||
-concurrency=250 \
|
||||
-headless-concurrency=100 \
|
||||
-payload-concurrency=250 \
|
||||
-bulk-size=250 \
|
||||
-headless-bulk-size=100 \
|
||||
-profile-mem="${PROFILE_MEM}" \
|
||||
env:
|
||||
DISABLE_STDOUT: "1"
|
||||
- run: mv "${PROFILE_MEM}.cpu" "${PGO_FILE}"
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: "pgo"
|
||||
path: "${{ env.PGO_FILE }}"
|
||||
overwrite: true
|
||||
|
||||
2
.github/workflows/govulncheck.yaml
vendored
2
.github/workflows/govulncheck.yaml
vendored
@@ -3,7 +3,7 @@ name: 🐛 govulncheck
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # Weekly
|
||||
workflow_dispatch:
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
govulncheck:
|
||||
|
||||
4
.github/workflows/perf-regression.yaml
vendored
4
.github/workflows/perf-regression.yaml
vendored
@@ -1,8 +1,8 @@
|
||||
name: 🔨 Performance Regression
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
workflow_call: {}
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
perf-regression:
|
||||
|
||||
43
.github/workflows/perf-test.yaml
vendored
43
.github/workflows/perf-test.yaml
vendored
@@ -1,43 +0,0 @@
|
||||
name: 🔨 Performance Test
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # Weekly
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
perf-test:
|
||||
strategy:
|
||||
matrix:
|
||||
count: [50, 100, 150]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'projectdiscovery/nuclei'
|
||||
env:
|
||||
LIST_FILE: "/tmp/targets-${{ matrix.count }}.txt"
|
||||
PROFILE_MEM: "/tmp/nuclei-perf-test-${{ matrix.count }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- run: make verify
|
||||
- name: Generate list
|
||||
run: for i in {1..${{ matrix.count }}}; do echo "https://honey.scanme.sh/?_=${i}" >> "${LIST_FILE}"; done
|
||||
- run: go run . -l "${LIST_FILE}" -profile-mem="${PROFILE_MEM}"
|
||||
env:
|
||||
NUCLEI_ARGS: host-error-stats
|
||||
working-directory: cmd/nuclei/
|
||||
- uses: projectdiscovery/actions/flamegraph@v1
|
||||
id: flamegraph-cpu
|
||||
with:
|
||||
profile: "${{ env.PROFILE_MEM }}.cpu"
|
||||
name: "${{ env.FLAMEGRAPH_NAME }} CPU profiles"
|
||||
continue-on-error: true
|
||||
- uses: projectdiscovery/actions/flamegraph@v1
|
||||
id: flamegraph-mem
|
||||
with:
|
||||
profile: "${{ env.PROFILE_MEM }}.mem"
|
||||
name: "${{ env.FLAMEGRAPH_NAME }} memory profiles"
|
||||
continue-on-error: true
|
||||
- if: ${{ steps.flamegraph-mem.outputs.message == '' }}
|
||||
run: |
|
||||
echo "::notice::CPU flamegraph: ${{ steps.flamegraph-cpu.outputs.url }}"
|
||||
echo "::notice::Memory (heap) flamegraph: ${{ steps.flamegraph-mem.outputs.url }}"
|
||||
18
.github/workflows/release.yaml
vendored
18
.github/workflows/release.yaml
vendored
@@ -4,16 +4,28 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
jobs:
|
||||
pgo:
|
||||
name: "Generate PGO"
|
||||
uses: ./.github/workflows/generate-pgo.yaml
|
||||
|
||||
release:
|
||||
name: "Release"
|
||||
needs: ["pgo"]
|
||||
runs-on: ubuntu-latest-16-cores
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: "pgo"
|
||||
path: "cmd/nuclei/"
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
with:
|
||||
go-version: "stable"
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
|
||||
41
.github/workflows/tests.yaml
vendored
41
.github/workflows/tests.yaml
vendored
@@ -19,7 +19,7 @@ concurrency:
|
||||
jobs:
|
||||
lint:
|
||||
name: "Lint"
|
||||
if: "${{ !endsWith(github.actor, '[bot]') }}"
|
||||
if: ${{ !endsWith(github.actor, '[bot]') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -152,47 +152,16 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
with:
|
||||
go-version: "stable"
|
||||
- uses: projectdiscovery/actions/goreleaser@v1
|
||||
|
||||
flamegraph:
|
||||
name: "Flamegraph"
|
||||
needs: ["tests"]
|
||||
env:
|
||||
PROFILE_MEM: "/tmp/nuclei"
|
||||
TARGET_URL: "http://scanme.sh/a/?b=c"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- run: make build
|
||||
- name: "Setup environment (push)"
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: |
|
||||
echo "PROFILE_MEM=${PROFILE_MEM}-${GITHUB_REF_NAME}-${GITHUB_SHA}" >> $GITHUB_ENV
|
||||
echo "FLAMEGRAPH_NAME=nuclei-${GITHUB_REF_NAME} (${GITHUB_SHA})" >> $GITHUB_ENV
|
||||
- name: "Setup environment (pull_request)"
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
echo "PROFILE_MEM=${PROFILE_MEM}-pr-${{ github.event.number }}" >> $GITHUB_ENV
|
||||
echo "FLAMEGRAPH_NAME=nuclei (PR #${{ github.event.number }})" >> $GITHUB_ENV
|
||||
- run: ./bin/nuclei -silent -update-templates
|
||||
- run: ./bin/nuclei -silent -u "${TARGET_URL}" -profile-mem="${PROFILE_MEM}"
|
||||
- uses: projectdiscovery/actions/flamegraph@v1
|
||||
id: flamegraph-cpu
|
||||
with:
|
||||
profile: "${{ env.PROFILE_MEM }}.cpu"
|
||||
name: "${{ env.FLAMEGRAPH_NAME }} CPU profiles"
|
||||
continue-on-error: true
|
||||
- uses: projectdiscovery/actions/flamegraph@v1
|
||||
id: flamegraph-mem
|
||||
with:
|
||||
profile: "${{ env.PROFILE_MEM }}.mem"
|
||||
name: "${{ env.FLAMEGRAPH_NAME }} memory profiles"
|
||||
continue-on-error: true
|
||||
- if: ${{ steps.flamegraph-mem.outputs.message == '' }}
|
||||
run: |
|
||||
echo "::notice::CPU flamegraph: ${{ steps.flamegraph-cpu.outputs.url }}"
|
||||
echo "::notice::Memory (heap) flamegraph: ${{ steps.flamegraph-mem.outputs.url }}"
|
||||
uses: ./.github/workflows/flamegraph.yaml
|
||||
|
||||
perf-regression:
|
||||
name: "Performance regression"
|
||||
needs: ["tests"]
|
||||
uses: ./.github/workflows/perf-regression.yaml
|
||||
|
||||
@@ -11,8 +11,9 @@ builds:
|
||||
id: nuclei-cli
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos: [windows,linux,darwin]
|
||||
goarch: [amd64,'386',arm,arm64]
|
||||
- GOEXPERIMENT=greenteagc
|
||||
goos: [windows, linux, darwin]
|
||||
goarch: [amd64, '386', arm, arm64]
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: '386'
|
||||
|
||||
@@ -26,6 +26,7 @@ var codeTestCases = []TestCaseInfo{
|
||||
{Path: "protocols/code/pre-condition.yaml", TestCase: &codePreCondition{}, DisableOn: isCodeDisabled},
|
||||
{Path: "protocols/code/sh-virtual.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsLinux() || isCodeDisabled() }},
|
||||
{Path: "protocols/code/py-virtual.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsLinux() || isCodeDisabled() }},
|
||||
{Path: "protocols/code/pwsh-echo.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return isCodeDisabled() }},
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -17,16 +17,21 @@ var jsTestcases = []TestCaseInfo{
|
||||
{Path: "protocols/javascript/net-https.yaml", TestCase: &javascriptNetHttps{}},
|
||||
{Path: "protocols/javascript/oracle-auth-test.yaml", TestCase: &javascriptOracleAuthTest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
|
||||
{Path: "protocols/javascript/vnc-pass-brute.yaml", TestCase: &javascriptVncPassBrute{}},
|
||||
{Path: "protocols/javascript/postgres-pass-brute.yaml", TestCase: &javascriptPostgresPassBrute{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
|
||||
{Path: "protocols/javascript/mysql-connect.yaml", TestCase: &javascriptMySQLConnect{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
|
||||
{Path: "protocols/javascript/multi-ports.yaml", TestCase: &javascriptMultiPortsSSH{}},
|
||||
{Path: "protocols/javascript/no-port-args.yaml", TestCase: &javascriptNoPortArgs{}},
|
||||
}
|
||||
|
||||
var (
|
||||
redisResource *dockertest.Resource
|
||||
sshResource *dockertest.Resource
|
||||
oracleResource *dockertest.Resource
|
||||
vncResource *dockertest.Resource
|
||||
pool *dockertest.Pool
|
||||
defaultRetry = 3
|
||||
redisResource *dockertest.Resource
|
||||
sshResource *dockertest.Resource
|
||||
oracleResource *dockertest.Resource
|
||||
vncResource *dockertest.Resource
|
||||
postgresResource *dockertest.Resource
|
||||
mysqlResource *dockertest.Resource
|
||||
pool *dockertest.Pool
|
||||
defaultRetry = 3
|
||||
)
|
||||
|
||||
type javascriptNetHttps struct{}
|
||||
@@ -168,6 +173,70 @@ func (j *javascriptVncPassBrute) Execute(filePath string) error {
|
||||
return multierr.Combine(errs...)
|
||||
}
|
||||
|
||||
type javascriptPostgresPassBrute struct{}
|
||||
|
||||
func (j *javascriptPostgresPassBrute) Execute(filePath string) error {
|
||||
if postgresResource == nil || pool == nil {
|
||||
// skip test as postgres is not running
|
||||
return nil
|
||||
}
|
||||
tempPort := postgresResource.GetPort("5432/tcp")
|
||||
finalURL := "localhost:" + tempPort
|
||||
defer purge(postgresResource)
|
||||
errs := []error{}
|
||||
for i := 0; i < defaultRetry; i++ {
|
||||
results := []string{}
|
||||
var err error
|
||||
_ = pool.Retry(func() error {
|
||||
//let postgres server start
|
||||
time.Sleep(3 * time.Second)
|
||||
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err == nil {
|
||||
return nil
|
||||
} else {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return multierr.Combine(errs...)
|
||||
}
|
||||
|
||||
type javascriptMySQLConnect struct{}
|
||||
|
||||
func (j *javascriptMySQLConnect) Execute(filePath string) error {
|
||||
if mysqlResource == nil || pool == nil {
|
||||
// skip test as mysql is not running
|
||||
return nil
|
||||
}
|
||||
tempPort := mysqlResource.GetPort("3306/tcp")
|
||||
finalURL := "localhost:" + tempPort
|
||||
defer purge(mysqlResource)
|
||||
errs := []error{}
|
||||
for i := 0; i < defaultRetry; i++ {
|
||||
results := []string{}
|
||||
var err error
|
||||
_ = pool.Retry(func() error {
|
||||
//let mysql server start
|
||||
time.Sleep(5 * time.Second)
|
||||
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err == nil {
|
||||
return nil
|
||||
} else {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return multierr.Combine(errs...)
|
||||
}
|
||||
|
||||
type javascriptMultiPortsSSH struct{}
|
||||
|
||||
func (j *javascriptMultiPortsSSH) Execute(filePath string) error {
|
||||
@@ -179,6 +248,16 @@ func (j *javascriptMultiPortsSSH) Execute(filePath string) error {
|
||||
return expectResultsCount(results, 1)
|
||||
}
|
||||
|
||||
type javascriptNoPortArgs struct{}
|
||||
|
||||
func (j *javascriptNoPortArgs) Execute(filePath string) error {
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "yo.dawg", debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return expectResultsCount(results, 1)
|
||||
}
|
||||
|
||||
// purge any given resource if it is not nil
|
||||
func purge(resource *dockertest.Resource) {
|
||||
if resource != nil && pool != nil {
|
||||
@@ -281,4 +360,41 @@ func init() {
|
||||
if err := vncResource.Expire(30); err != nil {
|
||||
log.Printf("Could not expire resource: %s", err)
|
||||
}
|
||||
|
||||
// setup a temporary postgres instance
|
||||
postgresResource, err = pool.RunWithOptions(&dockertest.RunOptions{
|
||||
Repository: "postgres",
|
||||
Tag: "latest",
|
||||
Env: []string{
|
||||
"POSTGRES_PASSWORD=postgres",
|
||||
"POSTGRES_USER=postgres",
|
||||
},
|
||||
Platform: "linux/amd64",
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Could not start postgres resource: %s", err)
|
||||
return
|
||||
}
|
||||
// by default expire after 30 sec
|
||||
if err := postgresResource.Expire(30); err != nil {
|
||||
log.Printf("Could not expire postgres resource: %s", err)
|
||||
}
|
||||
|
||||
// setup a temporary mysql instance
|
||||
mysqlResource, err = pool.RunWithOptions(&dockertest.RunOptions{
|
||||
Repository: "mysql",
|
||||
Tag: "latest",
|
||||
Env: []string{
|
||||
"MYSQL_ROOT_PASSWORD=secret",
|
||||
},
|
||||
Platform: "linux/amd64",
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Could not start mysql resource: %s", err)
|
||||
return
|
||||
}
|
||||
// by default expire after 30 sec
|
||||
if err := mysqlResource.Expire(30); err != nil {
|
||||
log.Printf("Could not expire mysql resource: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,12 @@
|
||||
# # these severity labels or tags (does not affect exporters. set those globally)
|
||||
# deny-list:
|
||||
# severity: low
|
||||
# # duplicate-issue-check (optional) flag to enable duplicate tracking issue check
|
||||
# duplicate-issue-check: false
|
||||
# # duplicate-issue-page-size (optional) controls how many issues to fetch per page when searching for duplicates
|
||||
# duplicate-issue-page-size: 100
|
||||
# # duplicate-issue-max-pages (optional) limits how many pages to fetch when searching for duplicates (0 = no limit)
|
||||
# duplicate-issue-max-pages: 0
|
||||
#
|
||||
# Gitea contains configuration options for a gitea issue tracker
|
||||
#gitea:
|
||||
@@ -81,6 +87,10 @@
|
||||
# severity: low
|
||||
# # duplicate-issue-check (optional) flag to enable duplicate tracking issue check
|
||||
# duplicate-issue-check: false
|
||||
# # duplicate-issue-page-size (optional) controls how many issues to fetch per page when searching for duplicates
|
||||
# duplicate-issue-page-size: 100
|
||||
# # duplicate-issue-max-pages (optional) limits how many pages to fetch when searching for duplicates (0 = no limit)
|
||||
# duplicate-issue-max-pages: 0
|
||||
#
|
||||
# Jira contains configuration options for Jira issue tracker
|
||||
#jira:
|
||||
@@ -90,6 +100,9 @@
|
||||
# update-existing: false
|
||||
# # URL is the jira application url
|
||||
# url: https://localhost/jira
|
||||
# # site-url is the browsable URL for the Jira instance (optional)
|
||||
# # If not provided, issue.Self will be used. Useful for OAuth where issue.Self contains api.atlassian.com
|
||||
# site-url: https://your-company.atlassian.net
|
||||
# # account-id is the account-id of the Jira user or username in case of on-prem Jira
|
||||
# account-id: test-account-id
|
||||
# # email is the email of the user for Jira instance
|
||||
@@ -118,10 +131,10 @@
|
||||
# status-not: Closed
|
||||
# # Customfield supports name, id and freeform. name and id are to be used when the custom field is a dropdown.
|
||||
# # freeform can be used if the custom field is just a text entry
|
||||
# # Variables can be used to pull various pieces of data from the finding itself.
|
||||
# # Variables can be used to pull various pieces of data from the finding itself.
|
||||
# # Supported variables: $CVSSMetrics, $CVEID, $CWEID, $Host, $Severity, $CVSSScore, $Name
|
||||
# custom-fields:
|
||||
# customfield_00001:
|
||||
# customfield_00001:
|
||||
# name: "Nuclei"
|
||||
# customfield_00002:
|
||||
# freeform: $CVSSMetrics
|
||||
@@ -172,4 +185,4 @@
|
||||
# omit-raw: false
|
||||
# # determines the number of results to be kept in memory before writing it to the database or 0 to
|
||||
# # persist all in memory and write all results at the end (default)
|
||||
# batch-size: 0
|
||||
# batch-size: 0
|
||||
|
||||
42
go.mod
42
go.mod
@@ -22,12 +22,12 @@ require (
|
||||
github.com/olekukonko/tablewriter v1.0.8
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/projectdiscovery/clistats v0.1.1
|
||||
github.com/projectdiscovery/fastdialer v0.4.20
|
||||
github.com/projectdiscovery/hmap v0.0.98
|
||||
github.com/projectdiscovery/fastdialer v0.5.1
|
||||
github.com/projectdiscovery/hmap v0.0.99
|
||||
github.com/projectdiscovery/interactsh v1.2.4
|
||||
github.com/projectdiscovery/rawhttp v0.1.90
|
||||
github.com/projectdiscovery/retryabledns v1.0.111
|
||||
github.com/projectdiscovery/retryablehttp-go v1.1.1
|
||||
github.com/projectdiscovery/retryabledns v1.0.112
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.1
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.6
|
||||
github.com/remeh/sizedwaitgroup v1.0.0
|
||||
github.com/rs/xid v1.6.0
|
||||
@@ -39,9 +39,9 @@ require (
|
||||
github.com/valyala/fasttemplate v1.2.2
|
||||
github.com/weppos/publicsuffix-go v0.50.1
|
||||
go.uber.org/multierr v1.11.0
|
||||
golang.org/x/net v0.47.0
|
||||
golang.org/x/net v0.48.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/text v0.31.0
|
||||
golang.org/x/text v0.32.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
@@ -92,26 +92,26 @@ require (
|
||||
github.com/microsoft/go-mssqldb v1.9.2
|
||||
github.com/ory/dockertest/v3 v3.12.0
|
||||
github.com/praetorian-inc/fingerprintx v1.1.15
|
||||
github.com/projectdiscovery/dsl v0.8.8
|
||||
github.com/projectdiscovery/dsl v0.8.10
|
||||
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.64
|
||||
github.com/projectdiscovery/gologger v1.1.66
|
||||
github.com/projectdiscovery/gostruct v0.0.2
|
||||
github.com/projectdiscovery/gozero v0.1.1-0.20251027191944-a4ea43320b81
|
||||
github.com/projectdiscovery/httpx v1.7.4
|
||||
github.com/projectdiscovery/mapcidr v1.1.97
|
||||
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
|
||||
github.com/projectdiscovery/networkpolicy v0.1.32
|
||||
github.com/projectdiscovery/networkpolicy v0.1.33
|
||||
github.com/projectdiscovery/ratelimit v0.0.82
|
||||
github.com/projectdiscovery/rdap v0.9.0
|
||||
github.com/projectdiscovery/sarif v0.0.1
|
||||
github.com/projectdiscovery/tlsx v1.2.2
|
||||
github.com/projectdiscovery/uncover v1.2.0
|
||||
github.com/projectdiscovery/useragent v0.0.105
|
||||
github.com/projectdiscovery/utils v0.7.3
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.59
|
||||
github.com/projectdiscovery/useragent v0.0.106
|
||||
github.com/projectdiscovery/utils v0.8.0
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.61
|
||||
github.com/redis/go-redis/v9 v9.11.0
|
||||
github.com/seh-msft/burpxml v1.0.1
|
||||
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466
|
||||
@@ -124,7 +124,7 @@ require (
|
||||
github.com/zmap/zgrab2 v0.1.8
|
||||
gitlab.com/gitlab-org/api/client-go v0.130.1
|
||||
go.mongodb.org/mongo-driver v1.17.4
|
||||
golang.org/x/term v0.37.0
|
||||
golang.org/x/term v0.38.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
moul.io/http2curl v1.0.0
|
||||
)
|
||||
@@ -206,6 +206,7 @@ require (
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
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/docker/cli v27.4.1+incompatible // indirect
|
||||
github.com/docker/docker v28.3.3+incompatible // indirect
|
||||
@@ -325,10 +326,10 @@ require (
|
||||
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.14 // indirect
|
||||
github.com/projectdiscovery/cdncheck v1.2.16 // 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-20240226150047-2e2c51e35983 // indirect
|
||||
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 // indirect
|
||||
github.com/refraction-networking/utls v1.7.1 // indirect
|
||||
github.com/sashabaranov/go-openai v1.37.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
@@ -383,8 +384,7 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
gopkg.in/djherbis/times.v1 v1.3.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
mellium.im/sasl v0.3.2 // indirect
|
||||
)
|
||||
|
||||
@@ -407,12 +407,12 @@ require (
|
||||
go.etcd.io/bbolt v1.4.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
goftp.io/server/v2 v2.0.1 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/mod v0.30.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.38.0
|
||||
golang.org/x/tools v0.39.0
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
|
||||
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
|
||||
|
||||
85
go.sum
85
go.sum
@@ -314,6 +314,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354=
|
||||
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
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=
|
||||
@@ -825,14 +827,14 @@ 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.14 h1:mHfNMdFwpQziPxIZnE4i0UERU/8vOcxSTcjYwp1PA2Y=
|
||||
github.com/projectdiscovery/cdncheck v1.2.14/go.mod h1:wPckYZjRFQydtbosCS0beGjuAP5h4faX7eUqB8L3/cA=
|
||||
github.com/projectdiscovery/cdncheck v1.2.16 h1:m6sQh5VAWN2sVp3o8LSFtAfnv3V0Dpoqqw83ZwnrA2c=
|
||||
github.com/projectdiscovery/cdncheck v1.2.16/go.mod h1:2OsjLn4x7VsM+ZNccGU185pd43U+0h5gDDStU0GtNl8=
|
||||
github.com/projectdiscovery/clistats v0.1.1 h1:8mwbdbwTU4aT88TJvwIzTpiNeow3XnAB72JIg66c8wE=
|
||||
github.com/projectdiscovery/clistats v0.1.1/go.mod h1:4LtTC9Oy//RiuT1+76MfTg8Hqs7FQp1JIGBM3nHK6a0=
|
||||
github.com/projectdiscovery/dsl v0.8.8 h1:UlcCf/2hfqqfDlhHWun3xT0u310iLWMiRoVw1t3TTLg=
|
||||
github.com/projectdiscovery/dsl v0.8.8/go.mod h1:gXrMGOfBVu4UvgabOfXSMqeC5RnR93aeZ94+V32OlHI=
|
||||
github.com/projectdiscovery/fastdialer v0.4.20 h1:LBw2pB9Y/0Du+d/asyB4u5vF+QhvkPIo+SLcUaf2pG8=
|
||||
github.com/projectdiscovery/fastdialer v0.4.20/go.mod h1:GXzyco8SVX6E335FKpIiX0WSRbK2MsNwvqXCr195QtM=
|
||||
github.com/projectdiscovery/dsl v0.8.10 h1:7t8KMukLlNGiOPdESGCkpOxu9RAT3RLRBY0Z5CoZIgs=
|
||||
github.com/projectdiscovery/dsl v0.8.10/go.mod h1:66WXaiVEOA2LlZuH81/izzybA3s++zX/nrKwgQV/2S0=
|
||||
github.com/projectdiscovery/fastdialer v0.5.1 h1:/4PX1u80QfZ8+DdF9jdtz1in5eajJhw89/xouVc9/+c=
|
||||
github.com/projectdiscovery/fastdialer v0.5.1/go.mod h1:34SS9VxrrmUhO67LYioxjrAWOQ5/kwgJNj9CiFgRJso=
|
||||
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=
|
||||
@@ -843,38 +845,38 @@ github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb h1:rutG90
|
||||
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.64 h1:GQ1DplEo5YnVozPImYWQqkeU5rwkPBxjMzeZmirVMdA=
|
||||
github.com/projectdiscovery/gologger v1.1.64/go.mod h1:daez34xaA7LTazBb4t+Ccm9/9Kjvlsu4EY033lQdATA=
|
||||
github.com/projectdiscovery/gologger v1.1.66 h1:dYXfCWWNNiC7AgIDBkIF+3sTGyQbrpncsQEFQwyKCOg=
|
||||
github.com/projectdiscovery/gologger v1.1.66/go.mod h1:lm3vFKt52lXgo799dBtySe/1zRN1lMGUQuNb5YtwkYQ=
|
||||
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/gozero v0.1.1-0.20251027191944-a4ea43320b81 h1:yHh46pJovYbyiaHCV7oIDinFmy+Fyq36H1BowJgb0M0=
|
||||
github.com/projectdiscovery/gozero v0.1.1-0.20251027191944-a4ea43320b81/go.mod h1:9lmGPBDGZVANzCGjQg+V32n8Y3Cgjo/4kT0E88lsVTI=
|
||||
github.com/projectdiscovery/hmap v0.0.98 h1:XxYIi7yJCNiDAKCJXvuY9IBM5O6OgDgx4XHgKxkR4eg=
|
||||
github.com/projectdiscovery/hmap v0.0.98/go.mod h1:bgN5fuZPJMj2YnAGEEnCypoifCnALJixHEVQszktQIU=
|
||||
github.com/projectdiscovery/hmap v0.0.99 h1:XPfLnD3CUrMqVCIdpK9ozD7Xmp3simx3T+2j4WWhHnU=
|
||||
github.com/projectdiscovery/hmap v0.0.99/go.mod h1:koyUJi83K5G3w35ZLFXOYZIyYJsO+6hQrgDDN1RBrVE=
|
||||
github.com/projectdiscovery/httpx v1.7.4 h1:GUEklAZ71VKM0krmgck2Km5odJi5dwfo0euc2r88tj0=
|
||||
github.com/projectdiscovery/httpx v1.7.4/go.mod h1:sPnFYIXh0RAdjb02vpUGOsP5j28VDZs5khjMbXwEUGQ=
|
||||
github.com/projectdiscovery/interactsh v1.2.4 h1:WUSj+fxbcV53J64oIAhbYzCKD1w/IyenyRBhkI5jiqI=
|
||||
github.com/projectdiscovery/interactsh v1.2.4/go.mod h1:E/IVNZ80/WKz8zTwGJWQygxIbhlRmuzZFsZwcGSZTdc=
|
||||
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb h1:MGtI4oE12ruWv11ZlPXXd7hl/uAaQZrFvrIDYDeVMd8=
|
||||
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb/go.mod h1:vmgC0DTFCfoCLp0RAfsfYTZZan0QMVs+cmTbH6blfjk=
|
||||
github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 h1:ZScLodGSezQVwsQDtBSMFp72WDq0nNN+KE/5DHKY5QE=
|
||||
github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI=
|
||||
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 h1:eR+0HE//Ciyfwy3HC7fjRyKShSJHYoX2Pv7pPshjK/Q=
|
||||
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI=
|
||||
github.com/projectdiscovery/mapcidr v1.1.97 h1:7FkxNNVXp+m1rIu5Nv/2SrF9k4+LwP8QuWs2puwy+2w=
|
||||
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.32 h1:MD561zl0/fSzcNTlDlf/7pcQbYzulYoQssFBFjb2pcg=
|
||||
github.com/projectdiscovery/networkpolicy v0.1.32/go.mod h1:5x4rGh4XhnoYl9wACnZyrjDGKIB/bQqxw2KrIM5V+XU=
|
||||
github.com/projectdiscovery/networkpolicy v0.1.33 h1:bVgp+XpLEsQ7ZEJt3UaUqIwhI01MMdt7F2dfIKFQg/w=
|
||||
github.com/projectdiscovery/networkpolicy v0.1.33/go.mod h1:YAPddAXUc/lhoU85AFdvgOQKx8Qh8r0vzSjexRWk6Yk=
|
||||
github.com/projectdiscovery/ratelimit v0.0.82 h1:rtO5SQf5uQFu5zTahTaTcO06OxmG8EIF1qhdFPIyTak=
|
||||
github.com/projectdiscovery/ratelimit v0.0.82/go.mod h1:z076BrLkBb5yS7uhHNoCTf8X/BvFSGRxwQ8EzEL9afM=
|
||||
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/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.111 h1:iyMdCDgNmaSRJYcGqB+SLlvlw9WijlbJ6Q9OEpRAWsQ=
|
||||
github.com/projectdiscovery/retryabledns v1.0.111/go.mod h1:6TOPJ3QAE4reBu6bvsGsTcyEb+OypcKYFQH7yVsjyIM=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.1.1 h1:R1r5DgxXIW+AH98ifKxFpeFbZAXP/eP9bNOMpaO32As=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.1.1/go.mod h1:D+jVQkvQ3ZNDYX30SfxJQ4ylh//NMPq1Yf11lEo+oyA=
|
||||
github.com/projectdiscovery/retryabledns v1.0.112 h1:4iCiuo6jMnw/pdOZRzBQrbUOUu5tOeuvGupxVV8RDLw=
|
||||
github.com/projectdiscovery/retryabledns v1.0.112/go.mod h1:xsJTKbo+KGqd7+88z1naEUFJybLH2yjB/zUyOweA7k0=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.1 h1:ds0dcKa3565pYdIJrLcwcbrb9cH6MojY2uHwGPoXubg=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.3.1/go.mod h1:ISMHtd5DrSSDenQ23Vz9cIyIj3XlccZzZhuWHUJTa60=
|
||||
github.com/projectdiscovery/sarif v0.0.1 h1:C2Tyj0SGOKbCLgHrx83vaE6YkzXEVrMXYRGLkKCr/us=
|
||||
github.com/projectdiscovery/sarif v0.0.1/go.mod h1:cEYlDu8amcPf6b9dSakcz2nNnJsoz4aR6peERwV+wuQ=
|
||||
github.com/projectdiscovery/stringsutil v0.0.2 h1:uzmw3IVLJSMW1kEg8eCStG/cGbYYZAja8BH3LqqJXMA=
|
||||
@@ -883,12 +885,12 @@ github.com/projectdiscovery/tlsx v1.2.2 h1:Y96QBqeD2anpzEtBl4kqNbwzXh2TrzJuXfgiB
|
||||
github.com/projectdiscovery/tlsx v1.2.2/go.mod h1:ZJl9F1sSl0sdwE+lR0yuNHVX4Zx6tCSTqnNxnHCFZB4=
|
||||
github.com/projectdiscovery/uncover v1.2.0 h1:31tjYa0v8FB8Ch8hJTxb+2t63vsljdOo0OSFylJcX4M=
|
||||
github.com/projectdiscovery/uncover v1.2.0/go.mod h1:ozqKb++p39Kmh1SmwIpbQ9p0aVGPXuwsb4/X2Kvx6ms=
|
||||
github.com/projectdiscovery/useragent v0.0.105 h1:yFGFTfWZ/RZP5XbGRJtvKcbRNnlGI6xfPRXHb8DWdhg=
|
||||
github.com/projectdiscovery/useragent v0.0.105/go.mod h1:jWG1BD2yu8EC3olt6Amke7BiyLkXzpErI7Jtzr/tWZM=
|
||||
github.com/projectdiscovery/utils v0.7.3 h1:kX+77AA58yK6EZgkTRJEnK9V/7AZYzlXdcu/o/kJhFs=
|
||||
github.com/projectdiscovery/utils v0.7.3/go.mod h1:uDdQ3/VWomai98l+a3Ye/srDXdJ4xUIar/mSXlQ9gBM=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.59 h1:oztLKyEf3yC2ncUIfSXguVA6jwb7iaV5tuK1BF3K9CY=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.59/go.mod h1:lwuDLdAqWDZ1IL8OQnoNQ0t17UP9AQSvVuFcDAm4FpQ=
|
||||
github.com/projectdiscovery/useragent v0.0.106 h1:9fS08MRUUJvfBskTxcXY9TA4X1TwpH6iJ3P3YNaXNlo=
|
||||
github.com/projectdiscovery/useragent v0.0.106/go.mod h1:9oVMjgd7CchIsyeweyigIPtW83gpiGf2NtR6UM5XK+o=
|
||||
github.com/projectdiscovery/utils v0.8.0 h1:8d79OCs5xGDNXdKxMUKMY/lgQSUWJMYB1B2Sx+oiqkQ=
|
||||
github.com/projectdiscovery/utils v0.8.0/go.mod h1:CU6tjtyTRxBrnNek+GPJplw4IIHcXNZNKO09kWgqTdg=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.61 h1:TxiYJvXqReiscuWKtGKhFx3VxbVVjHOgECNX709AEX4=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.61/go.mod h1:8FtSVcmPRZU0g1euBpdSYEBHIvB7Zz9MOb754ZqZmfU=
|
||||
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=
|
||||
@@ -1214,8 +1216,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
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=
|
||||
@@ -1253,8 +1255,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.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
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=
|
||||
@@ -1305,8 +1307,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
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.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
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=
|
||||
@@ -1335,8 +1337,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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
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=
|
||||
@@ -1389,6 +1391,7 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1401,8 +1404,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
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=
|
||||
@@ -1416,8 +1419,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
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.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
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=
|
||||
@@ -1434,8 +1437,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
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=
|
||||
@@ -1489,8 +1492,8 @@ 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.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
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=
|
||||
@@ -1593,8 +1596,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 h1:K4u1NprbDNvKPczKfHLbwdOWHTZ0zfv2ow71H1nRnFU=
|
||||
gopkg.in/corvus-ch/zbase32.v1 v1.0.0/go.mod h1:T3oKkPOm4AV/bNXCNFUxRmlE9RUyBz/DSo0nK9U+c0Y=
|
||||
gopkg.in/djherbis/times.v1 v1.3.0 h1:uxMS4iMtH6Pwsxog094W0FYldiNnfY/xba00vq6C2+o=
|
||||
gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
|
||||
27
integration_tests/protocols/code/pwsh-echo.yaml
Normal file
27
integration_tests/protocols/code/pwsh-echo.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
id: pw-echo
|
||||
|
||||
info:
|
||||
name: PowerShell Echo Test
|
||||
author: pdteam
|
||||
severity: info
|
||||
description: Tests PowerShell execution with an echo-like operation.
|
||||
tags: test,powershell,echo
|
||||
|
||||
self-contained: true
|
||||
|
||||
code:
|
||||
- engine:
|
||||
- pwsh
|
||||
- powershell
|
||||
- powershell.exe
|
||||
args:
|
||||
- -ExecutionPolicy
|
||||
- Bypass
|
||||
pattern: "*.ps1"
|
||||
source: |
|
||||
Write-Output "test-output-success"
|
||||
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- "test-output-success"
|
||||
23
integration_tests/protocols/javascript/mysql-connect.yaml
Normal file
23
integration_tests/protocols/javascript/mysql-connect.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
id: mysql-connect
|
||||
|
||||
info:
|
||||
name: MySQL Connect Test
|
||||
author: pdteam
|
||||
severity: high
|
||||
|
||||
javascript:
|
||||
- pre-condition: |
|
||||
isPortOpen(Host, Port)
|
||||
code: |
|
||||
const mysql = require('nuclei/mysql');
|
||||
const client = new mysql.MySQLClient;
|
||||
success = client.Connect(Host, Port, User, Pass);
|
||||
args:
|
||||
Host: "{{Host}}"
|
||||
Port: "3306"
|
||||
User: "root"
|
||||
Pass: "secret"
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- "success == true"
|
||||
23
integration_tests/protocols/javascript/no-port-args.yaml
Normal file
23
integration_tests/protocols/javascript/no-port-args.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
id: javascript-no-port-args
|
||||
|
||||
info:
|
||||
name: JavaScript Template Without Port Args
|
||||
author: dwisiswant0
|
||||
severity: info
|
||||
description: |
|
||||
Test backwards compatibility for JavaScript templates without Port in args.
|
||||
Templates should execute even when Port arg is not explicitly specified.
|
||||
|
||||
javascript:
|
||||
- code: |
|
||||
// Simple JavaScript code that does not require Port
|
||||
let result = "executed";
|
||||
Export(result);
|
||||
|
||||
args:
|
||||
TestArg: "test-value"
|
||||
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- success == true
|
||||
@@ -0,0 +1,47 @@
|
||||
id: postgres-pass-brute
|
||||
|
||||
info:
|
||||
name: PostgreSQL Password Bruteforce
|
||||
author: pdteam
|
||||
severity: high
|
||||
description: |
|
||||
This template bruteforces passwords for protected PostgreSQL instances.
|
||||
If PostgreSQL is not protected with password, it is also matched.
|
||||
metadata:
|
||||
shodan-query: product:"PostgreSQL"
|
||||
tags: js,network,postgresql,authentication
|
||||
|
||||
javascript:
|
||||
- pre-condition: |
|
||||
isPortOpen(Host,Port)
|
||||
|
||||
code: |
|
||||
const postgres = require('nuclei/postgres');
|
||||
const client = new postgres.PGClient;
|
||||
success = client.Connect(Host, Port, User, Pass);
|
||||
|
||||
args:
|
||||
Host: "{{Host}}"
|
||||
Port: "5432"
|
||||
User: "{{usernames}}"
|
||||
Pass: "{{passwords}}"
|
||||
|
||||
attack: clusterbomb
|
||||
payloads:
|
||||
usernames:
|
||||
- postgres
|
||||
- admin
|
||||
- root
|
||||
passwords:
|
||||
- ""
|
||||
- postgres
|
||||
- password
|
||||
- admin
|
||||
- root
|
||||
stop-at-first-match: true
|
||||
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- "success == true"
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
|
||||
"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"
|
||||
@@ -238,6 +239,9 @@ func (e *NucleiEngine) closeInternal() {
|
||||
if e.tmpDir != "" {
|
||||
_ = os.RemoveAll(e.tmpDir)
|
||||
}
|
||||
if e.opts != nil {
|
||||
generators.ClearOptionsPayloadMap(e.opts)
|
||||
}
|
||||
}
|
||||
|
||||
// Close all resources used by nuclei engine
|
||||
|
||||
@@ -31,7 +31,7 @@ const (
|
||||
CLIConfigFileName = "config.yaml"
|
||||
ReportingConfigFilename = "reporting-config.yaml"
|
||||
// Version is the current version of nuclei
|
||||
Version = `v3.6.1`
|
||||
Version = `v3.6.2`
|
||||
// Directory Names of custom templates
|
||||
CustomS3TemplatesDirName = "s3"
|
||||
CustomGitHubTemplatesDirName = "github"
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func memoizedconnect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) {
|
||||
hash := "connect" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName)
|
||||
hash := "connect" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return connect(executionId, host, port, username, password, dbName)
|
||||
@@ -27,7 +27,7 @@ func memoizedconnect(executionId string, host string, port int, username string,
|
||||
}
|
||||
|
||||
func memoizedisMssql(executionId string, host string, port int) (bool, error) {
|
||||
hash := "isMssql" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "isMssql" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return isMssql(executionId, host, port)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func memoizedisMySQL(executionId string, host string, port int) (bool, error) {
|
||||
hash := "isMySQL" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "isMySQL" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return isMySQL(executionId, host, port)
|
||||
@@ -25,7 +25,7 @@ func memoizedisMySQL(executionId string, host string, port int) (bool, error) {
|
||||
}
|
||||
|
||||
func memoizedfingerprintMySQL(executionId string, host string, port int) (MySQLInfo, error) {
|
||||
hash := "fingerprintMySQL" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "fingerprintMySQL" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return fingerprintMySQL(executionId, host, port)
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
||||
)
|
||||
|
||||
func memoizedconnectWithDSN(dsn string) (bool, error) {
|
||||
hash := "connectWithDSN" + ":" + fmt.Sprint(dsn)
|
||||
func memoizedconnectWithDSN(executionId string, dsn string) (bool, error) {
|
||||
hash := "connectWithDSN" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(dsn)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return connectWithDSN(dsn)
|
||||
return connectWithDSN(executionId, dsn)
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -108,7 +108,7 @@ func (c *MySQLClient) Connect(ctx context.Context, host string, port int, userna
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return connectWithDSN(dsn)
|
||||
return connectWithDSN(executionId, dsn)
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -190,8 +190,9 @@ func fingerprintMySQL(executionId string, host string, port int) (MySQLInfo, err
|
||||
// const client = new mysql.MySQLClient;
|
||||
// const connected = client.ConnectWithDSN('username:password@tcp(acme.com:3306)/');
|
||||
// ```
|
||||
func (c *MySQLClient) ConnectWithDSN(dsn string) (bool, error) {
|
||||
return memoizedconnectWithDSN(dsn)
|
||||
func (c *MySQLClient) ConnectWithDSN(ctx context.Context, dsn string) (bool, error) {
|
||||
executionId := ctx.Value("executionId").(string)
|
||||
return memoizedconnectWithDSN(executionId, dsn)
|
||||
}
|
||||
|
||||
// ExecuteQueryWithOpts connects to Mysql database using given credentials
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -72,7 +73,7 @@ func BuildDSN(opts MySQLOptions) (string, error) {
|
||||
}
|
||||
|
||||
// @memo
|
||||
func connectWithDSN(dsn string) (bool, error) {
|
||||
func connectWithDSN(executionId string, dsn string) (bool, error) {
|
||||
db, err := sql.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -83,7 +84,8 @@ func connectWithDSN(dsn string) (bool, error) {
|
||||
db.SetMaxOpenConns(1)
|
||||
db.SetMaxIdleConns(0)
|
||||
|
||||
_, err = db.Exec("select 1")
|
||||
ctx := context.WithValue(context.Background(), "executionId", executionId) // nolint: staticcheck
|
||||
err = db.PingContext(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func memoizedisOracle(executionId string, host string, port int) (IsOracleResponse, error) {
|
||||
hash := "isOracle" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "isOracle" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return isOracle(executionId, host, port)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func memoizedisPoP3(executionId string, host string, port int) (IsPOP3Response, error) {
|
||||
hash := "isPoP3" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "isPoP3" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return isPoP3(executionId, host, port)
|
||||
|
||||
@@ -5,15 +5,15 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap"
|
||||
|
||||
utils "github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
|
||||
|
||||
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
||||
)
|
||||
|
||||
func memoizedisPostgres(executionId string, host string, port int) (bool, error) {
|
||||
hash := "isPostgres" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "isPostgres" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return isPostgres(executionId, host, port)
|
||||
@@ -29,7 +29,7 @@ func memoizedisPostgres(executionId string, host string, port int) (bool, error)
|
||||
}
|
||||
|
||||
func memoizedexecuteQuery(executionId string, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {
|
||||
hash := "executeQuery" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName) + ":" + fmt.Sprint(query)
|
||||
hash := "executeQuery" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName) + ":" + fmt.Sprint(query)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return executeQuery(executionId, host, port, username, password, dbName, query)
|
||||
@@ -45,7 +45,7 @@ func memoizedexecuteQuery(executionId string, host string, port int, username st
|
||||
}
|
||||
|
||||
func memoizedconnect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) {
|
||||
hash := "connect" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName)
|
||||
hash := "connect" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return connect(executionId, host, port, username, password, dbName)
|
||||
|
||||
@@ -82,7 +82,7 @@ func isPostgres(executionId string, host string, port int) (bool, error) {
|
||||
// const client = new postgres.PGClient;
|
||||
// const connected = client.Connect('acme.com', 5432, 'username', 'password');
|
||||
// ```
|
||||
func (c *PGClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) {
|
||||
func (c *PGClient) Connect(ctx context.Context, host string, port int, username string, password string) (bool, error) {
|
||||
ok, err := c.IsPostgres(ctx, host, port)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -104,7 +104,7 @@ func (c *PGClient) Connect(ctx context.Context, host string, port int, username,
|
||||
// const result = client.ExecuteQuery('acme.com', 5432, 'username', 'password', 'dbname', 'select * from users');
|
||||
// log(to_json(result));
|
||||
// ```
|
||||
func (c *PGClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) {
|
||||
func (c *PGClient) ExecuteQuery(ctx context.Context, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {
|
||||
ok, err := c.IsPostgres(ctx, host, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -157,7 +157,7 @@ func executeQuery(executionId string, host string, port int, username string, pa
|
||||
// const client = new postgres.PGClient;
|
||||
// const connected = client.ConnectWithDB('acme.com', 5432, 'username', 'password', 'dbname');
|
||||
// ```
|
||||
func (c *PGClient) ConnectWithDB(ctx context.Context, host string, port int, username, password, dbName string) (bool, error) {
|
||||
func (c *PGClient) ConnectWithDB(ctx context.Context, host string, port int, username string, password string, dbName string) (bool, error) {
|
||||
ok, err := c.IsPostgres(ctx, host, port)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -207,7 +207,7 @@ func connect(executionId string, host string, port int, username string, passwor
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
_, err := db.Exec(ctx, "select 1")
|
||||
_, err := db.ExecContext(ctx, "select 1")
|
||||
if err != nil {
|
||||
switch true {
|
||||
case strings.Contains(err.Error(), "connect: connection refused"):
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func memoizedisRDP(executionId string, host string, port int) (IsRDPResponse, error) {
|
||||
hash := "isRDP" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "isRDP" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return isRDP(executionId, host, port)
|
||||
@@ -25,7 +25,7 @@ func memoizedisRDP(executionId string, host string, port int) (IsRDPResponse, er
|
||||
}
|
||||
|
||||
func memoizedcheckRDPAuth(executionId string, host string, port int) (CheckRDPAuthResponse, error) {
|
||||
hash := "checkRDPAuth" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "checkRDPAuth" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return checkRDPAuth(executionId, host, port)
|
||||
@@ -41,7 +41,7 @@ func memoizedcheckRDPAuth(executionId string, host string, port int) (CheckRDPAu
|
||||
}
|
||||
|
||||
func memoizedcheckRDPEncryption(executionId string, host string, port int) (RDPEncryptionResponse, error) {
|
||||
hash := "checkRDPEncryption" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "checkRDPEncryption" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return checkRDPEncryption(executionId, host, port)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func memoizedgetServerInfo(executionId string, host string, port int) (string, error) {
|
||||
hash := "getServerInfo" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "getServerInfo" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return getServerInfo(executionId, host, port)
|
||||
@@ -25,7 +25,7 @@ func memoizedgetServerInfo(executionId string, host string, port int) (string, e
|
||||
}
|
||||
|
||||
func memoizedconnect(executionId string, host string, port int, password string) (bool, error) {
|
||||
hash := "connect" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password)
|
||||
hash := "connect" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return connect(executionId, host, port, password)
|
||||
@@ -41,7 +41,7 @@ func memoizedconnect(executionId string, host string, port int, password string)
|
||||
}
|
||||
|
||||
func memoizedgetServerInfoAuth(executionId string, host string, port int, password string) (string, error) {
|
||||
hash := "getServerInfoAuth" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password)
|
||||
hash := "getServerInfoAuth" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return getServerInfoAuth(executionId, host, port, password)
|
||||
@@ -57,7 +57,7 @@ func memoizedgetServerInfoAuth(executionId string, host string, port int, passwo
|
||||
}
|
||||
|
||||
func memoizedisAuthenticated(executionId string, host string, port int) (bool, error) {
|
||||
hash := "isAuthenticated" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "isAuthenticated" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return isAuthenticated(executionId, host, port)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func memoizedisRsync(executionId string, host string, port int) (IsRsyncResponse, error) {
|
||||
hash := "isRsync" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "isRsync" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return isRsync(executionId, host, port)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func memoizedconnectSMBInfoMode(executionId string, host string, port int) (*smb.SMBLog, error) {
|
||||
hash := "connectSMBInfoMode" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "connectSMBInfoMode" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return connectSMBInfoMode(executionId, host, port)
|
||||
@@ -27,7 +27,7 @@ func memoizedconnectSMBInfoMode(executionId string, host string, port int) (*smb
|
||||
}
|
||||
|
||||
func memoizedlistShares(executionId string, host string, port int, user string, password string) ([]string, error) {
|
||||
hash := "listShares" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(user) + ":" + fmt.Sprint(password)
|
||||
hash := "listShares" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(user) + ":" + fmt.Sprint(password)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return listShares(executionId, host, port, user, password)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func memoizedcollectSMBv2Metadata(executionId string, host string, port int, timeout time.Duration) (*plugins.ServiceSMB, error) {
|
||||
hash := "collectSMBv2Metadata" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(timeout)
|
||||
hash := "collectSMBv2Metadata" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(timeout)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return collectSMBv2Metadata(executionId, host, port, timeout)
|
||||
|
||||
@@ -3,13 +3,14 @@ package smb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
||||
)
|
||||
|
||||
func memoizeddetectSMBGhost(executionId string, host string, port int) (bool, error) {
|
||||
hash := "detectSMBGhost" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "detectSMBGhost" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return detectSMBGhost(executionId, host, port)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func memoizedisTelnet(executionId string, host string, port int) (IsTelnetResponse, error) {
|
||||
hash := "isTelnet" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "isTelnet" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return isTelnet(executionId, host, port)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func memoizedisVNC(executionId string, host string, port int) (IsVNCResponse, error) {
|
||||
hash := "isVNC" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
hash := "isVNC" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
|
||||
|
||||
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
|
||||
return isVNC(executionId, host, port)
|
||||
|
||||
@@ -44,20 +44,51 @@ func MergeMapsMany(maps ...interface{}) map[string][]string {
|
||||
return m
|
||||
}
|
||||
|
||||
// MergeMaps merges two maps into a new map
|
||||
// MergeMaps merges multiple maps into a new map.
|
||||
//
|
||||
// Use [CopyMap] if you need to copy a single map.
|
||||
// Use [MergeMapsInto] to merge into an existing map.
|
||||
func MergeMaps(maps ...map[string]interface{}) map[string]interface{} {
|
||||
merged := make(map[string]interface{})
|
||||
mapsLen := 0
|
||||
for _, m := range maps {
|
||||
mapsLen += len(m)
|
||||
}
|
||||
|
||||
merged := make(map[string]interface{}, mapsLen)
|
||||
for _, m := range maps {
|
||||
maps0.Copy(merged, m)
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
||||
// CopyMap creates a shallow copy of a single map.
|
||||
func CopyMap(m map[string]interface{}) map[string]interface{} {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make(map[string]interface{}, len(m))
|
||||
maps0.Copy(result, m)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// MergeMapsInto copies all entries from src maps into dst (mutating dst).
|
||||
//
|
||||
// Use when dst is a fresh map the caller owns and wants to avoid allocation.
|
||||
func MergeMapsInto(dst map[string]interface{}, srcs ...map[string]interface{}) {
|
||||
for _, src := range srcs {
|
||||
maps0.Copy(dst, src)
|
||||
}
|
||||
}
|
||||
|
||||
// ExpandMapValues converts values from flat string to string slice
|
||||
func ExpandMapValues(m map[string]string) map[string][]string {
|
||||
m1 := make(map[string][]string, len(m))
|
||||
for k, v := range m {
|
||||
m1[k] = []string{v}
|
||||
}
|
||||
|
||||
return m1
|
||||
}
|
||||
|
||||
109
pkg/protocols/common/generators/maps_bench_test.go
Normal file
109
pkg/protocols/common/generators/maps_bench_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkMergeMaps(b *testing.B) {
|
||||
map1 := map[string]interface{}{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
"key4": "value4",
|
||||
"key5": "value5",
|
||||
}
|
||||
map2 := map[string]interface{}{
|
||||
"key6": "value6",
|
||||
"key7": "value7",
|
||||
"key8": "value8",
|
||||
"key9": "value9",
|
||||
"key10": "value10",
|
||||
}
|
||||
map3 := map[string]interface{}{
|
||||
"key11": "value11",
|
||||
"key12": "value12",
|
||||
"key13": "value13",
|
||||
}
|
||||
|
||||
for i := 1; i <= 3; i++ {
|
||||
b.Run(fmt.Sprintf("%d-maps", i), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
switch i {
|
||||
case 1:
|
||||
_ = MergeMaps(map1)
|
||||
case 2:
|
||||
_ = MergeMaps(map1, map2)
|
||||
case 3:
|
||||
_ = MergeMaps(map1, map2, map3)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCopyMap(b *testing.B) {
|
||||
map1 := map[string]interface{}{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
"key4": "value4",
|
||||
"key5": "value5",
|
||||
}
|
||||
|
||||
for i := 1; i <= 1; i++ {
|
||||
b.Run(fmt.Sprintf("%d-maps", i), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
switch i {
|
||||
case 1:
|
||||
_ = CopyMap(map1)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMergeMapsInto(b *testing.B) {
|
||||
map1 := map[string]interface{}{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
"key3": "value3",
|
||||
"key4": "value4",
|
||||
"key5": "value5",
|
||||
}
|
||||
map2 := map[string]interface{}{
|
||||
"key6": "value6",
|
||||
"key7": "value7",
|
||||
"key8": "value8",
|
||||
"key9": "value9",
|
||||
"key10": "value10",
|
||||
}
|
||||
map3 := map[string]interface{}{
|
||||
"key11": "value11",
|
||||
"key12": "value12",
|
||||
"key13": "value13",
|
||||
}
|
||||
map4 := map[string]interface{}{
|
||||
"key14": "value14",
|
||||
"key15": "value15",
|
||||
"key16": "value16",
|
||||
}
|
||||
|
||||
for i := 1; i <= 3; i++ {
|
||||
b.Run(fmt.Sprintf("%d-maps", i), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
switch i {
|
||||
case 1:
|
||||
MergeMapsInto(map1, map2)
|
||||
case 2:
|
||||
MergeMapsInto(map1, map2, map3)
|
||||
case 3:
|
||||
MergeMapsInto(map1, map2, map3, map4)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,32 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
)
|
||||
|
||||
// BuildPayloadFromOptions returns a map with the payloads provided via CLI
|
||||
// optionsPayloadMap caches the result of BuildPayloadFromOptions per options
|
||||
// pointer. This supports multiple SDK instances with different options running
|
||||
// concurrently.
|
||||
var optionsPayloadMap sync.Map // map[*types.Options]map[string]interface{}
|
||||
|
||||
// BuildPayloadFromOptions returns a map with the payloads provided via CLI.
|
||||
//
|
||||
// The result is cached per options pointer since options don't change during a run.
|
||||
// Returns a copy of the cached map to prevent concurrent modification issues.
|
||||
// Safe for concurrent use with multiple SDK instances.
|
||||
func BuildPayloadFromOptions(options *types.Options) map[string]interface{} {
|
||||
if options == nil {
|
||||
return make(map[string]interface{})
|
||||
}
|
||||
|
||||
if cached, ok := optionsPayloadMap.Load(options); ok {
|
||||
return CopyMap(cached.(map[string]interface{}))
|
||||
}
|
||||
|
||||
m := make(map[string]interface{})
|
||||
|
||||
// merge with vars
|
||||
if !options.Vars.IsEmpty() {
|
||||
m = MergeMaps(m, options.Vars.AsMap())
|
||||
@@ -16,5 +36,18 @@ func BuildPayloadFromOptions(options *types.Options) map[string]interface{} {
|
||||
if options.EnvironmentVariables {
|
||||
m = MergeMaps(EnvVars(), m)
|
||||
}
|
||||
return m
|
||||
|
||||
actual, _ := optionsPayloadMap.LoadOrStore(options, m)
|
||||
|
||||
// Return a copy to prevent concurrent writes to the cached map
|
||||
return CopyMap(actual.(map[string]interface{}))
|
||||
}
|
||||
|
||||
// ClearOptionsPayloadMap clears the cached options payload.
|
||||
// SDK users should call this when disposing of a NucleiEngine instance
|
||||
// to prevent memory leaks if creating many short-lived instances.
|
||||
func ClearOptionsPayloadMap(options *types.Options) {
|
||||
if options != nil {
|
||||
optionsPayloadMap.Delete(options)
|
||||
}
|
||||
}
|
||||
|
||||
45
pkg/protocols/common/generators/options_bench_test.go
Normal file
45
pkg/protocols/common/generators/options_bench_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/goflags"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
)
|
||||
|
||||
func BenchmarkBuildPayloadFromOptions(b *testing.B) {
|
||||
// Setup options with vars and env vars
|
||||
vars := goflags.RuntimeMap{}
|
||||
_ = vars.Set("key1=value1")
|
||||
_ = vars.Set("key2=value2")
|
||||
_ = vars.Set("key3=value3")
|
||||
_ = vars.Set("key4=value4")
|
||||
_ = vars.Set("key5=value5")
|
||||
|
||||
opts := &types.Options{
|
||||
Vars: vars,
|
||||
EnvironmentVariables: true, // This adds more entries
|
||||
}
|
||||
|
||||
b.Run("Sequential", func(b *testing.B) {
|
||||
ClearOptionsPayloadMap(opts)
|
||||
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
_ = BuildPayloadFromOptions(opts)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Parallel", func(b *testing.B) {
|
||||
ClearOptionsPayloadMap(opts)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
m := BuildPayloadFromOptions(opts)
|
||||
// Simulate typical usage - read a value
|
||||
_ = m["key1"]
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
92
pkg/protocols/common/generators/options_test.go
Normal file
92
pkg/protocols/common/generators/options_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/goflags"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBuildPayloadFromOptionsConcurrency(t *testing.T) {
|
||||
// Test that BuildPayloadFromOptions is safe for concurrent use
|
||||
// and returns independent copies that can be modified without races
|
||||
vars := goflags.RuntimeMap{}
|
||||
_ = vars.Set("key=value")
|
||||
|
||||
opts := &types.Options{
|
||||
Vars: vars,
|
||||
}
|
||||
|
||||
const numGoroutines = 100
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(numGoroutines)
|
||||
|
||||
// Each goroutine gets a map and modifies it
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
// Get the map (should be a copy of cached data)
|
||||
m := BuildPayloadFromOptions(opts)
|
||||
|
||||
// Modify it - this should not cause races
|
||||
m["goroutine_id"] = id
|
||||
m["test_key"] = "test_value"
|
||||
|
||||
// Verify original cached value is present
|
||||
require.Equal(t, "value", m["key"])
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestBuildPayloadFromOptionsCaching(t *testing.T) {
|
||||
// Test that caching actually works
|
||||
vars := goflags.RuntimeMap{}
|
||||
_ = vars.Set("cached=yes")
|
||||
|
||||
opts := &types.Options{
|
||||
Vars: vars,
|
||||
EnvironmentVariables: false,
|
||||
}
|
||||
|
||||
// First call - builds and caches
|
||||
m1 := BuildPayloadFromOptions(opts)
|
||||
require.Equal(t, "yes", m1["cached"])
|
||||
|
||||
// Second call - should return copy of cached result
|
||||
m2 := BuildPayloadFromOptions(opts)
|
||||
require.Equal(t, "yes", m2["cached"])
|
||||
|
||||
// Modify m1 - should not affect m2 since they're copies
|
||||
m1["modified"] = "in_m1"
|
||||
require.NotContains(t, m2, "modified")
|
||||
|
||||
// Modify m2 - should not affect future calls
|
||||
m2["modified"] = "in_m2"
|
||||
m3 := BuildPayloadFromOptions(opts)
|
||||
require.NotContains(t, m3, "modified")
|
||||
}
|
||||
|
||||
func TestClearOptionsPayloadMap(t *testing.T) {
|
||||
vars := goflags.RuntimeMap{}
|
||||
_ = vars.Set("temp=data")
|
||||
|
||||
opts := &types.Options{
|
||||
Vars: vars,
|
||||
}
|
||||
|
||||
// Build and cache
|
||||
m1 := BuildPayloadFromOptions(opts)
|
||||
require.Equal(t, "data", m1["temp"])
|
||||
|
||||
// Clear the cache
|
||||
ClearOptionsPayloadMap(opts)
|
||||
|
||||
// Verify it still works (rebuilds)
|
||||
m2 := BuildPayloadFromOptions(opts)
|
||||
require.Equal(t, "data", m2["temp"])
|
||||
}
|
||||
@@ -200,8 +200,14 @@ func initDialers(options *types.Options) error {
|
||||
addr += ":3306"
|
||||
}
|
||||
|
||||
executionId := ctx.Value("executionId").(string)
|
||||
var executionId string
|
||||
if val := ctx.Value("executionId"); val != nil {
|
||||
executionId = val.(string)
|
||||
}
|
||||
dialer := GetDialersWithId(executionId)
|
||||
if dialer == nil {
|
||||
return nil, fmt.Errorf("dialers not initialized for %s", executionId)
|
||||
}
|
||||
return dialer.Fastdialer.Dial(ctx, "tcp", addr)
|
||||
})
|
||||
|
||||
|
||||
@@ -70,18 +70,23 @@ func (variables *Variable) UnmarshalJSON(data []byte) error {
|
||||
// Evaluate returns a finished map of variables based on set values
|
||||
func (variables *Variable) Evaluate(values map[string]interface{}) map[string]interface{} {
|
||||
result := make(map[string]interface{}, variables.Len())
|
||||
combined := make(map[string]interface{}, len(values)+variables.Len())
|
||||
generators.MergeMapsInto(combined, values)
|
||||
|
||||
variables.ForEach(func(key string, value interface{}) {
|
||||
if sliceValue, ok := value.([]interface{}); ok {
|
||||
// slices cannot be evaluated
|
||||
result[key] = sliceValue
|
||||
combined[key] = sliceValue
|
||||
return
|
||||
}
|
||||
valueString := types.ToString(value)
|
||||
combined := generators.MergeMaps(values, result)
|
||||
if value, ok := combined[key]; ok {
|
||||
valueString = types.ToString(value)
|
||||
if existingValue, ok := combined[key]; ok {
|
||||
valueString = types.ToString(existingValue)
|
||||
}
|
||||
result[key] = evaluateVariableValue(valueString, combined, result)
|
||||
evaluated := evaluateVariableValueWithMap(valueString, combined)
|
||||
result[key] = evaluated
|
||||
combined[key] = evaluated
|
||||
})
|
||||
return result
|
||||
}
|
||||
@@ -98,29 +103,36 @@ func (variables *Variable) GetAll() map[string]interface{} {
|
||||
// EvaluateWithInteractsh returns evaluation results of variables with interactsh
|
||||
func (variables *Variable) EvaluateWithInteractsh(values map[string]interface{}, interact *interactsh.Client) (map[string]interface{}, []string) {
|
||||
result := make(map[string]interface{}, variables.Len())
|
||||
combined := make(map[string]interface{}, len(values)+variables.Len())
|
||||
generators.MergeMapsInto(combined, values)
|
||||
|
||||
var interactURLs []string
|
||||
variables.ForEach(func(key string, value interface{}) {
|
||||
if sliceValue, ok := value.([]interface{}); ok {
|
||||
// slices cannot be evaluated
|
||||
result[key] = sliceValue
|
||||
combined[key] = sliceValue
|
||||
return
|
||||
}
|
||||
valueString := types.ToString(value)
|
||||
if existingValue, ok := combined[key]; ok {
|
||||
valueString = types.ToString(existingValue)
|
||||
}
|
||||
if strings.Contains(valueString, "interactsh-url") {
|
||||
valueString, interactURLs = interact.Replace(valueString, interactURLs)
|
||||
}
|
||||
combined := generators.MergeMaps(values, result)
|
||||
if value, ok := combined[key]; ok {
|
||||
valueString = types.ToString(value)
|
||||
}
|
||||
result[key] = evaluateVariableValue(valueString, combined, result)
|
||||
evaluated := evaluateVariableValueWithMap(valueString, combined)
|
||||
result[key] = evaluated
|
||||
combined[key] = evaluated
|
||||
})
|
||||
return result, interactURLs
|
||||
}
|
||||
|
||||
// evaluateVariableValue expression and returns final value
|
||||
func evaluateVariableValue(expression string, values, processing map[string]interface{}) string {
|
||||
// evaluateVariableValue expression and returns final value.
|
||||
//
|
||||
// Deprecated: use evaluateVariableValueWithMap instead to avoid repeated map
|
||||
// merging overhead.
|
||||
func evaluateVariableValue(expression string, values, processing map[string]interface{}) string { // nolint
|
||||
finalMap := generators.MergeMaps(values, processing)
|
||||
result, err := expressions.Evaluate(expression, finalMap)
|
||||
if err != nil {
|
||||
@@ -130,6 +142,16 @@ func evaluateVariableValue(expression string, values, processing map[string]inte
|
||||
return result
|
||||
}
|
||||
|
||||
// evaluateVariableValueWithMap evaluates an expression with a pre-merged map.
|
||||
func evaluateVariableValueWithMap(expression string, combinedMap map[string]interface{}) string {
|
||||
result, err := expressions.Evaluate(expression, combinedMap)
|
||||
if err != nil {
|
||||
return expression
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// checkForLazyEval checks if the variables have any lazy evaluation i.e any dsl function
|
||||
// and sets the flag accordingly.
|
||||
func (variables *Variable) checkForLazyEval() bool {
|
||||
|
||||
80
pkg/protocols/common/variables/variables_bench_test.go
Normal file
80
pkg/protocols/common/variables/variables_bench_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package variables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
|
||||
)
|
||||
|
||||
func BenchmarkVariableEvaluate(b *testing.B) {
|
||||
// Setup variables with chained references and DSL functions
|
||||
variables := &Variable{
|
||||
LazyEval: true,
|
||||
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(5),
|
||||
}
|
||||
variables.Set("base", "testvalue")
|
||||
variables.Set("derived1", "{{base}}_suffix")
|
||||
variables.Set("derived2", "{{md5(derived1)}}")
|
||||
variables.Set("derived3", "prefix_{{derived2}}")
|
||||
variables.Set("final", "{{derived3}}_end")
|
||||
|
||||
inputValues := map[string]interface{}{
|
||||
"BaseURL": "http://example.com",
|
||||
"Host": "example.com",
|
||||
"Path": "/api/v1",
|
||||
}
|
||||
|
||||
b.Run("Evaluate", func(b *testing.B) {
|
||||
b.Run("5Variables", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
_ = variables.Evaluate(inputValues)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Parallel", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = variables.Evaluate(inputValues)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkVariableEvaluateScaling(b *testing.B) {
|
||||
// Test how the optimization scales with different variable counts
|
||||
inputValues := map[string]interface{}{
|
||||
"BaseURL": "http://example.com",
|
||||
"Host": "example.com",
|
||||
}
|
||||
|
||||
benchmarkSizes := []int{1, 5, 10, 20}
|
||||
|
||||
for _, size := range benchmarkSizes {
|
||||
variables := &Variable{
|
||||
LazyEval: true,
|
||||
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(size),
|
||||
}
|
||||
|
||||
// Create chain of variables
|
||||
for i := range size {
|
||||
varName := fmt.Sprintf("var%d", i)
|
||||
if i == 0 {
|
||||
variables.Set(varName, "initial")
|
||||
} else {
|
||||
prevVarName := fmt.Sprintf("var%d", i-1)
|
||||
variables.Set(varName, fmt.Sprintf("{{%s}}_step", prevVarName))
|
||||
}
|
||||
}
|
||||
|
||||
b.Run(fmt.Sprintf("Variables-%d", size), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
_ = variables.Evaluate(inputValues)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -147,3 +148,176 @@ func TestCheckForLazyEval(t *testing.T) {
|
||||
require.True(t, variables.LazyEval, "LazyEval flag should be true")
|
||||
})
|
||||
}
|
||||
|
||||
func TestVariablesEvaluateChained(t *testing.T) {
|
||||
t.Run("chained-variable-references", func(t *testing.T) {
|
||||
// Test that variables can reference previously defined variables
|
||||
// and that input values (like BaseURL) are available for evaluation
|
||||
// but not included in the result
|
||||
variables := &Variable{
|
||||
LazyEval: true, // skip auto-evaluation in UnmarshalYAML
|
||||
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(3),
|
||||
}
|
||||
variables.Set("a", "hello")
|
||||
variables.Set("b", "{{a}} world")
|
||||
variables.Set("c", "{{b}}!")
|
||||
|
||||
inputValues := map[string]interface{}{
|
||||
"BaseURL": "http://example.com",
|
||||
"Host": "example.com",
|
||||
}
|
||||
|
||||
result := variables.Evaluate(inputValues)
|
||||
|
||||
// Result should contain only the defined variables, not input values
|
||||
require.Len(t, result, 3, "result should contain exactly 3 variables")
|
||||
require.NotContains(t, result, "BaseURL", "result should not contain input values")
|
||||
require.NotContains(t, result, "Host", "result should not contain input values")
|
||||
|
||||
// Chained evaluation should work correctly
|
||||
require.Equal(t, "hello", result["a"])
|
||||
require.Equal(t, "hello world", result["b"])
|
||||
require.Equal(t, "hello world!", result["c"])
|
||||
})
|
||||
|
||||
t.Run("variables-using-input-values", func(t *testing.T) {
|
||||
// Test that variables can use input values in expressions
|
||||
variables := &Variable{
|
||||
LazyEval: true,
|
||||
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(2),
|
||||
}
|
||||
variables.Set("api_url", "{{BaseURL}}/api/v1")
|
||||
variables.Set("full_path", "{{api_url}}/users")
|
||||
|
||||
inputValues := map[string]interface{}{
|
||||
"BaseURL": "http://example.com",
|
||||
}
|
||||
|
||||
result := variables.Evaluate(inputValues)
|
||||
|
||||
require.Len(t, result, 2)
|
||||
require.Equal(t, "http://example.com/api/v1", result["api_url"])
|
||||
require.Equal(t, "http://example.com/api/v1/users", result["full_path"])
|
||||
require.NotContains(t, result, "BaseURL")
|
||||
})
|
||||
|
||||
t.Run("mixed-expressions-and-chaining", func(t *testing.T) {
|
||||
// Test combining DSL functions with chained variables
|
||||
variables := &Variable{
|
||||
LazyEval: true,
|
||||
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(3),
|
||||
}
|
||||
variables.Set("token", "secret123")
|
||||
variables.Set("hashed", "{{md5(token)}}")
|
||||
variables.Set("header", "X-Auth: {{hashed}}")
|
||||
|
||||
result := variables.Evaluate(map[string]interface{}{})
|
||||
|
||||
require.Equal(t, "secret123", result["token"])
|
||||
require.Equal(t, "5d7845ac6ee7cfffafc5fe5f35cf666d", result["hashed"]) // md5("secret123")
|
||||
require.Equal(t, "X-Auth: 5d7845ac6ee7cfffafc5fe5f35cf666d", result["header"])
|
||||
})
|
||||
|
||||
t.Run("evaluation-order-preserved", func(t *testing.T) {
|
||||
// Test that evaluation follows insertion order
|
||||
// (important for variables that depend on previously defined ones)
|
||||
variables := &Variable{
|
||||
LazyEval: true,
|
||||
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(4),
|
||||
}
|
||||
variables.Set("step1", "A")
|
||||
variables.Set("step2", "{{step1}}B")
|
||||
variables.Set("step3", "{{step2}}C")
|
||||
variables.Set("step4", "{{step3}}D")
|
||||
|
||||
result := variables.Evaluate(map[string]interface{}{})
|
||||
|
||||
require.Equal(t, "A", result["step1"])
|
||||
require.Equal(t, "AB", result["step2"])
|
||||
require.Equal(t, "ABC", result["step3"])
|
||||
require.Equal(t, "ABCD", result["step4"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestEvaluateWithInteractshOverrideOrder(t *testing.T) {
|
||||
// This test demonstrates a bug where interactsh URL replacement is wasted
|
||||
// when an input value exists for the same variable key.
|
||||
//
|
||||
// Bug scenario:
|
||||
// 1. Variable "callback" is defined with "{{interactsh-url}}"
|
||||
// 2. Input values contain "callback" with some other value
|
||||
// 3. The interactsh-url is replaced first (wasting an interactsh URL)
|
||||
// 4. Then immediately overwritten by the input value
|
||||
//
|
||||
// Expected behavior: Input override should be checked FIRST, then interactsh
|
||||
// replacement should happen on the final valueString.
|
||||
|
||||
t.Run("interactsh-replacement-with-input-override", func(t *testing.T) {
|
||||
variables := &Variable{
|
||||
LazyEval: true,
|
||||
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
|
||||
}
|
||||
variables.Set("callback", "{{interactsh-url}}")
|
||||
|
||||
// Input provides an override that also contains interactsh-url
|
||||
inputValues := map[string]interface{}{
|
||||
"callback": "https://custom.{{interactsh-url}}/path",
|
||||
}
|
||||
|
||||
// Create a real interactsh client for testing
|
||||
client, err := interactsh.New(&interactsh.Options{
|
||||
ServerURL: "oast.fun",
|
||||
CacheSize: 100,
|
||||
Eviction: 60 * time.Second,
|
||||
CooldownPeriod: 5 * time.Second,
|
||||
PollDuration: 5 * time.Second,
|
||||
DisableHttpFallback: true,
|
||||
})
|
||||
require.NoError(t, err, "could not create interactsh client")
|
||||
defer client.Close()
|
||||
|
||||
result, urls := variables.EvaluateWithInteractsh(inputValues, client)
|
||||
|
||||
// The input override contains interactsh-url, so it should be replaced
|
||||
// and we should have exactly 1 URL from the input override
|
||||
require.Len(t, urls, 1, "should have 1 interactsh URL from input override")
|
||||
|
||||
// The result should use the input override (with interactsh replaced)
|
||||
require.Contains(t, result["callback"], "https://custom.", "should use input override pattern")
|
||||
require.Contains(t, result["callback"], "/path", "should use input override pattern")
|
||||
require.NotContains(t, result["callback"], "{{interactsh-url}}", "interactsh should be replaced")
|
||||
})
|
||||
|
||||
t.Run("interactsh-replacement-without-input-override", func(t *testing.T) {
|
||||
variables := &Variable{
|
||||
LazyEval: true,
|
||||
InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),
|
||||
}
|
||||
variables.Set("callback", "{{interactsh-url}}")
|
||||
|
||||
// No input override for "callback"
|
||||
inputValues := map[string]interface{}{
|
||||
"other_key": "other_value",
|
||||
}
|
||||
|
||||
client, err := interactsh.New(&interactsh.Options{
|
||||
ServerURL: "oast.fun",
|
||||
CacheSize: 100,
|
||||
Eviction: 60 * time.Second,
|
||||
CooldownPeriod: 5 * time.Second,
|
||||
PollDuration: 5 * time.Second,
|
||||
DisableHttpFallback: true,
|
||||
})
|
||||
require.NoError(t, err, "could not create interactsh client")
|
||||
defer client.Close()
|
||||
|
||||
result, urls := variables.EvaluateWithInteractsh(inputValues, client)
|
||||
|
||||
// Should have 1 URL from the variable definition
|
||||
require.Len(t, urls, 1, "should have 1 interactsh URL")
|
||||
|
||||
// The result should be the replaced interactsh URL
|
||||
require.NotContains(t, result["callback"], "{{interactsh-url}}", "interactsh should be replaced")
|
||||
require.NotEmpty(t, result["callback"], "callback should have a value")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ func newHttpClient(options *types.Options) (*http.Client, error) {
|
||||
Renegotiation: tls.RenegotiateOnceAsClient,
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS10,
|
||||
ClientSessionCache: tls.NewLRUClientSessionCache(1024),
|
||||
}
|
||||
|
||||
if options.SNI != "" {
|
||||
|
||||
@@ -248,6 +248,7 @@ func wrappedGet(options *types.Options, configuration *Configuration) (*retryabl
|
||||
Renegotiation: tls.RenegotiateOnceAsClient,
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS10,
|
||||
ClientSessionCache: tls.NewLRUClientSessionCache(1024),
|
||||
}
|
||||
|
||||
if options.SNI != "" {
|
||||
|
||||
@@ -84,10 +84,10 @@ func (r *requestGenerator) nextValue() (value string, payloads map[string]interf
|
||||
r.applyMark(request, Once)
|
||||
}
|
||||
if hasPayloadIterator {
|
||||
return request, generators.MergeMaps(r.currentPayloads), r.okCurrentPayload
|
||||
return request, generators.CopyMap(r.currentPayloads), r.okCurrentPayload
|
||||
}
|
||||
// next should return a copy of payloads and not pointer to payload to avoid data race
|
||||
return request, generators.MergeMaps(r.currentPayloads), true
|
||||
return request, generators.CopyMap(r.currentPayloads), true
|
||||
} else {
|
||||
return "", nil, false
|
||||
}
|
||||
|
||||
@@ -287,6 +287,9 @@ func (request *Request) GetID() string {
|
||||
func (request *Request) ExecuteWithResults(target *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
|
||||
// Get default port(s) if specified in template
|
||||
ports := request.getPorts()
|
||||
if len(ports) == 0 {
|
||||
return request.executeWithResults("", target, dynamicValues, previous, callback)
|
||||
}
|
||||
|
||||
var errs []error
|
||||
|
||||
|
||||
@@ -42,6 +42,12 @@ type Options struct {
|
||||
DenyList *filters.Filter `yaml:"deny-list"`
|
||||
// DuplicateIssueCheck is a bool to enable duplicate tracking issue check and update the newest
|
||||
DuplicateIssueCheck bool `yaml:"duplicate-issue-check" default:"false"`
|
||||
// DuplicateIssuePageSize controls how many issues are fetched per page when searching for duplicates.
|
||||
// If unset or <=0, a default of 100 is used.
|
||||
DuplicateIssuePageSize int `yaml:"duplicate-issue-page-size" default:"100"`
|
||||
// DuplicateIssueMaxPages limits how many pages are fetched when searching for duplicates.
|
||||
// If unset or <=0, all pages are fetched until exhaustion.
|
||||
DuplicateIssueMaxPages int `yaml:"duplicate-issue-max-pages" default:"0"`
|
||||
|
||||
HttpClient *retryablehttp.Client `yaml:"-"`
|
||||
OmitRaw bool `yaml:"-"`
|
||||
@@ -151,21 +157,44 @@ func (i *Integration) ShouldFilter(event *output.ResultEvent) bool {
|
||||
}
|
||||
|
||||
func (i *Integration) findIssueByTitle(title string) (*gitea.Issue, error) {
|
||||
// Fetch issues in pages to ensure older issues are also checked for duplicates.
|
||||
pageSize := i.options.DuplicateIssuePageSize
|
||||
if pageSize <= 0 {
|
||||
pageSize = 100
|
||||
}
|
||||
maxPages := i.options.DuplicateIssueMaxPages
|
||||
|
||||
issueList, _, err := i.client.ListRepoIssues(i.options.ProjectOwner, i.options.ProjectName, gitea.ListIssueOption{
|
||||
opts := gitea.ListIssueOption{
|
||||
State: "all",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
ListOptions: gitea.ListOptions{
|
||||
Page: 1,
|
||||
PageSize: pageSize,
|
||||
},
|
||||
}
|
||||
|
||||
for _, issue := range issueList {
|
||||
if issue.Title == title {
|
||||
return issue, nil
|
||||
for {
|
||||
if maxPages > 0 && opts.Page > maxPages {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
issueList, _, err := i.client.ListRepoIssues(i.options.ProjectOwner, i.options.ProjectName, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, issue := range issueList {
|
||||
if issue.Title == title {
|
||||
return issue, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(issueList) < opts.PageSize {
|
||||
// Last page reached.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
opts.Page++
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Integration) getLabelIDsByNames(labels []string) ([]int64, error) {
|
||||
|
||||
@@ -41,6 +41,12 @@ type Options struct {
|
||||
DenyList *filters.Filter `yaml:"deny-list"`
|
||||
// DuplicateIssueCheck is a bool to enable duplicate tracking issue check and update the newest
|
||||
DuplicateIssueCheck bool `yaml:"duplicate-issue-check" default:"false"`
|
||||
// DuplicateIssuePageSize controls how many issues are fetched per page when searching for duplicates.
|
||||
// If unset or <=0, a default of 100 is used.
|
||||
DuplicateIssuePageSize int `yaml:"duplicate-issue-page-size" default:"100"`
|
||||
// DuplicateIssueMaxPages limits how many pages are fetched when searching for duplicates.
|
||||
// If unset or <=0, all pages are fetched until exhaustion.
|
||||
DuplicateIssueMaxPages int `yaml:"duplicate-issue-max-pages" default:"0"`
|
||||
|
||||
HttpClient *retryablehttp.Client `yaml:"-"`
|
||||
OmitRaw bool `yaml:"-"`
|
||||
@@ -80,39 +86,36 @@ func (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIss
|
||||
}
|
||||
customLabels := gitlab.LabelOptions(labels)
|
||||
assigneeIDs := []int{i.userID}
|
||||
|
||||
var issue *gitlab.Issue
|
||||
if i.options.DuplicateIssueCheck {
|
||||
searchIn := "title"
|
||||
searchState := "all"
|
||||
issues, _, err := i.client.Issues.ListProjectIssues(i.options.ProjectName, &gitlab.ListProjectIssuesOptions{
|
||||
In: &searchIn,
|
||||
State: &searchState,
|
||||
Search: &summary,
|
||||
var err error
|
||||
issue, err = i.findIssueByTitle(summary)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if issue != nil {
|
||||
_, _, err := i.client.Notes.CreateIssueNote(i.options.ProjectName, issue.IID, &gitlab.CreateIssueNoteOptions{
|
||||
Body: &description,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(issues) > 0 {
|
||||
issue := issues[0]
|
||||
_, _, err := i.client.Notes.CreateIssueNote(i.options.ProjectName, issue.IID, &gitlab.CreateIssueNoteOptions{
|
||||
Body: &description,
|
||||
if issue.State == "closed" {
|
||||
reopen := "reopen"
|
||||
_, _, err := i.client.Issues.UpdateIssue(i.options.ProjectName, issue.IID, &gitlab.UpdateIssueOptions{
|
||||
StateEvent: &reopen,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if issue.State == "closed" {
|
||||
reopen := "reopen"
|
||||
_, _, err := i.client.Issues.UpdateIssue(i.options.ProjectName, issue.IID, &gitlab.UpdateIssueOptions{
|
||||
StateEvent: &reopen,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &filters.CreateIssueResponse{
|
||||
IssueID: strconv.FormatInt(int64(issue.ID), 10),
|
||||
IssueURL: issue.WebURL,
|
||||
}, nil
|
||||
}
|
||||
return &filters.CreateIssueResponse{
|
||||
IssueID: strconv.FormatInt(int64(issue.ID), 10),
|
||||
IssueURL: issue.WebURL,
|
||||
}, nil
|
||||
}
|
||||
createdIssue, _, err := i.client.Issues.CreateIssue(i.options.ProjectName, &gitlab.CreateIssueOptions{
|
||||
Title: &summary,
|
||||
@@ -134,23 +137,15 @@ func (i *Integration) Name() string {
|
||||
}
|
||||
|
||||
func (i *Integration) CloseIssue(event *output.ResultEvent) error {
|
||||
searchIn := "title"
|
||||
searchState := "all"
|
||||
|
||||
summary := format.Summary(event)
|
||||
issues, _, err := i.client.Issues.ListProjectIssues(i.options.ProjectName, &gitlab.ListProjectIssuesOptions{
|
||||
In: &searchIn,
|
||||
State: &searchState,
|
||||
Search: &summary,
|
||||
})
|
||||
issue, err := i.findIssueByTitle(summary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(issues) <= 0 {
|
||||
if issue == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
issue := issues[0]
|
||||
state := "close"
|
||||
_, _, err = i.client.Issues.UpdateIssue(i.options.ProjectName, issue.IID, &gitlab.UpdateIssueOptions{
|
||||
StateEvent: &state,
|
||||
@@ -161,6 +156,49 @@ func (i *Integration) CloseIssue(event *output.ResultEvent) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Integration) findIssueByTitle(title string) (*gitlab.Issue, error) {
|
||||
pageSize := i.options.DuplicateIssuePageSize
|
||||
if pageSize <= 0 {
|
||||
pageSize = 100
|
||||
}
|
||||
maxPages := i.options.DuplicateIssueMaxPages
|
||||
|
||||
searchIn := "title"
|
||||
searchState := "all"
|
||||
page := 1
|
||||
|
||||
for {
|
||||
if maxPages > 0 && page > maxPages {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
issues, _, err := i.client.Issues.ListProjectIssues(i.options.ProjectName, &gitlab.ListProjectIssuesOptions{
|
||||
In: &searchIn,
|
||||
State: &searchState,
|
||||
Search: &title,
|
||||
ListOptions: gitlab.ListOptions{
|
||||
Page: page,
|
||||
PerPage: pageSize,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, issue := range issues {
|
||||
if issue.Title == title {
|
||||
return issue, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(issues) < pageSize {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldFilter determines if an issue should be logged to this tracker
|
||||
func (i *Integration) ShouldFilter(event *output.ResultEvent) bool {
|
||||
if i.options.AllowList != nil && !i.options.AllowList.GetMatch(event) {
|
||||
|
||||
@@ -184,6 +184,9 @@ type Options struct {
|
||||
UpdateExisting bool `yaml:"update-existing" json:"update_existing"`
|
||||
// URL is the URL of the jira server
|
||||
URL string `yaml:"url" json:"url" validate:"required"`
|
||||
// SiteURL is the browsable URL for the Jira instance (optional)
|
||||
// If not provided, issue.Self will be used. Useful for OAuth where issue.Self contains api.atlassian.com
|
||||
SiteURL string `yaml:"site-url" json:"site_url"`
|
||||
// AccountID is the accountID of the jira user.
|
||||
AccountID string `yaml:"account-id" json:"account_id" validate:"required"`
|
||||
// Email is the email of the user for jira instance
|
||||
@@ -346,16 +349,26 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) (*filters.Create
|
||||
}
|
||||
return nil, fmt.Errorf("%w => %s", err, data)
|
||||
}
|
||||
return getIssueResponseFromJira(createdIssue)
|
||||
return i.getIssueResponseFromJira(createdIssue)
|
||||
}
|
||||
|
||||
func getIssueResponseFromJira(issue *jira.Issue) (*filters.CreateIssueResponse, error) {
|
||||
parsed, err := url.Parse(issue.Self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (i *Integration) getIssueResponseFromJira(issue *jira.Issue) (*filters.CreateIssueResponse, error) {
|
||||
var issueURL string
|
||||
|
||||
// Use SiteURL if provided, otherwise fall back to original issue.Self logic
|
||||
if i.options.SiteURL != "" {
|
||||
// Use the configured site URL for browsable links (useful for OAuth)
|
||||
baseURL := strings.TrimRight(i.options.SiteURL, "/")
|
||||
issueURL = fmt.Sprintf("%s/browse/%s", baseURL, issue.Key)
|
||||
} else {
|
||||
// Fall back to original logic using issue.Self
|
||||
parsed, err := url.Parse(issue.Self)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsed.Path = fmt.Sprintf("/browse/%s", issue.Key)
|
||||
issueURL = parsed.String()
|
||||
}
|
||||
parsed.Path = fmt.Sprintf("/browse/%s", issue.Key)
|
||||
issueURL := parsed.String()
|
||||
|
||||
return &filters.CreateIssueResponse{
|
||||
IssueID: issue.ID,
|
||||
@@ -376,7 +389,7 @@ func (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIss
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not add comment to existing issue")
|
||||
}
|
||||
return getIssueResponseFromJira(&issue)
|
||||
return i.getIssueResponseFromJira(&issue)
|
||||
}
|
||||
}
|
||||
resp, err := i.CreateNewIssue(event)
|
||||
|
||||
@@ -5,7 +5,7 @@ import "github.com/projectdiscovery/nuclei/v3/pkg/operators"
|
||||
// Checks if template has matchers
|
||||
func hasMatchers(all []*operators.Operators) bool {
|
||||
for _, operator := range all {
|
||||
if len(operator.Matchers) > 0 {
|
||||
if operator != nil && len(operator.Matchers) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
34
pkg/tmplexec/flow/util_test.go
Normal file
34
pkg/tmplexec/flow/util_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package flow
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHasMatchersPanicRegression(t *testing.T) {
|
||||
// This test ensures that hasMatchers does not panic when passed a slice containing nil.
|
||||
// This was the source of a reported panic when a request had no local operators.
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
all := []*operators.Operators{nil}
|
||||
result := hasMatchers(all)
|
||||
require.False(t, result)
|
||||
}, "hasMatchers should not panic with nil element in slice")
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
all := []*operators.Operators{nil, {}}
|
||||
result := hasMatchers(all)
|
||||
require.False(t, result)
|
||||
}, "hasMatchers should not panic with mix of nil and empty operators")
|
||||
}
|
||||
|
||||
func TestHasOperatorsPanicRegression(t *testing.T) {
|
||||
// Also ensure hasOperators is safe
|
||||
require.NotPanics(t, func() {
|
||||
all := []*operators.Operators{nil}
|
||||
result := hasOperators(all)
|
||||
require.False(t, result)
|
||||
}, "hasOperators should not panic with nil element in slice")
|
||||
}
|
||||
Reference in New Issue
Block a user