mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-17 18:15:28 +00:00
Merge branch 'dev' into pr/6422
This commit is contained in:
commit
99a9ce398d
35
.claude/settings.local.json
Normal file
35
.claude/settings.local.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(find:*)",
|
||||||
|
"Bash(mkdir:*)",
|
||||||
|
"Bash(cp:*)",
|
||||||
|
"Bash(ls:*)",
|
||||||
|
"Bash(make:*)",
|
||||||
|
"Bash(go:*)",
|
||||||
|
"Bash(golangci-lint:*)",
|
||||||
|
"Bash(git merge:*)",
|
||||||
|
"Bash(git add:*)",
|
||||||
|
"Bash(git commit:*)",
|
||||||
|
"Bash(git push:*)",
|
||||||
|
"Bash(git pull:*)",
|
||||||
|
"Bash(git fetch:*)",
|
||||||
|
"Bash(git checkout:*)",
|
||||||
|
"WebFetch(*)",
|
||||||
|
"Write(*)",
|
||||||
|
"WebSearch(*)",
|
||||||
|
"MultiEdit(*)",
|
||||||
|
"Edit(*)",
|
||||||
|
"Bash(gh:*)",
|
||||||
|
"Bash(grep:*)",
|
||||||
|
"Bash(tree:*)",
|
||||||
|
"Bash(./nuclei:*)",
|
||||||
|
"WebFetch(domain:github.com)"
|
||||||
|
],
|
||||||
|
"deny": [
|
||||||
|
"Bash(make run:*)",
|
||||||
|
"Bash(./bin/nuclei:*)"
|
||||||
|
],
|
||||||
|
"defaultMode": "acceptEdits"
|
||||||
|
}
|
||||||
|
}
|
||||||
76
.github/DISCUSSION_TEMPLATE.md
vendored
Normal file
76
.github/DISCUSSION_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# Nuclei Discussion Guidelines
|
||||||
|
|
||||||
|
## Before Creating a Discussion
|
||||||
|
|
||||||
|
1. **Search existing discussions and issues** to avoid duplicates
|
||||||
|
2. **Check the documentation** and README first
|
||||||
|
3. **Browse the FAQ** and common questions
|
||||||
|
|
||||||
|
## Bug Reports in Discussions
|
||||||
|
|
||||||
|
When reporting a bug in [Q&A Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/q-a), please include:
|
||||||
|
|
||||||
|
### Required Information:
|
||||||
|
- **Clear title** with `[BUG]` prefix (e.g., "[BUG] Nuclei crashes when...")
|
||||||
|
- **Current behavior** - What's happening now?
|
||||||
|
- **Expected behavior** - What should happen instead?
|
||||||
|
- **Steps to reproduce** - Commands or actions that trigger the issue
|
||||||
|
- **Environment details**:
|
||||||
|
- OS and version
|
||||||
|
- Nuclei version (`nuclei -version`)
|
||||||
|
- Go version (if installed via `go install`)
|
||||||
|
- **Log output** - Run with `-verbose` or `-debug` for detailed logs
|
||||||
|
- **Redact sensitive information** - Remove target URLs, credentials, etc.
|
||||||
|
|
||||||
|
### After Discussion:
|
||||||
|
- Maintainers will review and validate the bug report
|
||||||
|
- Valid bugs will be converted to issues with proper labels and tracking
|
||||||
|
- Questions and misconfigurations will be resolved in the discussion
|
||||||
|
|
||||||
|
## Feature Requests in Discussions
|
||||||
|
|
||||||
|
When requesting a feature in [Ideas Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/ideas), please include:
|
||||||
|
|
||||||
|
### Required Information:
|
||||||
|
- **Clear title** with `[FEATURE]` prefix (e.g., "[FEATURE] Add support for...")
|
||||||
|
- **Feature description** - What do you want to be added?
|
||||||
|
- **Use case** - Why is this feature needed? What problem does it solve?
|
||||||
|
- **Implementation ideas** - If you have suggestions on how it could work
|
||||||
|
- **Alternatives considered** - What other solutions have you thought about?
|
||||||
|
|
||||||
|
### After Discussion:
|
||||||
|
- Community and maintainers will discuss the feasibility
|
||||||
|
- Popular and viable features will be converted to issues
|
||||||
|
- Similar features may be grouped together
|
||||||
|
- Rejected features will be explained in the discussion
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
For general questions, troubleshooting, and "how-to" topics:
|
||||||
|
- Use [Q&A Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/q-a)
|
||||||
|
- Join the [Discord server](https://discord.gg/projectdiscovery) #nuclei channel
|
||||||
|
- Check existing discussions for similar questions
|
||||||
|
|
||||||
|
## Discussion to Issue Conversion Process
|
||||||
|
|
||||||
|
Only maintainers can convert discussions to issues. The process:
|
||||||
|
|
||||||
|
1. **Validation** - Maintainers review the discussion for completeness and validity
|
||||||
|
2. **Classification** - Determine if it's a bug, feature, enhancement, etc.
|
||||||
|
3. **Issue creation** - Create a properly formatted issue with appropriate labels
|
||||||
|
4. **Linking** - Link the issue back to the original discussion
|
||||||
|
5. **Resolution** - Mark the discussion as resolved or close it
|
||||||
|
|
||||||
|
This process ensures:
|
||||||
|
- High-quality issues that are actionable
|
||||||
|
- Proper triage and labeling
|
||||||
|
- Reduced noise in the issue tracker
|
||||||
|
- Community involvement in the validation process
|
||||||
|
|
||||||
|
## Why This Process?
|
||||||
|
|
||||||
|
- **Better organization** - Issues contain only validated, actionable items
|
||||||
|
- **Community input** - Discussions allow for community feedback before escalation
|
||||||
|
- **Quality control** - Maintainers ensure proper formatting and information
|
||||||
|
- **Reduced maintenance** - Fewer invalid or duplicate issues to manage
|
||||||
|
- **Clear separation** - Questions vs. actual bugs/features are clearly distinguished
|
||||||
24
.github/ISSUE_TEMPLATE/config.yml
vendored
24
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -2,14 +2,22 @@ blank_issues_enabled: false
|
|||||||
|
|
||||||
contact_links:
|
contact_links:
|
||||||
|
|
||||||
- name: Ask an question / advise on using nuclei
|
- name: 🐛 Report a Bug (Start with Discussion)
|
||||||
url: https://github.com/projectdiscovery/nuclei/discussions/categories/q-a
|
url: https://github.com/orgs/projectdiscovery/discussions/new?category=q-a
|
||||||
about: Ask a question or request support for using nuclei
|
about: Start by reporting your issue in discussions for proper triage. Issues will be created after review to avoid duplicate/invalid reports.
|
||||||
|
|
||||||
- name: Share idea / feature to discuss for nuclei
|
- name: 💡 Request a Feature (Start with Discussion)
|
||||||
url: https://github.com/projectdiscovery/nuclei/discussions/categories/ideas
|
url: https://github.com/orgs/projectdiscovery/discussions/new?category=ideas
|
||||||
about: Share idea / feature to discuss for nuclei
|
about: Share your feature idea in discussions first. This helps validate and refine the request before creating an issue.
|
||||||
|
|
||||||
- name: Connect with PD Team (Discord)
|
- name: ❓ Ask Questions / Get Help
|
||||||
|
url: https://github.com/orgs/projectdiscovery/discussions
|
||||||
|
about: Get help and ask questions about using Nuclei. Many questions don't require issues.
|
||||||
|
|
||||||
|
- name: 🔍 Browse Existing Issues
|
||||||
|
url: https://github.com/projectdiscovery/nuclei/issues
|
||||||
|
about: Check existing issues to see if your problem has already been reported or is being worked on.
|
||||||
|
|
||||||
|
- name: 💬 Connect with PD Team (Discord)
|
||||||
url: https://discord.gg/projectdiscovery
|
url: https://discord.gg/projectdiscovery
|
||||||
about: Connect with PD Team for direct communication
|
about: Join our Discord for real-time discussions and community support on the #nuclei channel.
|
||||||
45
.github/ISSUE_TEMPLATE/reference-templates/README.md
vendored
Normal file
45
.github/ISSUE_TEMPLATE/reference-templates/README.md
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Issue Template References
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This folder contains the preserved issue templates that are **not** directly accessible to users. These templates serve as references for maintainers when converting discussions to issues.
|
||||||
|
|
||||||
|
## New Workflow
|
||||||
|
|
||||||
|
### For Users:
|
||||||
|
1. **All reports start in Discussions** - Users cannot create issues directly
|
||||||
|
2. Bug reports go to [Q&A Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/q-a)
|
||||||
|
3. Feature requests go to [Ideas Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/ideas)
|
||||||
|
4. This helps filter out duplicate questions, invalid reports, and ensures proper triage
|
||||||
|
|
||||||
|
### For Maintainers:
|
||||||
|
1. **Review discussions** in both Q&A and Ideas categories
|
||||||
|
2. **Validate the reports** - ensure they're actual bugs/valid feature requests
|
||||||
|
3. **Use reference templates** when converting discussions to issues:
|
||||||
|
- Copy content from `bug-report-reference.yml` or `feature-request-reference.yml`
|
||||||
|
- Create a new issue manually with the appropriate template structure
|
||||||
|
- Link back to the original discussion
|
||||||
|
- Close the discussion or mark it as resolved
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
- **Better triage**: Avoid cluttering issues with questions and invalid reports
|
||||||
|
- **Community involvement**: Discussions allow for community input before creating issues
|
||||||
|
- **Quality control**: Maintainers can ensure issues follow proper format and contain necessary information
|
||||||
|
- **Reduced noise**: Only validated, actionable items become issues
|
||||||
|
|
||||||
|
## Reference Templates
|
||||||
|
|
||||||
|
- `bug-report-reference.yml` - Use when converting bug reports from discussions to issues
|
||||||
|
- `feature-request-reference.yml` - Use when converting feature requests from discussions to issues
|
||||||
|
|
||||||
|
## Converting a Discussion to Issue
|
||||||
|
|
||||||
|
1. Identify a valid discussion that needs to become an issue
|
||||||
|
2. Go to the main repository's Issues tab
|
||||||
|
3. Click "New Issue"
|
||||||
|
4. Manually create the issue using the reference template structure
|
||||||
|
5. Include all relevant information from the discussion
|
||||||
|
6. Add a comment linking back to the original discussion
|
||||||
|
7. Apply appropriate labels
|
||||||
|
8. Close or mark the discussion as resolved with a link to the created issue
|
||||||
2
.github/workflows/auto-merge.yaml
vendored
2
.github/workflows/auto-merge.yaml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.actor == 'dependabot[bot]'
|
if: github.actor == 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DEPENDABOT_PAT }}
|
token: ${{ secrets.DEPENDABOT_PAT }}
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/compat-checks.yaml
vendored
2
.github/workflows/compat-checks.yaml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: projectdiscovery/actions/setup/go/compat-checks@v1
|
- uses: projectdiscovery/actions/setup/go/compat-checks@v1
|
||||||
with:
|
with:
|
||||||
release-test: true
|
release-test: true
|
||||||
|
|||||||
2
.github/workflows/generate-docs.yaml
vendored
2
.github/workflows/generate-docs.yaml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
if: "${{ !endsWith(github.actor, '[bot]') }}"
|
if: "${{ !endsWith(github.actor, '[bot]') }}"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: projectdiscovery/actions/setup/go@v1
|
- uses: projectdiscovery/actions/setup/go@v1
|
||||||
- uses: projectdiscovery/actions/setup/git@v1
|
- uses: projectdiscovery/actions/setup/git@v1
|
||||||
- run: make syntax-docs
|
- run: make syntax-docs
|
||||||
|
|||||||
2
.github/workflows/generate-pgo.yaml
vendored
2
.github/workflows/generate-pgo.yaml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
LIST_FILE: "/tmp/targets-${{ matrix.targets }}.txt"
|
LIST_FILE: "/tmp/targets-${{ matrix.targets }}.txt"
|
||||||
PROFILE_MEM: "/tmp/nuclei-profile-${{ matrix.targets }}-targets"
|
PROFILE_MEM: "/tmp/nuclei-profile-${{ matrix.targets }}-targets"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: projectdiscovery/actions/setup/git@v1
|
- uses: projectdiscovery/actions/setup/git@v1
|
||||||
- uses: projectdiscovery/actions/setup/go@v1
|
- uses: projectdiscovery/actions/setup/go@v1
|
||||||
- name: Generate list
|
- name: Generate list
|
||||||
|
|||||||
2
.github/workflows/govulncheck.yaml
vendored
2
.github/workflows/govulncheck.yaml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
OUTPUT: "/tmp/results.sarif"
|
OUTPUT: "/tmp/results.sarif"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: projectdiscovery/actions/setup/go@v1
|
- uses: projectdiscovery/actions/setup/go@v1
|
||||||
- run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
- run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
- run: govulncheck -scan package -format sarif ./... > $OUTPUT
|
- run: govulncheck -scan package -format sarif ./... > $OUTPUT
|
||||||
|
|||||||
2
.github/workflows/perf-regression.yaml
vendored
2
.github/workflows/perf-regression.yaml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
BENCH_OUT: "/tmp/bench.out"
|
BENCH_OUT: "/tmp/bench.out"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: projectdiscovery/actions/setup/go@v1
|
- uses: projectdiscovery/actions/setup/go@v1
|
||||||
- run: make build-test
|
- run: make build-test
|
||||||
- run: ./bin/nuclei.test -test.run - -test.bench=. -test.benchmem ./cmd/nuclei/ | tee $BENCH_OUT
|
- run: ./bin/nuclei.test -test.run - -test.bench=. -test.benchmem ./cmd/nuclei/ | tee $BENCH_OUT
|
||||||
|
|||||||
2
.github/workflows/perf-test.yaml
vendored
2
.github/workflows/perf-test.yaml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
LIST_FILE: "/tmp/targets-${{ matrix.count }}.txt"
|
LIST_FILE: "/tmp/targets-${{ matrix.count }}.txt"
|
||||||
PROFILE_MEM: "/tmp/nuclei-perf-test-${{ matrix.count }}"
|
PROFILE_MEM: "/tmp/nuclei-perf-test-${{ matrix.count }}"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: projectdiscovery/actions/setup/go@v1
|
- uses: projectdiscovery/actions/setup/go@v1
|
||||||
- run: make verify
|
- run: make verify
|
||||||
- name: Generate list
|
- name: Generate list
|
||||||
|
|||||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@ -10,7 +10,7 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest-16-cores
|
runs-on: ubuntu-latest-16-cores
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: projectdiscovery/actions/setup/go@v1
|
- uses: projectdiscovery/actions/setup/go@v1
|
||||||
|
|||||||
2
.github/workflows/stale.yaml
vendored
2
.github/workflows/stale.yaml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v9
|
- uses: actions/stale@v10
|
||||||
with:
|
with:
|
||||||
days-before-stale: 90
|
days-before-stale: 90
|
||||||
days-before-close: 7
|
days-before-close: 7
|
||||||
|
|||||||
18
.github/workflows/tests.yaml
vendored
18
.github/workflows/tests.yaml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
if: "${{ !endsWith(github.actor, '[bot]') }}"
|
if: "${{ !endsWith(github.actor, '[bot]') }}"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: projectdiscovery/actions/setup/go@v1
|
- uses: projectdiscovery/actions/setup/go@v1
|
||||||
- uses: projectdiscovery/actions/golangci-lint/v2@v1
|
- uses: projectdiscovery/actions/golangci-lint/v2@v1
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ jobs:
|
|||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||||
runs-on: "${{ matrix.os }}"
|
runs-on: "${{ matrix.os }}"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: projectdiscovery/actions/setup/go@v1
|
- uses: projectdiscovery/actions/setup/go@v1
|
||||||
- run: make vet
|
- run: make vet
|
||||||
- run: make build
|
- run: make build
|
||||||
@ -52,7 +52,7 @@ jobs:
|
|||||||
needs: ["tests"]
|
needs: ["tests"]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: projectdiscovery/actions/setup/go@v1
|
- uses: projectdiscovery/actions/setup/go@v1
|
||||||
- name: "Simple"
|
- name: "Simple"
|
||||||
run: go run .
|
run: go run .
|
||||||
@ -74,7 +74,7 @@ jobs:
|
|||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: projectdiscovery/actions/setup/go@v1
|
- uses: projectdiscovery/actions/setup/go@v1
|
||||||
- uses: projectdiscovery/actions/setup/python@v1
|
- uses: projectdiscovery/actions/setup/python@v1
|
||||||
- run: bash run.sh "${{ matrix.os }}"
|
- run: bash run.sh "${{ matrix.os }}"
|
||||||
@ -93,7 +93,7 @@ jobs:
|
|||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: projectdiscovery/actions/setup/go@v1
|
- uses: projectdiscovery/actions/setup/go@v1
|
||||||
- uses: projectdiscovery/actions/setup/python@v1
|
- uses: projectdiscovery/actions/setup/python@v1
|
||||||
- run: bash run.sh
|
- run: bash run.sh
|
||||||
@ -106,7 +106,7 @@ jobs:
|
|||||||
needs: ["tests"]
|
needs: ["tests"]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: projectdiscovery/actions/setup/go@v1
|
- uses: projectdiscovery/actions/setup/go@v1
|
||||||
- run: make template-validate
|
- run: make template-validate
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
security-events: write
|
security-events: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: github/codeql-action/init@v3
|
- uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: 'go'
|
languages: 'go'
|
||||||
@ -131,7 +131,7 @@ jobs:
|
|||||||
needs: ["tests"]
|
needs: ["tests"]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: projectdiscovery/actions/setup/go@v1
|
- uses: projectdiscovery/actions/setup/go@v1
|
||||||
- uses: projectdiscovery/actions/goreleaser@v1
|
- uses: projectdiscovery/actions/goreleaser@v1
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ jobs:
|
|||||||
TARGET_URL: "http://scanme.sh/a/?b=c"
|
TARGET_URL: "http://scanme.sh/a/?b=c"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- run: make build
|
- run: make build
|
||||||
- name: "Setup environment (push)"
|
- name: "Setup environment (push)"
|
||||||
if: ${{ github.event_name == 'push' }}
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -28,6 +28,8 @@
|
|||||||
/scrapefunc
|
/scrapefunc
|
||||||
/scrapefuncs
|
/scrapefuncs
|
||||||
/tsgen
|
/tsgen
|
||||||
|
/integration_tests/integration-test
|
||||||
|
/integration_tests/nuclei
|
||||||
|
|
||||||
# Templates
|
# Templates
|
||||||
/*.yaml
|
/*.yaml
|
||||||
|
|||||||
83
CLAUDE.md
Normal file
83
CLAUDE.md
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Nuclei is a modern, high-performance vulnerability scanner built in Go that leverages YAML-based templates for customizable vulnerability detection. It supports multiple protocols (HTTP, DNS, TCP, SSL, WebSocket, WHOIS, JavaScript, Code) and is designed for zero false positives through real-world condition simulation.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Building and Testing
|
||||||
|
- `make build` - Build the main nuclei binary to ./bin/nuclei
|
||||||
|
- `make test` - Run unit tests with race detection
|
||||||
|
- `make integration` - Run integration tests (builds and runs test suite)
|
||||||
|
- `make functional` - Run functional tests
|
||||||
|
- `make vet` - Run go vet for code analysis
|
||||||
|
- `make tidy` - Clean up go modules
|
||||||
|
|
||||||
|
### Validation and Linting
|
||||||
|
- `make template-validate` - Validate nuclei templates using the built binary
|
||||||
|
- `go fmt ./...` - Format Go code
|
||||||
|
- `go vet ./...` - Static analysis
|
||||||
|
|
||||||
|
### Development Tools
|
||||||
|
- `make devtools-all` - Build all development tools (bindgen, tsgen, scrapefuncs)
|
||||||
|
- `make jsupdate-all` - Update JavaScript bindings and TypeScript definitions
|
||||||
|
- `make docs` - Generate documentation
|
||||||
|
- `make memogen` - Generate memoization code for JavaScript libraries
|
||||||
|
|
||||||
|
### Testing Specific Components
|
||||||
|
- Run single test: `go test -v ./pkg/path/to/package -run TestName`
|
||||||
|
- Integration tests are in `integration_tests/` and can be run via `make integration`
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
- **cmd/nuclei** - Main CLI entry point with flag parsing and configuration
|
||||||
|
- **internal/runner** - Core runner that orchestrates the entire scanning process
|
||||||
|
- **pkg/core** - Execution engine with work pools and template clustering
|
||||||
|
- **pkg/templates** - Template parsing, compilation, and management
|
||||||
|
- **pkg/protocols** - Protocol implementations (HTTP, DNS, Network, etc.)
|
||||||
|
- **pkg/operators** - Matching and extraction logic (matchers/extractors)
|
||||||
|
- **pkg/catalog** - Template discovery and loading from disk/remote sources
|
||||||
|
|
||||||
|
### Protocol Architecture
|
||||||
|
Each protocol (HTTP, DNS, Network, etc.) implements:
|
||||||
|
- Request interface with Compile(), ExecuteWithResults(), Match(), Extract() methods
|
||||||
|
- Operators embedding for matching/extraction functionality
|
||||||
|
- Protocol-specific request building and execution logic
|
||||||
|
|
||||||
|
### Template System
|
||||||
|
- Templates are YAML files defining vulnerability detection logic
|
||||||
|
- Compiled into executable requests with operators (matchers/extractors)
|
||||||
|
- Support for workflows (multi-step template execution)
|
||||||
|
- Template clustering optimizes identical requests across multiple templates
|
||||||
|
|
||||||
|
### Key Execution Flow
|
||||||
|
1. Template loading and compilation via pkg/catalog/loader
|
||||||
|
2. Input provider setup for targets
|
||||||
|
3. Engine creation with work pools for concurrency
|
||||||
|
4. Template execution with result collection via operators
|
||||||
|
5. Output writing and reporting integration
|
||||||
|
|
||||||
|
### JavaScript Integration
|
||||||
|
- Custom JavaScript runtime for code protocol templates
|
||||||
|
- Auto-generated bindings in pkg/js/generated/
|
||||||
|
- Library implementations in pkg/js/libs/
|
||||||
|
- Development tools for binding generation in pkg/js/devtools/
|
||||||
|
|
||||||
|
## Template Development
|
||||||
|
- Templates located in separate nuclei-templates repository
|
||||||
|
- YAML format with info, requests, and operators sections
|
||||||
|
- Support for multiple protocol types in single template
|
||||||
|
- Built-in DSL functions for dynamic content generation
|
||||||
|
- Template validation available via `make template-validate`
|
||||||
|
|
||||||
|
## Key Directories
|
||||||
|
- **lib/** - SDK for embedding nuclei as a library
|
||||||
|
- **examples/** - Usage examples for different scenarios
|
||||||
|
- **integration_tests/** - Integration test suite with protocol-specific tests
|
||||||
|
- **pkg/fuzz/** - Fuzzing engine and DAST capabilities
|
||||||
|
- **pkg/input/** - Input processing for various formats (Burp, OpenAPI, etc.)
|
||||||
|
- **pkg/reporting/** - Result export and issue tracking integrations
|
||||||
4
Makefile
4
Makefile
@ -150,6 +150,10 @@ template-validate:
|
|||||||
-et .github/ \
|
-et .github/ \
|
||||||
-et helpers/payloads/ \
|
-et helpers/payloads/ \
|
||||||
-et http/technologies \
|
-et http/technologies \
|
||||||
|
-t dns \
|
||||||
|
-t ssl \
|
||||||
|
-t network \
|
||||||
|
-t http/exposures \
|
||||||
-ept code
|
-ept code
|
||||||
./bin/nuclei -validate \
|
./bin/nuclei -validate \
|
||||||
-w workflows \
|
-w workflows \
|
||||||
|
|||||||
104
cmd/integration-test/exporters.go
Normal file
104
cmd/integration-test/exporters.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/mongo"
|
||||||
|
"github.com/testcontainers/testcontainers-go"
|
||||||
|
mongocontainer "github.com/testcontainers/testcontainers-go/modules/mongodb"
|
||||||
|
|
||||||
|
osutil "github.com/projectdiscovery/utils/os"
|
||||||
|
mongoclient "go.mongodb.org/mongo-driver/mongo"
|
||||||
|
mongooptions "go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dbName = "test"
|
||||||
|
dbImage = "mongo:8"
|
||||||
|
)
|
||||||
|
|
||||||
|
var exportersTestCases = []TestCaseInfo{
|
||||||
|
{Path: "exporters/mongo", TestCase: &mongoExporter{}, DisableOn: func() bool {
|
||||||
|
return osutil.IsWindows() || osutil.IsOSX()
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
type mongoExporter struct{}
|
||||||
|
|
||||||
|
func (m *mongoExporter) Execute(filepath string) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Start a MongoDB container
|
||||||
|
mongodbContainer, err := mongocontainer.Run(ctx, dbImage)
|
||||||
|
defer func() {
|
||||||
|
if err := testcontainers.TerminateContainer(mongodbContainer); err != nil {
|
||||||
|
log.Printf("failed to terminate container: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to start container: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
connString, err := mongodbContainer.ConnectionString(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get connection string for MongoDB container: %s", err)
|
||||||
|
}
|
||||||
|
connString = connString + dbName
|
||||||
|
|
||||||
|
// Create a MongoDB exporter and write a test result to the database
|
||||||
|
opts := mongo.Options{
|
||||||
|
ConnectionString: connString,
|
||||||
|
CollectionName: "test",
|
||||||
|
BatchSize: 1, // Ensure we write the result immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
exporter, err := mongo.New(&opts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create MongoDB exporter: %s", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := exporter.Close(); err != nil {
|
||||||
|
fmt.Printf("failed to close exporter: %s\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
res := &output.ResultEvent{
|
||||||
|
Request: "test request",
|
||||||
|
Response: "test response",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = exporter.Export(res)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to export result event to MongoDB: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the result was written to the database
|
||||||
|
clientOptions := mongooptions.Client().ApplyURI(connString)
|
||||||
|
client, err := mongoclient.Connect(ctx, clientOptions)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating MongoDB client: %s", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := client.Disconnect(ctx); err != nil {
|
||||||
|
fmt.Printf("failed to disconnect from MongoDB: %s\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
collection := client.Database(dbName).Collection(opts.CollectionName)
|
||||||
|
var actualRes output.ResultEvent
|
||||||
|
err = collection.FindOne(ctx, map[string]interface{}{"request": res.Request}).Decode(&actualRes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find document in MongoDB: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualRes.Request != res.Request || actualRes.Response != res.Response {
|
||||||
|
return fmt.Errorf("exported result does not match expected result: got %v, want %v", actualRes, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -196,7 +196,7 @@ func (d *httpDefaultMatcherCondition) Execute(filePath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if routerErr != nil {
|
if routerErr != nil {
|
||||||
return errkit.Append(errkit.New("failed to send http request to interactsh server"), routerErr)
|
return errkit.Wrap(routerErr, "failed to send http request to interactsh server")
|
||||||
}
|
}
|
||||||
if err := expectResultsCount(results, 1); err != nil {
|
if err := expectResultsCount(results, 1); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -628,10 +628,10 @@ func (h *httpRawWithParams) Execute(filePath string) error {
|
|||||||
// we intentionally use params["test"] instead of params.Get("test") to test the case where
|
// we intentionally use params["test"] instead of params.Get("test") to test the case where
|
||||||
// there are multiple parameters with the same name
|
// there are multiple parameters with the same name
|
||||||
if !reflect.DeepEqual(params["key1"], []string{"value1"}) {
|
if !reflect.DeepEqual(params["key1"], []string{"value1"}) {
|
||||||
errx = errkit.Append(errkit.New(fmt.Sprintf("expected %v, got %v", []string{"value1"}, params["key1"])), errx)
|
errx = errkit.Append(errx, errkit.New("key1 not found in params", "expected", []string{"value1"}, "got", params["key1"]))
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(params["key2"], []string{"value2"}) {
|
if !reflect.DeepEqual(params["key2"], []string{"value2"}) {
|
||||||
errx = errkit.Append(errkit.New(fmt.Sprintf("expected %v, got %v", []string{"value2"}, params["key2"])), errx)
|
errx = errkit.Append(errx, errkit.New("key2 not found in params", "expected", []string{"value2"}, "got", params["key2"]))
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(w, "Test is test raw-params-matcher text")
|
_, _ = fmt.Fprintf(w, "Test is test raw-params-matcher text")
|
||||||
})
|
})
|
||||||
@ -971,10 +971,10 @@ func (h *httpRequestSelfContainedWithParams) Execute(filePath string) error {
|
|||||||
// we intentionally use params["test"] instead of params.Get("test") to test the case where
|
// we intentionally use params["test"] instead of params.Get("test") to test the case where
|
||||||
// there are multiple parameters with the same name
|
// there are multiple parameters with the same name
|
||||||
if !reflect.DeepEqual(params["something"], []string{"here"}) {
|
if !reflect.DeepEqual(params["something"], []string{"here"}) {
|
||||||
errx = errkit.Append(errkit.New(fmt.Sprintf("expected %v, got %v", []string{"here"}, params["something"])), errx)
|
errx = errkit.Append(errx, errkit.New("something not found in params", "expected", []string{"here"}, "got", params["something"]))
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(params["key"], []string{"value"}) {
|
if !reflect.DeepEqual(params["key"], []string{"value"}) {
|
||||||
errx = errkit.Append(errkit.New(fmt.Sprintf("expected %v, got %v", []string{"value"}, params["key"])), errx)
|
errx = errkit.Append(errx, errkit.New("key not found in params", "expected", []string{"value"}, "got", params["key"]))
|
||||||
}
|
}
|
||||||
_, _ = w.Write([]byte("This is self-contained response"))
|
_, _ = w.Write([]byte("This is self-contained response"))
|
||||||
})
|
})
|
||||||
@ -1027,10 +1027,10 @@ func (h *httpRequestSelfContainedFileInput) Execute(filePath string) error {
|
|||||||
// create temp file
|
// create temp file
|
||||||
FileLoc, err := os.CreateTemp("", "self-contained-payload-*.txt")
|
FileLoc, err := os.CreateTemp("", "self-contained-payload-*.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("failed to create temp file"), err)
|
return errkit.Wrap(err, "failed to create temp file")
|
||||||
}
|
}
|
||||||
if _, err := FileLoc.Write([]byte("one\ntwo\n")); err != nil {
|
if _, err := FileLoc.Write([]byte("one\ntwo\n")); err != nil {
|
||||||
return errkit.Append(errkit.New("failed to write payload to temp file"), err)
|
return errkit.Wrap(err, "failed to write payload to temp file")
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = FileLoc.Close()
|
_ = FileLoc.Close()
|
||||||
@ -1046,7 +1046,7 @@ func (h *httpRequestSelfContainedFileInput) Execute(filePath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !sliceutil.ElementsMatch(gotReqToEndpoints, []string{"/one", "/two", "/one", "/two"}) {
|
if !sliceutil.ElementsMatch(gotReqToEndpoints, []string{"/one", "/two", "/one", "/two"}) {
|
||||||
return errkit.New(fmt.Sprintf("%s: expected requests to be sent to `/one` and `/two` endpoints but were sent to `%v`", filePath, gotReqToEndpoints)).Build()
|
return errkit.New("expected requests to be sent to `/one` and `/two` endpoints but were sent to `%v`", gotReqToEndpoints, "filePath", filePath)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,6 +57,7 @@ var (
|
|||||||
"flow": flowTestcases,
|
"flow": flowTestcases,
|
||||||
"javascript": jsTestcases,
|
"javascript": jsTestcases,
|
||||||
"matcher-status": matcherStatusTestcases,
|
"matcher-status": matcherStatusTestcases,
|
||||||
|
"exporters": exportersTestCases,
|
||||||
}
|
}
|
||||||
// flakyTests are run with a retry count of 3
|
// flakyTests are run with a retry count of 3
|
||||||
flakyTests = map[string]bool{
|
flakyTests = map[string]bool{
|
||||||
|
|||||||
@ -15,13 +15,17 @@ var jsTestcases = []TestCaseInfo{
|
|||||||
{Path: "protocols/javascript/ssh-server-fingerprint.yaml", TestCase: &javascriptSSHServerFingerprint{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
|
{Path: "protocols/javascript/ssh-server-fingerprint.yaml", TestCase: &javascriptSSHServerFingerprint{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
|
||||||
{Path: "protocols/javascript/net-multi-step.yaml", TestCase: &networkMultiStep{}},
|
{Path: "protocols/javascript/net-multi-step.yaml", TestCase: &networkMultiStep{}},
|
||||||
{Path: "protocols/javascript/net-https.yaml", TestCase: &javascriptNetHttps{}},
|
{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{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
redisResource *dockertest.Resource
|
redisResource *dockertest.Resource
|
||||||
sshResource *dockertest.Resource
|
sshResource *dockertest.Resource
|
||||||
pool *dockertest.Pool
|
oracleResource *dockertest.Resource
|
||||||
defaultRetry = 3
|
vncResource *dockertest.Resource
|
||||||
|
pool *dockertest.Pool
|
||||||
|
defaultRetry = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
type javascriptNetHttps struct{}
|
type javascriptNetHttps struct{}
|
||||||
@ -98,6 +102,71 @@ func (j *javascriptSSHServerFingerprint) Execute(filePath string) error {
|
|||||||
return multierr.Combine(errs...)
|
return multierr.Combine(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type javascriptOracleAuthTest struct{}
|
||||||
|
|
||||||
|
func (j *javascriptOracleAuthTest) Execute(filePath string) error {
|
||||||
|
if oracleResource == nil || pool == nil {
|
||||||
|
// skip test as oracle is not running
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tempPort := oracleResource.GetPort("1521/tcp")
|
||||||
|
finalURL := "localhost:" + tempPort
|
||||||
|
defer purge(oracleResource)
|
||||||
|
|
||||||
|
errs := []error{}
|
||||||
|
for i := 0; i < defaultRetry; i++ {
|
||||||
|
results := []string{}
|
||||||
|
var err error
|
||||||
|
_ = pool.Retry(func() error {
|
||||||
|
//let ssh 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 javascriptVncPassBrute struct{}
|
||||||
|
|
||||||
|
func (j *javascriptVncPassBrute) Execute(filePath string) error {
|
||||||
|
if vncResource == nil || pool == nil {
|
||||||
|
// skip test as vnc is not running
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tempPort := vncResource.GetPort("5900/tcp")
|
||||||
|
finalURL := "localhost:" + tempPort
|
||||||
|
defer purge(vncResource)
|
||||||
|
errs := []error{}
|
||||||
|
for i := 0; i < defaultRetry; i++ {
|
||||||
|
results := []string{}
|
||||||
|
var err error
|
||||||
|
_ = pool.Retry(func() error {
|
||||||
|
//let ssh 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...)
|
||||||
|
}
|
||||||
|
|
||||||
// purge any given resource if it is not nil
|
// purge any given resource if it is not nil
|
||||||
func purge(resource *dockertest.Resource) {
|
func purge(resource *dockertest.Resource) {
|
||||||
if resource != nil && pool != nil {
|
if resource != nil && pool != nil {
|
||||||
@ -163,4 +232,41 @@ func init() {
|
|||||||
if err := sshResource.Expire(30); err != nil {
|
if err := sshResource.Expire(30); err != nil {
|
||||||
log.Printf("Could not expire resource: %s", err)
|
log.Printf("Could not expire resource: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setup a temporary oracle instance
|
||||||
|
oracleResource, err = pool.RunWithOptions(&dockertest.RunOptions{
|
||||||
|
Repository: "gvenzl/oracle-xe",
|
||||||
|
Tag: "latest",
|
||||||
|
Env: []string{
|
||||||
|
"ORACLE_PASSWORD=mysecret",
|
||||||
|
},
|
||||||
|
Platform: "linux/amd64",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not start Oracle resource: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// by default expire after 30 sec
|
||||||
|
if err := oracleResource.Expire(30); err != nil {
|
||||||
|
log.Printf("Could not expire Oracle resource: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup a temporary vnc server
|
||||||
|
vncResource, err = pool.RunWithOptions(&dockertest.RunOptions{
|
||||||
|
Repository: "dorowu/ubuntu-desktop-lxde-vnc",
|
||||||
|
Tag: "latest",
|
||||||
|
Env: []string{
|
||||||
|
"VNC_PASSWORD=mysecret",
|
||||||
|
},
|
||||||
|
Platform: "linux/amd64",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not start resource: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// by default expire after 30 sec
|
||||||
|
if err := vncResource.Expire(30); err != nil {
|
||||||
|
log.Printf("Could not expire resource: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -223,7 +223,7 @@ type loadTemplateWithID struct{}
|
|||||||
func (h *loadTemplateWithID) Execute(nooop string) error {
|
func (h *loadTemplateWithID) Execute(nooop string) error {
|
||||||
results, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", "scanme.sh", "-id", "self-signed-ssl")
|
results, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", "scanme.sh", "-id", "self-signed-ssl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("failed to load template with id"), err)
|
return errkit.Wrap(err, "failed to load template with id")
|
||||||
}
|
}
|
||||||
return expectResultsCount(results, 1)
|
return expectResultsCount(results, 1)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ type profileLoaderByRelFile struct{}
|
|||||||
func (h *profileLoaderByRelFile) Execute(testName string) error {
|
func (h *profileLoaderByRelFile) Execute(testName string) error {
|
||||||
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-tl", "-tp", "cloud.yml")
|
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-tl", "-tp", "cloud.yml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("failed to load template with id"), err)
|
return errkit.Wrap(err, "failed to load template with id")
|
||||||
}
|
}
|
||||||
if len(results) <= 10 {
|
if len(results) <= 10 {
|
||||||
return fmt.Errorf("incorrect result: expected more results than %d, got %v", 10, len(results))
|
return fmt.Errorf("incorrect result: expected more results than %d, got %v", 10, len(results))
|
||||||
@ -31,7 +31,7 @@ type profileLoaderById struct{}
|
|||||||
func (h *profileLoaderById) Execute(testName string) error {
|
func (h *profileLoaderById) Execute(testName string) error {
|
||||||
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-tl", "-tp", "cloud")
|
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-tl", "-tp", "cloud")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("failed to load template with id"), err)
|
return errkit.Wrap(err, "failed to load template with id")
|
||||||
}
|
}
|
||||||
if len(results) <= 10 {
|
if len(results) <= 10 {
|
||||||
return fmt.Errorf("incorrect result: expected more results than %d, got %v", 10, len(results))
|
return fmt.Errorf("incorrect result: expected more results than %d, got %v", 10, len(results))
|
||||||
@ -45,7 +45,7 @@ type customProfileLoader struct{}
|
|||||||
func (h *customProfileLoader) Execute(filepath string) error {
|
func (h *customProfileLoader) Execute(filepath string) error {
|
||||||
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-tl", "-tp", filepath)
|
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-tl", "-tp", filepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("failed to load template with id"), err)
|
return errkit.Wrap(err, "failed to load template with id")
|
||||||
}
|
}
|
||||||
if len(results) < 1 {
|
if len(results) < 1 {
|
||||||
return fmt.Errorf("incorrect result: expected more results than %d, got %v", 1, len(results))
|
return fmt.Errorf("incorrect result: expected more results than %d, got %v", 1, len(results))
|
||||||
|
|||||||
@ -17,7 +17,7 @@ type templateDirWithTargetTest struct{}
|
|||||||
func (h *templateDirWithTargetTest) Execute(filePath string) error {
|
func (h *templateDirWithTargetTest) Execute(filePath string) error {
|
||||||
tempdir, err := os.MkdirTemp("", "nuclei-update-dir-*")
|
tempdir, err := os.MkdirTemp("", "nuclei-update-dir-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("failed to create temp dir"), err)
|
return errkit.Wrap(err, "failed to create temp dir")
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = os.RemoveAll(tempdir)
|
_ = os.RemoveAll(tempdir)
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import (
|
|||||||
_ "github.com/projectdiscovery/utils/pprof"
|
_ "github.com/projectdiscovery/utils/pprof"
|
||||||
stringsutil "github.com/projectdiscovery/utils/strings"
|
stringsutil "github.com/projectdiscovery/utils/strings"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/projectdiscovery/goflags"
|
"github.com/projectdiscovery/goflags"
|
||||||
"github.com/projectdiscovery/gologger/levels"
|
"github.com/projectdiscovery/gologger/levels"
|
||||||
@ -187,7 +188,7 @@ func main() {
|
|||||||
options.Logger.Info().Msgf("Creating resume file: %s\n", resumeFileName)
|
options.Logger.Info().Msgf("Creating resume file: %s\n", resumeFileName)
|
||||||
err := nucleiRunner.SaveResumeConfig(resumeFileName)
|
err := nucleiRunner.SaveResumeConfig(resumeFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("couldn't create crash resume file"), err)
|
return errkit.Wrap(err, "couldn't create crash resume file")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -263,6 +264,8 @@ on extensive configurability, massive extensibility and ease of use.`)
|
|||||||
flagSet.StringVarP(&options.InputFileMode, "input-mode", "im", "list", fmt.Sprintf("mode of input file (%v)", provider.SupportedInputFormats())),
|
flagSet.StringVarP(&options.InputFileMode, "input-mode", "im", "list", fmt.Sprintf("mode of input file (%v)", provider.SupportedInputFormats())),
|
||||||
flagSet.BoolVarP(&options.FormatUseRequiredOnly, "required-only", "ro", false, "use only required fields in input format when generating requests"),
|
flagSet.BoolVarP(&options.FormatUseRequiredOnly, "required-only", "ro", false, "use only required fields in input format when generating requests"),
|
||||||
flagSet.BoolVarP(&options.SkipFormatValidation, "skip-format-validation", "sfv", false, "skip format validation (like missing vars) when parsing input file"),
|
flagSet.BoolVarP(&options.SkipFormatValidation, "skip-format-validation", "sfv", false, "skip format validation (like missing vars) when parsing input file"),
|
||||||
|
flagSet.BoolVarP(&options.VarsTextTemplating, "vars-text-templating", "vtt", false, "enable text templating for vars in input file (only for yaml input mode)"),
|
||||||
|
flagSet.StringSliceVarP(&options.VarsFilePaths, "var-file-paths", "vfp", nil, "list of yaml file contained vars to inject into yaml input", goflags.CommaSeparatedStringSliceOptions),
|
||||||
)
|
)
|
||||||
|
|
||||||
flagSet.CreateGroup("templates", "Templates",
|
flagSet.CreateGroup("templates", "Templates",
|
||||||
@ -571,6 +574,7 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started
|
|||||||
config.DefaultConfig.SetConfigDir(customConfigDir)
|
config.DefaultConfig.SetConfigDir(customConfigDir)
|
||||||
readFlagsConfig(flagSet)
|
readFlagsConfig(flagSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
if !fileutil.FileExists(cfgFile) {
|
if !fileutil.FileExists(cfgFile) {
|
||||||
options.Logger.Fatal().Msgf("given config file '%s' does not exist", cfgFile)
|
options.Logger.Fatal().Msgf("given config file '%s' does not exist", cfgFile)
|
||||||
@ -579,6 +583,41 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started
|
|||||||
if err := flagSet.MergeConfigFile(cfgFile); err != nil {
|
if err := flagSet.MergeConfigFile(cfgFile); err != nil {
|
||||||
options.Logger.Fatal().Msgf("Could not read config: %s\n", err)
|
options.Logger.Fatal().Msgf("Could not read config: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !options.Vars.IsEmpty() {
|
||||||
|
// Maybe we should add vars to the config file as well even if they are set via flags?
|
||||||
|
file, err := os.Open(cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
gologger.Fatal().Msgf("Could not open config file: %s\n", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = file.Close()
|
||||||
|
}()
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
err = yaml.NewDecoder(file).Decode(&data)
|
||||||
|
if err != nil {
|
||||||
|
gologger.Fatal().Msgf("Could not decode config file: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
variables := data["var"]
|
||||||
|
if variables != nil {
|
||||||
|
if varSlice, ok := variables.([]interface{}); ok {
|
||||||
|
for _, value := range varSlice {
|
||||||
|
if strVal, ok := value.(string); ok {
|
||||||
|
err = options.Vars.Set(strVal)
|
||||||
|
if err != nil {
|
||||||
|
gologger.Warning().Msgf("Could not set variable from config file: %s\n", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gologger.Warning().Msgf("Skipping non-string variable in config: %#v", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gologger.Warning().Msgf("No 'var' section found in config file: %s", cfgFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if options.NewTemplatesDirectory != "" {
|
if options.NewTemplatesDirectory != "" {
|
||||||
config.DefaultConfig.SetTemplatesDir(options.NewTemplatesDirectory)
|
config.DefaultConfig.SetTemplatesDir(options.NewTemplatesDirectory)
|
||||||
|
|||||||
@ -243,7 +243,7 @@ func enhanceTemplate(data string) (string, bool, error) {
|
|||||||
return data, false, err
|
return data, false, err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
return data, false, errkit.New(fmt.Sprintf("unexpected status code: %v", resp.Status)).Build()
|
return data, false, errkit.New("unexpected status code: %v", resp.Status)
|
||||||
}
|
}
|
||||||
var templateResp TemplateResp
|
var templateResp TemplateResp
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&templateResp); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&templateResp); err != nil {
|
||||||
@ -254,20 +254,20 @@ func enhanceTemplate(data string) (string, bool, error) {
|
|||||||
}
|
}
|
||||||
if templateResp.ValidateErrorCount > 0 {
|
if templateResp.ValidateErrorCount > 0 {
|
||||||
if len(templateResp.ValidateError) > 0 {
|
if len(templateResp.ValidateError) > 0 {
|
||||||
return data, false, errkit.New(fmt.Sprintf("validate: %s: at line %v", templateResp.ValidateError[0].Message, templateResp.ValidateError[0].Mark.Line)).Build()
|
return data, false, errkit.New(templateResp.ValidateError[0].Message+": at line %v", templateResp.ValidateError[0].Mark.Line, "tag", "validate")
|
||||||
}
|
}
|
||||||
return data, false, errkit.New("validate: validation failed").Build()
|
return data, false, errkit.New("validation failed", "tag", "validate")
|
||||||
}
|
}
|
||||||
if templateResp.Error.Name != "" {
|
if templateResp.Error.Name != "" {
|
||||||
return data, false, errkit.New(templateResp.Error.Name).Build()
|
return data, false, errkit.New("%s", templateResp.Error.Name)
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(templateResp.Enhanced) == "" && !templateResp.Lint {
|
if strings.TrimSpace(templateResp.Enhanced) == "" && !templateResp.Lint {
|
||||||
if templateResp.LintError.Reason != "" {
|
if templateResp.LintError.Reason != "" {
|
||||||
return data, false, errkit.New(fmt.Sprintf("lint: %s : at line %v", templateResp.LintError.Reason, templateResp.LintError.Mark.Line)).Build()
|
return data, false, errkit.New(templateResp.LintError.Reason+" : at line %v", templateResp.LintError.Mark.Line, "tag", "lint")
|
||||||
}
|
}
|
||||||
return data, false, errkit.New(fmt.Sprintf("lint: at line: %v", templateResp.LintError.Mark.Line)).Build()
|
return data, false, errkit.New("at line: %v", templateResp.LintError.Mark.Line, "tag", "lint")
|
||||||
}
|
}
|
||||||
return data, false, errkit.New("template enhance failed").Build()
|
return data, false, errkit.New("template enhance failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatTemplate formats template data using templateman format api
|
// formatTemplate formats template data using templateman format api
|
||||||
@ -277,7 +277,7 @@ func formatTemplate(data string) (string, bool, error) {
|
|||||||
return data, false, err
|
return data, false, err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
return data, false, errkit.New(fmt.Sprintf("unexpected status code: %v", resp.Status)).Build()
|
return data, false, errkit.New("unexpected status code: %v", resp.Status)
|
||||||
}
|
}
|
||||||
var templateResp TemplateResp
|
var templateResp TemplateResp
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&templateResp); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&templateResp); err != nil {
|
||||||
@ -288,20 +288,20 @@ func formatTemplate(data string) (string, bool, error) {
|
|||||||
}
|
}
|
||||||
if templateResp.ValidateErrorCount > 0 {
|
if templateResp.ValidateErrorCount > 0 {
|
||||||
if len(templateResp.ValidateError) > 0 {
|
if len(templateResp.ValidateError) > 0 {
|
||||||
return data, false, errkit.New(fmt.Sprintf("validate: %s: at line %v", templateResp.ValidateError[0].Message, templateResp.ValidateError[0].Mark.Line)).Build()
|
return data, false, errkit.New(templateResp.ValidateError[0].Message+": at line %v", templateResp.ValidateError[0].Mark.Line, "tag", "validate")
|
||||||
}
|
}
|
||||||
return data, false, errkit.New("validate: validation failed").Build()
|
return data, false, errkit.New("validation failed", "tag", "validate")
|
||||||
}
|
}
|
||||||
if templateResp.Error.Name != "" {
|
if templateResp.Error.Name != "" {
|
||||||
return data, false, errkit.New(templateResp.Error.Name).Build()
|
return data, false, errkit.New("%s", templateResp.Error.Name)
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(templateResp.Updated) == "" && !templateResp.Lint {
|
if strings.TrimSpace(templateResp.Updated) == "" && !templateResp.Lint {
|
||||||
if templateResp.LintError.Reason != "" {
|
if templateResp.LintError.Reason != "" {
|
||||||
return data, false, errkit.New(fmt.Sprintf("lint: %s : at line %v", templateResp.LintError.Reason, templateResp.LintError.Mark.Line)).Build()
|
return data, false, errkit.New(templateResp.LintError.Reason+" : at line %v", templateResp.LintError.Mark.Line, "tag", "lint")
|
||||||
}
|
}
|
||||||
return data, false, errkit.New(fmt.Sprintf("lint: at line: %v", templateResp.LintError.Mark.Line)).Build()
|
return data, false, errkit.New("at line: %v", templateResp.LintError.Mark.Line, "tag", "lint")
|
||||||
}
|
}
|
||||||
return data, false, errkit.New("template format failed").Build()
|
return data, false, errkit.New("template format failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// lintTemplate lints template data using templateman lint api
|
// lintTemplate lints template data using templateman lint api
|
||||||
@ -311,7 +311,7 @@ func lintTemplate(data string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
return false, errkit.New(fmt.Sprintf("unexpected status code: %v", resp.Status)).Build()
|
return false, errkit.New("unexpected status code: %v", resp.Status)
|
||||||
}
|
}
|
||||||
var lintResp TemplateLintResp
|
var lintResp TemplateLintResp
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&lintResp); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&lintResp); err != nil {
|
||||||
@ -321,9 +321,9 @@ func lintTemplate(data string) (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
if lintResp.LintError.Reason != "" {
|
if lintResp.LintError.Reason != "" {
|
||||||
return false, errkit.New(fmt.Sprintf("lint: %s : at line %v", lintResp.LintError.Reason, lintResp.LintError.Mark.Line)).Build()
|
return false, errkit.New(lintResp.LintError.Reason+" : at line %v", lintResp.LintError.Mark.Line, "tag", "lint")
|
||||||
}
|
}
|
||||||
return false, errkit.New(fmt.Sprintf("lint: at line: %v", lintResp.LintError.Mark.Line)).Build()
|
return false, errkit.New("at line: %v", lintResp.LintError.Mark.Line, "tag", "lint")
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateTemplate validates template data using templateman validate api
|
// validateTemplate validates template data using templateman validate api
|
||||||
@ -333,7 +333,7 @@ func validateTemplate(data string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
return false, errkit.New(fmt.Sprintf("unexpected status code: %v", resp.Status)).Build()
|
return false, errkit.New("unexpected status code: %v", resp.Status)
|
||||||
}
|
}
|
||||||
var validateResp TemplateResp
|
var validateResp TemplateResp
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&validateResp); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&validateResp); err != nil {
|
||||||
@ -344,14 +344,14 @@ func validateTemplate(data string) (bool, error) {
|
|||||||
}
|
}
|
||||||
if validateResp.ValidateErrorCount > 0 {
|
if validateResp.ValidateErrorCount > 0 {
|
||||||
if len(validateResp.ValidateError) > 0 {
|
if len(validateResp.ValidateError) > 0 {
|
||||||
return false, errkit.New(fmt.Sprintf("validate: %s: at line %v", validateResp.ValidateError[0].Message, validateResp.ValidateError[0].Mark.Line)).Build()
|
return false, errkit.New(validateResp.ValidateError[0].Message+": at line %v", validateResp.ValidateError[0].Mark.Line, "tag", "validate")
|
||||||
}
|
}
|
||||||
return false, errkit.New("validate: validation failed").Build()
|
return false, errkit.New("validation failed", "tag", "validate")
|
||||||
}
|
}
|
||||||
if validateResp.Error.Name != "" {
|
if validateResp.Error.Name != "" {
|
||||||
return false, errkit.New(validateResp.Error.Name).Build()
|
return false, errkit.New("%s", validateResp.Error.Name)
|
||||||
}
|
}
|
||||||
return false, errkit.New("template validation failed").Build()
|
return false, errkit.New("template validation failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAndAddMaxRequests parses and adds max requests to templates
|
// parseAndAddMaxRequests parses and adds max requests to templates
|
||||||
|
|||||||
136
go.mod
136
go.mod
@ -1,6 +1,8 @@
|
|||||||
module github.com/projectdiscovery/nuclei/v3
|
module github.com/projectdiscovery/nuclei/v3
|
||||||
|
|
||||||
go 1.24.1
|
go 1.24.2
|
||||||
|
|
||||||
|
toolchain go1.24.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible
|
||||||
@ -20,12 +22,12 @@ require (
|
|||||||
github.com/olekukonko/tablewriter v1.0.8
|
github.com/olekukonko/tablewriter v1.0.8
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/projectdiscovery/clistats v0.1.1
|
github.com/projectdiscovery/clistats v0.1.1
|
||||||
github.com/projectdiscovery/fastdialer v0.4.4
|
github.com/projectdiscovery/fastdialer v0.4.9
|
||||||
github.com/projectdiscovery/hmap v0.0.92
|
github.com/projectdiscovery/hmap v0.0.93
|
||||||
github.com/projectdiscovery/interactsh v1.2.4
|
github.com/projectdiscovery/interactsh v1.2.4
|
||||||
github.com/projectdiscovery/rawhttp v0.1.90
|
github.com/projectdiscovery/rawhttp v0.1.90
|
||||||
github.com/projectdiscovery/retryabledns v1.0.105
|
github.com/projectdiscovery/retryabledns v1.0.107
|
||||||
github.com/projectdiscovery/retryablehttp-go v1.0.119
|
github.com/projectdiscovery/retryablehttp-go v1.0.123
|
||||||
github.com/projectdiscovery/yamldoc-go v1.0.6
|
github.com/projectdiscovery/yamldoc-go v1.0.6
|
||||||
github.com/remeh/sizedwaitgroup v1.0.0
|
github.com/remeh/sizedwaitgroup v1.0.0
|
||||||
github.com/rs/xid v1.6.0
|
github.com/rs/xid v1.6.0
|
||||||
@ -35,26 +37,27 @@ require (
|
|||||||
github.com/spf13/cast v1.9.2
|
github.com/spf13/cast v1.9.2
|
||||||
github.com/syndtr/goleveldb v1.0.0
|
github.com/syndtr/goleveldb v1.0.0
|
||||||
github.com/valyala/fasttemplate v1.2.2
|
github.com/valyala/fasttemplate v1.2.2
|
||||||
github.com/weppos/publicsuffix-go v0.40.3-0.20250408071509-6074bbe7fd39
|
github.com/weppos/publicsuffix-go v0.50.0
|
||||||
go.uber.org/multierr v1.11.0
|
go.uber.org/multierr v1.11.0
|
||||||
golang.org/x/net v0.41.0
|
golang.org/x/net v0.43.0
|
||||||
golang.org/x/oauth2 v0.30.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
golang.org/x/text v0.26.0
|
golang.org/x/text v0.29.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.gitea.io/sdk/gitea v0.21.0
|
carvel.dev/ytt v0.52.0
|
||||||
|
code.gitea.io/sdk/gitea v0.17.0
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0
|
||||||
github.com/DataDog/gostackparse v0.7.0
|
github.com/DataDog/gostackparse v0.7.0
|
||||||
github.com/Masterminds/semver/v3 v3.4.0
|
github.com/Masterminds/semver/v3 v3.2.1
|
||||||
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057
|
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057
|
||||||
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697
|
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697
|
||||||
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883
|
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883
|
||||||
github.com/alitto/pond v1.9.2
|
github.com/alitto/pond v1.9.2
|
||||||
github.com/antchfx/xmlquery v1.4.4
|
github.com/antchfx/xmlquery v1.4.4
|
||||||
github.com/antchfx/xpath v1.3.4
|
github.com/antchfx/xpath v1.3.3
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||||
github.com/aws/aws-sdk-go-v2 v1.36.5
|
github.com/aws/aws-sdk-go-v2 v1.36.5
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.29.17
|
github.com/aws/aws-sdk-go-v2/config v1.29.17
|
||||||
@ -87,49 +90,51 @@ require (
|
|||||||
github.com/microsoft/go-mssqldb v1.9.2
|
github.com/microsoft/go-mssqldb v1.9.2
|
||||||
github.com/ory/dockertest/v3 v3.12.0
|
github.com/ory/dockertest/v3 v3.12.0
|
||||||
github.com/praetorian-inc/fingerprintx v1.1.15
|
github.com/praetorian-inc/fingerprintx v1.1.15
|
||||||
github.com/projectdiscovery/dsl v0.5.0
|
github.com/projectdiscovery/dsl v0.6.0
|
||||||
github.com/projectdiscovery/fasttemplate v0.0.2
|
github.com/projectdiscovery/fasttemplate v0.0.2
|
||||||
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c
|
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c
|
||||||
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb
|
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb
|
||||||
github.com/projectdiscovery/goflags v0.1.74
|
github.com/projectdiscovery/goflags v0.1.74
|
||||||
github.com/projectdiscovery/gologger v1.1.54
|
github.com/projectdiscovery/gologger v1.1.54
|
||||||
github.com/projectdiscovery/gostruct v0.0.2
|
github.com/projectdiscovery/gostruct v0.0.2
|
||||||
github.com/projectdiscovery/gozero v0.0.3
|
github.com/projectdiscovery/gozero v0.1.0
|
||||||
github.com/projectdiscovery/httpx v1.7.0
|
github.com/projectdiscovery/httpx v1.7.2-0.20250911192144-fc425deb041a
|
||||||
github.com/projectdiscovery/mapcidr v1.1.34
|
github.com/projectdiscovery/mapcidr v1.1.34
|
||||||
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
|
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
|
||||||
github.com/projectdiscovery/networkpolicy v0.1.18
|
github.com/projectdiscovery/networkpolicy v0.1.23
|
||||||
github.com/projectdiscovery/ratelimit v0.0.81
|
github.com/projectdiscovery/ratelimit v0.0.82
|
||||||
github.com/projectdiscovery/rdap v0.9.0
|
github.com/projectdiscovery/rdap v0.9.0
|
||||||
github.com/projectdiscovery/sarif v0.0.1
|
github.com/projectdiscovery/sarif v0.0.1
|
||||||
github.com/projectdiscovery/tlsx v1.1.9
|
github.com/projectdiscovery/tlsx v1.2.1
|
||||||
github.com/projectdiscovery/uncover v1.1.0
|
github.com/projectdiscovery/uncover v1.1.0
|
||||||
github.com/projectdiscovery/useragent v0.0.101
|
github.com/projectdiscovery/useragent v0.0.101
|
||||||
github.com/projectdiscovery/utils v0.4.23
|
github.com/projectdiscovery/utils v0.5.0
|
||||||
github.com/projectdiscovery/wappalyzergo v0.2.36
|
github.com/projectdiscovery/wappalyzergo v0.2.45
|
||||||
github.com/redis/go-redis/v9 v9.11.0
|
github.com/redis/go-redis/v9 v9.11.0
|
||||||
github.com/seh-msft/burpxml v1.0.1
|
github.com/seh-msft/burpxml v1.0.1
|
||||||
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466
|
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/sijms/go-ora/v2 v2.9.0
|
||||||
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9
|
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9
|
||||||
|
github.com/testcontainers/testcontainers-go v0.38.0
|
||||||
|
github.com/testcontainers/testcontainers-go/modules/mongodb v0.37.0
|
||||||
github.com/yassinebenaid/godump v0.11.1
|
github.com/yassinebenaid/godump v0.11.1
|
||||||
github.com/zmap/zgrab2 v0.1.8
|
github.com/zmap/zgrab2 v0.1.8
|
||||||
gitlab.com/gitlab-org/api/client-go v0.130.1
|
gitlab.com/gitlab-org/api/client-go v0.130.1
|
||||||
go.mongodb.org/mongo-driver v1.17.4
|
go.mongodb.org/mongo-driver v1.17.4
|
||||||
golang.org/x/term v0.32.0
|
golang.org/x/term v0.34.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
moul.io/http2curl v1.0.0
|
moul.io/http2curl v1.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
aead.dev/minisign v0.2.0 // indirect
|
aead.dev/minisign v0.2.0 // indirect
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
dario.cat/mergo v1.0.2 // indirect
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a // indirect
|
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a // indirect
|
||||||
github.com/42wim/httpsig v1.2.2 // indirect
|
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
@ -144,6 +149,7 @@ require (
|
|||||||
github.com/alecthomas/kingpin/v2 v2.4.0 // indirect
|
github.com/alecthomas/kingpin/v2 v2.4.0 // indirect
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||||
|
github.com/alexsnet/go-vnc v0.1.0 // indirect
|
||||||
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect
|
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect
|
||||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
|
||||||
@ -179,27 +185,35 @@ require (
|
|||||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
|
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
github.com/cheggaaa/pb/v3 v3.1.4 // indirect
|
github.com/cheggaaa/pb/v3 v3.1.6 // indirect
|
||||||
github.com/cloudflare/cfssl v1.6.4 // indirect
|
github.com/cloudflare/cfssl v1.6.4 // indirect
|
||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect
|
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect
|
||||||
github.com/containerd/continuity v0.4.5 // indirect
|
github.com/containerd/continuity v0.4.5 // indirect
|
||||||
|
github.com/containerd/errdefs v1.0.0 // indirect
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||||
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
|
github.com/containerd/platforms v0.2.1 // indirect
|
||||||
|
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
github.com/docker/cli v27.4.1+incompatible // indirect
|
github.com/docker/cli v27.4.1+incompatible // indirect
|
||||||
github.com/docker/docker v28.0.0+incompatible // indirect
|
github.com/docker/docker v28.3.3+incompatible // indirect
|
||||||
github.com/docker/go-connections v0.5.0 // indirect
|
github.com/docker/go-connections v0.6.0 // indirect
|
||||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||||
|
github.com/ebitengine/purego v0.8.4 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
github.com/felixge/fgprof v0.9.5 // indirect
|
github.com/felixge/fgprof v0.9.5 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect
|
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/gaissmai/bart v0.20.5 // indirect
|
github.com/gaissmai/bart v0.24.0 // indirect
|
||||||
github.com/geoffgarside/ber v1.1.0 // indirect
|
github.com/geoffgarside/ber v1.1.0 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||||
@ -207,20 +221,22 @@ require (
|
|||||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
github.com/go-openapi/swag v0.23.0 // indirect
|
github.com/go-openapi/swag v0.23.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/certificate-transparency-go v1.1.4 // indirect
|
github.com/google/certificate-transparency-go v1.3.2 // indirect
|
||||||
github.com/google/go-github/v30 v30.1.0 // indirect
|
github.com/google/go-github/v30 v30.1.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
|
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
@ -243,6 +259,7 @@ require (
|
|||||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 // indirect
|
||||||
github.com/kataras/jwt v0.1.10 // indirect
|
github.com/kataras/jwt v0.1.10 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
@ -255,8 +272,9 @@ require (
|
|||||||
github.com/logrusorgru/aurora/v4 v4.0.0 // indirect
|
github.com/logrusorgru/aurora/v4 v4.0.0 // indirect
|
||||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 // indirect
|
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20250821153705-5981dea3221d // indirect
|
||||||
github.com/mackerelio/go-osstat v0.2.4 // indirect
|
github.com/mackerelio/go-osstat v0.2.4 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.10 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
@ -268,12 +286,17 @@ require (
|
|||||||
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect
|
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
github.com/moby/sys/user v0.3.0 // indirect
|
github.com/moby/go-archive v0.1.0 // indirect
|
||||||
github.com/moby/term v0.5.0 // indirect
|
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||||
|
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||||
|
github.com/moby/sys/user v0.4.0 // indirect
|
||||||
|
github.com/moby/sys/userns v0.1.0 // indirect
|
||||||
|
github.com/moby/term v0.5.2 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/muesli/reflow v0.3.0 // indirect
|
github.com/muesli/reflow v0.3.0 // indirect
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
github.com/nwaples/rardecode/v2 v2.1.0 // indirect
|
github.com/nwaples/rardecode/v2 v2.1.0 // indirect
|
||||||
@ -282,7 +305,7 @@ require (
|
|||||||
github.com/olekukonko/errors v1.1.0 // indirect
|
github.com/olekukonko/errors v1.1.0 // indirect
|
||||||
github.com/olekukonko/ll v0.0.9 // indirect
|
github.com/olekukonko/ll v0.0.9 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
github.com/opencontainers/runc v1.2.3 // indirect
|
github.com/opencontainers/runc v1.2.3 // indirect
|
||||||
github.com/openrdap/rdap v0.9.1 // indirect
|
github.com/openrdap/rdap v0.9.1 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||||
@ -291,10 +314,10 @@ require (
|
|||||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/projectdiscovery/asnmap v1.1.1 // indirect
|
github.com/projectdiscovery/asnmap v1.1.1 // indirect
|
||||||
github.com/projectdiscovery/blackrock v0.0.1 // indirect
|
github.com/projectdiscovery/blackrock v0.0.1 // indirect
|
||||||
github.com/projectdiscovery/cdncheck v1.1.26 // indirect
|
github.com/projectdiscovery/cdncheck v1.1.35 // indirect
|
||||||
github.com/projectdiscovery/freeport v0.0.7 // indirect
|
github.com/projectdiscovery/freeport v0.0.7 // indirect
|
||||||
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb // 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-20240226150047-2e2c51e35983 // indirect
|
||||||
@ -302,6 +325,7 @@ require (
|
|||||||
github.com/sashabaranov/go-openai v1.37.0 // indirect
|
github.com/sashabaranov/go-openai v1.37.0 // indirect
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||||
|
github.com/shirou/gopsutil/v4 v4.25.7 // indirect
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||||
@ -315,11 +339,11 @@ require (
|
|||||||
github.com/tidwall/rtred v0.1.2 // indirect
|
github.com/tidwall/rtred v0.1.2 // indirect
|
||||||
github.com/tidwall/tinyqueue v0.1.1 // indirect
|
github.com/tidwall/tinyqueue v0.1.1 // indirect
|
||||||
github.com/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect
|
github.com/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
@ -333,13 +357,18 @@ require (
|
|||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
github.com/ysmood/fetchup v0.2.3 // indirect
|
github.com/ysmood/fetchup v0.2.3 // indirect
|
||||||
github.com/ysmood/got v0.40.0 // indirect
|
github.com/ysmood/got v0.40.0 // indirect
|
||||||
github.com/yuin/goldmark v1.7.8 // indirect
|
github.com/yuin/goldmark v1.7.13 // indirect
|
||||||
github.com/yuin/goldmark-emoji v1.0.5 // indirect
|
github.com/yuin/goldmark-emoji v1.0.5 // indirect
|
||||||
github.com/zcalusic/sysinfo v1.0.2 // indirect
|
github.com/zcalusic/sysinfo v1.0.2 // indirect
|
||||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/sync v0.15.0 // indirect
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
gopkg.in/djherbis/times.v1 v1.3.0 // indirect
|
gopkg.in/djherbis/times.v1 v1.3.0 // indirect
|
||||||
mellium.im/sasl v0.3.2 // indirect
|
mellium.im/sasl v0.3.2 // indirect
|
||||||
)
|
)
|
||||||
@ -360,16 +389,16 @@ require (
|
|||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect
|
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect
|
||||||
github.com/zmap/zcrypto v0.0.0-20240512203510-0fef58d9a9db // indirect
|
github.com/zmap/zcrypto v0.0.0-20240512203510-0fef58d9a9db // indirect
|
||||||
go.etcd.io/bbolt v1.3.10 // indirect
|
go.etcd.io/bbolt v1.4.0 // indirect
|
||||||
go.uber.org/zap v1.25.0 // indirect
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
goftp.io/server/v2 v2.0.1 // indirect
|
goftp.io/server/v2 v2.0.1 // indirect
|
||||||
golang.org/x/crypto v0.39.0 // indirect
|
golang.org/x/crypto v0.41.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
|
||||||
golang.org/x/mod v0.25.0 // indirect
|
golang.org/x/mod v0.27.0 // indirect
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
golang.org/x/time v0.11.0 // indirect
|
golang.org/x/time v0.11.0 // indirect
|
||||||
golang.org/x/tools v0.34.0
|
golang.org/x/tools v0.36.0
|
||||||
google.golang.org/protobuf v1.35.1 // indirect
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
|
||||||
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
|
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
|
||||||
)
|
)
|
||||||
@ -382,3 +411,10 @@ require (
|
|||||||
|
|
||||||
// https://go.dev/ref/mod#go-mod-file-retract
|
// https://go.dev/ref/mod#go-mod-file-retract
|
||||||
retract v3.2.0 // retract due to broken js protocol issue
|
retract v3.2.0 // retract due to broken js protocol issue
|
||||||
|
|
||||||
|
// Fix genproto version conflicts
|
||||||
|
replace (
|
||||||
|
google.golang.org/genproto => google.golang.org/genproto v0.0.0-20240814211410-ddb44dafa142
|
||||||
|
google.golang.org/genproto/googleapis/api => google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142
|
||||||
|
google.golang.org/genproto/googleapis/rpc => google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1
|
||||||
|
)
|
||||||
|
|||||||
38
integration_tests/fuzz/fuzz-body.yaml
Normal file
38
integration_tests/fuzz/fuzz-body.yaml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
id: fuzz-body
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: fuzzing error sqli payloads in http req body
|
||||||
|
author: pdteam
|
||||||
|
severity: info
|
||||||
|
description: |
|
||||||
|
This template attempts to find SQL injection vulnerabilities by fuzzing http body
|
||||||
|
It automatically handles and parses json,xml,multipart form and x-www-form-urlencoded data
|
||||||
|
and performs fuzzing on the value of every key
|
||||||
|
|
||||||
|
http:
|
||||||
|
- pre-condition:
|
||||||
|
- type: dsl
|
||||||
|
dsl:
|
||||||
|
- method != "GET"
|
||||||
|
- method != "HEAD"
|
||||||
|
condition: and
|
||||||
|
|
||||||
|
payloads:
|
||||||
|
injection:
|
||||||
|
- "'"
|
||||||
|
- "\""
|
||||||
|
- ";"
|
||||||
|
|
||||||
|
fuzzing:
|
||||||
|
- part: body
|
||||||
|
type: postfix
|
||||||
|
mode: single
|
||||||
|
fuzz:
|
||||||
|
- '{{injection}}'
|
||||||
|
|
||||||
|
stop-at-first-match: true
|
||||||
|
matchers:
|
||||||
|
- type: word
|
||||||
|
words:
|
||||||
|
- "unrecognized token:"
|
||||||
|
- "null"
|
||||||
38
integration_tests/protocols/javascript/vnc-pass-brute.yaml
Normal file
38
integration_tests/protocols/javascript/vnc-pass-brute.yaml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
id: vnc-password-test
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: VNC Password Authentication Test
|
||||||
|
author: pdteam
|
||||||
|
severity: high
|
||||||
|
description: |
|
||||||
|
Tests VNC authentication with correct and incorrect passwords.
|
||||||
|
metadata:
|
||||||
|
shodan-query: product:"vnc"
|
||||||
|
tags: js,network,vnc,authentication
|
||||||
|
|
||||||
|
javascript:
|
||||||
|
- pre-condition: |
|
||||||
|
isPortOpen(Host,Port)
|
||||||
|
|
||||||
|
code: |
|
||||||
|
let vnc = require('nuclei/vnc');
|
||||||
|
let client = new vnc.VNCClient();
|
||||||
|
client.Connect(Host, Port, Password);
|
||||||
|
|
||||||
|
args:
|
||||||
|
Host: "{{Host}}"
|
||||||
|
Port: "5900"
|
||||||
|
Password: "{{passwords}}"
|
||||||
|
payloads:
|
||||||
|
passwords:
|
||||||
|
- ""
|
||||||
|
- root
|
||||||
|
- password
|
||||||
|
- admin
|
||||||
|
- mysecret
|
||||||
|
stop-at-first-match: true
|
||||||
|
|
||||||
|
matchers:
|
||||||
|
- type: dsl
|
||||||
|
dsl:
|
||||||
|
- "success == true"
|
||||||
@ -77,11 +77,11 @@ func NewUploadWriter(ctx context.Context, logger *gologger.Logger, creds *pdcpau
|
|||||||
output.WithJson(true, true),
|
output.WithJson(true, true),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New("could not create output writer"), err)
|
return nil, errkit.Wrap(err, "could not create output writer")
|
||||||
}
|
}
|
||||||
tmp, err := urlutil.Parse(creds.Server)
|
tmp, err := urlutil.Parse(creds.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New("could not parse server url"), err)
|
return nil, errkit.Wrap(err, "could not parse server url")
|
||||||
}
|
}
|
||||||
tmp.Path = uploadEndpoint
|
tmp.Path = uploadEndpoint
|
||||||
tmp.Update()
|
tmp.Update()
|
||||||
@ -199,7 +199,7 @@ func (u *UploadWriter) autoCommit(ctx context.Context, r *io.PipeReader) {
|
|||||||
// uploadChunk uploads a chunk of data to the server
|
// uploadChunk uploads a chunk of data to the server
|
||||||
func (u *UploadWriter) uploadChunk(buff *bytes.Buffer) error {
|
func (u *UploadWriter) uploadChunk(buff *bytes.Buffer) error {
|
||||||
if err := u.upload(buff.Bytes()); err != nil {
|
if err := u.upload(buff.Bytes()); err != nil {
|
||||||
return errkit.Append(errkit.New("could not upload chunk"), err)
|
return errkit.Wrap(err, "could not upload chunk")
|
||||||
}
|
}
|
||||||
// if successful, reset the buffer
|
// if successful, reset the buffer
|
||||||
buff.Reset()
|
buff.Reset()
|
||||||
@ -211,25 +211,25 @@ func (u *UploadWriter) uploadChunk(buff *bytes.Buffer) error {
|
|||||||
func (u *UploadWriter) upload(data []byte) error {
|
func (u *UploadWriter) upload(data []byte) error {
|
||||||
req, err := u.getRequest(data)
|
req, err := u.getRequest(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("could not create upload request"), err)
|
return errkit.Wrap(err, "could not create upload request")
|
||||||
}
|
}
|
||||||
resp, err := u.client.Do(req)
|
resp, err := u.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("could not upload results"), err)
|
return errkit.Wrap(err, "could not upload results")
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
}()
|
}()
|
||||||
bin, err := io.ReadAll(resp.Body)
|
bin, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("could not get id from response"), err)
|
return errkit.Wrap(err, "could not get id from response")
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("could not upload results got status code %v on %v", resp.StatusCode, resp.Request.URL.String())
|
return fmt.Errorf("could not upload results got status code %v on %v", resp.StatusCode, resp.Request.URL.String())
|
||||||
}
|
}
|
||||||
var uploadResp uploadResponse
|
var uploadResp uploadResponse
|
||||||
if err := json.Unmarshal(bin, &uploadResp); err != nil {
|
if err := json.Unmarshal(bin, &uploadResp); err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("could not unmarshal response got %v", string(bin))), err)
|
return errkit.Wrap(err, fmt.Sprintf("could not unmarshal response got %v", string(bin)))
|
||||||
}
|
}
|
||||||
if uploadResp.ID != "" && u.scanID == "" {
|
if uploadResp.ID != "" && u.scanID == "" {
|
||||||
u.scanID = uploadResp.ID
|
u.scanID = uploadResp.ID
|
||||||
@ -254,7 +254,7 @@ func (u *UploadWriter) getRequest(bin []byte) (*retryablehttp.Request, error) {
|
|||||||
}
|
}
|
||||||
req, err := retryablehttp.NewRequest(method, url, bytes.NewReader(bin))
|
req, err := retryablehttp.NewRequest(method, url, bytes.NewReader(bin))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New("could not create cloud upload request"), err)
|
return nil, errkit.Wrap(err, "could not create cloud upload request")
|
||||||
}
|
}
|
||||||
// add pdtm meta params
|
// add pdtm meta params
|
||||||
req.Params.Merge(updateutils.GetpdtmParams(config.Version))
|
req.Params.Merge(updateutils.GetpdtmParams(config.Version))
|
||||||
|
|||||||
@ -32,7 +32,7 @@ func GetAuthTmplStore(opts *types.Options, catalog catalog.Catalog, execOpts *pr
|
|||||||
for _, file := range opts.SecretsFile {
|
for _, file := range opts.SecretsFile {
|
||||||
data, err := authx.GetTemplatePathsFromSecretFile(file)
|
data, err := authx.GetTemplatePathsFromSecretFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New("failed to get template paths from secrets file"), err)
|
return nil, errkit.Wrap(err, "failed to get template paths from secrets file")
|
||||||
}
|
}
|
||||||
tmpls = append(tmpls, data...)
|
tmpls = append(tmpls, data...)
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ func GetAuthTmplStore(opts *types.Options, catalog catalog.Catalog, execOpts *pr
|
|||||||
cfg.StoreId = loader.AuthStoreId
|
cfg.StoreId = loader.AuthStoreId
|
||||||
store, err := loader.New(cfg)
|
store, err := loader.New(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New("failed to initialize dynamic auth templates store"), err)
|
return nil, errkit.Wrap(err, "failed to initialize dynamic auth templates store")
|
||||||
}
|
}
|
||||||
return store, nil
|
return store, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,7 +50,7 @@ func loadProxyServers(options *types.Options) error {
|
|||||||
}
|
}
|
||||||
proxyURL, err := url.Parse(aliveProxy)
|
proxyURL, err := url.Parse(aliveProxy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("failed to parse proxy got %v", err)), err)
|
return errkit.Wrapf(err, "failed to parse proxy got %v", err)
|
||||||
}
|
}
|
||||||
if options.ProxyInternal {
|
if options.ProxyInternal {
|
||||||
_ = os.Setenv(HTTP_PROXY_ENV, proxyURL.String())
|
_ = os.Setenv(HTTP_PROXY_ENV, proxyURL.String())
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/projectdiscovery/goflags"
|
"github.com/projectdiscovery/goflags"
|
||||||
"github.com/projectdiscovery/gologger"
|
"github.com/projectdiscovery/gologger"
|
||||||
"github.com/projectdiscovery/ratelimit"
|
"github.com/projectdiscovery/ratelimit"
|
||||||
|
"github.com/projectdiscovery/utils/errkit"
|
||||||
|
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
|
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
|
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
|
||||||
@ -102,7 +103,7 @@ type InteractshOpts interactsh.Options
|
|||||||
func WithInteractshOptions(opts InteractshOpts) NucleiSDKOptions {
|
func WithInteractshOptions(opts InteractshOpts) NucleiSDKOptions {
|
||||||
return func(e *NucleiEngine) error {
|
return func(e *NucleiEngine) error {
|
||||||
if e.mode == threadSafe {
|
if e.mode == threadSafe {
|
||||||
return ErrOptionsNotSupported("WithInteractshOptions")
|
return errkit.Wrap(ErrOptionsNotSupported, "WithInteractshOptions")
|
||||||
}
|
}
|
||||||
optsPtr := &opts
|
optsPtr := &opts
|
||||||
e.interactshOpts = (*interactsh.Options)(optsPtr)
|
e.interactshOpts = (*interactsh.Options)(optsPtr)
|
||||||
@ -229,7 +230,7 @@ type StatsOptions struct {
|
|||||||
func EnableStatsWithOpts(opts StatsOptions) NucleiSDKOptions {
|
func EnableStatsWithOpts(opts StatsOptions) NucleiSDKOptions {
|
||||||
return func(e *NucleiEngine) error {
|
return func(e *NucleiEngine) error {
|
||||||
if e.mode == threadSafe {
|
if e.mode == threadSafe {
|
||||||
return ErrOptionsNotSupported("EnableStatsWithOpts")
|
return errkit.Wrap(ErrOptionsNotSupported, "EnableStatsWithOpts")
|
||||||
}
|
}
|
||||||
if opts.Interval == 0 {
|
if opts.Interval == 0 {
|
||||||
opts.Interval = 5 //sec
|
opts.Interval = 5 //sec
|
||||||
@ -257,7 +258,7 @@ type VerbosityOptions struct {
|
|||||||
func WithVerbosity(opts VerbosityOptions) NucleiSDKOptions {
|
func WithVerbosity(opts VerbosityOptions) NucleiSDKOptions {
|
||||||
return func(e *NucleiEngine) error {
|
return func(e *NucleiEngine) error {
|
||||||
if e.mode == threadSafe {
|
if e.mode == threadSafe {
|
||||||
return ErrOptionsNotSupported("WithVerbosity")
|
return errkit.Wrap(ErrOptionsNotSupported, "WithVerbosity")
|
||||||
}
|
}
|
||||||
e.opts.Verbose = opts.Verbose
|
e.opts.Verbose = opts.Verbose
|
||||||
e.opts.Silent = opts.Silent
|
e.opts.Silent = opts.Silent
|
||||||
@ -290,7 +291,7 @@ type NetworkConfig struct {
|
|||||||
func WithNetworkConfig(opts NetworkConfig) NucleiSDKOptions {
|
func WithNetworkConfig(opts NetworkConfig) NucleiSDKOptions {
|
||||||
return func(e *NucleiEngine) error {
|
return func(e *NucleiEngine) error {
|
||||||
if e.mode == threadSafe {
|
if e.mode == threadSafe {
|
||||||
return ErrOptionsNotSupported("WithNetworkConfig")
|
return errkit.Wrap(ErrOptionsNotSupported, "WithNetworkConfig")
|
||||||
}
|
}
|
||||||
e.opts.NoHostErrors = opts.DisableMaxHostErr
|
e.opts.NoHostErrors = opts.DisableMaxHostErr
|
||||||
e.opts.MaxHostError = opts.MaxHostError
|
e.opts.MaxHostError = opts.MaxHostError
|
||||||
@ -321,7 +322,7 @@ func WithNetworkConfig(opts NetworkConfig) NucleiSDKOptions {
|
|||||||
func WithProxy(proxy []string, proxyInternalRequests bool) NucleiSDKOptions {
|
func WithProxy(proxy []string, proxyInternalRequests bool) NucleiSDKOptions {
|
||||||
return func(e *NucleiEngine) error {
|
return func(e *NucleiEngine) error {
|
||||||
if e.mode == threadSafe {
|
if e.mode == threadSafe {
|
||||||
return ErrOptionsNotSupported("WithProxy")
|
return errkit.Wrap(ErrOptionsNotSupported, "WithProxy")
|
||||||
}
|
}
|
||||||
e.opts.Proxy = proxy
|
e.opts.Proxy = proxy
|
||||||
e.opts.ProxyInternal = proxyInternalRequests
|
e.opts.ProxyInternal = proxyInternalRequests
|
||||||
@ -346,7 +347,7 @@ type OutputWriter output.Writer
|
|||||||
func UseOutputWriter(writer OutputWriter) NucleiSDKOptions {
|
func UseOutputWriter(writer OutputWriter) NucleiSDKOptions {
|
||||||
return func(e *NucleiEngine) error {
|
return func(e *NucleiEngine) error {
|
||||||
if e.mode == threadSafe {
|
if e.mode == threadSafe {
|
||||||
return ErrOptionsNotSupported("UseOutputWriter")
|
return errkit.Wrap(ErrOptionsNotSupported, "UseOutputWriter")
|
||||||
}
|
}
|
||||||
e.customWriter = writer
|
e.customWriter = writer
|
||||||
return nil
|
return nil
|
||||||
@ -361,7 +362,7 @@ type StatsWriter progress.Progress
|
|||||||
func UseStatsWriter(writer StatsWriter) NucleiSDKOptions {
|
func UseStatsWriter(writer StatsWriter) NucleiSDKOptions {
|
||||||
return func(e *NucleiEngine) error {
|
return func(e *NucleiEngine) error {
|
||||||
if e.mode == threadSafe {
|
if e.mode == threadSafe {
|
||||||
return ErrOptionsNotSupported("UseStatsWriter")
|
return errkit.Wrap(ErrOptionsNotSupported, "UseStatsWriter")
|
||||||
}
|
}
|
||||||
e.customProgress = writer
|
e.customProgress = writer
|
||||||
return nil
|
return nil
|
||||||
@ -375,7 +376,7 @@ func UseStatsWriter(writer StatsWriter) NucleiSDKOptions {
|
|||||||
func WithTemplateUpdateCallback(disableTemplatesAutoUpgrade bool, callback func(newVersion string)) NucleiSDKOptions {
|
func WithTemplateUpdateCallback(disableTemplatesAutoUpgrade bool, callback func(newVersion string)) NucleiSDKOptions {
|
||||||
return func(e *NucleiEngine) error {
|
return func(e *NucleiEngine) error {
|
||||||
if e.mode == threadSafe {
|
if e.mode == threadSafe {
|
||||||
return ErrOptionsNotSupported("WithTemplateUpdateCallback")
|
return errkit.Wrap(ErrOptionsNotSupported, "WithTemplateUpdateCallback")
|
||||||
}
|
}
|
||||||
e.disableTemplatesAutoUpgrade = disableTemplatesAutoUpgrade
|
e.disableTemplatesAutoUpgrade = disableTemplatesAutoUpgrade
|
||||||
e.onUpdateAvailableCallback = callback
|
e.onUpdateAvailableCallback = callback
|
||||||
@ -387,7 +388,7 @@ func WithTemplateUpdateCallback(disableTemplatesAutoUpgrade bool, callback func(
|
|||||||
func WithSandboxOptions(allowLocalFileAccess bool, restrictLocalNetworkAccess bool) NucleiSDKOptions {
|
func WithSandboxOptions(allowLocalFileAccess bool, restrictLocalNetworkAccess bool) NucleiSDKOptions {
|
||||||
return func(e *NucleiEngine) error {
|
return func(e *NucleiEngine) error {
|
||||||
if e.mode == threadSafe {
|
if e.mode == threadSafe {
|
||||||
return ErrOptionsNotSupported("WithSandboxOptions")
|
return errkit.Wrap(ErrOptionsNotSupported, "WithSandboxOptions")
|
||||||
}
|
}
|
||||||
e.opts.AllowLocalFileAccess = allowLocalFileAccess
|
e.opts.AllowLocalFileAccess = allowLocalFileAccess
|
||||||
e.opts.RestrictLocalNetworkAccess = restrictLocalNetworkAccess
|
e.opts.RestrictLocalNetworkAccess = restrictLocalNetworkAccess
|
||||||
|
|||||||
@ -147,13 +147,13 @@ func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, t
|
|||||||
// load templates
|
// load templates
|
||||||
workflowLoader, err := workflow.NewLoader(unsafeOpts.executerOpts)
|
workflowLoader, err := workflow.NewLoader(unsafeOpts.executerOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("Could not create workflow loader"), err)
|
return errkit.Wrapf(err, "Could not create workflow loader: %s", err)
|
||||||
}
|
}
|
||||||
unsafeOpts.executerOpts.WorkflowLoader = workflowLoader
|
unsafeOpts.executerOpts.WorkflowLoader = workflowLoader
|
||||||
|
|
||||||
store, err := loader.New(loader.NewConfig(tmpEngine.opts, e.eng.catalog, unsafeOpts.executerOpts))
|
store, err := loader.New(loader.NewConfig(tmpEngine.opts, e.eng.catalog, unsafeOpts.executerOpts))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("Could not create loader client"), err)
|
return errkit.Wrapf(err, "Could not create loader client: %s", err)
|
||||||
}
|
}
|
||||||
store.Load()
|
store.Load()
|
||||||
|
|
||||||
|
|||||||
18
lib/sdk.go
18
lib/sdk.go
@ -4,7 +4,6 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -38,18 +37,15 @@ type NucleiSDKOptions func(e *NucleiEngine) error
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrNotImplemented is returned when a feature is not implemented
|
// ErrNotImplemented is returned when a feature is not implemented
|
||||||
ErrNotImplemented = errkit.New("Not implemented").Build()
|
ErrNotImplemented = errkit.New("Not implemented")
|
||||||
// ErrNoTemplatesAvailable is returned when no templates are available to execute
|
// ErrNoTemplatesAvailable is returned when no templates are available to execute
|
||||||
ErrNoTemplatesAvailable = errkit.New("No templates available").Build()
|
ErrNoTemplatesAvailable = errkit.New("No templates available")
|
||||||
// ErrNoTargetsAvailable is returned when no targets are available to scan
|
// ErrNoTargetsAvailable is returned when no targets are available to scan
|
||||||
ErrNoTargetsAvailable = errkit.New("No targets available").Build()
|
ErrNoTargetsAvailable = errkit.New("No targets available")
|
||||||
|
// ErrOptionsNotSupported is returned when an option is not supported in thread safe mode
|
||||||
|
ErrOptionsNotSupported = errkit.New("Option not supported in thread safe mode")
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrOptionsNotSupported returns an error when an option is not supported in thread safe mode
|
|
||||||
func ErrOptionsNotSupported(option string) error {
|
|
||||||
return errkit.New(fmt.Sprintf("Option %v not supported in thread safe mode", option)).Build()
|
|
||||||
}
|
|
||||||
|
|
||||||
type engineMode uint
|
type engineMode uint
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -102,13 +98,13 @@ type NucleiEngine struct {
|
|||||||
func (e *NucleiEngine) LoadAllTemplates() error {
|
func (e *NucleiEngine) LoadAllTemplates() error {
|
||||||
workflowLoader, err := workflow.NewLoader(e.executerOpts)
|
workflowLoader, err := workflow.NewLoader(e.executerOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("Could not create workflow loader"), err)
|
return errkit.Wrapf(err, "Could not create workflow loader: %s", err)
|
||||||
}
|
}
|
||||||
e.executerOpts.WorkflowLoader = workflowLoader
|
e.executerOpts.WorkflowLoader = workflowLoader
|
||||||
|
|
||||||
e.store, err = loader.New(loader.NewConfig(e.opts, e.catalog, e.executerOpts))
|
e.store, err = loader.New(loader.NewConfig(e.opts, e.catalog, e.executerOpts))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("Could not create loader client"), err)
|
return errkit.Wrapf(err, "Could not create loader client: %s", err)
|
||||||
}
|
}
|
||||||
e.store.Load()
|
e.store.Load()
|
||||||
e.templatesLoaded = true
|
e.templatesLoaded = true
|
||||||
|
|||||||
@ -53,7 +53,7 @@ func (d *Dynamic) GetDomainAndDomainRegex() ([]string, []string) {
|
|||||||
|
|
||||||
func (d *Dynamic) UnmarshalJSON(data []byte) error {
|
func (d *Dynamic) UnmarshalJSON(data []byte) error {
|
||||||
if d == nil {
|
if d == nil {
|
||||||
return errkit.New("cannot unmarshal into nil Dynamic struct").Build()
|
return errkit.New("cannot unmarshal into nil Dynamic struct")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use an alias type (auxiliary) to avoid a recursive call in this method.
|
// Use an alias type (auxiliary) to avoid a recursive call in this method.
|
||||||
@ -72,10 +72,10 @@ func (d *Dynamic) UnmarshalJSON(data []byte) error {
|
|||||||
func (d *Dynamic) Validate() error {
|
func (d *Dynamic) Validate() error {
|
||||||
d.m = &sync.Mutex{}
|
d.m = &sync.Mutex{}
|
||||||
if d.TemplatePath == "" {
|
if d.TemplatePath == "" {
|
||||||
return errkit.New(" template-path is required for dynamic secret").Build()
|
return errkit.New(" template-path is required for dynamic secret")
|
||||||
}
|
}
|
||||||
if len(d.Variables) == 0 {
|
if len(d.Variables) == 0 {
|
||||||
return errkit.New("variables are required for dynamic secret").Build()
|
return errkit.New("variables are required for dynamic secret")
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.Secret != nil {
|
if d.Secret != nil {
|
||||||
|
|||||||
@ -237,7 +237,9 @@ func GetAuthDataFromYAML(data []byte) (*Authx, error) {
|
|||||||
var auth Authx
|
var auth Authx
|
||||||
err := yaml.Unmarshal(data, &auth)
|
err := yaml.Unmarshal(data, &auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New("could not unmarshal yaml"), err)
|
errorErr := errkit.FromError(err)
|
||||||
|
errorErr.Msgf("could not unmarshal yaml")
|
||||||
|
return nil, errorErr
|
||||||
}
|
}
|
||||||
return &auth, nil
|
return &auth, nil
|
||||||
}
|
}
|
||||||
@ -247,7 +249,9 @@ func GetAuthDataFromJSON(data []byte) (*Authx, error) {
|
|||||||
var auth Authx
|
var auth Authx
|
||||||
err := json.Unmarshal(data, &auth)
|
err := json.Unmarshal(data, &auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New("could not unmarshal json"), err)
|
errorErr := errkit.FromError(err)
|
||||||
|
errorErr.Msgf("could not unmarshal json")
|
||||||
|
return nil, errorErr
|
||||||
}
|
}
|
||||||
return &auth, nil
|
return &auth, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package authprovider
|
package authprovider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -31,16 +30,20 @@ func NewFileAuthProvider(path string, callback authx.LazyFetchSecret) (AuthProvi
|
|||||||
return nil, ErrNoSecrets
|
return nil, ErrNoSecrets
|
||||||
}
|
}
|
||||||
if len(store.Dynamic) > 0 && callback == nil {
|
if len(store.Dynamic) > 0 && callback == nil {
|
||||||
return nil, errkit.New("lazy fetch callback is required for dynamic secrets").Build()
|
return nil, errkit.New("lazy fetch callback is required for dynamic secrets")
|
||||||
}
|
}
|
||||||
for _, secret := range store.Secrets {
|
for _, secret := range store.Secrets {
|
||||||
if err := secret.Validate(); err != nil {
|
if err := secret.Validate(); err != nil {
|
||||||
return nil, errkit.Append(errkit.New(fmt.Sprintf("invalid secret in file: %s", path)), err)
|
errorErr := errkit.FromError(err)
|
||||||
|
errorErr.Msgf("invalid secret in file: %s", path)
|
||||||
|
return nil, errorErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, dynamic := range store.Dynamic {
|
for i, dynamic := range store.Dynamic {
|
||||||
if err := dynamic.Validate(); err != nil {
|
if err := dynamic.Validate(); err != nil {
|
||||||
return nil, errkit.Append(errkit.New(fmt.Sprintf("invalid dynamic in file: %s", path)), err)
|
errorErr := errkit.FromError(err)
|
||||||
|
errorErr.Msgf("invalid dynamic in file: %s", path)
|
||||||
|
return nil, errorErr
|
||||||
}
|
}
|
||||||
dynamic.SetLazyFetchCallback(callback)
|
dynamic.SetLazyFetchCallback(callback)
|
||||||
store.Dynamic[i] = dynamic
|
store.Dynamic[i] = dynamic
|
||||||
|
|||||||
@ -140,13 +140,13 @@ func (c *Config) UpdateNucleiIgnoreHash() error {
|
|||||||
if fileutil.FileExists(ignoreFilePath) {
|
if fileutil.FileExists(ignoreFilePath) {
|
||||||
bin, err := os.ReadFile(ignoreFilePath)
|
bin, err := os.ReadFile(ignoreFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("could not read nuclei ignore file"), err)
|
return errkit.Newf("could not read nuclei ignore file: %v", err)
|
||||||
}
|
}
|
||||||
c.NucleiIgnoreHash = fmt.Sprintf("%x", md5.Sum(bin))
|
c.NucleiIgnoreHash = fmt.Sprintf("%x", md5.Sum(bin))
|
||||||
// write config to disk
|
// write config to disk
|
||||||
return c.WriteTemplatesConfig()
|
return c.WriteTemplatesConfig()
|
||||||
}
|
}
|
||||||
return errkit.New("config: ignore file not found: could not update nuclei ignore hash").Build()
|
return errkit.New("ignore file not found: could not update nuclei ignore hash")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfigDir returns the nuclei configuration directory
|
// GetConfigDir returns the nuclei configuration directory
|
||||||
@ -257,7 +257,7 @@ func (c *Config) SetTemplatesVersion(version string) error {
|
|||||||
c.TemplateVersion = version
|
c.TemplateVersion = version
|
||||||
// write config to disk
|
// write config to disk
|
||||||
if err := c.WriteTemplatesConfig(); err != nil {
|
if err := c.WriteTemplatesConfig(); err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("could not write nuclei config file at %s", c.getTemplatesConfigFilePath())), err)
|
return errkit.Newf("could not write nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -265,15 +265,15 @@ func (c *Config) SetTemplatesVersion(version string) error {
|
|||||||
// ReadTemplatesConfig reads the nuclei templates config file
|
// ReadTemplatesConfig reads the nuclei templates config file
|
||||||
func (c *Config) ReadTemplatesConfig() error {
|
func (c *Config) ReadTemplatesConfig() error {
|
||||||
if !fileutil.FileExists(c.getTemplatesConfigFilePath()) {
|
if !fileutil.FileExists(c.getTemplatesConfigFilePath()) {
|
||||||
return errkit.New(fmt.Sprintf("config: nuclei config file at %s does not exist", c.getTemplatesConfigFilePath())).Build()
|
return errkit.Newf("nuclei config file at %s does not exist", c.getTemplatesConfigFilePath())
|
||||||
}
|
}
|
||||||
var cfg *Config
|
var cfg *Config
|
||||||
bin, err := os.ReadFile(c.getTemplatesConfigFilePath())
|
bin, err := os.ReadFile(c.getTemplatesConfigFilePath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("could not read nuclei config file at %s", c.getTemplatesConfigFilePath())), err)
|
return errkit.Newf("could not read nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err)
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(bin, &cfg); err != nil {
|
if err := json.Unmarshal(bin, &cfg); err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("could not unmarshal nuclei config file at %s", c.getTemplatesConfigFilePath())), err)
|
return errkit.Newf("could not unmarshal nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err)
|
||||||
}
|
}
|
||||||
// apply config
|
// apply config
|
||||||
c.TemplatesDirectory = cfg.TemplatesDirectory
|
c.TemplatesDirectory = cfg.TemplatesDirectory
|
||||||
@ -292,10 +292,10 @@ func (c *Config) WriteTemplatesConfig() error {
|
|||||||
}
|
}
|
||||||
bin, err := json.Marshal(c)
|
bin, err := json.Marshal(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("failed to marshal nuclei config"), err)
|
return errkit.Newf("failed to marshal nuclei config: %v", err)
|
||||||
}
|
}
|
||||||
if err = os.WriteFile(c.getTemplatesConfigFilePath(), bin, 0600); err != nil {
|
if err = os.WriteFile(c.getTemplatesConfigFilePath(), bin, 0600); err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("failed to write nuclei config file at %s", c.getTemplatesConfigFilePath())), err)
|
return errkit.Newf("failed to write nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -319,7 +319,7 @@ func (c *Config) getTemplatesConfigFilePath() string {
|
|||||||
func (c *Config) createConfigDirIfNotExists() error {
|
func (c *Config) createConfigDirIfNotExists() error {
|
||||||
if !fileutil.FolderExists(c.configDir) {
|
if !fileutil.FolderExists(c.configDir) {
|
||||||
if err := fileutil.CreateFolder(c.configDir); err != nil {
|
if err := fileutil.CreateFolder(c.configDir); err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("could not create nuclei config directory at %s", c.configDir)), err)
|
return errkit.Newf("could not create nuclei config directory at %s: %v", c.configDir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package loader
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -34,27 +33,27 @@ type AITemplateResponse struct {
|
|||||||
func getAIGeneratedTemplates(prompt string, options *types.Options) ([]string, error) {
|
func getAIGeneratedTemplates(prompt string, options *types.Options) ([]string, error) {
|
||||||
prompt = strings.TrimSpace(prompt)
|
prompt = strings.TrimSpace(prompt)
|
||||||
if len(prompt) < 5 {
|
if len(prompt) < 5 {
|
||||||
return nil, errkit.New("Prompt is too short. Please provide a more descriptive prompt").Build()
|
return nil, errkit.Newf("Prompt is too short. Please provide a more descriptive prompt")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(prompt) > 3000 {
|
if len(prompt) > 3000 {
|
||||||
return nil, errkit.New("Prompt is too long. Please limit to 3000 characters").Build()
|
return nil, errkit.Newf("Prompt is too long. Please limit to 3000 characters")
|
||||||
}
|
}
|
||||||
|
|
||||||
template, templateID, err := generateAITemplate(prompt)
|
template, templateID, err := generateAITemplate(prompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.New(fmt.Sprintf("Failed to generate template: %v", err)).Build()
|
return nil, errkit.Newf("Failed to generate template: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pdcpTemplateDir := filepath.Join(config.DefaultConfig.GetTemplateDir(), "pdcp")
|
pdcpTemplateDir := filepath.Join(config.DefaultConfig.GetTemplateDir(), "pdcp")
|
||||||
if err := os.MkdirAll(pdcpTemplateDir, 0755); err != nil {
|
if err := os.MkdirAll(pdcpTemplateDir, 0755); err != nil {
|
||||||
return nil, errkit.New(fmt.Sprintf("Failed to create pdcp template directory: %v", err)).Build()
|
return nil, errkit.Newf("Failed to create pdcp template directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
templateFile := filepath.Join(pdcpTemplateDir, templateID+".yaml")
|
templateFile := filepath.Join(pdcpTemplateDir, templateID+".yaml")
|
||||||
err = os.WriteFile(templateFile, []byte(template), 0644)
|
err = os.WriteFile(templateFile, []byte(template), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.New(fmt.Sprintf("Failed to generate template: %v", err)).Build()
|
return nil, errkit.Newf("Failed to generate template: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
options.Logger.Info().Msgf("Generated template available at: https://cloud.projectdiscovery.io/templates/%s", templateID)
|
options.Logger.Info().Msgf("Generated template available at: https://cloud.projectdiscovery.io/templates/%s", templateID)
|
||||||
@ -92,22 +91,22 @@ func generateAITemplate(prompt string) (string, string, error) {
|
|||||||
}
|
}
|
||||||
jsonBody, err := json.Marshal(reqBody)
|
jsonBody, err := json.Marshal(reqBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", errkit.New(fmt.Sprintf("Failed to marshal request body: %v", err)).Build()
|
return "", "", errkit.Newf("Failed to marshal request body: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPost, aiTemplateGeneratorAPIEndpoint, bytes.NewBuffer(jsonBody))
|
req, err := http.NewRequest(http.MethodPost, aiTemplateGeneratorAPIEndpoint, bytes.NewBuffer(jsonBody))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", errkit.New(fmt.Sprintf("Failed to create HTTP request: %v", err)).Build()
|
return "", "", errkit.Newf("Failed to create HTTP request: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ph := pdcpauth.PDCPCredHandler{}
|
ph := pdcpauth.PDCPCredHandler{}
|
||||||
creds, err := ph.GetCreds()
|
creds, err := ph.GetCreds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", errkit.New(fmt.Sprintf("Failed to get PDCP credentials: %v", err)).Build()
|
return "", "", errkit.Newf("Failed to get PDCP credentials: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if creds == nil {
|
if creds == nil {
|
||||||
return "", "", errkit.New("PDCP API Key not configured, Create one for free at https://cloud.projectdiscovery.io/").Build()
|
return "", "", errkit.Newf("PDCP API Key not configured, Create one for free at https://cloud.projectdiscovery.io/")
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
@ -115,28 +114,28 @@ func generateAITemplate(prompt string) (string, string, error) {
|
|||||||
|
|
||||||
resp, err := retryablehttp.DefaultClient().Do(req)
|
resp, err := retryablehttp.DefaultClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", errkit.New(fmt.Sprintf("Failed to send HTTP request: %v", err)).Build()
|
return "", "", errkit.Newf("Failed to send HTTP request: %v", err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusUnauthorized {
|
if resp.StatusCode == http.StatusUnauthorized {
|
||||||
return "", "", errkit.New("Invalid API Key or API Key not configured, Create one for free at https://cloud.projectdiscovery.io/").Build()
|
return "", "", errkit.Newf("Invalid API Key or API Key not configured, Create one for free at https://cloud.projectdiscovery.io/")
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
body, _ := io.ReadAll(resp.Body)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
return "", "", errkit.New(fmt.Sprintf("API returned status code %d: %s", resp.StatusCode, string(body))).Build()
|
return "", "", errkit.Newf("API returned status code %d: %s", resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
var result AITemplateResponse
|
var result AITemplateResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
return "", "", errkit.New(fmt.Sprintf("Failed to decode API response: %v", err)).Build()
|
return "", "", errkit.Newf("Failed to decode API response: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.TemplateID == "" || result.Completion == "" {
|
if result.TemplateID == "" || result.Completion == "" {
|
||||||
return "", "", errkit.New("Failed to generate template").Build()
|
return "", "", errkit.Newf("Failed to generate template")
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.Completion, result.TemplateID, nil
|
return result.Completion, result.TemplateID, nil
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/projectdiscovery/nuclei/v3/pkg/workflows"
|
"github.com/projectdiscovery/nuclei/v3/pkg/workflows"
|
||||||
"github.com/projectdiscovery/retryablehttp-go"
|
"github.com/projectdiscovery/retryablehttp-go"
|
||||||
"github.com/projectdiscovery/utils/errkit"
|
"github.com/projectdiscovery/utils/errkit"
|
||||||
|
mapsutil "github.com/projectdiscovery/utils/maps"
|
||||||
sliceutil "github.com/projectdiscovery/utils/slice"
|
sliceutil "github.com/projectdiscovery/utils/slice"
|
||||||
stringsutil "github.com/projectdiscovery/utils/strings"
|
stringsutil "github.com/projectdiscovery/utils/strings"
|
||||||
syncutil "github.com/projectdiscovery/utils/sync"
|
syncutil "github.com/projectdiscovery/utils/sync"
|
||||||
@ -238,7 +239,7 @@ func (store *Store) ReadTemplateFromURI(uri string, remote bool) ([]byte, error)
|
|||||||
uri = handleTemplatesEditorURLs(uri)
|
uri = handleTemplatesEditorURLs(uri)
|
||||||
remoteTemplates, _, err := getRemoteTemplatesAndWorkflows([]string{uri}, nil, store.config.RemoteTemplateDomainList)
|
remoteTemplates, _, err := getRemoteTemplatesAndWorkflows([]string{uri}, nil, store.config.RemoteTemplateDomainList)
|
||||||
if err != nil || len(remoteTemplates) == 0 {
|
if err != nil || len(remoteTemplates) == 0 {
|
||||||
return nil, errkit.Append(errkit.New(fmt.Sprintf("Could not load template %s: got %v", uri, remoteTemplates)), err)
|
return nil, errkit.Wrapf(err, "Could not load template %s: got %v", uri, remoteTemplates)
|
||||||
}
|
}
|
||||||
resp, err := retryablehttp.Get(remoteTemplates[0])
|
resp, err := retryablehttp.Get(remoteTemplates[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -315,6 +316,8 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
|
|||||||
}
|
}
|
||||||
templatesCache := parserItem.Cache()
|
templatesCache := parserItem.Cache()
|
||||||
|
|
||||||
|
loadedTemplateIDs := mapsutil.NewSyncLockMap[string, struct{}]()
|
||||||
|
|
||||||
for templatePath := range validPaths {
|
for templatePath := range validPaths {
|
||||||
template, _, _ := templatesCache.Has(templatePath)
|
template, _, _ := templatesCache.Has(templatePath)
|
||||||
|
|
||||||
@ -339,6 +342,12 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if template != nil {
|
if template != nil {
|
||||||
|
if loadedTemplateIDs.Has(template.ID) {
|
||||||
|
store.logger.Debug().Msgf("Skipping duplicate template ID '%s' from path '%s'", template.ID, templatePath)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = loadedTemplateIDs.Set(template.ID, struct{}{})
|
||||||
template.Path = templatePath
|
template.Path = templatePath
|
||||||
store.templates = append(store.templates, template)
|
store.templates = append(store.templates, template)
|
||||||
}
|
}
|
||||||
@ -492,8 +501,16 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templ
|
|||||||
templatePathMap := store.pathFilter.Match(includedTemplates)
|
templatePathMap := store.pathFilter.Match(includedTemplates)
|
||||||
|
|
||||||
loadedTemplates := sliceutil.NewSyncSlice[*templates.Template]()
|
loadedTemplates := sliceutil.NewSyncSlice[*templates.Template]()
|
||||||
|
loadedTemplateIDs := mapsutil.NewSyncLockMap[string, struct{}]()
|
||||||
|
|
||||||
loadTemplate := func(tmpl *templates.Template) {
|
loadTemplate := func(tmpl *templates.Template) {
|
||||||
|
if loadedTemplateIDs.Has(tmpl.ID) {
|
||||||
|
store.logger.Debug().Msgf("Skipping duplicate template ID '%s' from path '%s'", tmpl.ID, tmpl.Path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = loadedTemplateIDs.Set(tmpl.ID, struct{}{})
|
||||||
|
|
||||||
loadedTemplates.Append(tmpl)
|
loadedTemplates.Append(tmpl)
|
||||||
// increment signed/unsigned counters
|
// increment signed/unsigned counters
|
||||||
if tmpl.Verified {
|
if tmpl.Verified {
|
||||||
|
|||||||
5
pkg/external/customtemplates/azure_blob.go
vendored
5
pkg/external/customtemplates/azure_blob.go
vendored
@ -3,7 +3,6 @@ package customtemplates
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -30,7 +29,9 @@ func NewAzureProviders(options *types.Options) ([]*customTemplateAzureBlob, erro
|
|||||||
// Establish a connection to Azure and build a client object with which to download templates from Azure Blob Storage
|
// Establish a connection to Azure and build a client object with which to download templates from Azure Blob Storage
|
||||||
azClient, err := getAzureBlobClient(options.AzureTenantID, options.AzureClientID, options.AzureClientSecret, options.AzureServiceURL)
|
azClient, err := getAzureBlobClient(options.AzureTenantID, options.AzureClientID, options.AzureClientSecret, options.AzureServiceURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New(fmt.Sprintf("Error establishing Azure Blob client for %s", options.AzureContainerName)), err)
|
errx := errkit.FromError(err)
|
||||||
|
errx.Msgf("Error establishing Azure Blob client for %s", options.AzureContainerName)
|
||||||
|
return nil, errx
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new Azure Blob Storage container object
|
// Create a new Azure Blob Storage container object
|
||||||
|
|||||||
5
pkg/external/customtemplates/gitlab.go
vendored
5
pkg/external/customtemplates/gitlab.go
vendored
@ -3,7 +3,6 @@ package customtemplates
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@ -29,7 +28,9 @@ func NewGitLabProviders(options *types.Options) ([]*customTemplateGitLabRepo, er
|
|||||||
// Establish a connection to GitLab and build a client object with which to download templates from GitLab
|
// Establish a connection to GitLab and build a client object with which to download templates from GitLab
|
||||||
gitLabClient, err := getGitLabClient(options.GitLabServerURL, options.GitLabToken)
|
gitLabClient, err := getGitLabClient(options.GitLabServerURL, options.GitLabToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New(fmt.Sprintf("Error establishing GitLab client for %s %s", options.GitLabServerURL, err)), err)
|
errx := errkit.FromError(err)
|
||||||
|
errx.Msgf("Error establishing GitLab client for %s %s", options.GitLabServerURL, err)
|
||||||
|
return nil, errx
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new GitLab service client
|
// Create a new GitLab service client
|
||||||
|
|||||||
5
pkg/external/customtemplates/s3.go
vendored
5
pkg/external/customtemplates/s3.go
vendored
@ -2,7 +2,6 @@ package customtemplates
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -65,7 +64,9 @@ func NewS3Providers(options *types.Options) ([]*customTemplateS3Bucket, error) {
|
|||||||
if options.AwsBucketName != "" && !options.AwsTemplateDisableDownload {
|
if options.AwsBucketName != "" && !options.AwsTemplateDisableDownload {
|
||||||
s3c, err := getS3Client(context.TODO(), options.AwsAccessKey, options.AwsSecretKey, options.AwsRegion, options.AwsProfile)
|
s3c, err := getS3Client(context.TODO(), options.AwsAccessKey, options.AwsSecretKey, options.AwsRegion, options.AwsProfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New(fmt.Sprintf("error downloading s3 bucket %s", options.AwsBucketName)), err)
|
errx := errkit.FromError(err)
|
||||||
|
errx.Msgf("error downloading s3 bucket %s", options.AwsBucketName)
|
||||||
|
return nil, errx
|
||||||
}
|
}
|
||||||
ctBucket := &customTemplateS3Bucket{
|
ctBucket := &customTemplateS3Bucket{
|
||||||
bucketName: options.AwsBucketName,
|
bucketName: options.AwsBucketName,
|
||||||
|
|||||||
@ -38,7 +38,9 @@ func NewCustomTemplatesManager(options *types.Options) (*CustomTemplatesManager,
|
|||||||
// Add GitHub providers
|
// Add GitHub providers
|
||||||
githubProviders, err := NewGitHubProviders(options)
|
githubProviders, err := NewGitHubProviders(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New("could not create github providers for custom templates"), err)
|
errx := errkit.FromError(err)
|
||||||
|
errx.Msgf("could not create github providers for custom templates")
|
||||||
|
return nil, errx
|
||||||
}
|
}
|
||||||
for _, v := range githubProviders {
|
for _, v := range githubProviders {
|
||||||
ctm.providers = append(ctm.providers, v)
|
ctm.providers = append(ctm.providers, v)
|
||||||
@ -47,7 +49,9 @@ func NewCustomTemplatesManager(options *types.Options) (*CustomTemplatesManager,
|
|||||||
// Add AWS S3 providers
|
// Add AWS S3 providers
|
||||||
s3Providers, err := NewS3Providers(options)
|
s3Providers, err := NewS3Providers(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New("could not create s3 providers for custom templates"), err)
|
errx := errkit.FromError(err)
|
||||||
|
errx.Msgf("could not create s3 providers for custom templates")
|
||||||
|
return nil, errx
|
||||||
}
|
}
|
||||||
for _, v := range s3Providers {
|
for _, v := range s3Providers {
|
||||||
ctm.providers = append(ctm.providers, v)
|
ctm.providers = append(ctm.providers, v)
|
||||||
@ -56,7 +60,9 @@ func NewCustomTemplatesManager(options *types.Options) (*CustomTemplatesManager,
|
|||||||
// Add Azure providers
|
// Add Azure providers
|
||||||
azureProviders, err := NewAzureProviders(options)
|
azureProviders, err := NewAzureProviders(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New("could not create azure providers for custom templates"), err)
|
errx := errkit.FromError(err)
|
||||||
|
errx.Msgf("could not create azure providers for custom templates")
|
||||||
|
return nil, errx
|
||||||
}
|
}
|
||||||
for _, v := range azureProviders {
|
for _, v := range azureProviders {
|
||||||
ctm.providers = append(ctm.providers, v)
|
ctm.providers = append(ctm.providers, v)
|
||||||
@ -65,7 +71,9 @@ func NewCustomTemplatesManager(options *types.Options) (*CustomTemplatesManager,
|
|||||||
// Add GitLab providers
|
// Add GitLab providers
|
||||||
gitlabProviders, err := NewGitLabProviders(options)
|
gitlabProviders, err := NewGitLabProviders(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New("could not create gitlab providers for custom templates"), err)
|
errx := errkit.FromError(err)
|
||||||
|
errx.Msgf("could not create gitlab providers for custom templates")
|
||||||
|
return nil, errx
|
||||||
}
|
}
|
||||||
for _, v := range gitlabProviders {
|
for _, v := range gitlabProviders {
|
||||||
ctm.providers = append(ctm.providers, v)
|
ctm.providers = append(ctm.providers, v)
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat"
|
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat"
|
||||||
"github.com/projectdiscovery/retryablehttp-go"
|
"github.com/projectdiscovery/retryablehttp-go"
|
||||||
mapsutil "github.com/projectdiscovery/utils/maps"
|
|
||||||
urlutil "github.com/projectdiscovery/utils/url"
|
urlutil "github.com/projectdiscovery/utils/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,12 +37,18 @@ func (q *Path) Parse(req *retryablehttp.Request) (bool, error) {
|
|||||||
|
|
||||||
splitted := strings.Split(req.Path, "/")
|
splitted := strings.Split(req.Path, "/")
|
||||||
values := make(map[string]interface{})
|
values := make(map[string]interface{})
|
||||||
for i := range splitted {
|
for i, segment := range splitted {
|
||||||
pathTillNow := strings.Join(splitted[:i+1], "/")
|
if segment == "" && i == 0 {
|
||||||
if pathTillNow == "" {
|
// Skip the first empty segment from leading "/"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
values[strconv.Itoa(i)] = pathTillNow
|
if segment == "" {
|
||||||
|
// Skip any other empty segments
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Use 1-based indexing and store individual segments
|
||||||
|
key := strconv.Itoa(len(values) + 1)
|
||||||
|
values[key] = segment
|
||||||
}
|
}
|
||||||
q.value.SetParsed(dataformat.KVMap(values), "")
|
q.value.SetParsed(dataformat.KVMap(values), "")
|
||||||
return true, nil
|
return true, nil
|
||||||
@ -64,7 +69,7 @@ func (q *Path) Iterate(callback func(key string, value interface{}) error) (err
|
|||||||
// SetValue sets a value in the component
|
// SetValue sets a value in the component
|
||||||
// for a key
|
// for a key
|
||||||
func (q *Path) SetValue(key string, value string) error {
|
func (q *Path) SetValue(key string, value string) error {
|
||||||
escaped := urlutil.ParamEncode(value)
|
escaped := urlutil.PathEncode(value)
|
||||||
if !q.value.SetParsedValue(key, escaped) {
|
if !q.value.SetParsedValue(key, escaped) {
|
||||||
return ErrSetValue
|
return ErrSetValue
|
||||||
}
|
}
|
||||||
@ -82,40 +87,48 @@ func (q *Path) Delete(key string) error {
|
|||||||
// Rebuild returns a new request with the
|
// Rebuild returns a new request with the
|
||||||
// component rebuilt
|
// component rebuilt
|
||||||
func (q *Path) Rebuild() (*retryablehttp.Request, error) {
|
func (q *Path) Rebuild() (*retryablehttp.Request, error) {
|
||||||
originalValues := mapsutil.Map[string, any]{}
|
// Get the original path segments
|
||||||
splitted := strings.Split(q.req.Path, "/")
|
originalSplitted := strings.Split(q.req.Path, "/")
|
||||||
for i := range splitted {
|
|
||||||
pathTillNow := strings.Join(splitted[:i+1], "/")
|
// Create a new slice to hold the rebuilt segments
|
||||||
if pathTillNow == "" {
|
rebuiltSegments := make([]string, 0, len(originalSplitted))
|
||||||
continue
|
|
||||||
}
|
// Add the first empty segment (from leading "/")
|
||||||
originalValues[strconv.Itoa(i)] = pathTillNow
|
if len(originalSplitted) > 0 && originalSplitted[0] == "" {
|
||||||
|
rebuiltSegments = append(rebuiltSegments, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
originalPath := q.req.Path
|
// Process each segment
|
||||||
lengthSplitted := len(q.value.parsed.Map)
|
segmentIndex := 1 // 1-based indexing for our stored values
|
||||||
for i := lengthSplitted; i > 0; i-- {
|
for i := 1; i < len(originalSplitted); i++ {
|
||||||
key := strconv.Itoa(i)
|
originalSegment := originalSplitted[i]
|
||||||
|
if originalSegment == "" {
|
||||||
original, ok := originalValues.GetOrDefault(key, "").(string)
|
// Skip empty segments
|
||||||
if !ok {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
new, ok := q.value.parsed.Map.GetOrDefault(key, "").(string)
|
// Check if we have a replacement for this segment
|
||||||
if !ok {
|
key := strconv.Itoa(segmentIndex)
|
||||||
continue
|
if newValue, exists := q.value.parsed.Map.GetOrDefault(key, "").(string); exists && newValue != "" {
|
||||||
|
rebuiltSegments = append(rebuiltSegments, newValue)
|
||||||
|
} else {
|
||||||
|
rebuiltSegments = append(rebuiltSegments, originalSegment)
|
||||||
}
|
}
|
||||||
|
segmentIndex++
|
||||||
if new == original {
|
}
|
||||||
// no need to replace
|
|
||||||
continue
|
// Join the segments back into a path
|
||||||
}
|
rebuiltPath := strings.Join(rebuiltSegments, "/")
|
||||||
|
|
||||||
originalPath = strings.Replace(originalPath, original, new, 1)
|
if unescaped, err := urlutil.PathDecode(rebuiltPath); err == nil {
|
||||||
|
// this is handle the case where anyportion of path has url encoded data
|
||||||
|
// by default the http/request official library will escape/encode special characters in path
|
||||||
|
// to avoid double encoding we unescape/decode already encoded value
|
||||||
|
//
|
||||||
|
// if there is a invalid url encoded value like %99 then it will still be encoded as %2599 and not %99
|
||||||
|
// the only way to make sure it stays as %99 is to implement raw request and unsafe for fuzzing as well
|
||||||
|
rebuiltPath = unescaped
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuiltPath := originalPath
|
|
||||||
|
|
||||||
// Clone the request and update the path
|
// Clone the request and update the path
|
||||||
cloned := q.req.Clone(context.Background())
|
cloned := q.req.Clone(context.Background())
|
||||||
|
|||||||
@ -29,9 +29,9 @@ func TestURLComponent(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
require.Equal(t, []string{"1"}, keys, "unexpected keys")
|
require.Equal(t, []string{"1"}, keys, "unexpected keys")
|
||||||
require.Equal(t, []string{"/testpath"}, values, "unexpected values")
|
require.Equal(t, []string{"testpath"}, values, "unexpected values")
|
||||||
|
|
||||||
err = urlComponent.SetValue("1", "/newpath")
|
err = urlComponent.SetValue("1", "newpath")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -61,9 +61,10 @@ func TestURLComponent_NestedPaths(t *testing.T) {
|
|||||||
isSet := false
|
isSet := false
|
||||||
|
|
||||||
_ = path.Iterate(func(key string, value interface{}) error {
|
_ = path.Iterate(func(key string, value interface{}) error {
|
||||||
if !isSet && value.(string) == "/user/753" {
|
t.Logf("Key: %s, Value: %s", key, value.(string))
|
||||||
|
if !isSet && value.(string) == "753" {
|
||||||
isSet = true
|
isSet = true
|
||||||
if setErr := path.SetValue(key, "/user/753'"); setErr != nil {
|
if setErr := path.SetValue(key, "753'"); setErr != nil {
|
||||||
t.Fatal(setErr)
|
t.Fatal(setErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,6 +76,54 @@ func TestURLComponent_NestedPaths(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if newReq.Path != "/user/753'/profile" {
|
if newReq.Path != "/user/753'/profile" {
|
||||||
t.Fatal("expected path to be modified")
|
t.Fatalf("expected path to be '/user/753'/profile', got '%s'", newReq.Path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPathComponent_SQLInjection(t *testing.T) {
|
||||||
|
path := NewPath()
|
||||||
|
req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com/user/55/profile", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
found, err := path.Parse(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatal("expected path to be found")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Original path: %s", req.Path)
|
||||||
|
|
||||||
|
// Let's see what path segments are available for fuzzing
|
||||||
|
err = path.Iterate(func(key string, value interface{}) error {
|
||||||
|
t.Logf("Key: %s, Value: %s", key, value.(string))
|
||||||
|
|
||||||
|
// Try fuzzing the "55" segment specifically (which should be key "2")
|
||||||
|
if value.(string) == "55" {
|
||||||
|
if setErr := path.SetValue(key, "55 OR True"); setErr != nil {
|
||||||
|
t.Fatal(setErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newReq, err := path.Rebuild()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Modified path: %s", newReq.Path)
|
||||||
|
|
||||||
|
// Now with PathEncode, spaces are preserved correctly for SQL injection
|
||||||
|
if newReq.Path != "/user/55 OR True/profile" {
|
||||||
|
t.Fatalf("expected path to be '/user/55 OR True/profile', got '%s'", newReq.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's also test what the actual URL looks like
|
||||||
|
t.Logf("Full URL: %s", newReq.String())
|
||||||
|
}
|
||||||
|
|||||||
@ -49,42 +49,61 @@ func (m *MultiPartForm) Encode(data KV) (string, error) {
|
|||||||
var fw io.Writer
|
var fw io.Writer
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if filesArray, ok := value.([]interface{}); ok {
|
if fileMetadata, ok := m.filesMetadata[key]; ok {
|
||||||
fileMetadata, ok := m.filesMetadata[key]
|
if filesArray, isArray := value.([]any); isArray {
|
||||||
if !ok {
|
for _, file := range filesArray {
|
||||||
Itererr = fmt.Errorf("file metadata not found for key %s", key)
|
h := make(textproto.MIMEHeader)
|
||||||
return false
|
h.Set("Content-Disposition",
|
||||||
}
|
fmt.Sprintf(`form-data; name=%q; filename=%q`,
|
||||||
|
key, fileMetadata.Filename))
|
||||||
|
h.Set("Content-Type", fileMetadata.ContentType)
|
||||||
|
|
||||||
for _, file := range filesArray {
|
if fw, err = w.CreatePart(h); err != nil {
|
||||||
h := make(textproto.MIMEHeader)
|
Itererr = err
|
||||||
h.Set("Content-Disposition",
|
return false
|
||||||
fmt.Sprintf(`form-data; name=%q; filename=%q`,
|
}
|
||||||
key, fileMetadata.Filename))
|
|
||||||
h.Set("Content-Type", fileMetadata.ContentType)
|
|
||||||
|
|
||||||
if fw, err = w.CreatePart(h); err != nil {
|
if _, err = fw.Write([]byte(file.(string))); err != nil {
|
||||||
Itererr = err
|
Itererr = err
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = fw.Write([]byte(file.(string))); err != nil {
|
return true
|
||||||
Itererr = err
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add field
|
// Add field
|
||||||
if fw, err = w.CreateFormField(key); err != nil {
|
var values []string
|
||||||
Itererr = err
|
switch v := value.(type) {
|
||||||
return false
|
case nil:
|
||||||
|
values = []string{""}
|
||||||
|
case string:
|
||||||
|
values = []string{v}
|
||||||
|
case []string:
|
||||||
|
values = v
|
||||||
|
case []any:
|
||||||
|
values = make([]string, len(v))
|
||||||
|
for i, item := range v {
|
||||||
|
if item == nil {
|
||||||
|
values[i] = ""
|
||||||
|
} else {
|
||||||
|
values[i] = fmt.Sprint(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
values = []string{fmt.Sprintf("%v", v)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = fw.Write([]byte(value.(string))); err != nil {
|
for _, val := range values {
|
||||||
Itererr = err
|
if fw, err = w.CreateFormField(key); err != nil {
|
||||||
return false
|
Itererr = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if _, err = fw.Write([]byte(val)); err != nil {
|
||||||
|
Itererr = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|||||||
242
pkg/fuzz/dataformat/multipart_test.go
Normal file
242
pkg/fuzz/dataformat/multipart_test.go
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
package dataformat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
mapsutil "github.com/projectdiscovery/utils/maps"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMultiPartFormEncode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields map[string]any
|
||||||
|
wantErr bool
|
||||||
|
expected map[string]any
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "duplicate fields ([]string) - checkbox scenario",
|
||||||
|
fields: map[string]any{
|
||||||
|
"interests": []string{"sports", "music", "reading"},
|
||||||
|
"colors": []string{"red", "blue"},
|
||||||
|
},
|
||||||
|
expected: map[string]any{
|
||||||
|
"interests": []string{"sports", "music", "reading"},
|
||||||
|
"colors": []string{"red", "blue"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single string fields - backward compatibility",
|
||||||
|
fields: map[string]any{
|
||||||
|
"username": "john",
|
||||||
|
"email": "john@example.com",
|
||||||
|
},
|
||||||
|
expected: map[string]any{
|
||||||
|
"username": "john",
|
||||||
|
"email": "john@example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mixed types",
|
||||||
|
fields: map[string]any{
|
||||||
|
"string": "text",
|
||||||
|
"array": []string{"item1", "item2"},
|
||||||
|
"number": 42, // tests fmt.Sprint fallback
|
||||||
|
"float": 3.14, // tests float conversion
|
||||||
|
"boolean": true, // tests boolean conversion
|
||||||
|
"zero": 0, // tests zero value
|
||||||
|
"emptyStr": "", // tests empty string
|
||||||
|
"negative": -123, // tests negative number
|
||||||
|
"nil": nil, // tests nil value
|
||||||
|
"mixedArray": []any{"str", 123, false, nil}, // tests mixed type array
|
||||||
|
},
|
||||||
|
expected: map[string]any{
|
||||||
|
"string": "text",
|
||||||
|
"array": []string{"item1", "item2"},
|
||||||
|
"number": "42", // numbers are converted to strings in multipart
|
||||||
|
"float": "3.14", // floats are converted to strings
|
||||||
|
"boolean": "true", // booleans are converted to strings
|
||||||
|
"zero": "0", // zero value converted to string
|
||||||
|
"emptyStr": "", // empty string remains empty
|
||||||
|
"negative": "-123", // negative numbers converted to strings
|
||||||
|
"nil": "", // nil values converted to "" string
|
||||||
|
"mixedArray": []string{"str", "123", "false", ""}, // mixed array converted to string array
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty array - should not appear in output",
|
||||||
|
fields: map[string]any{
|
||||||
|
"emptyArray": []string{},
|
||||||
|
"normalField": "value",
|
||||||
|
},
|
||||||
|
expected: map[string]any{
|
||||||
|
"normalField": "value",
|
||||||
|
// emptyArray should not appear in decoded output
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Errorf("Test panicked: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
form := NewMultiPartForm()
|
||||||
|
form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"
|
||||||
|
|
||||||
|
kv := mapsutil.NewOrderedMap[string, any]()
|
||||||
|
for k, v := range tt.fields {
|
||||||
|
kv.Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded, err := form.Encode(KVOrderedMap(&kv))
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Decode the encoded multipart data
|
||||||
|
decoded, err := form.Decode(encoded)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Compare decoded values with expected values
|
||||||
|
for expectedKey, expectedValue := range tt.expected {
|
||||||
|
actualValue := decoded.Get(expectedKey)
|
||||||
|
switch expected := expectedValue.(type) {
|
||||||
|
case []string:
|
||||||
|
actual, ok := actualValue.([]string)
|
||||||
|
require.True(t, ok, "Expected []string for key %s, got %T", expectedKey, actualValue)
|
||||||
|
assert.ElementsMatch(t, expected, actual, "Values mismatch for key %s", expectedKey)
|
||||||
|
case []any:
|
||||||
|
actual, ok := actualValue.([]any)
|
||||||
|
require.True(t, ok, "Expected []any for key %s, got %T", expectedKey, actualValue)
|
||||||
|
assert.ElementsMatch(t, expected, actual, "Values mismatch for key %s", expectedKey)
|
||||||
|
case string:
|
||||||
|
actual, ok := actualValue.(string)
|
||||||
|
require.True(t, ok, "Expected string for key %s, got %T", expectedKey, actualValue)
|
||||||
|
assert.Equal(t, expected, actual, "Values mismatch for key %s", expectedKey)
|
||||||
|
default:
|
||||||
|
assert.Equal(t, expected, actualValue, "Values mismatch for key %s", expectedKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure no unexpected keys are present in decoded output
|
||||||
|
decoded.Iterate(func(key string, value any) bool {
|
||||||
|
_, exists := tt.expected[key]
|
||||||
|
assert.True(t, exists, "Unexpected key %s found in decoded output", key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Logf("Encoded output:\n%s", encoded)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiPartFormRoundTrip(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Errorf("Test panicked: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
form := NewMultiPartForm()
|
||||||
|
form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"
|
||||||
|
|
||||||
|
original := mapsutil.NewOrderedMap[string, any]()
|
||||||
|
original.Set("username", "john")
|
||||||
|
original.Set("interests", []string{"sports", "music", "reading"})
|
||||||
|
|
||||||
|
encoded, err := form.Encode(KVOrderedMap(&original))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
decoded, err := form.Decode(encoded)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "john", decoded.Get("username"))
|
||||||
|
assert.ElementsMatch(t, []string{"sports", "music", "reading"}, decoded.Get("interests"))
|
||||||
|
|
||||||
|
t.Logf("Encoded output:\n%s", encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiPartFormFileUpload(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
t.Errorf("Test panicked: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Test decoding of a manually crafted multipart form with files
|
||||||
|
form := NewMultiPartForm()
|
||||||
|
form.boundary = "----WebKitFormBoundaryFileUploadTest"
|
||||||
|
|
||||||
|
// Manually craft a multipart form with file uploads
|
||||||
|
multipartData := `------WebKitFormBoundaryFileUploadTest
|
||||||
|
Content-Disposition: form-data; name="name"
|
||||||
|
|
||||||
|
John Doe
|
||||||
|
------WebKitFormBoundaryFileUploadTest
|
||||||
|
Content-Disposition: form-data; name="email"
|
||||||
|
|
||||||
|
john@example.com
|
||||||
|
------WebKitFormBoundaryFileUploadTest
|
||||||
|
Content-Disposition: form-data; name="profile_picture"; filename="profile.jpg"
|
||||||
|
Content-Type: image/jpeg
|
||||||
|
|
||||||
|
fake_jpeg_binary_data_here
|
||||||
|
------WebKitFormBoundaryFileUploadTest
|
||||||
|
Content-Disposition: form-data; name="documents"; filename="resume.pdf"
|
||||||
|
Content-Type: application/pdf
|
||||||
|
|
||||||
|
fake_pdf_content_1
|
||||||
|
------WebKitFormBoundaryFileUploadTest
|
||||||
|
Content-Disposition: form-data; name="documents"; filename="cover_letter.pdf"
|
||||||
|
Content-Type: application/pdf
|
||||||
|
|
||||||
|
fake_pdf_content_2
|
||||||
|
------WebKitFormBoundaryFileUploadTest
|
||||||
|
Content-Disposition: form-data; name="skills"
|
||||||
|
|
||||||
|
Go
|
||||||
|
------WebKitFormBoundaryFileUploadTest
|
||||||
|
Content-Disposition: form-data; name="skills"
|
||||||
|
|
||||||
|
JavaScript
|
||||||
|
------WebKitFormBoundaryFileUploadTest
|
||||||
|
Content-Disposition: form-data; name="skills"
|
||||||
|
|
||||||
|
Python
|
||||||
|
------WebKitFormBoundaryFileUploadTest--
|
||||||
|
`
|
||||||
|
|
||||||
|
// Test decoding
|
||||||
|
decoded, err := form.Decode(multipartData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify regular fields
|
||||||
|
assert.Equal(t, "John Doe", decoded.Get("name"))
|
||||||
|
assert.Equal(t, "john@example.com", decoded.Get("email"))
|
||||||
|
assert.Equal(t, []string{"Go", "JavaScript", "Python"}, decoded.Get("skills"))
|
||||||
|
|
||||||
|
// Verify file fields
|
||||||
|
profilePicture := decoded.Get("profile_picture")
|
||||||
|
require.NotNil(t, profilePicture)
|
||||||
|
profileArray, ok := profilePicture.([]interface{})
|
||||||
|
require.True(t, ok, "Expected []interface{} for profile_picture")
|
||||||
|
require.Len(t, profileArray, 1)
|
||||||
|
assert.Equal(t, "fake_jpeg_binary_data_here", profileArray[0])
|
||||||
|
|
||||||
|
documents := decoded.Get("documents")
|
||||||
|
require.NotNil(t, documents)
|
||||||
|
documentsArray, ok := documents.([]interface{})
|
||||||
|
require.True(t, ok, "Expected []interface{} for documents")
|
||||||
|
require.Len(t, documentsArray, 2)
|
||||||
|
assert.Contains(t, documentsArray, "fake_pdf_content_1")
|
||||||
|
assert.Contains(t, documentsArray, "fake_pdf_content_2")
|
||||||
|
}
|
||||||
@ -23,10 +23,9 @@ import (
|
|||||||
urlutil "github.com/projectdiscovery/utils/url"
|
urlutil "github.com/projectdiscovery/utils/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrRuleNotApplicable returns a rule not applicable error
|
var (
|
||||||
func ErrRuleNotApplicable(reason interface{}) error {
|
ErrRuleNotApplicable = errkit.New("rule not applicable")
|
||||||
return errkit.New(fmt.Sprintf("rule not applicable: %v", reason)).Build()
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// IsErrRuleNotApplicable checks if an error is due to rule not applicable
|
// IsErrRuleNotApplicable checks if an error is due to rule not applicable
|
||||||
func IsErrRuleNotApplicable(err error) bool {
|
func IsErrRuleNotApplicable(err error) bool {
|
||||||
@ -90,10 +89,10 @@ type GeneratedRequest struct {
|
|||||||
// goroutines.
|
// goroutines.
|
||||||
func (rule *Rule) Execute(input *ExecuteRuleInput) (err error) {
|
func (rule *Rule) Execute(input *ExecuteRuleInput) (err error) {
|
||||||
if !rule.isInputURLValid(input.Input) {
|
if !rule.isInputURLValid(input.Input) {
|
||||||
return ErrRuleNotApplicable(fmt.Sprintf("invalid input url: %v", input.Input.MetaInput.Input))
|
return errkit.Newf("rule not applicable: invalid input url: %v", input.Input.MetaInput.Input)
|
||||||
}
|
}
|
||||||
if input.BaseRequest == nil && input.Input.MetaInput.ReqResp == nil {
|
if input.BaseRequest == nil && input.Input.MetaInput.ReqResp == nil {
|
||||||
return ErrRuleNotApplicable(fmt.Sprintf("both base request and reqresp are nil for %v", input.Input.MetaInput.Input))
|
return errkit.Newf("rule not applicable: both base request and reqresp are nil for %v", input.Input.MetaInput.Input)
|
||||||
}
|
}
|
||||||
|
|
||||||
var finalComponentList []component.Component
|
var finalComponentList []component.Component
|
||||||
@ -145,7 +144,7 @@ func (rule *Rule) Execute(input *ExecuteRuleInput) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(finalComponentList) == 0 {
|
if len(finalComponentList) == 0 {
|
||||||
return ErrRuleNotApplicable("no component matched on this rule")
|
return errkit.Newf("rule not applicable: no component matched on this rule")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseValues := input.Values
|
baseValues := input.Values
|
||||||
|
|||||||
@ -28,6 +28,12 @@ type InputFormatOptions struct {
|
|||||||
// RequiredOnly only uses required fields when generating requests
|
// RequiredOnly only uses required fields when generating requests
|
||||||
// instead of all fields
|
// instead of all fields
|
||||||
RequiredOnly bool
|
RequiredOnly bool
|
||||||
|
// VarsTextTemplating uses Variables and inject it into the input
|
||||||
|
// this is used for text templating of variables based on carvel ytt
|
||||||
|
// Only available for Yaml formats
|
||||||
|
VarsTextTemplating bool
|
||||||
|
// VarsFilePaths is the path to the file containing variables
|
||||||
|
VarsFilePaths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format is an interface implemented by all input formats
|
// Format is an interface implemented by all input formats
|
||||||
|
|||||||
@ -395,7 +395,7 @@ func generateRequestsFromOp(opts *generateReqOptions) error {
|
|||||||
func GetGlobalParamsForSecurityRequirement(schema *openapi3.T, requirement *openapi3.SecurityRequirements) ([]*openapi3.ParameterRef, error) {
|
func GetGlobalParamsForSecurityRequirement(schema *openapi3.T, requirement *openapi3.SecurityRequirements) ([]*openapi3.ParameterRef, error) {
|
||||||
globalParams := openapi3.NewParameters()
|
globalParams := openapi3.NewParameters()
|
||||||
if len(schema.Components.SecuritySchemes) == 0 {
|
if len(schema.Components.SecuritySchemes) == 0 {
|
||||||
return nil, errkit.New(fmt.Sprintf("openapi: security requirements (%+v) without any security schemes found in openapi file", schema.Security)).Build()
|
return nil, errkit.Newf("security requirements (%+v) without any security schemes found in openapi file", schema.Security)
|
||||||
}
|
}
|
||||||
found := false
|
found := false
|
||||||
// this api is protected for each security scheme pull its corresponding scheme
|
// this api is protected for each security scheme pull its corresponding scheme
|
||||||
@ -415,11 +415,11 @@ schemaLabel:
|
|||||||
}
|
}
|
||||||
if !found && len(security) > 1 {
|
if !found && len(security) > 1 {
|
||||||
// if this is case then both security schemes are required
|
// if this is case then both security schemes are required
|
||||||
return nil, errkit.New(fmt.Sprintf("openapi: security requirement (%+v) not found in openapi file", security)).Build()
|
return nil, errkit.Newf("security requirement (%+v) not found in openapi file", security)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
return nil, errkit.New(fmt.Sprintf("openapi: security requirement (%+v) not found in openapi file", requirement)).Build()
|
return nil, errkit.Newf("security requirement (%+v) not found in openapi file", requirement)
|
||||||
}
|
}
|
||||||
|
|
||||||
return globalParams, nil
|
return globalParams, nil
|
||||||
@ -428,12 +428,12 @@ schemaLabel:
|
|||||||
// GenerateParameterFromSecurityScheme generates an example from a schema object
|
// GenerateParameterFromSecurityScheme generates an example from a schema object
|
||||||
func GenerateParameterFromSecurityScheme(scheme *openapi3.SecuritySchemeRef) (*openapi3.Parameter, error) {
|
func GenerateParameterFromSecurityScheme(scheme *openapi3.SecuritySchemeRef) (*openapi3.Parameter, error) {
|
||||||
if !generic.EqualsAny(scheme.Value.Type, "http", "apiKey") {
|
if !generic.EqualsAny(scheme.Value.Type, "http", "apiKey") {
|
||||||
return nil, errkit.New(fmt.Sprintf("openapi: unsupported security scheme type (%s) found in openapi file", scheme.Value.Type)).Build()
|
return nil, errkit.Newf("unsupported security scheme type (%s) found in openapi file", scheme.Value.Type)
|
||||||
}
|
}
|
||||||
if scheme.Value.Type == "http" {
|
if scheme.Value.Type == "http" {
|
||||||
// check scheme
|
// check scheme
|
||||||
if !generic.EqualsAny(scheme.Value.Scheme, "basic", "bearer") {
|
if !generic.EqualsAny(scheme.Value.Scheme, "basic", "bearer") {
|
||||||
return nil, errkit.New(fmt.Sprintf("openapi: unsupported security scheme (%s) found in openapi file", scheme.Value.Scheme)).Build()
|
return nil, errkit.Newf("unsupported security scheme (%s) found in openapi file", scheme.Value.Scheme)
|
||||||
}
|
}
|
||||||
// HTTP authentication schemes basic or bearer use the Authorization header
|
// HTTP authentication schemes basic or bearer use the Authorization header
|
||||||
headerName := scheme.Value.Name
|
headerName := scheme.Value.Name
|
||||||
@ -458,10 +458,10 @@ func GenerateParameterFromSecurityScheme(scheme *openapi3.SecuritySchemeRef) (*o
|
|||||||
if scheme.Value.Type == "apiKey" {
|
if scheme.Value.Type == "apiKey" {
|
||||||
// validate name and in
|
// validate name and in
|
||||||
if scheme.Value.Name == "" {
|
if scheme.Value.Name == "" {
|
||||||
return nil, errkit.New(fmt.Sprintf("openapi: security scheme (%s) name is empty", scheme.Value.Type)).Build()
|
return nil, errkit.Newf("security scheme (%s) name is empty", scheme.Value.Type)
|
||||||
}
|
}
|
||||||
if !generic.EqualsAny(scheme.Value.In, "query", "header", "cookie") {
|
if !generic.EqualsAny(scheme.Value.In, "query", "header", "cookie") {
|
||||||
return nil, errkit.New(fmt.Sprintf("openapi: unsupported security scheme (%s) in (%s) found in openapi file", scheme.Value.Type, scheme.Value.In)).Build()
|
return nil, errkit.Newf("unsupported security scheme (%s) in (%s) found in openapi file", scheme.Value.Type, scheme.Value.In)
|
||||||
}
|
}
|
||||||
// create parameters using the scheme
|
// create parameters using the scheme
|
||||||
switch scheme.Value.In {
|
switch scheme.Value.In {
|
||||||
@ -482,5 +482,5 @@ func GenerateParameterFromSecurityScheme(scheme *openapi3.SecuritySchemeRef) (*o
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, errkit.New(fmt.Sprintf("openapi: unsupported security scheme type (%s) found in openapi file", scheme.Value.Type)).Build()
|
return nil, errkit.Newf("unsupported security scheme type (%s) found in openapi file", scheme.Value.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
25
pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml
vendored
Normal file
25
pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#@ load("@ytt:data", "data")
|
||||||
|
#@ load("@ytt:json", "json")
|
||||||
|
|
||||||
|
#@ def get_value(key, default=""):
|
||||||
|
#@ if hasattr(data.values, key):
|
||||||
|
#@ return str(getattr(data.values, key))
|
||||||
|
#@ else:
|
||||||
|
#@ return default
|
||||||
|
#@ end
|
||||||
|
#@ end
|
||||||
|
|
||||||
|
timestamp: 2024-02-20T19:24:13+05:32
|
||||||
|
url: https://ginandjuice.shop/users/3
|
||||||
|
request:
|
||||||
|
#@yaml/text-templated-strings
|
||||||
|
raw: |+
|
||||||
|
POST /users/3 HTTP/1.1
|
||||||
|
Host: ginandjuice.shop
|
||||||
|
Authorization: Bearer (@= get_value("token", "3x4mpl3t0k3n") @)
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
Content-Type: application/x-www-form-urlencoded
|
||||||
|
Connection: close
|
||||||
|
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
|
||||||
|
|
||||||
|
foo=(@= json.encode(data.values.foo) @)&bar=(@= get_value("bar") @)&debug=(@= get_value("debug", "false") @)
|
||||||
11
pkg/input/formats/testdata/ytt/ytt-profile.yaml
vendored
Normal file
11
pkg/input/formats/testdata/ytt/ytt-profile.yaml
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
list: pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml
|
||||||
|
input-mode: yaml
|
||||||
|
templates:
|
||||||
|
- integration_tests/fuzz/fuzz-body.yaml
|
||||||
|
var:
|
||||||
|
- debug=true
|
||||||
|
- bar=bar
|
||||||
|
vars-text-templating: true
|
||||||
|
var-file-paths:
|
||||||
|
- pkg/input/formats/testdata/ytt/ytt-vars.yaml
|
||||||
|
dast: true
|
||||||
3
pkg/input/formats/testdata/ytt/ytt-vars.yaml
vendored
Normal file
3
pkg/input/formats/testdata/ytt/ytt-vars.yaml
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
token: foobar
|
||||||
|
foo:
|
||||||
|
bar: baz
|
||||||
@ -1,8 +1,8 @@
|
|||||||
package yaml
|
package yaml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/projectdiscovery/gologger"
|
"github.com/projectdiscovery/gologger"
|
||||||
@ -46,23 +46,41 @@ func (j *YamlMultiDocFormat) SetOptions(options formats.InputFormatOptions) {
|
|||||||
// Parse parses the input and calls the provided callback
|
// Parse parses the input and calls the provided callback
|
||||||
// function for each RawRequest it discovers.
|
// function for each RawRequest it discovers.
|
||||||
func (j *YamlMultiDocFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error {
|
func (j *YamlMultiDocFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error {
|
||||||
decoder := YamlUtil.NewDecoder(input)
|
finalInput := input
|
||||||
|
|
||||||
|
// Apply text templating if enabled
|
||||||
|
if j.opts.VarsTextTemplating {
|
||||||
|
data, err := io.ReadAll(input)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not read input")
|
||||||
|
}
|
||||||
|
tpl := []string{string(data)}
|
||||||
|
dvs := mapToKeyValueSlice(j.opts.Variables)
|
||||||
|
finalData, err := ytt(tpl, dvs, j.opts.VarsFilePaths)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not apply ytt templating")
|
||||||
|
}
|
||||||
|
finalInput = bytes.NewReader(finalData)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := YamlUtil.NewDecoder(finalInput)
|
||||||
for {
|
for {
|
||||||
var request proxifyRequest
|
var request proxifyRequest
|
||||||
err := decoder.Decode(&request)
|
if err := decoder.Decode(&request); err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
|
}
|
||||||
|
return errors.Wrap(err, "could not decode yaml file")
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "could not decode json file")
|
raw := request.Request.Raw
|
||||||
}
|
if raw == "" {
|
||||||
if strings.TrimSpace(request.Request.Raw) == "" {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rawRequest, err := types.ParseRawRequestWithURL(request.Request.Raw, request.URL)
|
rawRequest, err := types.ParseRawRequestWithURL(raw, request.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gologger.Warning().Msgf("multidoc-yaml: Could not parse raw request %s: %s\n", request.URL, err)
|
gologger.Warning().Msgf("multidoc-yaml: Could not parse raw request %s: %s", request.URL, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
resultsCb(rawRequest)
|
resultsCb(rawRequest)
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package yaml
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
|
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -33,3 +35,48 @@ func TestYamlFormatterParse(t *testing.T) {
|
|||||||
require.Len(t, urls, len(expectedUrls), "invalid number of urls")
|
require.Len(t, urls, len(expectedUrls), "invalid number of urls")
|
||||||
require.ElementsMatch(t, urls, expectedUrls, "invalid urls")
|
require.ElementsMatch(t, urls, expectedUrls, "invalid urls")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestYamlFormatterParseWithVariables(t *testing.T) {
|
||||||
|
format := New()
|
||||||
|
proxifyYttFile := "../testdata/ytt/ginandjuice.ytt.yaml"
|
||||||
|
|
||||||
|
expectedUrls := []string{
|
||||||
|
"https://ginandjuice.shop/users/3",
|
||||||
|
}
|
||||||
|
|
||||||
|
format.SetOptions(formats.InputFormatOptions{
|
||||||
|
VarsTextTemplating: true,
|
||||||
|
Variables: map[string]interface{}{
|
||||||
|
"foo": "catalog",
|
||||||
|
"bar": "product",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
file, err := os.Open(proxifyYttFile)
|
||||||
|
require.Nilf(t, err, "error opening proxify ytt input file: %v", err)
|
||||||
|
defer func() {
|
||||||
|
_ = file.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var urls []string
|
||||||
|
err = format.Parse(file, func(request *types.RequestResponse) bool {
|
||||||
|
urls = append(urls, request.URL.String())
|
||||||
|
expectedRaw := `POST /users/3 HTTP/1.1
|
||||||
|
Host: ginandjuice.shop
|
||||||
|
Authorization: Bearer 3x4mpl3t0k3n
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
Content-Type: application/x-www-form-urlencoded
|
||||||
|
Connection: close
|
||||||
|
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
|
||||||
|
|
||||||
|
foo="catalog"&bar=product&debug=false`
|
||||||
|
normalised := strings.ReplaceAll(request.Request.Raw, "\r\n", "\n")
|
||||||
|
require.Equal(t, expectedRaw, strings.TrimSuffix(normalised, "\n"), "request raw does not match expected value")
|
||||||
|
|
||||||
|
return false
|
||||||
|
}, proxifyYttFile)
|
||||||
|
|
||||||
|
require.Nilf(t, err, "error parsing yaml file: %v", err)
|
||||||
|
require.Len(t, urls, len(expectedUrls), "invalid number of urls")
|
||||||
|
require.ElementsMatch(t, urls, expectedUrls, "invalid urls")
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
70
pkg/input/formats/yaml/ytt.go
Normal file
70
pkg/input/formats/yaml/ytt.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
yttcmd "carvel.dev/ytt/pkg/cmd/template"
|
||||||
|
yttui "carvel.dev/ytt/pkg/cmd/ui"
|
||||||
|
yttfiles "carvel.dev/ytt/pkg/files"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ytt(tpl, dvs []string, varFiles []string) ([]byte, error) {
|
||||||
|
// create and invoke ytt "template" command
|
||||||
|
templatingOptions := yttcmd.NewOptions()
|
||||||
|
|
||||||
|
input, err := templatesAsInput(tpl...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(varFiles) > 0 {
|
||||||
|
// Load vaarFiles into the templating options.
|
||||||
|
templatingOptions.DataValuesFlags.FromFiles = varFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
// equivalent to `--data-value-yaml`
|
||||||
|
templatingOptions.DataValuesFlags.KVsFromYAML = dvs
|
||||||
|
|
||||||
|
// for in-memory use, pipe output to "/dev/null"
|
||||||
|
noopUI := yttui.NewCustomWriterTTY(false, noopWriter{}, noopWriter{})
|
||||||
|
|
||||||
|
// Evaluate the template given the configured data values...
|
||||||
|
output := templatingOptions.RunWithFiles(input, noopUI)
|
||||||
|
if output.Err != nil {
|
||||||
|
return nil, output.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.DocSet.AsBytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// templatesAsInput conveniently wraps one or more strings, each in a files.File, into a template.Input.
|
||||||
|
func templatesAsInput(tpl ...string) (yttcmd.Input, error) {
|
||||||
|
var files []*yttfiles.File
|
||||||
|
for i, t := range tpl {
|
||||||
|
// to make this less brittle, you'll probably want to use well-defined names for `path`, here, for each input.
|
||||||
|
// this matters when you're processing errors which report based on these paths.
|
||||||
|
file, err := yttfiles.NewFileFromSource(yttfiles.NewBytesSource(fmt.Sprintf("tpl%d.yml", i), []byte(t)))
|
||||||
|
if err != nil {
|
||||||
|
return yttcmd.Input{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
files = append(files, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
return yttcmd.Input{Files: files}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapToKeyValueSlice(m map[string]interface{}) []string {
|
||||||
|
var result []string
|
||||||
|
for k, v := range m {
|
||||||
|
y, _ := yaml.Marshal(v)
|
||||||
|
result = append(result, fmt.Sprintf("%s=%s", k, strings.TrimSpace(string(y))))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
type noopWriter struct{}
|
||||||
|
|
||||||
|
func (w noopWriter) Write(data []byte) (int, error) { return len(data), nil }
|
||||||
@ -18,14 +18,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInactiveInput = fmt.Errorf("input is inactive")
|
ErrNotImplemented = errkit.New("provider does not implement method")
|
||||||
|
ErrInactiveInput = fmt.Errorf("input is inactive")
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNotImplemented returns an error when a provider does not implement a method
|
|
||||||
func ErrNotImplemented(provider, method string) error {
|
|
||||||
return errkit.New(fmt.Sprintf("provider %s does not implement %s", provider, method)).Build()
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MultiFormatInputProvider = "MultiFormatInputProvider"
|
MultiFormatInputProvider = "MultiFormatInputProvider"
|
||||||
ListInputProvider = "ListInputProvider"
|
ListInputProvider = "ListInputProvider"
|
||||||
@ -120,6 +116,8 @@ func NewInputProvider(opts InputOptions) (InputProvider, error) {
|
|||||||
Variables: generators.MergeMaps(extraVars, opts.Options.Vars.AsMap()),
|
Variables: generators.MergeMaps(extraVars, opts.Options.Vars.AsMap()),
|
||||||
SkipFormatValidation: opts.Options.SkipFormatValidation,
|
SkipFormatValidation: opts.Options.SkipFormatValidation,
|
||||||
RequiredOnly: opts.Options.FormatUseRequiredOnly,
|
RequiredOnly: opts.Options.FormatUseRequiredOnly,
|
||||||
|
VarsTextTemplating: opts.Options.VarsTextTemplating,
|
||||||
|
VarsFilePaths: opts.Options.VarsFilePaths,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,7 +80,7 @@ func (t *TemplateManager) FreshInstallIfNotExists() error {
|
|||||||
}
|
}
|
||||||
gologger.Info().Msgf("nuclei-templates are not installed, installing...")
|
gologger.Info().Msgf("nuclei-templates are not installed, installing...")
|
||||||
if err := t.installTemplatesAt(config.DefaultConfig.TemplatesDirectory); err != nil {
|
if err := t.installTemplatesAt(config.DefaultConfig.TemplatesDirectory); err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("failed to install templates at %s", config.DefaultConfig.TemplatesDirectory)), err)
|
return errkit.Wrapf(err, "failed to install templates at %s", config.DefaultConfig.TemplatesDirectory)
|
||||||
}
|
}
|
||||||
if t.CustomTemplates != nil {
|
if t.CustomTemplates != nil {
|
||||||
t.CustomTemplates.Download(context.TODO())
|
t.CustomTemplates.Download(context.TODO())
|
||||||
@ -121,7 +121,7 @@ func (t *TemplateManager) UpdateIfOutdated() error {
|
|||||||
func (t *TemplateManager) installTemplatesAt(dir string) error {
|
func (t *TemplateManager) installTemplatesAt(dir string) error {
|
||||||
if !fileutil.FolderExists(dir) {
|
if !fileutil.FolderExists(dir) {
|
||||||
if err := fileutil.CreateFolder(dir); err != nil {
|
if err := fileutil.CreateFolder(dir); err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("failed to create directory at %s", dir)), err)
|
return errkit.Wrapf(err, "failed to create directory at %s", dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.DisablePublicTemplates {
|
if t.DisablePublicTemplates {
|
||||||
@ -130,12 +130,12 @@ func (t *TemplateManager) installTemplatesAt(dir string) error {
|
|||||||
}
|
}
|
||||||
ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)
|
ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("failed to install templates at %s", dir)), err)
|
return errkit.Wrapf(err, "failed to install templates at %s", dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// write templates to disk
|
// write templates to disk
|
||||||
if err := t.writeTemplatesToDisk(ghrd, dir); err != nil {
|
if err := t.writeTemplatesToDisk(ghrd, dir); err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("failed to write templates to disk at %s", dir)), err)
|
return errkit.Wrapf(err, "failed to write templates to disk at %s", dir)
|
||||||
}
|
}
|
||||||
gologger.Info().Msgf("Successfully installed nuclei-templates at %s", dir)
|
gologger.Info().Msgf("Successfully installed nuclei-templates at %s", dir)
|
||||||
return nil
|
return nil
|
||||||
@ -156,7 +156,7 @@ func (t *TemplateManager) updateTemplatesAt(dir string) error {
|
|||||||
|
|
||||||
ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)
|
ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("failed to install templates at %s", dir)), err)
|
return errkit.Wrapf(err, "failed to install templates at %s", dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
latestVersion := ghrd.Latest.GetTagName()
|
latestVersion := ghrd.Latest.GetTagName()
|
||||||
@ -177,7 +177,7 @@ func (t *TemplateManager) updateTemplatesAt(dir string) error {
|
|||||||
newchecksums, err := t.getChecksumFromDir(dir)
|
newchecksums, err := t.getChecksumFromDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// unlikely this case will happen
|
// unlikely this case will happen
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("failed to get checksums from %s after update", dir)), err)
|
return errkit.Wrapf(err, "failed to get checksums from %s after update", dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// summarize all changes
|
// summarize all changes
|
||||||
@ -299,7 +299,7 @@ func (t *TemplateManager) writeTemplatesToDisk(ghrd *updateutils.GHReleaseDownlo
|
|||||||
bin, err := io.ReadAll(r)
|
bin, err := io.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if error occurs, iteration also stops
|
// if error occurs, iteration also stops
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("failed to read file %s", uri)), err)
|
return errkit.Wrapf(err, "failed to read file %s", uri)
|
||||||
}
|
}
|
||||||
// TODO: It might be better to just download index file from nuclei templates repo
|
// TODO: It might be better to just download index file from nuclei templates repo
|
||||||
// instead of creating it from scratch
|
// instead of creating it from scratch
|
||||||
@ -310,7 +310,7 @@ func (t *TemplateManager) writeTemplatesToDisk(ghrd *updateutils.GHReleaseDownlo
|
|||||||
if oldPath != writePath {
|
if oldPath != writePath {
|
||||||
// write new template at a new path and delete old template
|
// write new template at a new path and delete old template
|
||||||
if err := os.WriteFile(writePath, bin, f.Mode()); err != nil {
|
if err := os.WriteFile(writePath, bin, f.Mode()); err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("failed to write file %s", uri)), err)
|
return errkit.Wrapf(err, "failed to write file %s", uri)
|
||||||
}
|
}
|
||||||
// after successful write, remove old template
|
// after successful write, remove old template
|
||||||
if err := os.Remove(oldPath); err != nil {
|
if err := os.Remove(oldPath); err != nil {
|
||||||
@ -325,20 +325,20 @@ func (t *TemplateManager) writeTemplatesToDisk(ghrd *updateutils.GHReleaseDownlo
|
|||||||
}
|
}
|
||||||
err = ghrd.DownloadSourceWithCallback(!HideProgressBar, callbackFunc)
|
err = ghrd.DownloadSourceWithCallback(!HideProgressBar, callbackFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("failed to download templates"), err)
|
return errkit.Wrap(err, "failed to download templates")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := config.DefaultConfig.WriteTemplatesConfig(); err != nil {
|
if err := config.DefaultConfig.WriteTemplatesConfig(); err != nil {
|
||||||
return errkit.Append(errkit.New("failed to write templates config"), err)
|
return errkit.Wrap(err, "failed to write templates config")
|
||||||
}
|
}
|
||||||
// update ignore hash after writing new templates
|
// update ignore hash after writing new templates
|
||||||
if err := config.DefaultConfig.UpdateNucleiIgnoreHash(); err != nil {
|
if err := config.DefaultConfig.UpdateNucleiIgnoreHash(); err != nil {
|
||||||
return errkit.Append(errkit.New("failed to update nuclei ignore hash"), err)
|
return errkit.Wrap(err, "failed to update nuclei ignore hash")
|
||||||
}
|
}
|
||||||
|
|
||||||
// update templates version in config file
|
// update templates version in config file
|
||||||
if err := config.DefaultConfig.SetTemplatesVersion(ghrd.Latest.GetTagName()); err != nil {
|
if err := config.DefaultConfig.SetTemplatesVersion(ghrd.Latest.GetTagName()); err != nil {
|
||||||
return errkit.Append(errkit.New("failed to update templates version"), err)
|
return errkit.Wrap(err, "failed to update templates version")
|
||||||
}
|
}
|
||||||
|
|
||||||
PurgeEmptyDirectories(dir)
|
PurgeEmptyDirectories(dir)
|
||||||
@ -348,11 +348,11 @@ func (t *TemplateManager) writeTemplatesToDisk(ghrd *updateutils.GHReleaseDownlo
|
|||||||
|
|
||||||
index, err := config.GetNucleiTemplatesIndex()
|
index, err := config.GetNucleiTemplatesIndex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("failed to get nuclei templates index"), err)
|
return errkit.Wrap(err, "failed to get nuclei templates index")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = config.DefaultConfig.WriteTemplatesIndex(index); err != nil {
|
if err = config.DefaultConfig.WriteTemplatesIndex(index); err != nil {
|
||||||
return errkit.Append(errkit.New("failed to write nuclei templates index"), err)
|
return errkit.Wrap(err, "failed to write nuclei templates index")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !HideReleaseNotes {
|
if !HideReleaseNotes {
|
||||||
@ -448,8 +448,5 @@ func (t *TemplateManager) calculateChecksumMap(dir string) (map[string]string, e
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
return checksumMap, errkit.Wrap(err, "failed to calculate checksums of templates")
|
||||||
return nil, errkit.Append(errkit.New("failed to calculate checksums of templates"), err)
|
|
||||||
}
|
|
||||||
return checksumMap, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,7 @@ func getNewAdditionsFileFromGitHub(version string) ([]string, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, errkit.New("version not found").Build()
|
return nil, errkit.New("version not found")
|
||||||
}
|
}
|
||||||
data, err := io.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -21,17 +21,17 @@ func (p *EntityParser) scrapeAndCreate(typeName string) error {
|
|||||||
// get package
|
// get package
|
||||||
pkg, ok := p.imports[pkgName]
|
pkg, ok := p.imports[pkgName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errkit.New(fmt.Sprintf("package %v for type %v not found", pkgName, typeName)).Build()
|
return errkit.Newf("package %v for type %v not found", pkgName, typeName)
|
||||||
}
|
}
|
||||||
// get type
|
// get type
|
||||||
obj := pkg.Types.Scope().Lookup(baseTypeName)
|
obj := pkg.Types.Scope().Lookup(baseTypeName)
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
return errkit.New(fmt.Sprintf("type %v not found in package %+v", typeName, pkg)).Build()
|
return errkit.Newf("type %v not found in package %+v", typeName, pkg)
|
||||||
}
|
}
|
||||||
// Ensure the object is a type name
|
// Ensure the object is a type name
|
||||||
typeNameObj, ok := obj.(*types.TypeName)
|
typeNameObj, ok := obj.(*types.TypeName)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errkit.New(fmt.Sprintf("%v is not a type name", typeName)).Build()
|
return errkit.Newf("%v is not a type name", typeName)
|
||||||
}
|
}
|
||||||
// Ensure the type is a named struct type
|
// Ensure the type is a named struct type
|
||||||
namedStruct, ok := typeNameObj.Type().Underlying().(*types.Struct)
|
namedStruct, ok := typeNameObj.Type().Underlying().(*types.Struct)
|
||||||
|
|||||||
@ -15,12 +15,12 @@ func init() {
|
|||||||
module.Set(
|
module.Set(
|
||||||
gojs.Objects{
|
gojs.Objects{
|
||||||
// Functions
|
// Functions
|
||||||
"IsOracle": lib_oracle.IsOracle,
|
|
||||||
|
|
||||||
// Var and consts
|
// Var and consts
|
||||||
|
|
||||||
// Objects / Classes
|
// Objects / Classes
|
||||||
"IsOracleResponse": gojs.GetClassConstructor[lib_oracle.IsOracleResponse](&lib_oracle.IsOracleResponse{}),
|
"IsOracleResponse": gojs.GetClassConstructor[lib_oracle.IsOracleResponse](&lib_oracle.IsOracleResponse{}),
|
||||||
|
"OracleClient": gojs.GetClassConstructor[lib_oracle.OracleClient](&lib_oracle.OracleClient{}),
|
||||||
},
|
},
|
||||||
).Register()
|
).Register()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ func init() {
|
|||||||
|
|
||||||
// Objects / Classes
|
// Objects / Classes
|
||||||
"IsVNCResponse": gojs.GetClassConstructor[lib_vnc.IsVNCResponse](&lib_vnc.IsVNCResponse{}),
|
"IsVNCResponse": gojs.GetClassConstructor[lib_vnc.IsVNCResponse](&lib_vnc.IsVNCResponse{}),
|
||||||
|
"VNCClient": gojs.GetClassConstructor[lib_vnc.VNCClient](&lib_vnc.VNCClient{}),
|
||||||
},
|
},
|
||||||
).Register()
|
).Register()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,106 @@
|
|||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IsOracle checks if a host is running an Oracle server
|
|
||||||
* @example
|
|
||||||
* ```javascript
|
|
||||||
* const oracle = require('nuclei/oracle');
|
|
||||||
* const isOracle = oracle.IsOracle('acme.com', 1521);
|
|
||||||
* log(toJSON(isOracle));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function IsOracle(host: string, port: number): IsOracleResponse | null {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IsOracleResponse is the response from the IsOracle function.
|
* IsOracleResponse is the response from the IsOracle function.
|
||||||
* this is returned by IsOracle function.
|
* this is returned by IsOracle function.
|
||||||
* @example
|
* @example
|
||||||
* ```javascript
|
* ```javascript
|
||||||
* const oracle = require('nuclei/oracle');
|
* const oracle = require('nuclei/oracle');
|
||||||
* const isOracle = oracle.IsOracle('acme.com', 1521);
|
* const client = new oracle.OracleClient();
|
||||||
|
* const isOracle = client.IsOracle('acme.com', 1521);
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export interface IsOracleResponse {
|
export interface IsOracleResponse {
|
||||||
|
|
||||||
IsOracle?: boolean,
|
IsOracle?: boolean,
|
||||||
|
|
||||||
Banner?: string,
|
Banner?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client is a client for Oracle database.
|
||||||
|
* Internally client uses go-ora driver.
|
||||||
|
* @example
|
||||||
|
* ```javascript
|
||||||
|
* const oracle = require('nuclei/oracle');
|
||||||
|
* const client = new oracle.OracleClient();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export class OracleClient {
|
||||||
|
// Constructor of OracleClient
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect connects to an Oracle database
|
||||||
|
* @example
|
||||||
|
* ```javascript
|
||||||
|
* const oracle = require('nuclei/oracle');
|
||||||
|
* const client = new oracle.OracleClient();
|
||||||
|
* client.Connect('acme.com', 1521, 'XE', 'user', 'password');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public Connect(host: string, port: number, serviceName: string, username: string, password: string): boolean | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConnectWithDSN connects to an Oracle database using a DSN string
|
||||||
|
* @example
|
||||||
|
* ```javascript
|
||||||
|
* const oracle = require('nuclei/oracle');
|
||||||
|
* const client = new oracle.OracleClient();
|
||||||
|
* client.ConnectWithDSN('oracle://user:password@host:port/service', 'SELECT @@version');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public ConnectWithDSN(dsn: string): boolean | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IsOracle checks if a host is running an Oracle server
|
||||||
|
* @example
|
||||||
|
* ```javascript
|
||||||
|
* const oracle = require('nuclei/oracle');
|
||||||
|
* const isOracle = oracle.IsOracle('acme.com', 1521);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public IsOracle(host: string, port: number): IsOracleResponse | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExecuteQuery connects to Oracle database using given credentials and executes a query.
|
||||||
|
* It returns the results of the query or an error if something goes wrong.
|
||||||
|
* @example
|
||||||
|
* ```javascript
|
||||||
|
* const oracle = require('nuclei/oracle');
|
||||||
|
* const client = new oracle.OracleClient();
|
||||||
|
* const result = client.ExecuteQuery('acme.com', 1521, 'username', 'password', 'XE', 'SELECT * FROM dual');
|
||||||
|
* log(to_json(result));
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public ExecuteQuery(host: string, port: number, username: string, password: string, dbName: string, query: string): SQLResult | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExecuteQueryWithDSN executes a query on an Oracle database using a DSN
|
||||||
|
* @example
|
||||||
|
* ```javascript
|
||||||
|
* const oracle = require('nuclei/oracle');
|
||||||
|
* const client = new oracle.OracleClient();
|
||||||
|
* const result = client.ExecuteQueryWithDSN('oracle://user:password@host:port/service', 'SELECT * FROM dual');
|
||||||
|
* log(to_json(result));
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public ExecuteQueryWithDSN(dsn: string, query: string): SQLResult | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQLResult Interface
|
||||||
|
*/
|
||||||
|
export interface SQLResult {
|
||||||
|
Count?: number,
|
||||||
|
Columns?: string[],
|
||||||
|
Rows?: any[],
|
||||||
|
}
|
||||||
|
|||||||
@ -33,3 +33,34 @@ export interface IsVNCResponse {
|
|||||||
Banner?: string,
|
Banner?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VNCClient is a client for VNC servers.
|
||||||
|
* @example
|
||||||
|
* ```javascript
|
||||||
|
* const vnc = require('nuclei/vnc');
|
||||||
|
* const client = new vnc.VNCClient();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export class VNCClient {
|
||||||
|
|
||||||
|
|
||||||
|
// Constructor of VNCClient
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect connects to VNC server using given password.
|
||||||
|
* If connection and authentication is successful, it returns true.
|
||||||
|
* If connection or authentication is unsuccessful, it returns false and error.
|
||||||
|
* The connection is closed after the function returns.
|
||||||
|
* @example
|
||||||
|
* ```javascript
|
||||||
|
* const vnc = require('nuclei/vnc');
|
||||||
|
* const client = new vnc.VNCClient();
|
||||||
|
* const connected = client.Connect('acme.com', 5900, 'password');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public Connect(host: string, port: number, password: string): boolean | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -257,7 +256,7 @@ func RegisterNativeScripts(runtime *goja.Runtime) error {
|
|||||||
// import default modules
|
// import default modules
|
||||||
_, err = runtime.RunString(defaultImports)
|
_, err = runtime.RunString(defaultImports)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("could not import default modules %v", defaultImports)), err)
|
return errkit.Wrapf(err, "could not import default modules %v", defaultImports)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -59,7 +59,7 @@ func wrapModuleFunc(runtime *goja.Runtime, fn interface{}) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only wrap if first parameter is context.Context
|
// Only wrap if first parameter is context.Context
|
||||||
if fnType.NumIn() == 0 || fnType.In(0) != reflect.TypeOf((*context.Context)(nil)).Elem() {
|
if fnType.NumIn() == 0 || fnType.In(0) != reflect.TypeFor[context.Context]() {
|
||||||
return fn // Return original function unchanged if it doesn't have context.Context as first arg
|
return fn // Return original function unchanged if it doesn't have context.Context as first arg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package gojs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/Mzack9999/goja"
|
"github.com/Mzack9999/goja"
|
||||||
@ -10,8 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidFuncOpts = errkit.New("invalid function options: %v").Build()
|
ErrInvalidFuncOpts = errkit.New("invalid function options")
|
||||||
ErrNilRuntime = errkit.New("runtime is nil").Build()
|
ErrNilRuntime = errkit.New("runtime is nil")
|
||||||
)
|
)
|
||||||
|
|
||||||
type FuncOpts struct {
|
type FuncOpts struct {
|
||||||
@ -35,7 +34,7 @@ func wrapWithContext(runtime *goja.Runtime, fn interface{}) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only wrap if first parameter is context.Context
|
// Only wrap if first parameter is context.Context
|
||||||
if fnType.NumIn() == 0 || fnType.In(0) != reflect.TypeOf((*context.Context)(nil)).Elem() {
|
if fnType.NumIn() == 0 || fnType.In(0) != reflect.TypeFor[context.Context]() {
|
||||||
return fn // Return original function unchanged if it doesn't have context.Context as first arg
|
return fn // Return original function unchanged if it doesn't have context.Context as first arg
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +83,7 @@ func RegisterFuncWithSignature(runtime *goja.Runtime, opts FuncOpts) error {
|
|||||||
return ErrNilRuntime
|
return ErrNilRuntime
|
||||||
}
|
}
|
||||||
if !opts.valid() {
|
if !opts.valid() {
|
||||||
return errkit.New(fmt.Sprintf("invalid function options: name: %s, signatures: %v, description: %s", opts.Name, opts.Signatures, opts.Description)).Build()
|
return errkit.Newf("invalid function options: name: %s, signatures: %v, description: %s", opts.Name, opts.Signatures, opts.Description)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap the function with context injection
|
// Wrap the function with context injection
|
||||||
|
|||||||
@ -63,7 +63,7 @@ func connect(executionId string, host string, port int, username string, passwor
|
|||||||
}
|
}
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return false, protocolstate.ErrHostDenied(host)
|
return false, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
target := net.JoinHostPort(host, fmt.Sprintf("%d", port))
|
target := net.JoinHostPort(host, fmt.Sprintf("%d", port))
|
||||||
@ -118,7 +118,7 @@ func (c *MSSQLClient) IsMssql(ctx context.Context, host string, port int) (bool,
|
|||||||
func isMssql(executionId string, host string, port int) (bool, error) {
|
func isMssql(executionId string, host string, port int) (bool, error) {
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return false, protocolstate.ErrHostDenied(host)
|
return false, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer := protocolstate.GetDialersWithId(executionId)
|
dialer := protocolstate.GetDialersWithId(executionId)
|
||||||
@ -162,7 +162,7 @@ func (c *MSSQLClient) ExecuteQuery(ctx context.Context, host string, port int, u
|
|||||||
}
|
}
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return nil, protocolstate.ErrHostDenied(host)
|
return nil, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
target := net.JoinHostPort(host, fmt.Sprintf("%d", port))
|
target := net.JoinHostPort(host, fmt.Sprintf("%d", port))
|
||||||
|
|||||||
@ -45,7 +45,7 @@ func (c *MySQLClient) IsMySQL(ctx context.Context, host string, port int) (bool,
|
|||||||
func isMySQL(executionId string, host string, port int) (bool, error) {
|
func isMySQL(executionId string, host string, port int) (bool, error) {
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return false, protocolstate.ErrHostDenied(host)
|
return false, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
dialer := protocolstate.GetDialersWithId(executionId)
|
dialer := protocolstate.GetDialersWithId(executionId)
|
||||||
if dialer == nil {
|
if dialer == nil {
|
||||||
@ -85,7 +85,7 @@ func (c *MySQLClient) Connect(ctx context.Context, host string, port int, userna
|
|||||||
executionId := ctx.Value("executionId").(string)
|
executionId := ctx.Value("executionId").(string)
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return false, protocolstate.ErrHostDenied(host)
|
return false, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
// executing queries implies the remote mysql service
|
// executing queries implies the remote mysql service
|
||||||
@ -144,7 +144,7 @@ func fingerprintMySQL(executionId string, host string, port int) (MySQLInfo, err
|
|||||||
info := MySQLInfo{}
|
info := MySQLInfo{}
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return info, protocolstate.ErrHostDenied(host)
|
return info, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
dialer := protocolstate.GetDialersWithId(executionId)
|
dialer := protocolstate.GetDialersWithId(executionId)
|
||||||
if dialer == nil {
|
if dialer == nil {
|
||||||
@ -209,7 +209,7 @@ func (c *MySQLClient) ExecuteQueryWithOpts(ctx context.Context, opts MySQLOption
|
|||||||
executionId := ctx.Value("executionId").(string)
|
executionId := ctx.Value("executionId").(string)
|
||||||
if !protocolstate.IsHostAllowed(executionId, opts.Host) {
|
if !protocolstate.IsHostAllowed(executionId, opts.Host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return nil, protocolstate.ErrHostDenied(opts.Host)
|
return nil, protocolstate.ErrHostDenied.Msgf(opts.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
// executing queries implies the remote mysql service
|
// executing queries implies the remote mysql service
|
||||||
|
|||||||
@ -201,7 +201,7 @@ func (c *NetConn) RecvFull(N int) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
bin, err := reader.ConnReadNWithTimeout(c.conn, int64(N), c.timeout)
|
bin, err := reader.ConnReadNWithTimeout(c.conn, int64(N), c.timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, errkit.Append(errkit.New(fmt.Sprintf("failed to read %d bytes", N)), err)
|
return []byte{}, errkit.Wrapf(err, "failed to read %d bytes", N)
|
||||||
}
|
}
|
||||||
return bin, nil
|
return bin, nil
|
||||||
}
|
}
|
||||||
@ -226,7 +226,7 @@ func (c *NetConn) Recv(N int) ([]byte, error) {
|
|||||||
b := make([]byte, N)
|
b := make([]byte, N)
|
||||||
n, err := c.conn.Read(b)
|
n, err := c.conn.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, errkit.Append(errkit.New(fmt.Sprintf("failed to read %d bytes", N)), err)
|
return []byte{}, errkit.Wrapf(err, "failed to read %d bytes", N)
|
||||||
}
|
}
|
||||||
return b[:n], nil
|
return b[:n], nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package oracle
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -9,7 +10,9 @@ import (
|
|||||||
|
|
||||||
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
|
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
|
||||||
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/oracledb"
|
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/oracledb"
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
||||||
|
goora "github.com/sijms/go-ora/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -24,6 +27,16 @@ type (
|
|||||||
IsOracle bool
|
IsOracle bool
|
||||||
Banner string
|
Banner string
|
||||||
}
|
}
|
||||||
|
// Client is a client for Oracle database.
|
||||||
|
// Internally client uses oracle/godror driver.
|
||||||
|
// @example
|
||||||
|
// ```javascript
|
||||||
|
// const oracle = require('nuclei/oracle');
|
||||||
|
// const client = new oracle.OracleClient();
|
||||||
|
// ```
|
||||||
|
OracleClient struct {
|
||||||
|
connector *goora.OracleConnector
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsOracle checks if a host is running an Oracle server
|
// IsOracle checks if a host is running an Oracle server
|
||||||
@ -33,7 +46,7 @@ type (
|
|||||||
// const isOracle = oracle.IsOracle('acme.com', 1521);
|
// const isOracle = oracle.IsOracle('acme.com', 1521);
|
||||||
// log(toJSON(isOracle));
|
// log(toJSON(isOracle));
|
||||||
// ```
|
// ```
|
||||||
func IsOracle(ctx context.Context, host string, port int) (IsOracleResponse, error) {
|
func (c *OracleClient) IsOracle(ctx context.Context, host string, port int) (IsOracleResponse, error) {
|
||||||
executionId := ctx.Value("executionId").(string)
|
executionId := ctx.Value("executionId").(string)
|
||||||
return memoizedisOracle(executionId, host, port)
|
return memoizedisOracle(executionId, host, port)
|
||||||
}
|
}
|
||||||
@ -69,3 +82,129 @@ func isOracle(executionId string, host string, port int) (IsOracleResponse, erro
|
|||||||
resp.IsOracle = true
|
resp.IsOracle = true
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *OracleClient) oracleDbInstance(connStr string, executionId string) (*goora.OracleConnector, error) {
|
||||||
|
if c.connector != nil {
|
||||||
|
return c.connector, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
connector := goora.NewConnector(connStr)
|
||||||
|
oraConnector, ok := connector.(*goora.OracleConnector)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to cast connector to OracleConnector")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create custom dialer wrapper
|
||||||
|
customDialer := &oracleCustomDialer{
|
||||||
|
executionId: executionId,
|
||||||
|
}
|
||||||
|
|
||||||
|
oraConnector.Dialer(customDialer)
|
||||||
|
|
||||||
|
c.connector = oraConnector
|
||||||
|
|
||||||
|
return oraConnector, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect connects to an Oracle database
|
||||||
|
// @example
|
||||||
|
// ```javascript
|
||||||
|
// const oracle = require('nuclei/oracle');
|
||||||
|
// const client = new oracle.OracleClient;
|
||||||
|
// client.Connect('acme.com', 1521, 'XE', 'user', 'password');
|
||||||
|
// ```
|
||||||
|
func (c *OracleClient) Connect(ctx context.Context, host string, port int, serviceName string, username string, password string) (bool, error) {
|
||||||
|
connStr := goora.BuildUrl(host, port, serviceName, username, password, nil)
|
||||||
|
|
||||||
|
return c.ConnectWithDSN(ctx, connStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OracleClient) ConnectWithDSN(ctx context.Context, dsn string) (bool, error) {
|
||||||
|
executionId := ctx.Value("executionId").(string)
|
||||||
|
|
||||||
|
connector, err := c.oracleDbInstance(dsn, executionId)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
db := sql.OpenDB(connector)
|
||||||
|
defer func() {
|
||||||
|
_ = db.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
db.SetMaxOpenConns(1)
|
||||||
|
db.SetMaxIdleConns(0)
|
||||||
|
|
||||||
|
// Test the connection
|
||||||
|
err = db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteQuery connects to MS SQL database using given credentials and executes a query.
|
||||||
|
// It returns the results of the query or an error if something goes wrong.
|
||||||
|
// @example
|
||||||
|
// ```javascript
|
||||||
|
// const oracle = require('nuclei/oracle');
|
||||||
|
// const client = new oracle.OracleClient;
|
||||||
|
// const result = client.ExecuteQuery('acme.com', 1521, 'username', 'password', 'XE', 'SELECT @@version');
|
||||||
|
// log(to_json(result));
|
||||||
|
// ```
|
||||||
|
func (c *OracleClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) {
|
||||||
|
if host == "" || port <= 0 {
|
||||||
|
return nil, fmt.Errorf("invalid host or port")
|
||||||
|
}
|
||||||
|
|
||||||
|
isOracleResp, err := c.IsOracle(ctx, host, port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !isOracleResp.IsOracle {
|
||||||
|
return nil, fmt.Errorf("not a oracle service")
|
||||||
|
}
|
||||||
|
|
||||||
|
connStr := goora.BuildUrl(host, port, dbName, username, password, nil)
|
||||||
|
|
||||||
|
return c.ExecuteQueryWithDSN(ctx, connStr, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteQueryWithDSN executes a query on an Oracle database using a DSN
|
||||||
|
// @example
|
||||||
|
// ```javascript
|
||||||
|
// const oracle = require('nuclei/oracle');
|
||||||
|
// const client = new oracle.OracleClient;
|
||||||
|
// const result = client.ExecuteQueryWithDSN('oracle://user:password@host:port/service', 'SELECT @@version');
|
||||||
|
// log(to_json(result));
|
||||||
|
// ```
|
||||||
|
func (c *OracleClient) ExecuteQueryWithDSN(ctx context.Context, dsn string, query string) (*utils.SQLResult, error) {
|
||||||
|
executionId := ctx.Value("executionId").(string)
|
||||||
|
|
||||||
|
connector, err := c.oracleDbInstance(dsn, executionId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
db := sql.OpenDB(connector)
|
||||||
|
defer func() {
|
||||||
|
_ = db.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
db.SetMaxOpenConns(1)
|
||||||
|
db.SetMaxIdleConns(0)
|
||||||
|
|
||||||
|
rows, err := db.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := utils.UnmarshalSQLRows(rows)
|
||||||
|
if err != nil {
|
||||||
|
if data != nil && len(data.Rows) > 0 {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|||||||
42
pkg/js/libs/oracle/oracledialer.go
Normal file
42
pkg/js/libs/oracle/oracledialer.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package oracle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// oracleCustomDialer implements the dialer interface expected by go-ora
|
||||||
|
type oracleCustomDialer struct {
|
||||||
|
executionId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *oracleCustomDialer) dialWithCtx(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
dialers := protocolstate.GetDialersWithId(o.executionId)
|
||||||
|
if dialers == nil {
|
||||||
|
return nil, fmt.Errorf("dialers not initialized for %s", o.executionId)
|
||||||
|
}
|
||||||
|
if !protocolstate.IsHostAllowed(o.executionId, address) {
|
||||||
|
// host is not valid according to network policy
|
||||||
|
return nil, protocolstate.ErrHostDenied.Msgf(address)
|
||||||
|
}
|
||||||
|
return dialers.Fastdialer.Dial(ctx, network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *oracleCustomDialer) Dial(network, address string) (net.Conn, error) {
|
||||||
|
return o.dialWithCtx(context.TODO(), network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *oracleCustomDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return o.dialWithCtx(ctx, network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *oracleCustomDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
return o.dialWithCtx(ctx, network, address)
|
||||||
|
}
|
||||||
@ -122,7 +122,7 @@ func (c *PGClient) ExecuteQuery(ctx context.Context, host string, port int, user
|
|||||||
func executeQuery(executionId string, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {
|
func executeQuery(executionId string, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return nil, protocolstate.ErrHostDenied(host)
|
return nil, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
target := net.JoinHostPort(host, fmt.Sprintf("%d", port))
|
target := net.JoinHostPort(host, fmt.Sprintf("%d", port))
|
||||||
@ -179,7 +179,7 @@ func connect(executionId string, host string, port int, username string, passwor
|
|||||||
|
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return false, protocolstate.ErrHostDenied(host)
|
return false, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
target := net.JoinHostPort(host, fmt.Sprintf("%d", port))
|
target := net.JoinHostPort(host, fmt.Sprintf("%d", port))
|
||||||
|
|||||||
@ -27,7 +27,7 @@ func GetServerInfo(ctx context.Context, host string, port int) (string, error) {
|
|||||||
func getServerInfo(executionId string, host string, port int) (string, error) {
|
func getServerInfo(executionId string, host string, port int) (string, error) {
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return "", protocolstate.ErrHostDenied(host)
|
return "", protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
// create a new client
|
// create a new client
|
||||||
client := redis.NewClient(&redis.Options{
|
client := redis.NewClient(&redis.Options{
|
||||||
@ -69,7 +69,7 @@ func Connect(ctx context.Context, host string, port int, password string) (bool,
|
|||||||
func connect(executionId string, host string, port int, password string) (bool, error) {
|
func connect(executionId string, host string, port int, password string) (bool, error) {
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return false, protocolstate.ErrHostDenied(host)
|
return false, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
// create a new client
|
// create a new client
|
||||||
client := redis.NewClient(&redis.Options{
|
client := redis.NewClient(&redis.Options{
|
||||||
@ -109,7 +109,7 @@ func GetServerInfoAuth(ctx context.Context, host string, port int, password stri
|
|||||||
func getServerInfoAuth(executionId string, host string, port int, password string) (string, error) {
|
func getServerInfoAuth(executionId string, host string, port int, password string) (string, error) {
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return "", protocolstate.ErrHostDenied(host)
|
return "", protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
// create a new client
|
// create a new client
|
||||||
client := redis.NewClient(&redis.Options{
|
client := redis.NewClient(&redis.Options{
|
||||||
@ -181,7 +181,7 @@ func RunLuaScript(ctx context.Context, host string, port int, password string, s
|
|||||||
executionId := ctx.Value("executionId").(string)
|
executionId := ctx.Value("executionId").(string)
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return false, protocolstate.ErrHostDenied(host)
|
return false, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
// create a new client
|
// create a new client
|
||||||
client := redis.NewClient(&redis.Options{
|
client := redis.NewClient(&redis.Options{
|
||||||
|
|||||||
@ -43,7 +43,7 @@ func (c *SMBClient) ConnectSMBInfoMode(ctx context.Context, host string, port in
|
|||||||
func connectSMBInfoMode(executionId string, host string, port int) (*smb.SMBLog, error) {
|
func connectSMBInfoMode(executionId string, host string, port int) (*smb.SMBLog, error) {
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return nil, protocolstate.ErrHostDenied(host)
|
return nil, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
dialer := protocolstate.GetDialersWithId(executionId)
|
dialer := protocolstate.GetDialersWithId(executionId)
|
||||||
if dialer == nil {
|
if dialer == nil {
|
||||||
@ -90,7 +90,7 @@ func (c *SMBClient) ListSMBv2Metadata(ctx context.Context, host string, port int
|
|||||||
executionId := ctx.Value("executionId").(string)
|
executionId := ctx.Value("executionId").(string)
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return nil, protocolstate.ErrHostDenied(host)
|
return nil, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
return memoizedcollectSMBv2Metadata(executionId, host, port, 5*time.Second)
|
return memoizedcollectSMBv2Metadata(executionId, host, port, 5*time.Second)
|
||||||
}
|
}
|
||||||
@ -119,7 +119,7 @@ func (c *SMBClient) ListShares(ctx context.Context, host string, port int, user,
|
|||||||
func listShares(executionId string, host string, port int, user string, password string) ([]string, error) {
|
func listShares(executionId string, host string, port int, user string, password string) ([]string, error) {
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return nil, protocolstate.ErrHostDenied(host)
|
return nil, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
dialer := protocolstate.GetDialersWithId(executionId)
|
dialer := protocolstate.GetDialersWithId(executionId)
|
||||||
if dialer == nil {
|
if dialer == nil {
|
||||||
|
|||||||
@ -35,7 +35,7 @@ func (c *SMBClient) DetectSMBGhost(ctx context.Context, host string, port int) (
|
|||||||
func detectSMBGhost(executionId string, host string, port int) (bool, error) {
|
func detectSMBGhost(executionId string, host string, port int) (bool, error) {
|
||||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return false, protocolstate.ErrHostDenied(host)
|
return false, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
}
|
}
|
||||||
addr := net.JoinHostPort(host, strconv.Itoa(port))
|
addr := net.JoinHostPort(host, strconv.Itoa(port))
|
||||||
dialer := protocolstate.GetDialersWithId(executionId)
|
dialer := protocolstate.GetDialersWithId(executionId)
|
||||||
|
|||||||
@ -68,7 +68,7 @@ func NewSMTPClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Objec
|
|||||||
executionId := c.nj.ExecutionId()
|
executionId := c.nj.ExecutionId()
|
||||||
|
|
||||||
// check if this is allowed address
|
// check if this is allowed address
|
||||||
c.nj.Require(protocolstate.IsHostAllowed(executionId, host+":"+port), protocolstate.ErrHostDenied(host+":"+port).Error())
|
c.nj.Require(protocolstate.IsHostAllowed(executionId, host+":"+port), protocolstate.ErrHostDenied.Msgf(host+":"+port).Error())
|
||||||
|
|
||||||
// Link Constructor to Client and return
|
// Link Constructor to Client and return
|
||||||
return utils.LinkConstructor(call, runtime, c)
|
return utils.LinkConstructor(call, runtime, c)
|
||||||
|
|||||||
@ -129,7 +129,7 @@ func (c *SSHClient) ConnectSSHInfoMode(ctx context.Context, host string, port in
|
|||||||
// ```
|
// ```
|
||||||
func (c *SSHClient) Run(cmd string) (string, error) {
|
func (c *SSHClient) Run(cmd string) (string, error) {
|
||||||
if c.connection == nil {
|
if c.connection == nil {
|
||||||
return "", errkit.New("no connection").Build()
|
return "", errkit.New("no connection")
|
||||||
}
|
}
|
||||||
session, err := c.connection.NewSession()
|
session, err := c.connection.NewSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -177,14 +177,14 @@ type connectOptions struct {
|
|||||||
|
|
||||||
func (c *connectOptions) validate() error {
|
func (c *connectOptions) validate() error {
|
||||||
if c.Host == "" {
|
if c.Host == "" {
|
||||||
return errkit.New("host is required").Build()
|
return errkit.New("host is required")
|
||||||
}
|
}
|
||||||
if c.Port <= 0 {
|
if c.Port <= 0 {
|
||||||
return errkit.New("port is required").Build()
|
return errkit.New("port is required")
|
||||||
}
|
}
|
||||||
if !protocolstate.IsHostAllowed(c.ExecutionId, c.Host) {
|
if !protocolstate.IsHostAllowed(c.ExecutionId, c.Host) {
|
||||||
// host is not valid according to network policy
|
// host is not valid according to network policy
|
||||||
return protocolstate.ErrHostDenied(c.Host)
|
return protocolstate.ErrHostDenied.Msgf(c.Host)
|
||||||
}
|
}
|
||||||
if c.Timeout == 0 {
|
if c.Timeout == 0 {
|
||||||
c.Timeout = 10 * time.Second
|
c.Timeout = 10 * time.Second
|
||||||
|
|||||||
@ -7,9 +7,11 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
vnclib "github.com/alexsnet/go-vnc"
|
||||||
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
|
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
|
||||||
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/vnc"
|
vncplugin "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/vnc"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
||||||
|
stringsutil "github.com/projectdiscovery/utils/strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -24,8 +26,89 @@ type (
|
|||||||
IsVNC bool
|
IsVNC bool
|
||||||
Banner string
|
Banner string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VNCClient is a client for VNC servers.
|
||||||
|
// @example
|
||||||
|
// ```javascript
|
||||||
|
// const vnc = require('nuclei/vnc');
|
||||||
|
// const client = new vnc.VNCClient();
|
||||||
|
// const connected = client.Connect('acme.com', 5900, 'password');
|
||||||
|
// log(toJSON(connected));
|
||||||
|
// ```
|
||||||
|
VNCClient struct{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Connect connects to VNC server using given password.
|
||||||
|
// If connection and authentication is successful, it returns true.
|
||||||
|
// If connection or authentication is unsuccessful, it returns false and error.
|
||||||
|
// The connection is closed after the function returns.
|
||||||
|
// @example
|
||||||
|
// ```javascript
|
||||||
|
// const vnc = require('nuclei/vnc');
|
||||||
|
// const client = new vnc.VNCClient();
|
||||||
|
// const connected = client.Connect('acme.com', 5900, 'password');
|
||||||
|
// ```
|
||||||
|
func (c *VNCClient) Connect(ctx context.Context, host string, port int, password string) (bool, error) {
|
||||||
|
executionId := ctx.Value("executionId").(string)
|
||||||
|
return connect(executionId, host, port, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect attempts to authenticate with a VNC server using the given password
|
||||||
|
func connect(executionId string, host string, port int, password string) (bool, error) {
|
||||||
|
if host == "" || port <= 0 {
|
||||||
|
return false, fmt.Errorf("invalid host or port")
|
||||||
|
}
|
||||||
|
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||||
|
// host is not valid according to network policy
|
||||||
|
return false, protocolstate.ErrHostDenied.Msgf(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer := protocolstate.GetDialersWithId(executionId)
|
||||||
|
if dialer == nil {
|
||||||
|
return false, fmt.Errorf("dialers not initialized for %s", executionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = conn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set connection timeout
|
||||||
|
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
|
||||||
|
// Create VNC client config with password
|
||||||
|
vncConfig := vnclib.NewClientConfig(password)
|
||||||
|
|
||||||
|
// Attempt to connect and authenticate
|
||||||
|
c, err := vnclib.Connect(context.TODO(), conn, vncConfig)
|
||||||
|
if err != nil {
|
||||||
|
// Check for specific authentication errors
|
||||||
|
if isAuthError(err) {
|
||||||
|
return false, nil // Authentication failed, but connection succeeded
|
||||||
|
}
|
||||||
|
return false, err // Connection or other error
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
_ = c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAuthError checks if the error is an authentication failure
|
||||||
|
func isAuthError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for common VNC authentication error messages
|
||||||
|
errStr := err.Error()
|
||||||
|
return stringsutil.ContainsAnyI(errStr, "authentication", "auth", "password", "invalid", "failed")
|
||||||
|
}
|
||||||
|
|
||||||
// IsVNC checks if a host is running a VNC server.
|
// IsVNC checks if a host is running a VNC server.
|
||||||
// It returns a boolean indicating if the host is running a VNC server
|
// It returns a boolean indicating if the host is running a VNC server
|
||||||
// and the banner of the VNC server.
|
// and the banner of the VNC server.
|
||||||
@ -57,7 +140,7 @@ func isVNC(executionId string, host string, port int) (IsVNCResponse, error) {
|
|||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
vncPlugin := vnc.VNCPlugin{}
|
vncPlugin := vncplugin.VNCPlugin{}
|
||||||
service, err := vncPlugin.Run(conn, timeout, plugins.Target{Host: host})
|
service, err := vncPlugin.Run(conn, timeout, plugins.Target{Host: host})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/Mzack9999/goja"
|
"github.com/Mzack9999/goja"
|
||||||
"github.com/alecthomas/chroma/quick"
|
"github.com/alecthomas/chroma/quick"
|
||||||
"github.com/ditashi/jsbeautifier-go/jsbeautifier"
|
"github.com/ditashi/jsbeautifier-go/jsbeautifier"
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/projectdiscovery/gologger"
|
"github.com/projectdiscovery/gologger"
|
||||||
"github.com/projectdiscovery/gozero"
|
"github.com/projectdiscovery/gozero"
|
||||||
@ -113,7 +112,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
|
|||||||
if options.Options.Validate {
|
if options.Options.Validate {
|
||||||
options.Logger.Error().Msgf("%s <- %s", errMsg, err)
|
options.Logger.Error().Msgf("%s <- %s", errMsg, err)
|
||||||
} else {
|
} else {
|
||||||
return errkit.Append(errkit.New(errMsg), err)
|
return errkit.Wrap(err, errMsg)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
request.gozero = engine
|
request.gozero = engine
|
||||||
@ -132,7 +131,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
|
|||||||
compiled.ExcludeMatchers = options.ExcludeMatchers
|
compiled.ExcludeMatchers = options.ExcludeMatchers
|
||||||
compiled.TemplateID = options.TemplateID
|
compiled.TemplateID = options.TemplateID
|
||||||
if err := compiled.Compile(); err != nil {
|
if err := compiled.Compile(); err != nil {
|
||||||
return errors.Wrap(err, "could not compile operators")
|
return errkit.Wrap(err, "could not compile operators")
|
||||||
}
|
}
|
||||||
for _, matcher := range compiled.Matchers {
|
for _, matcher := range compiled.Matchers {
|
||||||
// default matcher part for code protocol is response
|
// default matcher part for code protocol is response
|
||||||
@ -153,7 +152,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
|
|||||||
if request.PreCondition != "" {
|
if request.PreCondition != "" {
|
||||||
preConditionCompiled, err := compiler.SourceAutoMode(request.PreCondition, false)
|
preConditionCompiled, err := compiler.SourceAutoMode(request.PreCondition, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.New(fmt.Sprintf("%s: could not compile pre-condition: %s", request.TemplateID, err)).Build()
|
return errkit.Newf("could not compile pre-condition: %s", err)
|
||||||
}
|
}
|
||||||
request.preConditionCompiled = preConditionCompiled
|
request.preConditionCompiled = preConditionCompiled
|
||||||
}
|
}
|
||||||
@ -230,7 +229,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
|
|||||||
Context: input.Context(),
|
Context: input.Context(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.New(fmt.Sprintf("%s: could not execute pre-condition: %s", request.TemplateID, err)).Build()
|
return errkit.Newf("could not execute pre-condition: %s", err)
|
||||||
}
|
}
|
||||||
if !result.GetSuccess() || types.ToString(result["error"]) != "" {
|
if !result.GetSuccess() || types.ToString(result["error"]) != "" {
|
||||||
gologger.Warning().Msgf("[%s] Precondition for request %s was not satisfied\n", request.TemplateID, request.PreCondition)
|
gologger.Warning().Msgf("[%s] Precondition for request %s was not satisfied\n", request.TemplateID, request.PreCondition)
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
defaultInteractionDuration = 60 * time.Second
|
defaultInteractionDuration = 60 * time.Second
|
||||||
interactshURLMarkerRegex = regexp.MustCompile(`(%7[B|b]|\{){2}(interactsh-url(?:_[0-9]+){0,3})(%7[D|d]|\}){2}`)
|
interactshURLMarkerRegex = regexp.MustCompile(`(%7[B|b]|\{){2}(interactsh-url(?:_[0-9]+){0,3})(%7[D|d]|\}){2}`)
|
||||||
|
|
||||||
ErrInteractshClientNotInitialized = errors.New("interactsh client not initialized")
|
ErrInteractshClientNotInitialized = errors.New("interactsh client not initialized")
|
||||||
)
|
)
|
||||||
|
|||||||
@ -88,7 +88,7 @@ func (c *Client) poll() error {
|
|||||||
KeepAliveInterval: time.Minute,
|
KeepAliveInterval: time.Minute,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("could not create client"), err)
|
return errkit.Wrap(err, "could not create client")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.interactsh = interactsh
|
c.interactsh = interactsh
|
||||||
@ -109,7 +109,7 @@ func (c *Client) poll() error {
|
|||||||
// If we don't have any request for this ID, add it to temporary
|
// If we don't have any request for this ID, add it to temporary
|
||||||
// lru cache, so we can correlate when we get an add request.
|
// lru cache, so we can correlate when we get an add request.
|
||||||
items, err := c.interactions.Get(interaction.UniqueID)
|
items, err := c.interactions.Get(interaction.UniqueID)
|
||||||
if errors.Is(err, gcache.KeyNotFoundError) || items == nil {
|
if errkit.Is(err, gcache.KeyNotFoundError) || items == nil {
|
||||||
_ = c.interactions.SetWithExpire(interaction.UniqueID, []*server.Interaction{interaction}, defaultInteractionDuration)
|
_ = c.interactions.SetWithExpire(interaction.UniqueID, []*server.Interaction{interaction}, defaultInteractionDuration)
|
||||||
} else {
|
} else {
|
||||||
items = append(items, interaction)
|
items = append(items, interaction)
|
||||||
@ -128,7 +128,7 @@ func (c *Client) poll() error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("could not perform interactsh polling"), err)
|
return errkit.Wrap(err, "could not perform interactsh polling")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -239,7 +239,7 @@ func (c *Client) URL() (string, error) {
|
|||||||
err = c.poll()
|
err = c.poll()
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errkit.Append(ErrInteractshClientNotInitialized, err)
|
return "", errkit.Wrap(ErrInteractshClientNotInitialized, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.interactsh == nil {
|
if c.interactsh == nil {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package protocolstate
|
package protocolstate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||||
@ -68,12 +67,12 @@ func NormalizePath(options *types.Options, filePath string) (string, error) {
|
|||||||
}
|
}
|
||||||
cleaned, err := fileutil.ResolveNClean(filePath, config.DefaultConfig.GetTemplateDir())
|
cleaned, err := fileutil.ResolveNClean(filePath, config.DefaultConfig.GetTemplateDir())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errkit.Append(errkit.New(fmt.Sprintf("could not resolve and clean path %v", filePath)), err)
|
return "", errkit.Wrapf(err, "could not resolve and clean path %v", filePath)
|
||||||
}
|
}
|
||||||
// only allow files inside nuclei-templates directory
|
// only allow files inside nuclei-templates directory
|
||||||
// even current working directory is not allowed
|
// even current working directory is not allowed
|
||||||
if strings.HasPrefix(cleaned, config.DefaultConfig.GetTemplateDir()) {
|
if strings.HasPrefix(cleaned, config.DefaultConfig.GetTemplateDir()) {
|
||||||
return cleaned, nil
|
return cleaned, nil
|
||||||
}
|
}
|
||||||
return "", errkit.New(fmt.Sprintf("path %v is outside nuclei-template directory and -lfa is not enabled", filePath)).Build()
|
return "", errkit.Newf("path %v is outside nuclei-template directory and -lfa is not enabled", filePath)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package protocolstate
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -18,14 +17,18 @@ import (
|
|||||||
|
|
||||||
// initialize state of headless protocol
|
// initialize state of headless protocol
|
||||||
|
|
||||||
// ErrURLDenied returns an error when a URL is denied by network policy
|
var (
|
||||||
func ErrURLDenied(url, rule string) error {
|
ErrURLDenied = errkit.New("headless: url dropped by rule")
|
||||||
return errkit.New(fmt.Sprintf("headless: url %v dropped by rule: %v", url, rule)).Build()
|
ErrHostDenied = errorTemplate{format: "host %v dropped by network policy"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// errorTemplate provides a way to create formatted errors like the old errorutil.NewWithFmt
|
||||||
|
type errorTemplate struct {
|
||||||
|
format string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrHostDenied returns an error when a host is denied by network policy
|
func (e errorTemplate) Msgf(args ...interface{}) error {
|
||||||
func ErrHostDenied(host string) error {
|
return errkit.Newf(e.format, args...)
|
||||||
return errkit.New(fmt.Sprintf("host %v dropped by network policy", host)).Build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetNetworkPolicy(ctx context.Context) *networkpolicy.NetworkPolicy {
|
func GetNetworkPolicy(ctx context.Context) *networkpolicy.NetworkPolicy {
|
||||||
@ -47,15 +50,15 @@ func ValidateNFailRequest(options *types.Options, page *rod.Page, e *proto.Fetch
|
|||||||
normalized := strings.ToLower(reqURL) // normalize url to lowercase
|
normalized := strings.ToLower(reqURL) // normalize url to lowercase
|
||||||
normalized = strings.TrimSpace(normalized) // trim leading & trailing whitespaces
|
normalized = strings.TrimSpace(normalized) // trim leading & trailing whitespaces
|
||||||
if !IsLfaAllowed(options) && stringsutil.HasPrefixI(normalized, "file:") {
|
if !IsLfaAllowed(options) && stringsutil.HasPrefixI(normalized, "file:") {
|
||||||
return multierr.Combine(FailWithReason(page, e), ErrURLDenied(reqURL, "use of file:// protocol disabled use '-lfa' to enable"))
|
return multierr.Combine(FailWithReason(page, e), errkit.Newf("headless: url %v dropped by rule: %v", reqURL, "use of file:// protocol disabled use '-lfa' to enable"))
|
||||||
}
|
}
|
||||||
// validate potential invalid schemes
|
// validate potential invalid schemes
|
||||||
// javascript protocol is allowed for xss fuzzing
|
// javascript protocol is allowed for xss fuzzing
|
||||||
if stringsutil.HasPrefixAnyI(normalized, "ftp:", "externalfile:", "chrome:", "chrome-extension:") {
|
if stringsutil.HasPrefixAnyI(normalized, "ftp:", "externalfile:", "chrome:", "chrome-extension:") {
|
||||||
return multierr.Combine(FailWithReason(page, e), ErrURLDenied(reqURL, "protocol blocked by network policy"))
|
return multierr.Combine(FailWithReason(page, e), errkit.Newf("headless: url %v dropped by rule: %v", reqURL, "protocol blocked by network policy"))
|
||||||
}
|
}
|
||||||
if !isValidHost(options, reqURL) {
|
if !isValidHost(options, reqURL) {
|
||||||
return multierr.Combine(FailWithReason(page, e), ErrURLDenied(reqURL, "address blocked by network policy"))
|
return multierr.Combine(FailWithReason(page, e), errkit.Newf("headless: url %v dropped by rule: %v", reqURL, "address blocked by network policy"))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,7 +78,7 @@ func (i *Instance) Run(ctx *contextargs.Context, actions []*Action, payloads map
|
|||||||
target := ctx.MetaInput.Input
|
target := ctx.MetaInput.Input
|
||||||
input, err := urlutil.Parse(target)
|
input, err := urlutil.Parse(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errkit.Append(errkit.New(fmt.Sprintf("could not parse URL %s", target)), err)
|
return nil, nil, errkit.Wrapf(err, "could not parse URL %s", target)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasTrailingSlash := httputil.HasTrailingSlash(target)
|
hasTrailingSlash := httputil.HasTrailingSlash(target)
|
||||||
|
|||||||
@ -31,8 +31,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errinvalidArguments = errkit.New("invalid arguments provided").Build()
|
errinvalidArguments = errkit.New("invalid arguments provided")
|
||||||
ErrLFAccessDenied = errkit.New("Use -allow-local-file-access flag to enable local file access").Build()
|
ErrLFAccessDenied = errkit.New("Use -allow-local-file-access flag to enable local file access")
|
||||||
// ErrActionExecDealine is the error returned when alloted time for action execution exceeds
|
// ErrActionExecDealine is the error returned when alloted time for action execution exceeds
|
||||||
ErrActionExecDealine = errkit.New("headless action execution deadline exceeded").SetKind(errkit.ErrKindDeadline).Build()
|
ErrActionExecDealine = errkit.New("headless action execution deadline exceeded").SetKind(errkit.ErrKindDeadline).Build()
|
||||||
)
|
)
|
||||||
@ -59,7 +59,7 @@ func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action) (ou
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
err = errkit.New(fmt.Sprintf("panic on headless action: %v", r)).Build()
|
err = errkit.Newf("panic on headless action: %v", r)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action) (ou
|
|||||||
for _, waitFunc := range waitFuncs {
|
for _, waitFunc := range waitFuncs {
|
||||||
if waitFunc != nil {
|
if waitFunc != nil {
|
||||||
if err := waitFunc(); err != nil {
|
if err := waitFunc(); err != nil {
|
||||||
return nil, errkit.Append(errkit.New("error occurred while executing waitFunc"), err)
|
return nil, errkit.Wrap(err, "error occurred while executing waitFunc")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -400,7 +400,7 @@ func (p *Page) NavigateURL(action *Action, out ActionData) error {
|
|||||||
|
|
||||||
parsedURL, err := urlutil.ParseURL(url, true)
|
parsedURL, err := urlutil.ParseURL(url, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.New(fmt.Sprintf("headless: failed to parse url %v while creating http request", url)).Build()
|
return errkit.Newf("failed to parse url %v while creating http request", url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== parameter automerge =====
|
// ===== parameter automerge =====
|
||||||
@ -410,7 +410,7 @@ func (p *Page) NavigateURL(action *Action, out ActionData) error {
|
|||||||
parsedURL.Params = finalparams
|
parsedURL.Params = finalparams
|
||||||
|
|
||||||
if err := p.page.Navigate(parsedURL.String()); err != nil {
|
if err := p.page.Navigate(parsedURL.String()); err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("could not navigate to url %s", parsedURL.String())), err)
|
return errkit.Wrapf(err, "could not navigate to url %s", parsedURL.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
p.updateLastNavigatedURL()
|
p.updateLastNavigatedURL()
|
||||||
@ -524,14 +524,14 @@ func (p *Page) Screenshot(act *Action, out ActionData) error {
|
|||||||
|
|
||||||
to, err = fileutil.CleanPath(to)
|
to, err = fileutil.CleanPath(to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.New(fmt.Sprintf("could not clean output screenshot path %s", to)).Build()
|
return errkit.Newf("could not clean output screenshot path %s", to)
|
||||||
}
|
}
|
||||||
|
|
||||||
// allow if targetPath is child of current working directory
|
// allow if targetPath is child of current working directory
|
||||||
if !protocolstate.IsLfaAllowed(p.options.Options) {
|
if !protocolstate.IsLfaAllowed(p.options.Options) {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("could not get current working directory"), err)
|
return errkit.Wrap(err, "could not get current working directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(to, cwd) {
|
if !strings.HasPrefix(to, cwd) {
|
||||||
@ -550,7 +550,7 @@ func (p *Page) Screenshot(act *Action, out ActionData) error {
|
|||||||
// creates new directory if needed based on path `to`
|
// creates new directory if needed based on path `to`
|
||||||
// TODO: replace all permission bits with fileutil constants (https://github.com/projectdiscovery/utils/issues/113)
|
// TODO: replace all permission bits with fileutil constants (https://github.com/projectdiscovery/utils/issues/113)
|
||||||
if err := os.MkdirAll(filepath.Dir(to), 0700); err != nil {
|
if err := os.MkdirAll(filepath.Dir(to), 0700); err != nil {
|
||||||
return errkit.Append(errkit.New("failed to create directory while writing screenshot"), err)
|
return errkit.Wrap(err, "failed to create directory while writing screenshot")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -562,7 +562,7 @@ func (p *Page) Screenshot(act *Action, out ActionData) error {
|
|||||||
|
|
||||||
if fileutil.FileExists(filePath) {
|
if fileutil.FileExists(filePath) {
|
||||||
// return custom error as overwriting files is not supported
|
// return custom error as overwriting files is not supported
|
||||||
return errkit.New(fmt.Sprintf("screenshot: failed to write screenshot, file %v already exists", filePath)).Build()
|
return errkit.Newf("failed to write screenshot, file %v already exists", filePath)
|
||||||
}
|
}
|
||||||
err = os.WriteFile(filePath, data, 0540)
|
err = os.WriteFile(filePath, data, 0540)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -805,12 +805,12 @@ func (p *Page) WaitEvent(act *Action, out ActionData) (func() error, error) {
|
|||||||
|
|
||||||
gotType := proto.GetType(event)
|
gotType := proto.GetType(event)
|
||||||
if gotType == nil {
|
if gotType == nil {
|
||||||
return nil, errkit.New(fmt.Sprintf("event %q does not exist", event)).Build()
|
return nil, errkit.Newf("event %q does not exist", event)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp, ok := reflect.New(gotType).Interface().(proto.Event)
|
tmp, ok := reflect.New(gotType).Interface().(proto.Event)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errkit.New(fmt.Sprintf("event %q is not a page event", event)).Build()
|
return nil, errkit.Newf("event %q is not a page event", event)
|
||||||
}
|
}
|
||||||
|
|
||||||
waitEvent = tmp
|
waitEvent = tmp
|
||||||
@ -947,7 +947,7 @@ func (p *Page) getActionArg(action *Action, arg string) (string, error) {
|
|||||||
|
|
||||||
err = expressions.ContainsUnresolvedVariables(exprs...)
|
err = expressions.ContainsUnresolvedVariables(exprs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errkit.Append(errkit.New(fmt.Sprintf("argument %q, value: %q", arg, argValue)), err)
|
return "", errkit.Wrapf(err, "argument %q, value: %q", arg, argValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
argValue, err = expressions.Evaluate(argValue, p.variables)
|
argValue, err = expressions.Evaluate(argValue, p.variables)
|
||||||
|
|||||||
@ -37,18 +37,42 @@ const (
|
|||||||
ReqURLPatternKey = "req_url_pattern"
|
ReqURLPatternKey = "req_url_pattern"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrEvalExpression returns an error when helper expressions cannot be evaluated
|
// ErrEvalExpression
|
||||||
func ErrEvalExpression(tag string) func(error) error {
|
type errorTemplate struct {
|
||||||
return func(err error) error {
|
format string
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("%s: could not evaluate helper expressions", tag)), err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrUnresolvedVars returns an error when unresolved variables are found in request
|
func (e errorTemplate) Wrap(err error) wrapperError {
|
||||||
func ErrUnresolvedVars(vars string) error {
|
return wrapperError{template: e, err: err}
|
||||||
return errkit.New(fmt.Sprintf("unresolved variables `%v` found in request", vars)).Build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e errorTemplate) Msgf(args ...interface{}) error {
|
||||||
|
return errkit.Newf(e.format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrapperError struct {
|
||||||
|
template errorTemplate
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wrapperError) WithTag(tag string) error {
|
||||||
|
return errkit.Wrap(w.err, w.template.format)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wrapperError) Msgf(format string, args ...interface{}) error {
|
||||||
|
return errkit.Wrapf(w.err, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wrapperError) Error() string {
|
||||||
|
return errkit.Wrap(w.err, w.template.format).Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrEvalExpression
|
||||||
|
var (
|
||||||
|
ErrEvalExpression = errorTemplate{"could not evaluate helper expressions"}
|
||||||
|
ErrUnresolvedVars = errorTemplate{"unresolved variables `%v` found in request"}
|
||||||
|
)
|
||||||
|
|
||||||
// generatedRequest is a single generated request wrapped for a template request
|
// generatedRequest is a single generated request wrapped for a template request
|
||||||
type generatedRequest struct {
|
type generatedRequest struct {
|
||||||
original *Request
|
original *Request
|
||||||
@ -199,7 +223,7 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context,
|
|||||||
for payloadName, payloadValue := range payloads {
|
for payloadName, payloadValue := range payloads {
|
||||||
payloads[payloadName], err = expressions.Evaluate(types.ToString(payloadValue), allVars)
|
payloads[payloadName], err = expressions.Evaluate(types.ToString(payloadValue), allVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrEvalExpression("http")(err)
|
return nil, errkit.Wrap(err, "could not evaluate helper expressions")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// finalVars contains allVars and any generator/fuzzing specific payloads
|
// finalVars contains allVars and any generator/fuzzing specific payloads
|
||||||
@ -216,7 +240,7 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context,
|
|||||||
// Evaluate (replace) variable with final values
|
// Evaluate (replace) variable with final values
|
||||||
reqData, err = expressions.Evaluate(reqData, finalVars)
|
reqData, err = expressions.Evaluate(reqData, finalVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrEvalExpression("http")(err)
|
return nil, errkit.Wrap(err, "could not evaluate helper expressions")
|
||||||
}
|
}
|
||||||
|
|
||||||
if isRawRequest {
|
if isRawRequest {
|
||||||
@ -225,7 +249,7 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context,
|
|||||||
|
|
||||||
reqURL, err := urlutil.ParseAbsoluteURL(reqData, true)
|
reqURL, err := urlutil.ParseAbsoluteURL(reqData, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.New(fmt.Sprintf("http: failed to parse url %v while creating http request", reqData)).Build()
|
return nil, errkit.Newf("failed to parse url %v while creating http request", reqData)
|
||||||
}
|
}
|
||||||
// while merging parameters first preference is given to target params
|
// while merging parameters first preference is given to target params
|
||||||
finalparams := parsed.Params
|
finalparams := parsed.Params
|
||||||
@ -258,7 +282,7 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st
|
|||||||
// evaluate request
|
// evaluate request
|
||||||
data, err := expressions.Evaluate(data, values)
|
data, err := expressions.Evaluate(data, values)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrEvalExpression("self-contained")(err)
|
return nil, errkit.Wrap(err, "could not evaluate helper expressions")
|
||||||
}
|
}
|
||||||
// If the request is a raw request, get the URL from the request
|
// If the request is a raw request, get the URL from the request
|
||||||
// header and use it to make the request.
|
// header and use it to make the request.
|
||||||
@ -281,7 +305,7 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := expressions.ContainsUnresolvedVariables(parts[1]); err != nil && !r.request.SkipVariablesCheck {
|
if err := expressions.ContainsUnresolvedVariables(parts[1]); err != nil && !r.request.SkipVariablesCheck {
|
||||||
return nil, ErrUnresolvedVars(parts[1])
|
return nil, errkit.Newf("unresolved variables `%v` found in request", parts[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed, err := urlutil.ParseURL(parts[1], true)
|
parsed, err := urlutil.ParseURL(parts[1], true)
|
||||||
@ -295,19 +319,19 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st
|
|||||||
// Evaluate (replace) variable with final values
|
// Evaluate (replace) variable with final values
|
||||||
data, err = expressions.Evaluate(data, values)
|
data, err = expressions.Evaluate(data, values)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrEvalExpression("self-contained")(err)
|
return nil, errkit.Wrap(err, "could not evaluate helper expressions")
|
||||||
}
|
}
|
||||||
return r.generateRawRequest(ctx, data, parsed, values, payloads)
|
return r.generateRawRequest(ctx, data, parsed, values, payloads)
|
||||||
}
|
}
|
||||||
if err := expressions.ContainsUnresolvedVariables(data); err != nil && !r.request.SkipVariablesCheck {
|
if err := expressions.ContainsUnresolvedVariables(data); err != nil && !r.request.SkipVariablesCheck {
|
||||||
// early exit: if there are any unresolved variables in `path` after evaluation
|
// early exit: if there are any unresolved variables in `path` after evaluation
|
||||||
// then return early since this will definitely fail
|
// then return early since this will definitely fail
|
||||||
return nil, ErrUnresolvedVars(data)
|
return nil, errkit.Newf("unresolved variables `%v` found in request", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
urlx, err := urlutil.ParseURL(data, true)
|
urlx, err := urlutil.ParseURL(data, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.New(fmt.Sprintf("self-contained: failed to parse %v in self contained request: %s", data, err)).Build()
|
return nil, errkit.Wrapf(err, "failed to parse %v in self contained request", data)
|
||||||
}
|
}
|
||||||
return r.generateHttpRequest(ctx, urlx, values, payloads)
|
return r.generateHttpRequest(ctx, urlx, values, payloads)
|
||||||
}
|
}
|
||||||
@ -318,7 +342,7 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st
|
|||||||
func (r *requestGenerator) generateHttpRequest(ctx context.Context, urlx *urlutil.URL, finalVars, generatorValues map[string]interface{}) (*generatedRequest, error) {
|
func (r *requestGenerator) generateHttpRequest(ctx context.Context, urlx *urlutil.URL, finalVars, generatorValues map[string]interface{}) (*generatedRequest, error) {
|
||||||
method, err := expressions.Evaluate(r.request.Method.String(), finalVars)
|
method, err := expressions.Evaluate(r.request.Method.String(), finalVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrEvalExpression("http")(err)
|
return nil, errkit.Wrap(err, "failed to evaluate while generating http request")
|
||||||
}
|
}
|
||||||
// Build a request on the specified URL
|
// Build a request on the specified URL
|
||||||
req, err := retryablehttp.NewRequestFromURLWithContext(ctx, method, urlx, nil)
|
req, err := retryablehttp.NewRequestFromURLWithContext(ctx, method, urlx, nil)
|
||||||
@ -347,7 +371,7 @@ func (r *requestGenerator) generateRawRequest(ctx context.Context, rawRequest st
|
|||||||
rawRequestData, err = raw.Parse(rawRequest, baseURL, r.request.Unsafe, r.request.DisablePathAutomerge)
|
rawRequestData, err = raw.Parse(rawRequest, baseURL, r.request.Unsafe, r.request.DisablePathAutomerge)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New("failed to parse raw request"), err)
|
return nil, errkit.Wrap(err, "failed to parse raw request")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsafe option uses rawhttp library
|
// Unsafe option uses rawhttp library
|
||||||
@ -363,7 +387,7 @@ func (r *requestGenerator) generateRawRequest(ctx context.Context, rawRequest st
|
|||||||
}
|
}
|
||||||
urlx, err := urlutil.ParseAbsoluteURL(rawRequestData.FullURL, true)
|
urlx, err := urlutil.ParseAbsoluteURL(rawRequestData.FullURL, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.New(fmt.Sprintf("raw: failed to create request with url %v got %v", rawRequestData.FullURL, err)).Build()
|
return nil, errkit.Wrapf(err, "failed to create request with url %v got %v", rawRequestData.FullURL, err)
|
||||||
}
|
}
|
||||||
req, err := retryablehttp.NewRequestFromURLWithContext(ctx, rawRequestData.Method, urlx, rawRequestData.Data)
|
req, err := retryablehttp.NewRequestFromURLWithContext(ctx, rawRequestData.Method, urlx, rawRequestData.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -420,7 +444,7 @@ func (r *requestGenerator) fillRequest(req *retryablehttp.Request, values map[st
|
|||||||
}
|
}
|
||||||
value, err := expressions.Evaluate(value, values)
|
value, err := expressions.Evaluate(value, values)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrEvalExpression("http")(err)
|
return nil, errkit.Wrap(err, "failed to evaluate while adding headers to request")
|
||||||
}
|
}
|
||||||
req.Header[header] = []string{value}
|
req.Header[header] = []string{value}
|
||||||
if header == "Host" {
|
if header == "Host" {
|
||||||
@ -441,7 +465,7 @@ func (r *requestGenerator) fillRequest(req *retryablehttp.Request, values map[st
|
|||||||
}
|
}
|
||||||
body, err := expressions.Evaluate(body, values)
|
body, err := expressions.Evaluate(body, values)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrEvalExpression("http")(err)
|
return nil, errkit.Wrap(err, "could not evaluate helper expressions")
|
||||||
}
|
}
|
||||||
bodyReader, err := readerutil.NewReusableReadCloser([]byte(body))
|
bodyReader, err := readerutil.NewReusableReadCloser([]byte(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -48,7 +48,7 @@ func Parse(request string, inputURL *urlutil.URL, unsafe, disablePathAutomerge b
|
|||||||
case strings.HasPrefix(rawrequest.Path, "http") && !unsafe:
|
case strings.HasPrefix(rawrequest.Path, "http") && !unsafe:
|
||||||
urlx, err := urlutil.ParseURL(rawrequest.Path, true)
|
urlx, err := urlutil.ParseURL(rawrequest.Path, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.New(fmt.Sprintf("raw: failed to parse url %v from template: %s", rawrequest.Path, err)).Build()
|
return nil, errkit.Wrapf(err, "failed to parse url %v from template", rawrequest.Path)
|
||||||
}
|
}
|
||||||
cloned := inputURL.Clone()
|
cloned := inputURL.Clone()
|
||||||
cloned.Params.IncludeEquals = true
|
cloned.Params.IncludeEquals = true
|
||||||
@ -57,7 +57,7 @@ func Parse(request string, inputURL *urlutil.URL, unsafe, disablePathAutomerge b
|
|||||||
}
|
}
|
||||||
parseErr := cloned.MergePath(urlx.GetRelativePath(), true)
|
parseErr := cloned.MergePath(urlx.GetRelativePath(), true)
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
return nil, errkit.Append(errkit.New(fmt.Sprintf("raw: could not automergepath for template path %v", urlx.GetRelativePath())), parseErr)
|
return nil, errkit.Wrapf(parseErr, "could not automergepath for template path %v", urlx.GetRelativePath())
|
||||||
}
|
}
|
||||||
rawrequest.Path = cloned.GetRelativePath()
|
rawrequest.Path = cloned.GetRelativePath()
|
||||||
// If unsafe changes must be made in raw request string itself
|
// If unsafe changes must be made in raw request string itself
|
||||||
@ -94,7 +94,7 @@ func Parse(request string, inputURL *urlutil.URL, unsafe, disablePathAutomerge b
|
|||||||
}
|
}
|
||||||
err = cloned.MergePath(rawrequest.Path, true)
|
err = cloned.MergePath(rawrequest.Path, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.New(fmt.Sprintf("raw: failed to automerge %v from unsafe template: %s", rawrequest.Path, err)).Build()
|
return nil, errkit.Wrapf(err, "failed to automerge %v from unsafe template", rawrequest.Path)
|
||||||
}
|
}
|
||||||
unsafeRelativePath = cloned.GetRelativePath()
|
unsafeRelativePath = cloned.GetRelativePath()
|
||||||
}
|
}
|
||||||
@ -116,7 +116,7 @@ func Parse(request string, inputURL *urlutil.URL, unsafe, disablePathAutomerge b
|
|||||||
}
|
}
|
||||||
parseErr := cloned.MergePath(rawrequest.Path, true)
|
parseErr := cloned.MergePath(rawrequest.Path, true)
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
return nil, errkit.Append(errkit.New(fmt.Sprintf("raw: could not automergepath for template path %v", rawrequest.Path)), parseErr)
|
return nil, errkit.Wrapf(parseErr, "could not automergepath for template path %v", rawrequest.Path)
|
||||||
}
|
}
|
||||||
rawrequest.Path = cloned.GetRelativePath()
|
rawrequest.Path = cloned.GetRelativePath()
|
||||||
}
|
}
|
||||||
@ -145,18 +145,18 @@ func ParseRawRequest(request string, unsafe bool) (*Request, error) {
|
|||||||
if strings.HasPrefix(req.Path, "http") {
|
if strings.HasPrefix(req.Path, "http") {
|
||||||
urlx, err := urlutil.Parse(req.Path)
|
urlx, err := urlutil.Parse(req.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.Append(errkit.New(fmt.Sprintf("failed to parse url %v", req.Path)), err)
|
return nil, errkit.Wrapf(err, "failed to parse url %v", req.Path)
|
||||||
}
|
}
|
||||||
req.Path = urlx.GetRelativePath()
|
req.Path = urlx.GetRelativePath()
|
||||||
req.FullURL = urlx.String()
|
req.FullURL = urlx.String()
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if req.Path == "" {
|
if req.Path == "" {
|
||||||
return nil, errkit.New("self-contained-raw: path cannot be empty in self contained request").Build()
|
return nil, errkit.New("path cannot be empty in self contained request")
|
||||||
}
|
}
|
||||||
// given url is relative construct one using Host Header
|
// given url is relative construct one using Host Header
|
||||||
if _, ok := req.Headers["Host"]; !ok {
|
if _, ok := req.Headers["Host"]; !ok {
|
||||||
return nil, errkit.New("self-contained-raw: host header is required for relative path").Build()
|
return nil, errkit.New("host header is required for relative path")
|
||||||
}
|
}
|
||||||
// Review: Current default scheme in self contained templates if relative path is provided is http
|
// Review: Current default scheme in self contained templates if relative path is provided is http
|
||||||
req.FullURL = fmt.Sprintf("%s://%s%s", urlutil.HTTP, strings.TrimSpace(req.Headers["Host"]), req.Path)
|
req.FullURL = fmt.Sprintf("%s://%s%s", urlutil.HTTP, strings.TrimSpace(req.Headers["Host"]), req.Path)
|
||||||
|
|||||||
@ -60,7 +60,7 @@ func (a *AWSSigner) SignHTTP(ctx context.Context, request *http.Request) error {
|
|||||||
// contentHash is sha256 hash of response body
|
// contentHash is sha256 hash of response body
|
||||||
contentHash := a.getPayloadHash(request)
|
contentHash := a.getPayloadHash(request)
|
||||||
if err := a.signer.SignHTTP(ctx, *a.creds, request, contentHash, a.options.Service, a.options.Region, time.Now()); err != nil {
|
if err := a.signer.SignHTTP(ctx, *a.creds, request, contentHash, a.options.Service, a.options.Region, time.Now()); err != nil {
|
||||||
return errkit.Append(errkit.New("failed to sign http request using aws v4 signer"), err)
|
return errkit.Wrap(err, "failed to sign http request using aws v4 signer")
|
||||||
}
|
}
|
||||||
// add x-amz-content-sha256 header to request
|
// add x-amz-content-sha256 header to request
|
||||||
request.Header.Set("x-amz-content-sha256", contentHash)
|
request.Header.Set("x-amz-content-sha256", contentHash)
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -16,14 +15,14 @@ func dump(req *generatedRequest, reqURL string) ([]byte, error) {
|
|||||||
// Use a clone to avoid a race condition with the http transport
|
// Use a clone to avoid a race condition with the http transport
|
||||||
bin, err := req.request.Clone(req.request.Context()).Dump()
|
bin, err := req.request.Clone(req.request.Context()).Dump()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.New(fmt.Sprintf("http: could not dump request: %v: %s", req.request.String(), err)).Build()
|
return nil, errkit.Wrapf(err, "could not dump request: %v", req.request.String())
|
||||||
}
|
}
|
||||||
return bin, nil
|
return bin, nil
|
||||||
}
|
}
|
||||||
rawHttpOptions := &rawhttp.Options{CustomHeaders: req.rawRequest.UnsafeHeaders, CustomRawBytes: req.rawRequest.UnsafeRawBytes}
|
rawHttpOptions := &rawhttp.Options{CustomHeaders: req.rawRequest.UnsafeHeaders, CustomRawBytes: req.rawRequest.UnsafeRawBytes}
|
||||||
bin, err := rawhttp.DumpRequestRaw(req.rawRequest.Method, reqURL, req.rawRequest.Path, generators.ExpandMapValues(req.rawRequest.Headers), io.NopCloser(strings.NewReader(req.rawRequest.Data)), rawHttpOptions)
|
bin, err := rawhttp.DumpRequestRaw(req.rawRequest.Method, reqURL, req.rawRequest.Path, generators.ExpandMapValues(req.rawRequest.Headers), io.NopCloser(strings.NewReader(req.rawRequest.Data)), rawHttpOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errkit.New(fmt.Sprintf("http: could not dump request: %v: %s", reqURL, err)).Build()
|
return nil, errkit.Wrapf(err, "could not dump request: %v", reqURL)
|
||||||
}
|
}
|
||||||
return bin, nil
|
return bin, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -127,14 +127,14 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := compiled.Compile(); err != nil {
|
if err := compiled.Compile(); err != nil {
|
||||||
return errkit.New(fmt.Sprintf("%s: could not compile operators got %v", request.TemplateID, err)).Build()
|
return errkit.Newf("could not compile operators got %v", err)
|
||||||
}
|
}
|
||||||
request.CompiledOperators = compiled
|
request.CompiledOperators = compiled
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Port" is a special variable and it should not contains any dsl expressions
|
// "Port" is a special variable and it should not contains any dsl expressions
|
||||||
if strings.Contains(request.getPort(), "{{") {
|
if strings.Contains(request.getPort(), "{{") {
|
||||||
return errkit.New(fmt.Sprintf("%s: 'Port' variable cannot contain any dsl expressions", request.TemplateID)).Build()
|
return errkit.New("'Port' variable cannot contain any dsl expressions")
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.Init != "" {
|
if request.Init != "" {
|
||||||
@ -218,11 +218,11 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
|
|||||||
|
|
||||||
initCompiled, err := compiler.SourceAutoMode(request.Init, false)
|
initCompiled, err := compiler.SourceAutoMode(request.Init, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.New(fmt.Sprintf("%s: could not compile init code: %s", request.TemplateID, err)).Build()
|
return errkit.Newf("could not compile init code: %s", err)
|
||||||
}
|
}
|
||||||
result, err := request.options.JsCompiler.ExecuteWithOptions(initCompiled, args, opts)
|
result, err := request.options.JsCompiler.ExecuteWithOptions(initCompiled, args, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.New(fmt.Sprintf("%s: could not execute pre-condition: %s", request.TemplateID, err)).Build()
|
return errkit.Newf("could not execute pre-condition: %s", err)
|
||||||
}
|
}
|
||||||
if types.ToString(result["error"]) != "" {
|
if types.ToString(result["error"]) != "" {
|
||||||
gologger.Warning().Msgf("[%s] Init failed with error %v\n", request.TemplateID, result["error"])
|
gologger.Warning().Msgf("[%s] Init failed with error %v\n", request.TemplateID, result["error"])
|
||||||
@ -239,7 +239,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
|
|||||||
if request.PreCondition != "" {
|
if request.PreCondition != "" {
|
||||||
preConditionCompiled, err := compiler.SourceAutoMode(request.PreCondition, false)
|
preConditionCompiled, err := compiler.SourceAutoMode(request.PreCondition, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.New(fmt.Sprintf("%s: could not compile pre-condition: %s", request.TemplateID, err)).Build()
|
return errkit.Newf("could not compile pre-condition: %s", err)
|
||||||
}
|
}
|
||||||
request.preConditionCompiled = preConditionCompiled
|
request.preConditionCompiled = preConditionCompiled
|
||||||
}
|
}
|
||||||
@ -248,7 +248,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
|
|||||||
if request.Code != "" {
|
if request.Code != "" {
|
||||||
scriptCompiled, err := compiler.SourceAutoMode(request.Code, false)
|
scriptCompiled, err := compiler.SourceAutoMode(request.Code, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.New(fmt.Sprintf("%s: could not compile javascript code: %s", request.TemplateID, err)).Build()
|
return errkit.Newf("could not compile javascript code: %s", err)
|
||||||
}
|
}
|
||||||
request.scriptCompiled = scriptCompiled
|
request.scriptCompiled = scriptCompiled
|
||||||
}
|
}
|
||||||
|
|||||||
29
pkg/protocols/javascript/testcases/oracle-auth-test.yaml
Normal file
29
pkg/protocols/javascript/testcases/oracle-auth-test.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
id: oracle-auth-test
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: Oracle - Authentication Test
|
||||||
|
author: pdteam
|
||||||
|
severity: info
|
||||||
|
tags: js,oracle,network,auth
|
||||||
|
|
||||||
|
javascript:
|
||||||
|
- pre-condition: |
|
||||||
|
isPortOpen(Host,Port);
|
||||||
|
code: |
|
||||||
|
let o = require('nuclei/oracle');
|
||||||
|
let c = o.OracleClient();
|
||||||
|
c.Connect(Host, Port, ServiceName, User, Pass);
|
||||||
|
|
||||||
|
args:
|
||||||
|
ServiceName: "XE"
|
||||||
|
Host: "{{Host}}"
|
||||||
|
Port: "1521"
|
||||||
|
User: "system"
|
||||||
|
Pass: "{{passwords}}"
|
||||||
|
payloads:
|
||||||
|
passwords:
|
||||||
|
- mysecret
|
||||||
|
matchers:
|
||||||
|
- type: dsl
|
||||||
|
dsl:
|
||||||
|
- "response == true"
|
||||||
@ -1,7 +1,6 @@
|
|||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -197,10 +196,10 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
|
|||||||
}
|
}
|
||||||
portInt, err := strconv.Atoi(port)
|
portInt, err := strconv.Atoi(port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("could not parse port %v from '%s'", port, request.Port)), err)
|
return errkit.Wrapf(err, "could not parse port %v from '%s'", port, request.Port)
|
||||||
}
|
}
|
||||||
if portInt < 1 || portInt > 65535 {
|
if portInt < 1 || portInt > 65535 {
|
||||||
return errkit.New(fmt.Sprintf("%s: port %v is not in valid range", request.TemplateID, portInt)).Build()
|
return errkit.Newf("port %v is not in valid range", portInt)
|
||||||
}
|
}
|
||||||
request.ports = append(request.ports, port)
|
request.ports = append(request.ports, port)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -362,7 +362,7 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac
|
|||||||
if input.Read > 0 {
|
if input.Read > 0 {
|
||||||
buffer, err := ConnReadNWithTimeout(conn, int64(input.Read), request.options.Options.GetTimeouts().TcpReadTimeout)
|
buffer, err := ConnReadNWithTimeout(conn, int64(input.Read), request.options.Options.GetTimeouts().TcpReadTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("could not read response from connection"), err)
|
return errkit.Wrap(err, "could not read response from connection")
|
||||||
}
|
}
|
||||||
|
|
||||||
responseBuilder.Write(buffer)
|
responseBuilder.Write(buffer)
|
||||||
|
|||||||
@ -121,7 +121,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
|
|||||||
CustomDialer: options.CustomFastdialer,
|
CustomDialer: options.CustomFastdialer,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("ssl: could not get network client"), err)
|
return errkit.Wrap(err, "could not get network client")
|
||||||
}
|
}
|
||||||
request.dialer = client
|
request.dialer = client
|
||||||
switch {
|
switch {
|
||||||
@ -130,7 +130,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
|
|||||||
request.ScanMode = "auto"
|
request.ScanMode = "auto"
|
||||||
|
|
||||||
case !stringsutil.EqualFoldAny(request.ScanMode, "auto", "openssl", "ztls", "ctls"):
|
case !stringsutil.EqualFoldAny(request.ScanMode, "auto", "openssl", "ztls", "ctls"):
|
||||||
return errkit.New(fmt.Sprintf("%s: template %v does not contain valid scan-mode", request.TemplateID, request.TemplateID)).Build()
|
return errkit.Newf("template %v does not contain valid scan-mode", request.TemplateID)
|
||||||
|
|
||||||
case request.ScanMode == "openssl" && !openssl.IsAvailable():
|
case request.ScanMode == "openssl" && !openssl.IsAvailable():
|
||||||
// if openssl is not installed instead of failing "auto" scanmode is used
|
// if openssl is not installed instead of failing "auto" scanmode is used
|
||||||
@ -169,7 +169,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
|
|||||||
|
|
||||||
tlsxService, err := tlsx.New(tlsxOptions)
|
tlsxService, err := tlsx.New(tlsxOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.New(fmt.Sprintf("%s: could not create tlsx service", request.TemplateID)).Build()
|
return errkit.New("could not create tlsx service")
|
||||||
}
|
}
|
||||||
request.tlsx = tlsxService
|
request.tlsx = tlsxService
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
|
|||||||
compiled.ExcludeMatchers = options.ExcludeMatchers
|
compiled.ExcludeMatchers = options.ExcludeMatchers
|
||||||
compiled.TemplateID = options.TemplateID
|
compiled.TemplateID = options.TemplateID
|
||||||
if err := compiled.Compile(); err != nil {
|
if err := compiled.Compile(); err != nil {
|
||||||
return errkit.New(fmt.Sprintf("%s: could not compile operators got %v", request.TemplateID, err)).Build()
|
return errkit.Newf("could not compile operators got %v", err)
|
||||||
}
|
}
|
||||||
request.CompiledOperators = compiled
|
request.CompiledOperators = compiled
|
||||||
}
|
}
|
||||||
@ -236,7 +236,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
|
|||||||
addressToDial := string(finalAddress)
|
addressToDial := string(finalAddress)
|
||||||
host, port, err := net.SplitHostPort(addressToDial)
|
host, port, err := net.SplitHostPort(addressToDial)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errkit.Append(errkit.New("could not split input host port"), err)
|
return errkit.Wrap(err, "could not split input host port")
|
||||||
}
|
}
|
||||||
|
|
||||||
var hostIp string
|
var hostIp string
|
||||||
@ -250,7 +250,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
requestOptions.Output.Request(requestOptions.TemplateID, input.MetaInput.Input, request.Type().String(), err)
|
requestOptions.Output.Request(requestOptions.TemplateID, input.MetaInput.Input, request.Type().String(), err)
|
||||||
requestOptions.Progress.IncrementFailedRequestsBy(1)
|
requestOptions.Progress.IncrementFailedRequestsBy(1)
|
||||||
return errkit.Append(errkit.New(fmt.Sprintf("%s: could not connect to server", request.TemplateID)), err)
|
return errkit.Wrap(err, "could not connect to server")
|
||||||
}
|
}
|
||||||
|
|
||||||
requestOptions.Output.Request(requestOptions.TemplateID, hostPort, request.Type().String(), err)
|
requestOptions.Output.Request(requestOptions.TemplateID, hostPort, request.Type().String(), err)
|
||||||
@ -287,7 +287,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
|
|||||||
|
|
||||||
// if response is not struct compatible, error out
|
// if response is not struct compatible, error out
|
||||||
if !structs.IsStruct(response) {
|
if !structs.IsStruct(response) {
|
||||||
return errkit.New(fmt.Sprintf("ssl: response cannot be parsed into a struct: %v", response)).Build()
|
return errkit.Newf("response cannot be parsed into a struct: %v", response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert response to key value pairs and first cert chain item as well
|
// Convert response to key value pairs and first cert chain item as well
|
||||||
@ -307,7 +307,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
|
|||||||
|
|
||||||
// if certificate response is not struct compatible, error out
|
// if certificate response is not struct compatible, error out
|
||||||
if !structs.IsStruct(response.CertificateResponse) {
|
if !structs.IsStruct(response.CertificateResponse) {
|
||||||
return errkit.New(fmt.Sprintf("ssl: certificate response cannot be parsed into a struct: %v", response.CertificateResponse)).Build()
|
return errkit.Newf("certificate response cannot be parsed into a struct: %v", response.CertificateResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
responseParsed = structs.New(response.CertificateResponse)
|
responseParsed = structs.New(response.CertificateResponse)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user