diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml
index 0c65e536ca..3d6642fc59 100644
--- a/.github/auto_assign.yml
+++ b/.github/auto_assign.yml
@@ -1,6 +1,7 @@
addReviewers: true
reviewers:
- dogancanbakir
+ - dwisiswant0
numberOfReviewers: 1
skipKeywords:
diff --git a/.gitignore b/.gitignore
index 146b0a892c..6145103342 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,4 +42,9 @@ pkg/protocols/common/helpers/deserialization/testdata/ValueObject2.ser
vendor
# Headless `screenshot` action
-*.png
\ No newline at end of file
+*.png
+
+# Profiling & tracing
+*.prof
+*.pprof
+*.trace
\ No newline at end of file
diff --git a/DESIGN.md b/DESIGN.md
index 9d92e28f8d..af0e27baf9 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -457,26 +457,49 @@ func (template *Template) compileProtocolRequests(options protocols.ExecuterOpti
That's it, you've added a new protocol to Nuclei. The next good step would be to write integration tests which are described in `integration-tests` and `cmd/integration-tests` directories.
-## Profiling Instructions
+## Profiling and Tracing
-To enable dumping of Memory profiling data, `-profile-mem` flag can be used along with path to a file. This writes a pprof formatted file which can be used for investigate resource usage with `pprof` tool.
+To analyze Nuclei's performance and resource usage, you can generate memory profiles and trace files using the `-profile-mem` flag:
-```console
-$ nuclei -t nuclei-templates/ -u https://example.com -profile-mem mem.pprof
+```bash
+nuclei -t nuclei-templates/ -u https://example.com -profile-mem=nuclei-$(git describe --tags)
```
-To view profile data in pprof, first install pprof. Then run the below command -
+This command creates two files:
-```console
-$ go tool pprof mem.pprof
+* `nuclei.prof`: Memory (heap) profile
+* `nuclei.trace`: Execution trace
+
+### Analyzing the Memory Profile
+
+1. View the profile in the terminal:
+
+```bash
+go tool pprof nuclei.prof
+```
+
+2. Display top memory consumers:
+
+```bash
+go tool pprof -top nuclei.prof | grep "$(go list -m)" | head -10
```
-To open a web UI on a port to visualize debug data, the below command can be used.
+3. Visualize the profile in a web browser:
-```console
-$ go tool pprof -http=:8081 mem.pprof
+```bash
+go tool pprof -http=:$(shuf -i 1000-99999 -n 1) nuclei.prof
```
+### Analyzing the Trace File
+
+To examine the execution trace:
+
+```bash
+go tool trace nuclei.trace
+```
+
+These tools help identify performance bottlenecks and memory leaks, allowing for targeted optimizations of Nuclei's codebase.
+
## Project Structure
- [pkg/reporting](./pkg/reporting) - Reporting modules for nuclei.
diff --git a/README.md b/README.md
index 412d348645..02cacb9ccf 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,4 @@
-
-
-
-
-
-Fast and customisable vulnerability scanner based on simple YAML based DSL.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- How •
- Install •
- Documentation •
- Credits •
- FAQs •
- Join Discord
-
+![nuclei](/static/nuclei-cover-image.png)
English •
@@ -34,78 +8,103 @@
Spanish •
日本語
-
----
-
-Nuclei is used to send requests across targets based on a template, leading to zero false positives and providing fast scanning on a large number of hosts. Nuclei offers scanning for a variety of protocols, including TCP, DNS, HTTP, SSL, File, Whois, Websocket, Headless, Code etc. With powerful and flexible templating, Nuclei can be used to model all kinds of security checks.
+
-We have a [dedicated repository](https://github.com/projectdiscovery/nuclei-templates) that houses various type of vulnerability templates contributed by **more than 300** security researchers and engineers.
+
-## How it works
+
+
+
+
+
+
+
+---
-
-
-
+
+
+
+
+Nuclei is a modern, high-performance vulnerability scanner that leverages simple YAML-based templates. It empowers you to design custom vulnerability detection scenarios that mimic real-world conditions, leading to zero false positives.
+
+- Simple YAML format for creating and customizing vulnerability templates.
+- Contributed by thousands of security professionals to tackle trending vulnerabilities.
+- Reduce false positives by simulating real-world steps to verify a vulnerability.
+- Ultra-fast parallel scan processing and request clustering.
+- Integrate into CI/CD pipelines for vulnerability detection and regression testing.
+- Supports multiple protocols like TCP, DNS, HTTP, SSL, WHOIS JavaScript, Code and more.
+- Integrate with Jira, Splunk, GitHub, Elastic, GitLab.
+
+## Table of Contents
+
+- [Get Started](#get-started)
+ - [1. Nuclei CLI](#1-nuclei-cli)
+ - [2. Pro and Enterprise Editions](#2-pro-and-enterprise-editions)
+- [Documentation](#documentation)
+ - [Command Line Flags](#command-line-flags)
+ - [Single target scan](#single-target-scan)
+ - [Scanning multiple targets](#scanning-multiple-targets)
+ - [Network scan](#network-scan)
+ - [Scanning with your custom template](#scanning-with-your-custom-template)
+ - [Connect Nuclei to ProjectDiscovery](#connect-nuclei-to-projectdiscovery)
+- [Nuclei Templates, Community and Rewards 💎](#nuclei-templates-community-and-rewards-)
+- [Our Mission](#our-mission)
+- [Contributors ❤️](#contributors-️)
+- [License](#license)
+---
| :exclamation: **Disclaimer** |
|---------------------------------|
| **This project is in active development**. Expect breaking changes with releases. Review the release changelog before updating. |
-| This project was primarily built to be used as a standalone CLI tool. **Running nuclei as a service may pose security risks.** It's recommended to use with caution and additional security measures. |
+| This project is primarily built to be used as a standalone CLI tool. **Running nuclei as a service may pose security risks.** It's recommended to use with caution and additional security measures. |
-# Install Nuclei
+## Get Started
-Nuclei requires **go1.21** to install successfully. Run the following command to install the latest version -
+### **1. Nuclei CLI**
-```sh
-go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
-```
+Install Nuclei on your machine. Get started by following the installation guide [here](https://docs.projectdiscovery.io/tools/nuclei/install?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme). Additionally, We provide [a free cloud tier](https://cloud.projectdiscovery.io/sign-up) and comes with a generous monthly free limits:
-
- Brew
-
- ```sh
- brew install nuclei
- ```
-
-
-
- Docker
-
- ```sh
- docker pull projectdiscovery/nuclei:latest
- ```
-
-
+- Store and visualize your vulnerability findings
+- Write and manage your nuclei templates
+- Access latest nuclei templates
+- Discover and store your targets
+
+### **2. Pro and Enterprise Editions**
-**More installation [methods can be found here](https://docs.projectdiscovery.io/tools/nuclei/install).**
+For security teams and enterprises, we provide a cloud-hosted service built on top of Nuclei OSS, fine-tuned to help you continuously run vulnerability scans at scale with your team and existing workflows:
-
-
-
+- 50x faster scans
+- Large scale scanning with high accuracy
+- Integrations with cloud services (AWS, GCP, Azure, CloudFlare, Fastly, Terraform, Kubernetes)
+- Jira, Slack, Linear, APIs and Webhooks
+- Executive and compliance reporting
+- Plus: Real-time scanning, SAML SSO, SOC 2 compliant platform (with EU and US hosting options), shared team workspaces, and more
+- We're constantly [adding new features](https://feedback.projectdiscovery.io/changelog)!
+- **Ideal for:** Pentesters, security teams, and enterprises
-### Nuclei Templates
+## Documentation
-Nuclei has built-in support for automatic template download/update as default since version [v2.5.2](https://github.com/projectdiscovery/nuclei/releases/tag/v2.5.2). [**Nuclei-Templates**](https://github.com/projectdiscovery/nuclei-templates) project provides a community-contributed list of ready-to-use templates that is constantly updated.
+Browse the full Nuclei [documentation here](https://docs.projectdiscovery.io/tools/nuclei/running). If you’re new to Nuclei, check out our [foundational Youtube series.](https://www.youtube.com/playlist?list=PLZRbR9aMzTTpItEdeNSulo8bYsvil80Rl)
-You may still use the `update-templates` flag to update the nuclei templates at any time; You can write your own checks for your individual workflow and needs following Nuclei's [templating guide](https://docs.projectdiscovery.io/templates/).
+
-The YAML DSL reference syntax is available [here](SYNTAX-REFERENCE.md).
+
- |
-
-
+
-### Usage
+### Command Line Flags
+
+To display all the flags for the tool:
```sh
nuclei -h
```
-This will display help for the tool. Here are all the switches it supports.
-
+
+ Expand full help flags
```console
Nuclei is a fast, template based vulnerability scanner focusing
@@ -279,23 +278,24 @@ HEADLESS:
-lha, -list-headless-action list available headless actions
DEBUG:
- -debug show all requests and responses
- -dreq, -debug-req show all sent requests
- -dresp, -debug-resp show all received responses
- -p, -proxy string[] list of http/socks5 proxy to use (comma separated or file input)
- -pi, -proxy-internal proxy all internal requests
- -ldf, -list-dsl-function list all supported DSL function signatures
- -tlog, -trace-log string file to write sent requests trace log
- -elog, -error-log string file to write sent requests error log
- -version show nuclei version
- -hm, -hang-monitor enable nuclei hang monitoring
- -v, -verbose show verbose output
- -profile-mem string optional nuclei memory profile dump file
- -vv display templates loaded for scan
- -svd, -show-var-dump show variables dump for debugging
- -ep, -enable-pprof enable pprof debugging server
- -tv, -templates-version shows the version of the installed nuclei-templates
- -hc, -health-check run diagnostic check up
+ -debug show all requests and responses
+ -dreq, -debug-req show all sent requests
+ -dresp, -debug-resp show all received responses
+ -p, -proxy string[] list of http/socks5 proxy to use (comma separated or file input)
+ -pi, -proxy-internal proxy all internal requests
+ -ldf, -list-dsl-function list all supported DSL function signatures
+ -tlog, -trace-log string file to write sent requests trace log
+ -elog, -error-log string file to write sent requests error log
+ -version show nuclei version
+ -hm, -hang-monitor enable nuclei hang monitoring
+ -v, -verbose show verbose output
+ -profile-mem string generate memory (heap) profile & trace files
+ -vv display templates loaded for scan
+ -svd, -show-var-dump show variables dump for debugging
+ -vdl, -var-dump-limit int limit the number of characters displayed in var dump (default 255)
+ -ep, -enable-pprof enable pprof debugging server
+ -tv, -templates-version shows the version of the installed nuclei-templates
+ -hc, -health-check run diagnostic check up
UPDATE:
-up, -update update nuclei engine to the latest released version
@@ -310,11 +310,13 @@ STATISTICS:
-mp, -metrics-port int port to expose nuclei metrics on (default 9092)
CLOUD:
- -auth configure projectdiscovery cloud (pdcp) api key (default true)
- -tid, -team-id string upload scan results to given team id (optional) (default "none")
- -cup, -cloud-upload upload scan results to pdcp dashboard
- -sid, -scan-id string upload scan results to existing scan id (optional)
- -sname, -scan-name string scan name to set (optional)
+ -auth configure projectdiscovery cloud (pdcp) api key (default true)
+ -tid, -team-id string upload scan results to given team id (optional) (default "none")
+ -cup, -cloud-upload upload scan results to pdcp dashboard [DEPRECATED use -dashboard]
+ -sid, -scan-id string upload scan results to existing scan id (optional)
+ -sname, -scan-name string scan name to set (optional)
+ -pd, -dashboard upload / view nuclei results in projectdiscovery cloud (pdcp) UI dashboard
+ -pdu, -dashboard-upload string upload / view nuclei results file (jsonl) in projectdiscovery cloud (pdcp) UI dashboard
AUTHENTICATION:
-sf, -secret-file string[] path to config file containing secrets for nuclei authenticated scan
@@ -323,59 +325,189 @@ AUTHENTICATION:
EXAMPLES:
Run nuclei on single host:
- $ nuclei -target example.com
+ $ nuclei -target example.com
Run nuclei with specific template directories:
- $ nuclei -target example.com -t http/cves/ -t ssl
+ $ nuclei -target example.com -t http/cves/ -t ssl
Run nuclei against a list of hosts:
- $ nuclei -list hosts.txt
+ $ nuclei -list hosts.txt
Run nuclei with a JSON output:
- $ nuclei -target example.com -json-export output.json
+ $ nuclei -target example.com -json-export output.json
Run nuclei with sorted Markdown outputs (with environment variables):
- $ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/
+ $ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/
Additional documentation is available at: https://docs.nuclei.sh/getting-started/running
```
-### Running Nuclei
+Additional documentation is available at: [https://docs.nuclei.sh/getting-started/running](https://docs.nuclei.sh/getting-started/running?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme)
-See https://docs.projectdiscovery.io/tools/nuclei/running for details on running Nuclei
+
-### Using Nuclei From Go Code
+### Single target scan
-Complete guide of using Nuclei as Library/SDK is available at [godoc](https://pkg.go.dev/github.com/projectdiscovery/nuclei/v3/lib#section-readme)
+To perform a quick scan on web-application:
+```sh
+nuclei -target https://example.com
+```
-### Resources
+### Scanning multiple targets
-You can access the main documentation for Nuclei at https://docs.projectdiscovery.io/tools/nuclei/, and learn more about Nuclei in the cloud with [ProjectDiscovery Cloud Platform](https://cloud.projectdiscovery.io)
+Nuclei can handle bulk scanning by providing a list of targets. You can use a file containing multiple URLs.
-See https://docs.projectdiscovery.io/tools/nuclei/resources for more resources and videos about Nuclei!
+```sh
+nuclei -targets urls.txt
+```
-### Credits
+### Network scan
-Thanks to all the amazing [community contributors for sending PRs](https://github.com/projectdiscovery/nuclei/graphs/contributors) and keeping this project updated. :heart:
+This will scan the entire subnet for network-related issues, such as open ports or misconfigured services.
+
+```sh
+nuclei -target 192.168.1.0/24
+```
-If you have an idea or some kind of improvement, you are welcome to contribute and participate in the Project, feel free to send your PR.
+### Scanning with your custom template
-
-
-
-
+To write and use your own template, create a `.yaml` file with specific rules, then use it as follows.
+
+```sh
+nuclei -u https://example.com -t /path/to/your-template.yaml
+```
+
+### Connect Nuclei to ProjectDiscovery
+
+You can run the scans on your machine and upload the results to the cloud platform for further analysis and remediation.
+
+```sh
+nuclei -target https://example.com -dashboard
+```
+
+> [!NOTE]
+> This feature is absolutely free and does not require any subscription. For a detailed guide, refer to the [documentation](https://docs.projectdiscovery.io/cloud/scanning/nuclei-scan?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme).
+
+## Nuclei Templates, Community and Rewards 💎
+[Nuclei templates](https://github.com/projectdiscovery/nuclei-templates) are based on the concepts of YAML based template files that define how the requests will be sent and processed. This allows easy extensibility capabilities to nuclei. The templates are written in YAML which specifies a simple human-readable format to quickly define the execution process.
+
+Try it online with our free AI powered Nuclei Templates Editor by [clicking here.](https://cloud.projectdiscovery.io/templates)
+
+Nuclei Templates offer a streamlined way to identify and communicate vulnerabilities, combining essential details like severity ratings and detection methods. This open-source, community-developed tool accelerates threat response and is widely recognized in the cybersecurity world. Nuclei templates are actively contributed by thousands of security researchers globally. We run two programs for our contributors: [Pioneers](https://projectdiscovery.io/pioneers) and [💎 bounties](https://github.com/projectdiscovery/nuclei-templates/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22%F0%9F%92%8E%20Bounty%22).
+
+
+
+
+#### Examples
+
+Visit [our documentation](https://docs.projectdiscovery.io/templates/introduction) for use cases and ideas.
+
+| Use case | Nuclei template |
+| :----------------------------------- | :------------------------------------------------- |
+| Detect known CVEs | **[CVE-2021-44228 (Log4Shell)](https://cloud.projectdiscovery.io/public/CVE-2021-45046)** |
+| Identify Out-of-Band vulnerabilities | **[Blind SQL Injection via OOB](https://cloud.projectdiscovery.io/public/CVE-2024-22120)** |
+| SQL Injection detection | **[Generic SQL Injection](https://cloud.projectdiscovery.io/public/CVE-2022-34265)** |
+| Cross-Site Scripting (XSS) | **[Reflected XSS Detection](https://cloud.projectdiscovery.io/public/CVE-2023-4173)** |
+| Default or weak passwords | **[Default Credentials Check](https://cloud.projectdiscovery.io/public/airflow-default-login)** |
+| Secret files or data exposure | **[Sensitive File Disclosure](https://cloud.projectdiscovery.io/public/airflow-configuration-exposure)** |
+| Identify open redirects | **[Open Redirect Detection](https://cloud.projectdiscovery.io/public/open-redirect)** |
+| Detect subdomain takeovers | **[Subdomain Takeover Templates](https://cloud.projectdiscovery.io/public/azure-takeover-detection)** |
+| Security misconfigurations | **[Unprotected Jenkins Console](https://cloud.projectdiscovery.io/public/unauthenticated-jenkins)** |
+| Weak SSL/TLS configurations | **[SSL Certificate Expiry](https://cloud.projectdiscovery.io/public/expired-ssl)** |
+| Misconfigured cloud services | **[Open S3 Bucket Detection](https://cloud.projectdiscovery.io/public/s3-public-read-acp)** |
+| Remote code execution vulnerabilities| **[RCE Detection Templates](https://cloud.projectdiscovery.io/public/CVE-2024-29824)** |
+| Directory traversal attacks | **[Path Traversal Detection](https://cloud.projectdiscovery.io/public/oracle-fatwire-lfi)** |
+| File inclusion vulnerabilities | **[Local/Remote File Inclusion](https://cloud.projectdiscovery.io/public/CVE-2023-6977)** |
+
-Do also check out the below similar open-source projects that may fit in your workflow:
+## Our Mission
-[FFuF](https://github.com/ffuf/ffuf), [Qsfuzz](https://github.com/ameenmaali/qsfuzz), [Inception](https://github.com/proabiral/inception), [Snallygaster](https://github.com/hannob/snallygaster), [Gofingerprint](https://github.com/Static-Flow/gofingerprint), [Sn1per](https://github.com/1N3/Sn1per/tree/master/templates), [Google tsunami](https://github.com/google/tsunami-security-scanner), [Jaeles](https://github.com/jaeles-project/jaeles), [ChopChop](https://github.com/michelin/ChopChop)
+Traditional vulnerability scanners were built decades ago. They are closed-source, incredibly slow, and vendor-driven. Today's attackers are mass exploiting newly released CVEs across the internet within days, unlike the years it used to take. This shift requires a completely different approach to tackling trending exploits on the internet.
+
+We built Nuclei to solve this challenge. We made the entire scanning engine framework open and customizable—allowing the global security community to collaborate and tackle the trending attack vectors and vulnerabilities on the internet. Nuclei is now used and contributed by Fortune 500 enterprises, government agencies, universities.
+
+You can participate by contributing to our code, [templates library](https://github.com/projectdiscovery/nuclei-templates), or [joining our team.](https://projectdiscovery.io/)
+
+## Contributors :heart:
+
+Thanks to all the amazing [community contributors for sending PRs](https://github.com/projectdiscovery/nuclei/graphs/contributors) and keeping this project updated. :heart:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-### License
+## License
-Nuclei is distributed under [MIT License](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)
+Nuclei is distributed under [MIT License](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md).
-
-
-
+
diff --git a/SYNTAX-REFERENCE.md b/SYNTAX-REFERENCE.md
index 9ec413d3ed..6d1d9effc4 100755
--- a/SYNTAX-REFERENCE.md
+++ b/SYNTAX-REFERENCE.md
@@ -1651,6 +1651,19 @@ FuzzPreConditionOperator is the operator between multiple PreConditions for fuzz
+
+
+global-matchers
bool
+
+
+
+
+GlobalMatchers marks matchers as static and applies globally to all result events from other templates
+
+
+
+
+
@@ -3210,6 +3223,19 @@ read-all: false
+
+
+stop-at-first-match
bool
+
+
+
+
+StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.
+
+
+
+
+
@@ -3764,11 +3790,33 @@ Appears in:
Part Definitions:
+- template-id
- ID of the template executed
+- template-info
- Info Block of the template executed
+- template-path
- Path of the template executed
+- host
- Host is the input to the template
+- port
- Port is the port of the host
+- matched
- Matched is the input which was matched upon
- type
- Type is the type of request made
+- timestamp
- Timestamp is the time when the request was made
- response
- JSON SSL protocol handshake details
+- cipher
- Cipher is the encryption algorithm used
+- domains
- Domains are the list of domain names in the certificate
+- fingerprint_hash
- Fingerprint hash is the unique identifier of the certificate
+- ip
- IP is the IP address of the server
+- issuer_cn
- Issuer CN is the common name of the certificate issuer
+- issuer_dn
- Issuer DN is the distinguished name of the certificate issuer
+- issuer_org
- Issuer organization is the organization of the certificate issuer
- not_after
- Timestamp after which the remote cert expires
-- host
- Host is the input to the template
-- matched
- Matched is the input which was matched upon
+- not_before
- Timestamp before which the certificate is not valid
+- probe_status
- Probe status indicates if the probe was successful
+- serial
- Serial is the serial number of the certificate
+- sni
- SNI is the server name indication used in the handshake
+- subject_an
- Subject AN is the list of subject alternative names
+- subject_cn
- Subject CN is the common name of the certificate subject
+- subject_dn
- Subject DN is the distinguished name of the certificate subject
+- subject_org
- Subject organization is the organization of the certificate subject
+- tls_connection
- TLS connection is the type of TLS connection used
+- tls_version
- TLS version is the version of the TLS protocol used
diff --git a/cmd/nuclei/issue-tracker-config.yaml b/cmd/nuclei/issue-tracker-config.yaml
index b7e0e6dafc..07ccf828f2 100644
--- a/cmd/nuclei/issue-tracker-config.yaml
+++ b/cmd/nuclei/issue-tracker-config.yaml
@@ -162,3 +162,14 @@
# duplicate-issue-check: false
# # open-state-id is the ID of the open state in Linear
# open-state-id: ""
+#mongodb:
+# # the connection string to the MongoDB database
+# # (e.g., mongodb://root:example@localhost:27017/nuclei?ssl=false&authSource=admin)
+# connection-string: ""
+# # the name of the collection to store the issues
+# collection-name: ""
+# # excludes the Request and Response from the results (helps with filesize)
+# omit-raw: false
+# # determines the number of results to be kept in memory before writing it to the database or 0 to
+# # persist all in memory and write all results at the end (default)
+# batch-size: 0
\ No newline at end of file
diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go
index 8fe5b29573..5e95a8b1b2 100644
--- a/cmd/nuclei/main.go
+++ b/cmd/nuclei/main.go
@@ -9,6 +9,7 @@ import (
"path/filepath"
"runtime"
"runtime/pprof"
+ "runtime/trace"
"strings"
"time"
@@ -103,21 +104,40 @@ func main() {
return
}
- // Profiling related code
+ // Profiling & tracing related code
if memProfile != "" {
- f, err := os.Create(memProfile)
+ memProfile = strings.TrimSuffix(memProfile, filepath.Ext(memProfile)) + ".prof"
+ memProfileFile, err := os.Create(memProfile)
if err != nil {
- gologger.Fatal().Msgf("profile: could not create memory profile %q: %v", memProfile, err)
+ gologger.Fatal().Msgf("profile: could not create memory profile %q file: %v", memProfile, err)
}
- old := runtime.MemProfileRate
+
+ traceFilepath := strings.TrimSuffix(memProfile, filepath.Ext(memProfile)) + ".trace"
+ traceFile, err := os.Create(traceFilepath)
+ if err != nil {
+ gologger.Fatal().Msgf("profile: could not create trace %q file: %v", traceFilepath, err)
+ }
+
+ oldMemProfileRate := runtime.MemProfileRate
runtime.MemProfileRate = 4096
- gologger.Print().Msgf("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, memProfile)
+
+ // Start tracing
+ if err := trace.Start(traceFile); err != nil {
+ gologger.Fatal().Msgf("profile: could not start trace: %v", err)
+ }
defer func() {
- _ = pprof.Lookup("heap").WriteTo(f, 0)
- f.Close()
- runtime.MemProfileRate = old
- gologger.Print().Msgf("profile: memory profiling disabled, %s", memProfile)
+ // Start CPU profiling
+ if err := pprof.WriteHeapProfile(memProfileFile); err != nil {
+ gologger.Fatal().Msgf("profile: could not start CPU profile: %v", err)
+ }
+ memProfileFile.Close()
+ traceFile.Close()
+ trace.Stop()
+ runtime.MemProfileRate = oldMemProfileRate
+
+ gologger.Info().Msgf("Memory profile saved at %q", memProfile)
+ gologger.Info().Msgf("Traced at %q", traceFilepath)
}()
}
@@ -402,9 +422,10 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.CallbackVar(printVersion, "version", "show nuclei version"),
flagSet.BoolVarP(&options.HangMonitor, "hang-monitor", "hm", false, "enable nuclei hang monitoring"),
flagSet.BoolVarP(&options.Verbose, "verbose", "v", false, "show verbose output"),
- flagSet.StringVar(&memProfile, "profile-mem", "", "optional nuclei memory profile dump file"),
+ flagSet.StringVar(&memProfile, "profile-mem", "", "generate memory (heap) profile & trace files"),
flagSet.BoolVar(&options.VerboseVerbose, "vv", false, "display templates loaded for scan"),
flagSet.BoolVarP(&options.ShowVarDump, "show-var-dump", "svd", false, "show variables dump for debugging"),
+ flagSet.IntVarP(&options.VarDumpLimit, "var-dump-limit", "vdl", 255, "limit the number of characters displayed in var dump"),
flagSet.BoolVarP(&options.EnablePprof, "enable-pprof", "ep", false, "enable pprof debugging server"),
flagSet.CallbackVarP(printTemplateVersion, "templates-version", "tv", "shows the version of the installed nuclei-templates"),
flagSet.BoolVarP(&options.HealthCheck, "health-check", "hc", false, "run diagnostic check up"),
diff --git a/gh_retry.sh b/gh_retry.sh
index bf212e1290..43542276b3 100755
--- a/gh_retry.sh
+++ b/gh_retry.sh
@@ -21,9 +21,9 @@ WORKFLOW="Build Test"
GREP_ERROR_PATTERN='Test "http/interactsh.yaml" failed'
#Set fonts for Help.
-NORM=`tput sgr0`
-BOLD=`tput bold`
-REV=`tput smso`
+NORM=$(tput sgr0)
+BOLD=$(tput bold)
+REV=$(tput smso)
HELP()
{
@@ -73,7 +73,7 @@ function print_bold() {
function retry_failed_jobs() {
print_bold "Checking failed workflows for branch $BRANCH before $BEFORE"
- date=`date +%Y-%m-%d'T'%H:%M'Z' -d "$BEFORE"`
+ date=$(date +%Y-%m-%d'T'%H:%M'Z' -d "$BEFORE")
workflowIds=$(gh run list --limit "$LIMIT" --json headBranch,status,name,conclusion,databaseId,updatedAt | jq -c '.[] |
select ( .headBranch==$branch ) |
diff --git a/go.mod b/go.mod
index f2adacca21..db236f5908 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/projectdiscovery/nuclei/v3
-go 1.21
+go 1.21.0
require (
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible
@@ -104,7 +104,9 @@ require (
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466
github.com/stretchr/testify v1.9.0
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9
+ github.com/yassinebenaid/godump v0.10.0
github.com/zmap/zgrab2 v0.1.8-0.20230806160807-97ba87c0e706
+ go.mongodb.org/mongo-driver v1.17.0
golang.org/x/term v0.24.0
gopkg.in/yaml.v3 v3.0.1
moul.io/http2curl v1.0.0
@@ -195,6 +197,7 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
+ github.com/montanaflynn/stats v0.7.1 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
@@ -228,9 +231,13 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
+ github.com/xdg-go/pbkdf2 v1.0.0 // indirect
+ github.com/xdg-go/scram v1.1.2 // indirect
+ github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
+ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/got v0.34.1 // indirect
github.com/yuin/goldmark v1.7.4 // indirect
diff --git a/go.sum b/go.sum
index da71e9b287..556ba04f24 100644
--- a/go.sum
+++ b/go.sum
@@ -755,6 +755,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
+github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
+github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
@@ -1085,6 +1087,12 @@ github.com/xanzy/go-gitlab v0.107.0 h1:P2CT9Uy9yN9lJo3FLxpMZ4xj6uWcpnigXsjvqJ6nd
github.com/xanzy/go-gitlab v0.107.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
+github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
+github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
+github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
+github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@@ -1096,8 +1104,12 @@ github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/yassinebenaid/godump v0.10.0 h1:FolBA+Ix5uwUiXYBBYOsf1VkT5+0f4gtFNTkYTiIR08=
+github.com/yassinebenaid/godump v0.10.0/go.mod h1:dc/0w8wmg6kVIvNGAzbKH1Oa54dXQx8SNKh4dPRyW44=
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
+github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
+github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=
github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
@@ -1150,6 +1162,8 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.mongodb.org/mongo-driver v1.17.0 h1:Hp4q2MCjvY19ViwimTs00wHi7G4yzxh4/2+nTx8r40k=
+go.mongodb.org/mongo-driver v1.17.0/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
diff --git a/internal/runner/options.go b/internal/runner/options.go
index 2872b96a7a..4ad62a855a 100644
--- a/internal/runner/options.go
+++ b/internal/runner/options.go
@@ -68,6 +68,7 @@ func ParseOptions(options *types.Options) {
if options.ShowVarDump {
vardump.EnableVarDump = true
+ vardump.Limit = options.VarDumpLimit
}
if options.ShowActions {
gologger.Info().Msgf("Showing available headless actions: ")
diff --git a/internal/runner/runner.go b/internal/runner/runner.go
index 0b6da592d3..516bd7ca50 100644
--- a/internal/runner/runner.go
+++ b/internal/runner/runner.go
@@ -48,6 +48,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/automaticscan"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
@@ -475,6 +476,7 @@ func (r *Runner) RunEnumeration() error {
TemporaryDirectory: r.tmpDir,
Parser: r.parser,
FuzzParamsFrequency: fuzzFreqCache,
+ GlobalMatchers: globalmatchers.New(),
}
if config.DefaultConfig.IsDebugArgEnabled(config.DebugExportURLPattern) {
diff --git a/lib/sdk.go b/lib/sdk.go
index daeb68c14b..9bd46f4766 100644
--- a/lib/sdk.go
+++ b/lib/sdk.go
@@ -100,6 +100,7 @@ func (e *NucleiEngine) LoadAllTemplates() error {
return errorutil.New("Could not create loader client: %s\n", err)
}
e.store.Load()
+ e.templatesLoaded = true
return nil
}
diff --git a/lib/sdk_private.go b/lib/sdk_private.go
index cacd9a1ca1..f63c7b1c34 100644
--- a/lib/sdk_private.go
+++ b/lib/sdk_private.go
@@ -3,6 +3,7 @@ package nuclei
import (
"context"
"fmt"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input"
"strings"
"sync"
"time"
@@ -171,6 +172,7 @@ func (e *NucleiEngine) init(ctx context.Context) error {
ResumeCfg: types.NewResumeCfg(),
Browser: e.browserInstance,
Parser: e.parser,
+ InputHelper: input.NewHelper(),
}
if len(e.opts.SecretsFile) > 0 {
authTmplStore, err := runner.GetAuthTmplStore(*e.opts, e.catalog, e.executerOpts)
diff --git a/nuclei-jsonschema.json b/nuclei-jsonschema.json
index b4ed1d2982..97477e1f7e 100644
--- a/nuclei-jsonschema.json
+++ b/nuclei-jsonschema.json
@@ -889,6 +889,11 @@
],
"title": "condition between the filters",
"description": "Operator to use between multiple per-conditions"
+ },
+ "global-matchers": {
+ "type": "boolean",
+ "title": "global matchers",
+ "description": "marks matchers as static and applies globally to all result events from other templates"
}
},
"additionalProperties": false,
@@ -1365,6 +1370,11 @@
"title": "read all response stream",
"description": "Read all response stream till the server stops sending"
},
+ "stop-at-first-match": {
+ "type": "boolean",
+ "title": "stop at first match",
+ "description": "Stop the execution after a match is found"
+ },
"matchers": {
"items": {
"$ref": "#/$defs/matchers.Matcher"
diff --git a/pkg/catalog/config/constants.go b/pkg/catalog/config/constants.go
index e29af00cdd..7d11b6f71e 100644
--- a/pkg/catalog/config/constants.go
+++ b/pkg/catalog/config/constants.go
@@ -31,7 +31,7 @@ const (
CLIConfigFileName = "config.yaml"
ReportingConfigFilename = "reporting-config.yaml"
// Version is the current version of nuclei
- Version = `v3.3.4`
+ Version = `v3.3.5`
// Directory Names of custom templates
CustomS3TemplatesDirName = "s3"
CustomGitHubTemplatesDirName = "github"
diff --git a/pkg/catalog/loader/loader.go b/pkg/catalog/loader/loader.go
index 48f14a4054..94818020b7 100644
--- a/pkg/catalog/loader/loader.go
+++ b/pkg/catalog/loader/loader.go
@@ -353,6 +353,20 @@ func (store *Store) areWorkflowOrTemplatesValid(filteredTemplatePaths map[string
if isParsingError("Error occurred parsing template %s: %s\n", templatePath, err) {
areTemplatesValid = false
}
+ } else if template == nil {
+ // NOTE(dwisiswant0): possibly global matchers template.
+ // This could definitely be handled better, for example by returning an
+ // `ErrGlobalMatchersTemplate` during `templates.Parse` and checking it
+ // with `errors.Is`.
+ //
+ // However, I’m not sure if every reference to it should be handled
+ // that way. Returning a `templates.Template` pointer would mean it’s
+ // an active template (sending requests), and adding a specific field
+ // like `isGlobalMatchers` in `templates.Template` (then checking it
+ // with a `*templates.Template.IsGlobalMatchersEnabled` method) would
+ // just introduce more unknown issues - like during template
+ // clustering, AFAIK.
+ continue
} else {
if existingTemplatePath, found := templateIDPathMap[template.ID]; !found {
templateIDPathMap[template.ID] = templatePath
diff --git a/pkg/fuzz/dataformat/multipart.go b/pkg/fuzz/dataformat/multipart.go
index d7e40af10c..227025d22b 100644
--- a/pkg/fuzz/dataformat/multipart.go
+++ b/pkg/fuzz/dataformat/multipart.go
@@ -6,12 +6,19 @@ import (
"io"
"mime"
"mime/multipart"
+ "net/textproto"
mapsutil "github.com/projectdiscovery/utils/maps"
)
type MultiPartForm struct {
- boundary string
+ boundary string
+ filesMetadata map[string]FileMetadata
+}
+
+type FileMetadata struct {
+ ContentType string
+ Filename string
}
var (
@@ -41,11 +48,40 @@ func (m *MultiPartForm) Encode(data KV) (string, error) {
data.Iterate(func(key string, value any) bool {
var fw io.Writer
var err error
+
+ if filesArray, ok := value.([]interface{}); ok {
+ fileMetadata, ok := m.filesMetadata[key]
+ if !ok {
+ Itererr = fmt.Errorf("file metadata not found for key %s", key)
+ return false
+ }
+
+ for _, file := range filesArray {
+ h := make(textproto.MIMEHeader)
+ h.Set("Content-Disposition",
+ 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 {
+ Itererr = err
+ return false
+ }
+
+ if _, err = fw.Write([]byte(file.(string))); err != nil {
+ Itererr = err
+ return false
+ }
+ }
+ return true
+ }
+
// Add field
if fw, err = w.CreateFormField(key); err != nil {
Itererr = err
return false
}
+
if _, err = fw.Write([]byte(value.(string))); err != nil {
Itererr = err
return false
@@ -98,6 +134,7 @@ func (m *MultiPartForm) Decode(data string) (KV, error) {
result.Set(key, values[0])
}
}
+ m.filesMetadata = make(map[string]FileMetadata)
for key, files := range form.File {
fileContents := []interface{}{}
for _, fileHeader := range files {
@@ -112,6 +149,11 @@ func (m *MultiPartForm) Decode(data string) (KV, error) {
return KV{}, err
}
fileContents = append(fileContents, buffer.String())
+
+ m.filesMetadata[key] = FileMetadata{
+ ContentType: fileHeader.Header.Get("Content-Type"),
+ Filename: fileHeader.Filename,
+ }
}
result.Set(key, fileContents)
}
diff --git a/pkg/js/libs/ldap/ldap.go b/pkg/js/libs/ldap/ldap.go
index 3961fa7b7e..a8f0eb0c8e 100644
--- a/pkg/js/libs/ldap/ldap.go
+++ b/pkg/js/libs/ldap/ldap.go
@@ -155,7 +155,7 @@ func NewClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// client.Authenticate('user', 'password');
// ```
-func (c *Client) Authenticate(username, password string) {
+func (c *Client) Authenticate(username, password string) bool {
c.nj.Require(c.conn != nil, "no existing connection")
if c.BaseDN == "" {
c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(c.Realm, "."), ",dc="))
@@ -163,19 +163,21 @@ func (c *Client) Authenticate(username, password string) {
if err := c.conn.NTLMBind(c.Realm, username, password); err == nil {
// if bind with NTLMBind(), there is nothing
// else to do, you are authenticated
- return
+ return true
}
+ var err error
switch password {
case "":
- if err := c.conn.UnauthenticatedBind(username); err != nil {
+ if err = c.conn.UnauthenticatedBind(username); err != nil {
c.nj.ThrowError(err)
}
default:
- if err := c.conn.Bind(username, password); err != nil {
+ if err = c.conn.Bind(username, password); err != nil {
c.nj.ThrowError(err)
}
}
+ return err == nil
}
// AuthenticateWithNTLMHash authenticates with the ldap server using the given username and NTLM hash
@@ -185,14 +187,16 @@ func (c *Client) Authenticate(username, password string) {
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// client.AuthenticateWithNTLMHash('pdtm', 'hash');
// ```
-func (c *Client) AuthenticateWithNTLMHash(username, hash string) {
+func (c *Client) AuthenticateWithNTLMHash(username, hash string) bool {
c.nj.Require(c.conn != nil, "no existing connection")
if c.BaseDN == "" {
c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(c.Realm, "."), ",dc="))
}
- if err := c.conn.NTLMBindWithHash(c.Realm, username, hash); err != nil {
+ var err error
+ if err = c.conn.NTLMBindWithHash(c.Realm, username, hash); err != nil {
c.nj.ThrowError(err)
}
+ return err == nil
}
// Search accepts whatever filter and returns a list of maps having provided attributes
@@ -279,9 +283,10 @@ func (c *Client) CollectMetadata() Metadata {
}
metadata.BaseDN = c.BaseDN
+ // Use scope as Base since Root DSE doesn't have subentries
srMetadata := ldap.NewSearchRequest(
"",
- ldap.ScopeWholeSubtree,
+ ldap.ScopeBaseObject,
ldap.NeverDerefAliases,
0, 0, false,
"(objectClass=*)",
diff --git a/pkg/output/format_screen.go b/pkg/output/format_screen.go
index 9d0f03efaf..3cdcec1e15 100644
--- a/pkg/output/format_screen.go
+++ b/pkg/output/format_screen.go
@@ -39,6 +39,11 @@ func (w *StandardWriter) formatScreen(output *ResultEvent) []byte {
}
}
+ if output.GlobalMatchers {
+ builder.WriteString("] [")
+ builder.WriteString(w.aurora.BrightMagenta("global").String())
+ }
+
builder.WriteString("] [")
builder.WriteString(w.aurora.BrightBlue(output.Type).String())
builder.WriteString("] ")
diff --git a/pkg/output/output.go b/pkg/output/output.go
index fbc9f71306..9d84fd20fc 100644
--- a/pkg/output/output.go
+++ b/pkg/output/output.go
@@ -184,6 +184,9 @@ type ResultEvent struct {
MatcherStatus bool `json:"matcher-status"`
// Lines is the line count for the specified match
Lines []int `json:"matched-line,omitempty"`
+ // GlobalMatchers identifies whether the matches was detected in the response
+ // of another template's result event
+ GlobalMatchers bool `json:"global-matchers,omitempty"`
// IssueTrackers is the metadata for issue trackers
IssueTrackers map[string]IssueTrackerMetadata `json:"issue_trackers,omitempty"`
diff --git a/pkg/protocols/code/code.go b/pkg/protocols/code/code.go
index 87a0128645..4503b2117f 100644
--- a/pkg/protocols/code/code.go
+++ b/pkg/protocols/code/code.go
@@ -235,7 +235,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
gologger.Verbose().Msgf("[%s] Executed code on local machine %v", request.options.TemplateID, input.MetaInput.Input)
if vardump.EnableVarDump {
- gologger.Debug().Msgf("Code Protocol request variables: \n%s\n", vardump.DumpVariables(allvars))
+ gologger.Debug().Msgf("Code Protocol request variables: %s\n", vardump.DumpVariables(allvars))
}
if request.options.Options.Debug || request.options.Options.DebugRequests {
diff --git a/pkg/protocols/common/globalmatchers/globalmatchers.go b/pkg/protocols/common/globalmatchers/globalmatchers.go
new file mode 100644
index 0000000000..321759f927
--- /dev/null
+++ b/pkg/protocols/common/globalmatchers/globalmatchers.go
@@ -0,0 +1,84 @@
+package globalmatchers
+
+import (
+ "maps"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/model"
+ "github.com/projectdiscovery/nuclei/v3/pkg/operators"
+ "github.com/projectdiscovery/nuclei/v3/pkg/output"
+)
+
+// Storage is a struct that holds the global matchers
+type Storage struct {
+ requests []*Item
+}
+
+// Callback is called when a global matcher is matched.
+// It receives internal event & result of the operator execution.
+type Callback func(event output.InternalEvent, result *operators.Result)
+
+// Item is a struct that holds the global matchers
+// details for a template
+type Item struct {
+ TemplateID string
+ TemplatePath string
+ TemplateInfo model.Info
+ Operators []*operators.Operators
+}
+
+// New creates a new storage for global matchers
+func New() *Storage {
+ return &Storage{requests: make([]*Item, 0)}
+}
+
+// hasStorage checks if the Storage is initialized
+func (s *Storage) hasStorage() bool {
+ return s != nil
+}
+
+// AddOperator adds a new operator to the global matchers
+func (s *Storage) AddOperator(item *Item) {
+ if !s.hasStorage() {
+ return
+ }
+
+ s.requests = append(s.requests, item)
+}
+
+// HasMatchers returns true if we have global matchers
+func (s *Storage) HasMatchers() bool {
+ if !s.hasStorage() {
+ return false
+ }
+
+ return len(s.requests) > 0
+}
+
+// Match matches the global matchers against the response
+func (s *Storage) Match(
+ event output.InternalEvent,
+ matchFunc operators.MatchFunc,
+ extractFunc operators.ExtractFunc,
+ isDebug bool,
+ callback Callback,
+) {
+ for _, item := range s.requests {
+ for _, operator := range item.Operators {
+ newEvent := maps.Clone(event)
+ newEvent.Set("origin-template-id", event["template-id"])
+ newEvent.Set("origin-template-info", event["template-info"])
+ newEvent.Set("origin-template-path", event["template-path"])
+ newEvent.Set("template-id", item.TemplateID)
+ newEvent.Set("template-info", item.TemplateInfo)
+ newEvent.Set("template-path", item.TemplatePath)
+ newEvent.Set("global-matchers", true)
+
+ result, matched := operator.Execute(newEvent, matchFunc, extractFunc, isDebug)
+ if !matched {
+ continue
+ }
+
+ callback(newEvent, result)
+ }
+ }
+}
diff --git a/pkg/protocols/common/helpers/eventcreator/eventcreator.go b/pkg/protocols/common/helpers/eventcreator/eventcreator.go
index 078963ceed..22c0a7810d 100644
--- a/pkg/protocols/common/helpers/eventcreator/eventcreator.go
+++ b/pkg/protocols/common/helpers/eventcreator/eventcreator.go
@@ -24,7 +24,7 @@ func CreateEventWithAdditionalOptions(request protocols.Request, outputEvent out
// Dump response variables if ran in debug mode
if vardump.EnableVarDump {
protoName := cases.Title(language.English).String(request.Type().String())
- gologger.Debug().Msgf("%v Protocol response variables: \n%s\n", protoName, vardump.DumpVariables(outputEvent))
+ gologger.Debug().Msgf("%v Protocol response variables: %s\n", protoName, vardump.DumpVariables(outputEvent))
}
for _, compiledOperator := range request.GetCompiledOperators() {
if compiledOperator != nil {
diff --git a/pkg/protocols/common/protocolstate/memguardian.go b/pkg/protocols/common/protocolstate/memguardian.go
index 1db1e0a775..b5c8cffc75 100644
--- a/pkg/protocols/common/protocolstate/memguardian.go
+++ b/pkg/protocols/common/protocolstate/memguardian.go
@@ -77,9 +77,8 @@ var muGlobalChange sync.Mutex
// Global setting
func GlobalGuardBytesBufferAlloc() error {
- if muGlobalChange.TryLock() {
+ if !muGlobalChange.TryLock() {
return nil
-
}
defer muGlobalChange.Unlock()
@@ -95,9 +94,8 @@ func GlobalGuardBytesBufferAlloc() error {
// Global setting
func GlobalRestoreBytesBufferAlloc() {
- if muGlobalChange.TryLock() {
+ if !muGlobalChange.TryLock() {
return
-
}
defer muGlobalChange.Unlock()
diff --git a/pkg/protocols/common/utils/vardump/dump.go b/pkg/protocols/common/utils/vardump/dump.go
index ab4f56b113..82de85e67b 100644
--- a/pkg/protocols/common/utils/vardump/dump.go
+++ b/pkg/protocols/common/utils/vardump/dump.go
@@ -1,53 +1,67 @@
package vardump
import (
- "strconv"
"strings"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
mapsutil "github.com/projectdiscovery/utils/maps"
+ "github.com/yassinebenaid/godump"
)
-// EnableVarDump enables var dump for debugging optionally
-var EnableVarDump bool
+// variables is a map of variables
+type variables = map[string]any
-// DumpVariables writes the truncated dump of variables to a string
-// in a formatted key-value manner.
-//
-// The values are truncated to return 50 characters from start and end.
-func DumpVariables(data map[string]interface{}) string {
- var counter int
+// DumpVariables dumps the variables in a pretty format
+func DumpVariables(data variables) string {
+ if !EnableVarDump {
+ return ""
+ }
+
+ d := godump.Dumper{
+ Indentation: " ",
+ HidePrivateFields: false,
+ ShowPrimitiveNamedTypes: true,
+ }
+
+ d.Theme = godump.Theme{
+ String: godump.RGB{R: 138, G: 201, B: 38},
+ Quotes: godump.RGB{R: 112, G: 214, B: 255},
+ Bool: godump.RGB{R: 249, G: 87, B: 56},
+ Number: godump.RGB{R: 10, G: 178, B: 242},
+ Types: godump.RGB{R: 0, G: 150, B: 199},
+ Address: godump.RGB{R: 205, G: 93, B: 0},
+ PointerTag: godump.RGB{R: 110, G: 110, B: 110},
+ Nil: godump.RGB{R: 219, G: 57, B: 26},
+ Func: godump.RGB{R: 160, G: 90, B: 220},
+ Fields: godump.RGB{R: 189, G: 176, B: 194},
+ Chan: godump.RGB{R: 195, G: 154, B: 76},
+ UnsafePointer: godump.RGB{R: 89, G: 193, B: 180},
+ Braces: godump.RGB{R: 185, G: 86, B: 86},
+ }
- buffer := &strings.Builder{}
- buffer.Grow(len(data) * 78) // grow buffer to an approximate size
+ return d.Sprint(process(data, Limit))
+}
- builder := &strings.Builder{}
- // sort keys for deterministic output
+// process is a helper function that processes the variables
+// and returns a new map of variables
+func process(data variables, limit int) variables {
keys := mapsutil.GetSortedKeys(data)
+ vars := make(variables)
+
+ if limit == 0 {
+ limit = 255
+ }
for _, k := range keys {
- v := data[k]
- valueString := types.ToString(v)
-
- counter++
- if len(valueString) > 50 {
- builder.Grow(56)
- builder.WriteString(valueString[0:25])
- builder.WriteString(" .... ")
- builder.WriteString(valueString[len(valueString)-25:])
- valueString = builder.String()
- builder.Reset()
+ v := types.ToString(data[k])
+ v = strings.ReplaceAll(strings.ReplaceAll(v, "\r", " "), "\n", " ")
+ if len(v) > limit {
+ v = v[:limit]
+ v += " [...]"
}
- valueString = strings.ReplaceAll(strings.ReplaceAll(valueString, "\r", " "), "\n", " ")
-
- buffer.WriteString("\t")
- buffer.WriteString(strconv.Itoa(counter))
- buffer.WriteString(". ")
- buffer.WriteString(k)
- buffer.WriteString(" => ")
- buffer.WriteString(valueString)
- buffer.WriteString("\n")
+
+ vars[k] = v
}
- final := buffer.String()
- return final
+
+ return vars
}
diff --git a/pkg/protocols/common/utils/vardump/dump_test.go b/pkg/protocols/common/utils/vardump/dump_test.go
new file mode 100644
index 0000000000..9929fa5318
--- /dev/null
+++ b/pkg/protocols/common/utils/vardump/dump_test.go
@@ -0,0 +1,55 @@
+package vardump
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDumpVariables(t *testing.T) {
+ // Enable var dump for testing
+ EnableVarDump = true
+
+ // Test case
+ testVars := variables{
+ "string": "test",
+ "int": 42,
+ "bool": true,
+ "slice": []string{"a", "b", "c"},
+ }
+
+ result := DumpVariables(testVars)
+
+ // Assertions
+ assert.NotEmpty(t, result)
+ assert.Contains(t, result, "string")
+ assert.Contains(t, result, "test")
+ assert.Contains(t, result, "int")
+ assert.Contains(t, result, "42")
+ assert.Contains(t, result, "bool")
+ assert.Contains(t, result, "true")
+ assert.Contains(t, result, "slice")
+ assert.Contains(t, result, "a")
+ assert.Contains(t, result, "b")
+ assert.Contains(t, result, "c")
+
+ // Test with EnableVarDump set to false
+ EnableVarDump = false
+ result = DumpVariables(testVars)
+ assert.Empty(t, result)
+}
+
+func TestProcess(t *testing.T) {
+ testVars := variables{
+ "short": "short string",
+ "long": strings.Repeat("a", 300),
+ "number": 42,
+ }
+
+ processed := process(testVars, 255)
+
+ assert.Equal(t, "short string", processed["short"])
+ assert.Equal(t, strings.Repeat("a", 255)+" [...]", processed["long"])
+ assert.Equal(t, "42", processed["number"])
+}
diff --git a/pkg/protocols/common/utils/vardump/vars.go b/pkg/protocols/common/utils/vardump/vars.go
new file mode 100644
index 0000000000..f5e18bce69
--- /dev/null
+++ b/pkg/protocols/common/utils/vardump/vars.go
@@ -0,0 +1,8 @@
+package vardump
+
+var (
+ // EnableVarDump enables var dump for debugging optionally
+ EnableVarDump bool
+ // Limit is the maximum characters to be dumped
+ Limit int = 255
+)
diff --git a/pkg/protocols/dns/request.go b/pkg/protocols/dns/request.go
index 9457845270..6e82c047bc 100644
--- a/pkg/protocols/dns/request.go
+++ b/pkg/protocols/dns/request.go
@@ -108,7 +108,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata,
func (request *Request) execute(input *contextargs.Context, domain string, metadata, previous output.InternalEvent, vars map[string]interface{}, callback protocols.OutputEventCallback) error {
var err error
if vardump.EnableVarDump {
- gologger.Debug().Msgf("DNS Protocol request variables: \n%s\n", vardump.DumpVariables(vars))
+ gologger.Debug().Msgf("DNS Protocol request variables: %s\n", vardump.DumpVariables(vars))
}
// Compile each request for the template based on the URL
diff --git a/pkg/protocols/headless/engine/page_actions.go b/pkg/protocols/headless/engine/page_actions.go
index 18ed515662..640bb76214 100644
--- a/pkg/protocols/headless/engine/page_actions.go
+++ b/pkg/protocols/headless/engine/page_actions.go
@@ -334,7 +334,7 @@ func (p *Page) NavigateURL(action *Action, out ActionData, allvars map[string]in
allvars = generators.MergeMaps(allvars, defaultReqVars)
if vardump.EnableVarDump {
- gologger.Debug().Msgf("Headless Protocol request variables: \n%s\n", vardump.DumpVariables(allvars))
+ gologger.Debug().Msgf("Headless Protocol request variables: %s\n", vardump.DumpVariables(allvars))
}
// Evaluate the target url with all variables
diff --git a/pkg/protocols/headless/request.go b/pkg/protocols/headless/request.go
index 5f9b53174e..aae70aa347 100644
--- a/pkg/protocols/headless/request.go
+++ b/pkg/protocols/headless/request.go
@@ -122,7 +122,7 @@ func (request *Request) executeRequestWithPayloads(input *contextargs.Context, p
defer instance.Close()
if vardump.EnableVarDump {
- gologger.Debug().Msgf("Headless Protocol request variables: \n%s\n", vardump.DumpVariables(payloads))
+ gologger.Debug().Msgf("Headless Protocol request variables: %s\n", vardump.DumpVariables(payloads))
}
instance.SetInteractsh(request.options.Interactsh)
diff --git a/pkg/protocols/http/build_request.go b/pkg/protocols/http/build_request.go
index 1b046bffd8..bc3b41244e 100644
--- a/pkg/protocols/http/build_request.go
+++ b/pkg/protocols/http/build_request.go
@@ -204,7 +204,7 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context,
finalVars := generators.MergeMaps(allVars, payloads)
if vardump.EnableVarDump {
- gologger.Debug().Msgf("HTTP Protocol request variables: \n%s\n", vardump.DumpVariables(finalVars))
+ gologger.Debug().Msgf("HTTP Protocol request variables: %s\n", vardump.DumpVariables(finalVars))
}
// Note: If possible any changes to current logic (i.e evaluate -> then parse URL)
diff --git a/pkg/protocols/http/http.go b/pkg/protocols/http/http.go
index 844bf8c579..bd089edd47 100644
--- a/pkg/protocols/http/http.go
+++ b/pkg/protocols/http/http.go
@@ -223,6 +223,9 @@ type Request struct {
// FuzzPreConditionOperator is the operator between multiple PreConditions for fuzzing Default is OR
FuzzPreConditionOperator string `yaml:"pre-condition-operator,omitempty" json:"pre-condition-operator,omitempty" jsonschema:"title=condition between the filters,description=Operator to use between multiple per-conditions,enum=and,enum=or"`
fuzzPreConditionOperator matchers.ConditionType `yaml:"-" json:"-"`
+ // description: |
+ // GlobalMatchers marks matchers as static and applies globally to all result events from other templates
+ GlobalMatchers bool `yaml:"global-matchers,omitempty" json:"global-matchers,omitempty" jsonschema:"title=global matchers,description=marks matchers as static and applies globally to all result events from other templates"`
}
func (e Request) JSONSchemaExtend(schema *jsonschema.Schema) {
diff --git a/pkg/protocols/http/operators.go b/pkg/protocols/http/operators.go
index d630bfd8b0..9e7d58af0a 100644
--- a/pkg/protocols/http/operators.go
+++ b/pkg/protocols/http/operators.go
@@ -166,6 +166,10 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
if types.ToString(wrapped.InternalEvent["path"]) != "" {
fields.Path = types.ToString(wrapped.InternalEvent["path"])
}
+ var isGlobalMatchers bool
+ if value, ok := wrapped.InternalEvent["global-matchers"]; ok {
+ isGlobalMatchers = value.(bool)
+ }
data := &output.ResultEvent{
TemplateID: types.ToString(wrapped.InternalEvent["template-id"]),
TemplatePath: types.ToString(wrapped.InternalEvent["template-path"]),
@@ -183,6 +187,7 @@ func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent
Timestamp: time.Now(),
MatcherStatus: true,
IP: fields.Ip,
+ GlobalMatchers: isGlobalMatchers,
Request: types.ToString(wrapped.InternalEvent["request"]),
Response: request.truncateResponse(wrapped.InternalEvent["response"]),
CURLCommand: types.ToString(wrapped.InternalEvent["curl-command"]),
diff --git a/pkg/protocols/http/request.go b/pkg/protocols/http/request.go
index 994e065582..f74020eefb 100644
--- a/pkg/protocols/http/request.go
+++ b/pkg/protocols/http/request.go
@@ -973,13 +973,22 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
// prune signature internal values if any
request.pruneSignatureInternalValues(generatedRequest.meta)
- event := eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(generatedRequest.dynamicValues, finalEvent), request.options.Options.Debug || request.options.Options.DebugResponse, func(internalWrappedEvent *output.InternalWrappedEvent) {
+ interimEvent := generators.MergeMaps(generatedRequest.dynamicValues, finalEvent)
+ isDebug := request.options.Options.Debug || request.options.Options.DebugResponse
+ event := eventcreator.CreateEventWithAdditionalOptions(request, interimEvent, isDebug, func(internalWrappedEvent *output.InternalWrappedEvent) {
internalWrappedEvent.OperatorsResult.PayloadValues = generatedRequest.meta
})
+
if hasInteractMatchers {
event.UsesInteractsh = true
}
+ if request.options.GlobalMatchers.HasMatchers() {
+ request.options.GlobalMatchers.Match(interimEvent, request.Match, request.Extract, isDebug, func(event output.InternalEvent, result *operators.Result) {
+ callback(eventcreator.CreateEventWithOperatorResults(request, event, result))
+ })
+ }
+
// if requrlpattern is enabled, only then it is reflected in result event else it is empty string
// consult @Ice3man543 before changing this logic (context: vuln_hash)
if request.options.ExportReqURLPattern {
diff --git a/pkg/protocols/javascript/js.go b/pkg/protocols/javascript/js.go
index 0bd26b88a5..5953c9c296 100644
--- a/pkg/protocols/javascript/js.go
+++ b/pkg/protocols/javascript/js.go
@@ -319,7 +319,7 @@ func (request *Request) ExecuteWithResults(target *contextargs.Context, dynamicV
templateCtx.Merge(payloadValues)
if vardump.EnableVarDump {
- gologger.Debug().Msgf("Javascript Protocol request variables: \n%s\n", vardump.DumpVariables(payloadValues))
+ gologger.Debug().Msgf("JavaScript Protocol request variables: %s\n", vardump.DumpVariables(payloadValues))
}
if request.PreCondition != "" {
diff --git a/pkg/protocols/network/network.go b/pkg/protocols/network/network.go
index 70d618dcb5..c90f5e7019 100644
--- a/pkg/protocols/network/network.go
+++ b/pkg/protocols/network/network.go
@@ -85,6 +85,10 @@ type Request struct {
// SelfContained specifies if the request is self-contained.
SelfContained bool `yaml:"-" json:"-"`
+ // description: |
+ // StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.
+ StopAtFirstMatch bool `yaml:"stop-at-first-match,omitempty" json:"stop-at-first-match,omitempty" jsonschema:"title=stop at first match,description=Stop the execution after a match is found"`
+
// description: |
// ports is post processed list of ports to scan (obtained from Port)
ports []string `yaml:"-" json:"-"`
diff --git a/pkg/protocols/network/request.go b/pkg/protocols/network/request.go
index 32d4ae3494..3579acd3b8 100644
--- a/pkg/protocols/network/request.go
+++ b/pkg/protocols/network/request.go
@@ -8,6 +8,7 @@ import (
"os"
"strings"
"sync"
+ "sync/atomic"
"time"
"github.com/pkg/errors"
@@ -99,6 +100,16 @@ func (request *Request) ExecuteWithResults(target *contextargs.Context, metadata
gologger.Verbose().Msgf("[%v] got errors while checking open ports: %s\n", request.options.TemplateID, err)
}
+ // stop at first match if requested
+ atomicBool := &atomic.Bool{}
+ shouldStopAtFirstMatch := request.StopAtFirstMatch || request.options.StopAtFirstMatch || request.options.Options.StopAtFirstMatch
+ wrappedCallback := func(event *output.InternalWrappedEvent) {
+ if event != nil && event.HasOperatorResult() {
+ atomicBool.Store(true)
+ }
+ callback(event)
+ }
+
for _, port := range ports {
input := target.Clone()
// use network port updates input with new port requested in template file
@@ -107,9 +118,12 @@ func (request *Request) ExecuteWithResults(target *contextargs.Context, metadata
if err := input.UseNetworkPort(port, request.ExcludePorts); err != nil {
gologger.Debug().Msgf("Could not network port from constants: %s\n", err)
}
- if err := request.executeOnTarget(input, visitedAddresses, metadata, previous, callback); err != nil {
+ if err := request.executeOnTarget(input, visitedAddresses, metadata, previous, wrappedCallback); err != nil {
return err
}
+ if shouldStopAtFirstMatch && atomicBool.Load() {
+ break
+ }
}
return nil
@@ -141,6 +155,16 @@ func (request *Request) executeOnTarget(input *contextargs.Context, visited maps
variablesMap := request.options.Variables.Evaluate(variables)
variables = generators.MergeMaps(variablesMap, variables, request.options.Constants)
+ // stop at first match if requested
+ atomicBool := &atomic.Bool{}
+ shouldStopAtFirstMatch := request.StopAtFirstMatch || request.options.StopAtFirstMatch || request.options.Options.StopAtFirstMatch
+ wrappedCallback := func(event *output.InternalWrappedEvent) {
+ if event != nil && event.HasOperatorResult() {
+ atomicBool.Store(true)
+ }
+ callback(event)
+ }
+
for _, kv := range request.addresses {
select {
case <-input.Context().Done():
@@ -154,12 +178,13 @@ func (request *Request) executeOnTarget(input *contextargs.Context, visited maps
continue
}
visited.Set(actualAddress, struct{}{})
-
- if err = request.executeAddress(variables, actualAddress, address, input, kv.tls, previous, callback); err != nil {
+ if err = request.executeAddress(variables, actualAddress, address, input, kv.tls, previous, wrappedCallback); err != nil {
outputEvent := request.responseToDSLMap("", "", "", address, "")
callback(&output.InternalWrappedEvent{InternalEvent: outputEvent})
gologger.Warning().Msgf("[%v] Could not make network request for (%s) : %s\n", request.options.TemplateID, actualAddress, err)
- continue
+ }
+ if shouldStopAtFirstMatch && atomicBool.Load() {
+ break
}
}
return err
@@ -283,36 +308,37 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac
interimValues := generators.MergeMaps(variables, payloads)
if vardump.EnableVarDump {
- gologger.Debug().Msgf("Network Protocol request variables: \n%s\n", vardump.DumpVariables(interimValues))
+ gologger.Debug().Msgf("Network Protocol request variables: %s\n", vardump.DumpVariables(interimValues))
}
inputEvents := make(map[string]interface{})
for _, input := range request.Inputs {
- data := []byte(input.Data)
+ dataInBytes := []byte(input.Data)
+ var err error
- if request.options.Interactsh != nil {
- var transformedData string
- transformedData, interactshURLs = request.options.Interactsh.Replace(string(data), []string{})
- data = []byte(transformedData)
- }
-
- finalData, err := expressions.EvaluateByte(data, interimValues)
+ dataInBytes, err = expressions.EvaluateByte(dataInBytes, interimValues)
if err != nil {
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
request.options.Progress.IncrementFailedRequestsBy(1)
return errors.Wrap(err, "could not evaluate template expressions")
}
- reqBuilder.Write(finalData)
+ data := string(dataInBytes)
+ if request.options.Interactsh != nil {
+ data, interactshURLs = request.options.Interactsh.Replace(data, []string{})
+ dataInBytes = []byte(data)
+ }
+
+ reqBuilder.Write(dataInBytes)
- if err := expressions.ContainsUnresolvedVariables(string(finalData)); err != nil {
+ if err := expressions.ContainsUnresolvedVariables(data); err != nil {
gologger.Warning().Msgf("[%s] Could not make network request for %s: %v\n", request.options.TemplateID, actualAddress, err)
return nil
}
if input.Type.GetType() == hexType {
- finalData, err = hex.DecodeString(string(finalData))
+ dataInBytes, err = hex.DecodeString(data)
if err != nil {
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
request.options.Progress.IncrementFailedRequestsBy(1)
@@ -320,7 +346,7 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac
}
}
- if _, err := conn.Write(finalData); err != nil {
+ if _, err := conn.Write(dataInBytes); err != nil {
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
request.options.Progress.IncrementFailedRequestsBy(1)
return errors.Wrap(err, "could not write request to server")
diff --git a/pkg/protocols/protocols.go b/pkg/protocols/protocols.go
index a9d50c1481..9ead70321d 100644
--- a/pkg/protocols/protocols.go
+++ b/pkg/protocols/protocols.go
@@ -25,6 +25,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
"github.com/projectdiscovery/nuclei/v3/pkg/projectfile"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers"
@@ -126,6 +127,8 @@ type ExecutorOptions struct {
// ExportReqURLPattern exports the request URL pattern
// in ResultEvent it contains the exact url pattern (ex: {{BaseURL}}/{{randstr}}/xyz) used in the request
ExportReqURLPattern bool
+ // GlobalMatchers is the storage for global matchers with http passive templates
+ GlobalMatchers *globalmatchers.Storage
}
// todo: centralizing components is not feasible with current clogged architecture
diff --git a/pkg/protocols/ssl/ssl.go b/pkg/protocols/ssl/ssl.go
index 681043d3bf..fd0dae83db 100644
--- a/pkg/protocols/ssl/ssl.go
+++ b/pkg/protocols/ssl/ssl.go
@@ -222,7 +222,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
payloadValues = generators.MergeMaps(variablesMap, payloadValues, request.options.Constants)
if vardump.EnableVarDump {
- gologger.Debug().Msgf("SSL Protocol request variables: \n%s\n", vardump.DumpVariables(payloadValues))
+ gologger.Debug().Msgf("SSL Protocol request variables: %s\n", vardump.DumpVariables(payloadValues))
}
finalAddress, dataErr := expressions.EvaluateByte([]byte(request.Address), payloadValues)
@@ -347,11 +347,33 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
// description. Multiple definitions are separated by commas.
// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.
var RequestPartDefinitions = map[string]string{
- "type": "Type is the type of request made",
- "response": "JSON SSL protocol handshake details",
- "not_after": "Timestamp after which the remote cert expires",
- "host": "Host is the input to the template",
- "matched": "Matched is the input which was matched upon",
+ "template-id": "ID of the template executed",
+ "template-info": "Info Block of the template executed",
+ "template-path": "Path of the template executed",
+ "host": "Host is the input to the template",
+ "port": "Port is the port of the host",
+ "matched": "Matched is the input which was matched upon",
+ "type": "Type is the type of request made",
+ "timestamp": "Timestamp is the time when the request was made",
+ "response": "JSON SSL protocol handshake details",
+ "cipher": "Cipher is the encryption algorithm used",
+ "domains": "Domains are the list of domain names in the certificate",
+ "fingerprint_hash": "Fingerprint hash is the unique identifier of the certificate",
+ "ip": "IP is the IP address of the server",
+ "issuer_cn": "Issuer CN is the common name of the certificate issuer",
+ "issuer_dn": "Issuer DN is the distinguished name of the certificate issuer",
+ "issuer_org": "Issuer organization is the organization of the certificate issuer",
+ "not_after": "Timestamp after which the remote cert expires",
+ "not_before": "Timestamp before which the certificate is not valid",
+ "probe_status": "Probe status indicates if the probe was successful",
+ "serial": "Serial is the serial number of the certificate",
+ "sni": "SNI is the server name indication used in the handshake",
+ "subject_an": "Subject AN is the list of subject alternative names",
+ "subject_cn": "Subject CN is the common name of the certificate subject",
+ "subject_dn": "Subject DN is the distinguished name of the certificate subject",
+ "subject_org": "Subject organization is the organization of the certificate subject",
+ "tls_connection": "TLS connection is the type of TLS connection used",
+ "tls_version": "TLS version is the version of the TLS protocol used",
}
// Match performs matching operation for a matcher on model and returns:
diff --git a/pkg/protocols/websocket/websocket.go b/pkg/protocols/websocket/websocket.go
index aa099ef43a..8eeeedf217 100644
--- a/pkg/protocols/websocket/websocket.go
+++ b/pkg/protocols/websocket/websocket.go
@@ -207,7 +207,7 @@ func (request *Request) executeRequestWithPayloads(target *contextargs.Context,
}
if vardump.EnableVarDump {
- gologger.Debug().Msgf("Websocket Protocol request variables: \n%s\n", vardump.DumpVariables(payloadValues))
+ gologger.Debug().Msgf("WebSocket Protocol request variables: %s\n", vardump.DumpVariables(payloadValues))
}
finalAddress, dataErr := expressions.EvaluateByte([]byte(request.Address), payloadValues)
diff --git a/pkg/protocols/whois/whois.go b/pkg/protocols/whois/whois.go
index 9963ec19f8..91d0edcf8a 100644
--- a/pkg/protocols/whois/whois.go
+++ b/pkg/protocols/whois/whois.go
@@ -99,7 +99,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
variables := generators.MergeMaps(vars, defaultVars, optionVars, dynamicValues, request.options.Constants)
if vardump.EnableVarDump {
- gologger.Debug().Msgf("Whois Protocol request variables: \n%s\n", vardump.DumpVariables(variables))
+ gologger.Debug().Msgf("Whois Protocol request variables: %s\n", vardump.DumpVariables(variables))
}
// and replace placeholders
diff --git a/pkg/reporting/exporters/mongo/mongo.go b/pkg/reporting/exporters/mongo/mongo.go
new file mode 100644
index 0000000000..faf8bb579b
--- /dev/null
+++ b/pkg/reporting/exporters/mongo/mongo.go
@@ -0,0 +1,155 @@
+package mongo
+
+import (
+ "context"
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/output"
+ "go.mongodb.org/mongo-driver/mongo"
+ "net/url"
+ "os"
+ "strings"
+ "sync"
+
+ mongooptions "go.mongodb.org/mongo-driver/mongo/options"
+)
+
+type Exporter struct {
+ options *Options
+ mutex *sync.Mutex
+ rows []output.ResultEvent
+ collection *mongo.Collection
+ connection *mongo.Client
+}
+
+// Options contains the configuration options for MongoDB exporter client
+type Options struct {
+ // ConnectionString is the connection string to the MongoDB database
+ ConnectionString string `yaml:"connection-string"`
+ // CollectionName is the name of the MongoDB collection in which to store the results
+ CollectionName string `yaml:"collection-name"`
+ // OmitRaw excludes the Request and Response from the results (helps with filesize)
+ OmitRaw bool `yaml:"omit-raw"`
+ // BatchSize determines the number of results to be kept in memory before writing it to the database or 0 to
+ // persist all in memory and write all results at the end (default)
+ BatchSize int `yaml:"batch-size"`
+}
+
+// New creates a new MongoDB exporter integration client based on options.
+func New(options *Options) (*Exporter, error) {
+ exporter := &Exporter{
+ mutex: &sync.Mutex{},
+ options: options,
+ rows: []output.ResultEvent{},
+ }
+
+ // If the environment variable for the connection string is set, then use that instead. This allows for easier
+ // management of sensitive items such as credentials
+ envConnectionString := os.Getenv("MONGO_CONNECTION_STRING")
+ if envConnectionString != "" {
+ options.ConnectionString = envConnectionString
+ gologger.Info().Msgf("Using connection string from environment variable MONGO_CONNECTION_STRING")
+ }
+
+ // Create the connection to the database
+ clientOptions := mongooptions.Client().ApplyURI(options.ConnectionString)
+
+ // Create a new client and connect to the MongoDB server
+ client, err := mongo.Connect(context.TODO(), clientOptions)
+ if err != nil {
+ gologger.Error().Msgf("Error creating MongoDB client: %s", err)
+ return nil, err
+ }
+
+ // Ensure the connection is valid
+ err = client.Ping(context.Background(), nil)
+ if err != nil {
+ gologger.Error().Msgf("Error connecting to MongoDB: %s", err)
+ return nil, err
+ }
+
+ // Get the database from the connection string to set the database and collection
+ parsed, err := url.Parse(options.ConnectionString)
+ if err != nil {
+ gologger.Error().Msgf("Error parsing connection string: %s", options.ConnectionString)
+ return nil, err
+ }
+
+ databaseName := strings.TrimPrefix(parsed.Path, "/")
+
+ if databaseName == "" {
+ return nil, errors.New("error getting database name from connection string")
+ }
+
+ exporter.connection = client
+ exporter.collection = client.Database(databaseName).Collection(options.CollectionName)
+
+ return exporter, nil
+}
+
+// Export writes a result document to the configured MongoDB collection
+// in the database configured by the connection string
+func (exporter *Exporter) Export(event *output.ResultEvent) error {
+ exporter.mutex.Lock()
+ defer exporter.mutex.Unlock()
+
+ if exporter.options.OmitRaw {
+ event.Request = ""
+ event.Response = ""
+ }
+
+ // Add the row to the queue to be processed
+ exporter.rows = append(exporter.rows, *event)
+
+ // If the batch size is greater than 0 and the number of rows has reached the batch, flush it to the database
+ if exporter.options.BatchSize > 0 && len(exporter.rows) >= exporter.options.BatchSize {
+ err := exporter.WriteRows()
+ if err != nil {
+ // The error is already logged, return it to bubble up to the caller
+ return err
+ }
+ }
+
+ return nil
+}
+
+// WriteRows writes all rows from the rows list to the MongoDB collection and removes them from the list
+func (exporter *Exporter) WriteRows() error {
+ // Loop through the rows and write them, removing them as they're entered
+ for len(exporter.rows) > 0 {
+ data := exporter.rows[0]
+
+ // Write the data to the database
+ _, err := exporter.collection.InsertOne(context.TODO(), data)
+ if err != nil {
+ gologger.Fatal().Msgf("Error inserting record into MongoDB collection: %s", err)
+ return err
+ }
+
+ // Remove the item from the list
+ exporter.rows = exporter.rows[1:]
+ }
+
+ return nil
+}
+
+func (exporter *Exporter) Close() error {
+ exporter.mutex.Lock()
+ defer exporter.mutex.Unlock()
+
+ // Write all pending rows
+ err := exporter.WriteRows()
+ if err != nil {
+ // The error is already logged, return it to bubble up to the caller
+ return err
+ }
+
+ // Close the database connection
+ err = exporter.connection.Disconnect(context.TODO())
+ if err != nil {
+ gologger.Error().Msgf("Error disconnecting from MongoDB: %s", err)
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/reporting/options.go b/pkg/reporting/options.go
index c5090de014..bda9b6c28d 100644
--- a/pkg/reporting/options.go
+++ b/pkg/reporting/options.go
@@ -5,6 +5,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonexporter"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonl"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown"
+ "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/mongo"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/sarif"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/splunk"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
@@ -44,6 +45,8 @@ type Options struct {
JSONExporter *jsonexporter.Options `yaml:"json"`
// JSONLExporter contains configuration options for JSONL Exporter Module
JSONLExporter *jsonl.Options `yaml:"jsonl"`
+ // MongoDBExporter containers the configuration options for the MongoDB Exporter Module
+ MongoDBExporter *mongo.Options `yaml:"mongodb"`
HttpClient *retryablehttp.Client `yaml:"-"`
OmitRaw bool `yaml:"-"`
diff --git a/pkg/reporting/reporting.go b/pkg/reporting/reporting.go
index c6a7d63e10..ddc5428636 100644
--- a/pkg/reporting/reporting.go
+++ b/pkg/reporting/reporting.go
@@ -2,6 +2,7 @@ package reporting
import (
"fmt"
+ "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/mongo"
"os"
"strings"
"sync/atomic"
@@ -166,6 +167,13 @@ func New(options *Options, db string, doNotDedupe bool) (Client, error) {
}
client.exporters = append(client.exporters, exporter)
}
+ if options.MongoDBExporter != nil {
+ exporter, err := mongo.New(options.MongoDBExporter)
+ if err != nil {
+ return nil, errorutil.NewWithErr(err).Wrap(ErrExportClientCreation)
+ }
+ client.exporters = append(client.exporters, exporter)
+ }
if doNotDedupe {
return client, nil
@@ -212,6 +220,7 @@ func CreateConfigIfNotExists() error {
SplunkExporter: &splunk.Options{},
JSONExporter: &json_exporter.Options{},
JSONLExporter: &jsonl.Options{},
+ MongoDBExporter: &mongo.Options{},
}
reportingFile, err := os.Create(reportingConfig)
if err != nil {
diff --git a/pkg/templates/compile.go b/pkg/templates/compile.go
index 01af3a999b..b4005afe81 100644
--- a/pkg/templates/compile.go
+++ b/pkg/templates/compile.go
@@ -20,6 +20,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/offlinehttp"
"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec"
@@ -81,6 +82,18 @@ func Parse(filePath string, preprocessor Preprocessor, options protocols.Executo
if err != nil {
return nil, err
}
+ if template.isGlobalMatchersEnabled() {
+ item := &globalmatchers.Item{
+ TemplateID: template.ID,
+ TemplatePath: filePath,
+ TemplateInfo: template.Info,
+ }
+ for _, request := range template.RequestsHTTP {
+ item.Operators = append(item.Operators, request.CompiledOperators)
+ }
+ options.GlobalMatchers.AddOperator(item)
+ return nil, nil
+ }
// Compile the workflow request
if len(template.Workflows) > 0 {
compiled := &template.Workflow
@@ -96,6 +109,25 @@ func Parse(filePath string, preprocessor Preprocessor, options protocols.Executo
return template, nil
}
+// isGlobalMatchersEnabled checks if any of requests in the template
+// have global matchers enabled. It iterates through all requests and
+// returns true if at least one request has global matchers enabled;
+// otherwise, it returns false.
+//
+// Note: This method only checks the `RequestsHTTP`
+// field of the template, which is specific to http-protocol-based
+// templates.
+//
+// TODO: support all protocols.
+func (template *Template) isGlobalMatchersEnabled() bool {
+ for _, request := range template.RequestsHTTP {
+ if request.GlobalMatchers {
+ return true
+ }
+ }
+ return false
+}
+
// parseSelfContainedRequests parses the self contained template requests.
func (template *Template) parseSelfContainedRequests() {
if template.Signature.Value.String() != "" {
diff --git a/pkg/templates/templates_doc.go b/pkg/templates/templates_doc.go
index 827e583d3b..9bf754e24c 100644
--- a/pkg/templates/templates_doc.go
+++ b/pkg/templates/templates_doc.go
@@ -459,7 +459,7 @@ func init() {
Value: "HTTP response headers in name:value format",
},
}
- HTTPRequestDoc.Fields = make([]encoder.Doc, 36)
+ HTTPRequestDoc.Fields = make([]encoder.Doc, 37)
HTTPRequestDoc.Fields[0].Name = "path"
HTTPRequestDoc.Fields[0].Type = "[]string"
HTTPRequestDoc.Fields[0].Note = ""
@@ -668,6 +668,11 @@ func init() {
HTTPRequestDoc.Fields[35].Note = ""
HTTPRequestDoc.Fields[35].Description = "FuzzPreConditionOperator is the operator between multiple PreConditions for fuzzing Default is OR"
HTTPRequestDoc.Fields[35].Comments[encoder.LineComment] = "FuzzPreConditionOperator is the operator between multiple PreConditions for fuzzing Default is OR"
+ HTTPRequestDoc.Fields[36].Name = "global-matchers"
+ HTTPRequestDoc.Fields[36].Type = "bool"
+ HTTPRequestDoc.Fields[36].Note = ""
+ HTTPRequestDoc.Fields[36].Description = "GlobalMatchers marks matchers as static and applies globally to all result events from other templates"
+ HTTPRequestDoc.Fields[36].Comments[encoder.LineComment] = "GlobalMatchers marks matchers as static and applies globally to all result events from other templates"
GENERATORSAttackTypeHolderDoc.Type = "generators.AttackTypeHolder"
GENERATORSAttackTypeHolderDoc.Comments[encoder.LineComment] = " AttackTypeHolder is used to hold internal type of the protocol"
@@ -1329,7 +1334,7 @@ func init() {
Value: "Full Network protocol data",
},
}
- NETWORKRequestDoc.Fields = make([]encoder.Doc, 10)
+ NETWORKRequestDoc.Fields = make([]encoder.Doc, 11)
NETWORKRequestDoc.Fields[0].Name = "id"
NETWORKRequestDoc.Fields[0].Type = "string"
NETWORKRequestDoc.Fields[0].Note = ""
@@ -1388,6 +1393,11 @@ func init() {
NETWORKRequestDoc.Fields[9].Comments[encoder.LineComment] = "ReadAll determines if the data stream should be read till the end regardless of the size"
NETWORKRequestDoc.Fields[9].AddExample("", false)
+ NETWORKRequestDoc.Fields[10].Name = "stop-at-first-match"
+ NETWORKRequestDoc.Fields[10].Type = "bool"
+ NETWORKRequestDoc.Fields[10].Note = ""
+ NETWORKRequestDoc.Fields[10].Description = "StopAtFirstMatch stops the execution of the requests and template as soon as a match is found."
+ NETWORKRequestDoc.Fields[10].Comments[encoder.LineComment] = "StopAtFirstMatch stops the execution of the requests and template as soon as a match is found."
NETWORKInputDoc.Type = "network.Input"
NETWORKInputDoc.Comments[encoder.LineComment] = ""
@@ -1656,25 +1666,113 @@ func init() {
},
}
SSLRequestDoc.PartDefinitions = []encoder.KeyValue{
+ {
+ Key: "template-id",
+ Value: "ID of the template executed",
+ },
+ {
+ Key: "template-info",
+ Value: "Info Block of the template executed",
+ },
+ {
+ Key: "template-path",
+ Value: "Path of the template executed",
+ },
+ {
+ Key: "host",
+ Value: "Host is the input to the template",
+ },
+ {
+ Key: "port",
+ Value: "Port is the port of the host",
+ },
+ {
+ Key: "matched",
+ Value: "Matched is the input which was matched upon",
+ },
{
Key: "type",
Value: "Type is the type of request made",
},
+ {
+ Key: "timestamp",
+ Value: "Timestamp is the time when the request was made",
+ },
{
Key: "response",
Value: "JSON SSL protocol handshake details",
},
+ {
+ Key: "cipher",
+ Value: "Cipher is the encryption algorithm used",
+ },
+ {
+ Key: "domains",
+ Value: "Domains are the list of domain names in the certificate",
+ },
+ {
+ Key: "fingerprint_hash",
+ Value: "Fingerprint hash is the unique identifier of the certificate",
+ },
+ {
+ Key: "ip",
+ Value: "IP is the IP address of the server",
+ },
+ {
+ Key: "issuer_cn",
+ Value: "Issuer CN is the common name of the certificate issuer",
+ },
+ {
+ Key: "issuer_dn",
+ Value: "Issuer DN is the distinguished name of the certificate issuer",
+ },
+ {
+ Key: "issuer_org",
+ Value: "Issuer organization is the organization of the certificate issuer",
+ },
{
Key: "not_after",
Value: "Timestamp after which the remote cert expires",
},
{
- Key: "host",
- Value: "Host is the input to the template",
+ Key: "not_before",
+ Value: "Timestamp before which the certificate is not valid",
},
{
- Key: "matched",
- Value: "Matched is the input which was matched upon",
+ Key: "probe_status",
+ Value: "Probe status indicates if the probe was successful",
+ },
+ {
+ Key: "serial",
+ Value: "Serial is the serial number of the certificate",
+ },
+ {
+ Key: "sni",
+ Value: "SNI is the server name indication used in the handshake",
+ },
+ {
+ Key: "subject_an",
+ Value: "Subject AN is the list of subject alternative names",
+ },
+ {
+ Key: "subject_cn",
+ Value: "Subject CN is the common name of the certificate subject",
+ },
+ {
+ Key: "subject_dn",
+ Value: "Subject DN is the distinguished name of the certificate subject",
+ },
+ {
+ Key: "subject_org",
+ Value: "Subject organization is the organization of the certificate subject",
+ },
+ {
+ Key: "tls_connection",
+ Value: "TLS connection is the type of TLS connection used",
+ },
+ {
+ Key: "tls_version",
+ Value: "TLS version is the version of the TLS protocol used",
},
}
SSLRequestDoc.Fields = make([]encoder.Doc, 9)
diff --git a/pkg/tmplexec/exec.go b/pkg/tmplexec/exec.go
index 279d03d849..0caefe6024 100644
--- a/pkg/tmplexec/exec.go
+++ b/pkg/tmplexec/exec.go
@@ -174,7 +174,15 @@ func (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
if !event.HasOperatorResult() && event.InternalEvent != nil {
lastMatcherEvent = event
} else {
- if writer.WriteResult(event, e.options.Output, e.options.Progress, e.options.IssuesClient) {
+ var isGlobalMatchers bool
+ isGlobalMatchers, _ = event.InternalEvent["global-matchers"].(bool)
+ // NOTE(dwisiswant0): Don't store `matched` on a `global-matchers` template.
+ // This will end up generating 2 events from the same `scan.ScanContext` if
+ // one of the templates has `global-matchers` enabled. This way,
+ // non-`global-matchers` templates can enter the `writeFailureCallback`
+ // func to log failure output.
+ wr := writer.WriteResult(event, e.options.Output, e.options.Progress, e.options.IssuesClient)
+ if wr && !isGlobalMatchers {
matched.Store(true)
} else {
lastMatcherEvent = event
diff --git a/pkg/types/types.go b/pkg/types/types.go
index 9cc88f49ff..f6e7ab4470 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -206,6 +206,8 @@ type Options struct {
VerboseVerbose bool
// ShowVarDump displays variable dump
ShowVarDump bool
+ // VarDumpLimit limits the number of characters displayed in var dump
+ VarDumpLimit int
// No-Color disables the colored output.
NoColor bool
// UpdateTemplates updates the templates installed at startup (also used by cloud to update datasources)
diff --git a/static/nuclei-cover-image.png b/static/nuclei-cover-image.png
new file mode 100644
index 0000000000..caab6fb2d4
Binary files /dev/null and b/static/nuclei-cover-image.png differ
diff --git a/static/nuclei-cover.png b/static/nuclei-cover.png
new file mode 100644
index 0000000000..caab6fb2d4
Binary files /dev/null and b/static/nuclei-cover.png differ
diff --git a/static/nuclei-getting-started.png b/static/nuclei-getting-started.png
new file mode 100644
index 0000000000..9d57eae784
Binary files /dev/null and b/static/nuclei-getting-started.png differ
diff --git a/static/nuclei-template-example.png b/static/nuclei-template-example.png
new file mode 100644
index 0000000000..22d71daaf8
Binary files /dev/null and b/static/nuclei-template-example.png differ
diff --git a/static/nuclei-templates-teamcity-example.png b/static/nuclei-templates-teamcity-example.png
new file mode 100644
index 0000000000..83fc404c16
Binary files /dev/null and b/static/nuclei-templates-teamcity-example.png differ
diff --git a/static/nuclei-templates-teamcity.png b/static/nuclei-templates-teamcity.png
new file mode 100644
index 0000000000..04d1581267
Binary files /dev/null and b/static/nuclei-templates-teamcity.png differ
diff --git a/static/nuclei-write-your-first-template.png b/static/nuclei-write-your-first-template.png
new file mode 100644
index 0000000000..0c352d7062
Binary files /dev/null and b/static/nuclei-write-your-first-template.png differ
diff --git a/static/projectdiscovery-browse-results.gif b/static/projectdiscovery-browse-results.gif
new file mode 100644
index 0000000000..7dced0f3b3
Binary files /dev/null and b/static/projectdiscovery-browse-results.gif differ
diff --git a/static/teamcity-example.png b/static/teamcity-example.png
new file mode 100644
index 0000000000..f70b084b24
Binary files /dev/null and b/static/teamcity-example.png differ