Merge branch 'dev' into pr/6422

This commit is contained in:
Mzack9999 2025-09-12 18:25:18 +02:00
commit 99a9ce398d
117 changed files with 3522 additions and 691 deletions

View 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
View 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

View File

@ -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.

View 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

View File

@ -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 }}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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
View 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

View File

@ -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 \

View 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
}

View File

@ -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
} }

View File

@ -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{

View File

@ -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)
}
} }

View File

@ -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)
} }

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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
View File

@ -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
)

1229
go.sum

File diff suppressed because it is too large Load Diff

View 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"

View 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"

View File

@ -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))

View File

@ -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
} }

View File

@ -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())

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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 {

View File

@ -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
} }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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())

View File

@ -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())
}

View File

@ -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
}) })

View 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")
}

View File

@ -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

View File

@ -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

View File

@ -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)
} }

View 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") @)

View 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

View File

@ -0,0 +1,3 @@
token: foobar
foo:
bar: baz

View File

@ -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)

View File

@ -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")
}

View 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 }

View File

@ -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,
}, },
}) })
} }

View File

@ -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
} }

View File

@ -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 {

View File

@ -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)

View File

@ -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()
} }

View File

@ -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()
} }

View File

@ -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[],
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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
} }

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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
} }

View File

@ -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
}

View 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)
}

View File

@ -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))

View File

@ -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{

View File

@ -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 {

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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")
) )

View File

@ -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 {

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -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)

View File

@ -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)

View File

@ -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 {

View File

@ -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)

View File

@ -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)

View File

@ -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
} }

View File

@ -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
} }

View 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"

View File

@ -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)
} }

View File

@ -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)

View File

@ -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