You’ve done it. After hours of meticulous work, laboring over the keyboard with no end in sight, you have finally uncovered a bug in a target. Congrats! But as you begin writing your report, a thought nags in the back of your mind:
“This same bug could be widespread, across multiple targets in the program’s scope…”
It very well could be. But how are you going to manually test across a vast number of targets? If only there was a way to quickly and easily reproduce this finding…
The brilliant team at ProjectDiscovery developed a solution: Nuclei. Nuclei is an open-source scanning engine. Nuclei can be used to detect security vulnerabilities, misconfigurations, and exposed services across web applications, infrastructure, cloud environments, and networks.
The proverbial gasoline to the Nuclei engine are the templates that define requests and parse responses for indications of a security issue, giving you the ability to systematically replicate your discoveries across targets en masse.
Now, let’s learn how to duplicate findings (and not the bad kind of duplication).
Installing Nuclei
Nuclei requires Go > 1.21. Install it for your operating system or check your current version by running go version in your terminal. Also, if you have not already added the go/bin directory to $PATH so the executables are available globally, refer to these instructions.
Nuclei can be installed using the following command (or using these alternative methods):
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
Nuclei templates
With an active, global community of hackers and regular maintenance, Nuclei stays up-to-date with templates for the latest exploits and attack vectors. You can view the templates within the installation in the ~/nuclei-templates/
directory. To update to the latest templates, run nuclei -update-templates
in your terminal.
Nuclei templates are structured using YAML Ain’t Markup Language (YAML), a human-readable data serialization language, primarily composed of key-value pairs. YAML, being a “superset” of JSON, offers a more readable syntax using indentation and newlines (pretty) instead of braces and brackets (ew).
While the template’s overall structure is defined in YAML, Nuclei also provides a separate Domain Specific Language (DSL) for writing tool instructions. Basically, DSL uses their own syntax to make writing complex expressions easier.
This combination of YAML and DSL makes Nuclei templates both powerful and easy to read.
Lets take a look at the robots-txt.yaml template written by CasperGN and TheZakMan with additional comments throughout:
# Template for detecting robots.txt files.
id: robots-txt
info:
name: robots.txt file
author: CasperGN,TheZakMan
severity: info
metadata:
max-request: 1 # Maximum number of requests allowed.
tags: miscellaneous,misc,generic
http:
- method: GET
path:
- "{{BaseURL}}/robots.txt" # Checks for robots.txt at the root.
# Handle redirects.
host-redirects: true
max-redirects: 2
matchers-condition: and
matchers:
# Check for common robots.txt directives.
- type: word
words:
- 'User-agent:'
- 'Disallow:'
- 'Allow:'
# Verify content type is text/plain.
- type: word
part: header
words:
- text/plain
# Ensure response is valid and not too small.
- type: dsl
dsl:
- "len(body)>=140 && status_code==200"
# Template hash/digest that acts as a checksum to verify the template's integrity.
# digest: 490a004630440220483c89872ac45742f715d053eb97b7b97c1a23e981027e2cb467df256ea0dca4022047fbfda47c570bf41d34c4ddf5d76df4d2fa6bc27a4856d0ff95cdb944aa1203:922c64590222798bb761d5b6d8e72950
This template will produce the following request:
GET /robots.txt HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15
Connection: close
Accept: */*
Accept-Language: en
Accept-Encoding: gzip
Using Nuclei
You can view the command line option flags with: nuclei -h
Let’s cover the basic usage of the Nuclei tool, starting with a basic scan using the robots-txt.yaml
template
. Run the following command in your terminal:
nuclei -u https://example.com -t ~/nuclei-templates/http/miscellaneous/robots-txt.yaml
Since
https://example.com has no robots.txt
file, you should see the following output:
However, if your target does have a robots.txt
file
, the output will reflect the discovery:
As you can see, templates that are successful result in a line of output with a syntax of:
[template-id] [protocol] [severity] <impacted target>
If this template included an extractor
expression, it would also be included in the output line as a field:
[template-id] [protocol] [severity] <impacted target> [extracted data]
Targeting
You can run Nuclei against a single host, as demonstrated above, or against a list of multiple targets (think of the possibilities) in a file using the -l
flag:
nuclei -l hosts.txt
The target list can specify hosts using URLs, IP addresses, FQDNs, CIDRs, or ASNs – each on a newline. You can specify the format of the input file by adding the -input-mode flag followed by the mode: list, burp, jsonl, yaml, openai, or swagger.
For additional information on conducting mass scanning, view the official documentation.
Listing templates
To list all the available templates, run the following command:
nuclei -tl
“Sheesh, that is a lot to parse through. What if I want to filter the output?” – you, circa right now
You’re in luck. Nuclei provides filtering flags that can be used with -tl
:
- -tags: List templates with matching category tags.
- -severity: List templates by vulnerability severity rating.
- -author: List templates written by a certain author.
When using a combination of these flags, the conditional operator AND is used. If multiple values are provided, the OR operator is used. For example, the following command will output all templates tagged as a cve with a high and critical severity rating:
nuclei -tags cve -severity high,critical -tl
Selecting templates
By default, the majority of the templates in the community repository are executed. After entering a scan command, Nuclei will output the number of templates to be executed as well as the number of requests that will be generated:
If you would like to customize which templates will be used, you can either select a specific template (or multiple templates) or use the same filtering flags discussed earlier. Here are some examples:
Selecting one or more specific templates:
nuclei -u https://example.com -t ~/nuclei-templates/http/miscellaneous/robots-txt.yaml,~/nuclei-templates/http/exposures/backups/backup-directory-listing.yaml
Selecting one or more template directories:
nuclei -u https://example.com -t miscellaneous/,exposures/
Selecting templates by matching category tags:
nuclei -u https://example.com -tags miscellaneous,exposures
Selecting templates by vulnerability severity rating:
nuclei -u https://example.com -severity critical,high
Selecting templates by author:
nuclei -u https://example.com -author ninjeeter
Selecting templates using multiple filter flags:
The following command will only select templates that are critical
OR high
in vulnerability severity rating AND
written by ninjeeter:
nuclei -u https://example.com -severity critical,high -author ninjeeter
You can also exclude templates entirely based on their filename/s or tag/s. For example, the following command will include all templates from the miscellaneous
directory in the scan besides the robots-txt.yaml
template:
nuclei -u https://www.google.com -t miscellaneous/ -exclude-templates miscellaneous/robots-txt.yaml
You can view the templates that are not included in default scans in the ~/.config/nuclei/.nuclei-ignore
file. While you should not edit this file directly, if you want to use one of the denylist templates, you can overwrite the configuration using the -include-templates
or -include-tags
.
Reporting
You can even generate Markdown reports so you can copy and paste submissions of your findings using the -markdown-export
flag. How convenient is that?
To include the request and response in your report, use the -include-rr
flag. The following command will generate a .md
file in your current directory:
nuclei -u https://www.google.com -t http/miscellaneous/robots-txt.yaml -include-rr -markdown-export ./
Although you may need to make some slight format changes, the resulting file will allow you to streamline the reporting process:
Out-of-band testing
Through integration with ProjectDiscovery’s Interactsh tool, you can discover “blind” vulnerabilities that do not produce immediate responses or outcomes.
Custom templates
Anyone can download the tool and use the same default templates from the community repository. Meaning you’re likely to submit duplicate reports (the bad kind).
The true power of Nuclei is harnessed when you create and use your own custom templates.
However, since these are unique in nature, let’s dive into how Nuclei templates are structured to give you a foundational level of knowledge that will help you write your own.
id
The id acts as a unique identifier used to reference the template. The only restriction on its value is it cannot contain spaces.
info
The info block contains key-value pairs that provide general information about the template including its:
- name: Serves as a cosmetic name. Usually, it succinctly describes the template attack vector.
- author: This key states the author or authors of the template.
- severity: Provides the severity rating of the template vulnerability (info, low, medium, high, or critical).
- description: Provides a more detailed description of the template.
- impact: Describes the potential outcome if the vulnerability is successfully exploited.
- remediation: Includes actions that can be taken to fix the vulnerability.
- reference: Provides external references to the vulnerability.
- classification: Provides standardized vulnerability identifiers and categorization from recognized security frameworks and organizations (cvss-metrics, cvss-score, cve-id, cwe-id, epss-score, cpe, etc.).
- tags: One or more keywords that help categorize the template. These tags describe the vulnerability type, affected technology, testing methodology, etc.
metadata
A metadata block can be nested within the info block to provide additional template information and configuration rules:
- verified: true: This key will be included if the proof-of-concept, guide, or product details pertinent to the template have been reviewed by the ProjectDiscovery team.
- max-request: Specifies the maximum number of requests that the template will generate during execution.
- vendor: Identifies the manufacturer or provider affected by the template vulnerability.
- product: Specifies the service that the template targets.
A metadata block can also integrate ProjectDiscovery’s uncover tool, to run the template against targets returned from search engine queries.
Protocol/type
The next block/s in the template defines the protocol or template type. The available protocols that start the network request block are:
- dns: Performs DNS lookups and analysis.
- http: Sends HTTP/S requests.
- ssl: Tests SSL/TLS configurations.
- tcp: Tests TCP services and protocols.
- websocket: Interacts with WebSocket endpoints.
- whois: Queries WHOIS information.
The available types are:
- code: Enables the use of custom scripts or commands by leveraging the operating system.
- file: Enables scanning and analysis of files.
- headless: Enables programmatic automation of browser actions.
- javascript: Enables the use of JavaScript to address vulnerabilities that cannot be detected or exploited using a standard protocol.
matchers
DSL expressions used to identify specific patterns in responses that are indicative of a vulnerability are referred to as matchers. There are seven types of matchers:
- status: Matches against status codes.
- size: Matches against the response size in bytes.
- word: Matches against strings.
- regex: Matches patterns using regular expressions.
- binary: Matches hexadecimal patterns in binary responses.
- dsl: Enables complex matching using dynamic expressions and helper functions.
- xpath: Matches XML/HTTP content using XPath queries.
By default, these matching expressions will search in response bodies. However, you can specify the element/s to be parsed:
- content_length: The value of the Content-Length header.
- status_code: The response status code.
- all_headers: Every response header.
- body: The response body data as a string.
- header_name: A specific header.
- raw: The entire response.
When multiple values or regular expressions are used, the AND and OR operators can be used to create conditional processes.
You can also specify an encoding type using encoding: <type>.
View examples of both basic and complex matchers here.
extractors
DSL expressions used to extract and display response data are referred to as extractors. There are five types of extractors:
regex
: Extracts data matching regular expressions.kval
: Extracts key-value pairs from response headers or cookies.json
: Extracts JSON data using syntax similar to that used in jq.dsl
: Extracts data based on expressions.xpath
: Extracts XML/HTTP data.
View examples of both basic and complex extractors here.
Helper functions
Helper functions are built-in utilities that can be used within Nuclei templates to manipulate data, generate values, and perform common operations. These functions can be used in various parts of templates such as within requests and as variable definitions.
There are both standard helper functions as well as JavaScript helper functions.
Also view documentation on preprocessors.
Variables
Template variables allow you to set values that remain constant throughout the template execution. Variables can be used in paths, headers, request bodies, matcher conditions, and extractor patterns. They can be simple strings or helper functions.
For example, the following will use three variables in a GET request:
# Basic variable definition.
variables:
api_version: "v1"
# Helper functions must be enclosed in {{}}.
random_string: "{{rand_str_alpha(8)}}"
encoded_data: "{{base64('data')}}"
http:
- method: GET
path:
- "{{BaseURL}}/api/{{string_var}}/{{random_string}}"
headers:
Authorization: "Bearer {{encoded_data}}"
View examples of variable definitions and usage here.
Payloads
If you want to use dynamic placeholders for payloads or fuzzing, you can provide multiple values directly in the template or reference a file:
# Using a list and file.
payloads:
username:
- "admin"
- "administrator"
- "root"
password: /path/to/file.txt
body: "username={{username}}&password={{password}}"
# Numbered payloads.
payloads:
userId:
- "1"
- "2"
- "3"
path:
- "{{BaseURL}}/account/{{userId}}"
Conclusion
While we have covered the basics of Nuclei, it only scratches the surface of what this tool is capable of. The real power of Nuclei lies in its extensibility and your own creativity when it comes to crafting custom templates.
Now that you have a basic understanding of the tool, you are one step closer to creating a trove of custom templates. Each one you write becomes a reusable asset, essentially becoming an extension of you as a bug bounty hunter. The time invested in mastering this amazing tool will pay dividends in the future.
In the meantime, you can join the ProjectDiscovery Discord server to interact with the community.
Before we go, I will leave you a starting point for both a GET and POST request.
Until next time,
Ninjeeter
# HTTP GET request.
http:
# Define the HTTP request method and target path.
- method: GET
path:
- "{{BaseURL}}/admin"
# Browser-like headers to prevent being blocked by WAF.
headers:
User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"
Accept-Language: "en-US,en;q=0.9"
Accept-Encoding: "gzip, deflate, br"
Connection: "keep-alive"
Sec-Ch-Ua: "Chromium;v=122"
Sec-Ch-Ua-Mobile: "?0"
Sec-Ch-Ua-Platform: "Windows"
Sec-Fetch-Dest: "document"
Sec-Fetch-Mode: "navigate"
Sec-Fetch-Site: "none"
Sec-Fetch-User: "?1"
# Uncomment the following lines to allow redirects.
#redirects: true
#max-redirects: 3
This will generate the following request:
GET /admin HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Sec-Ch-Ua: Chromium;v=122
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: Windows
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
# HTTP POST request.
http:
# Define the HTTP request method and target path.
- method: POST
path:
- "{{BaseURL}}/comments/new"
# Browser-like headers to prevent being blocked by WAF.
headers:
Host: "{{Hostname}}"
User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
Accept: "*/*"
Accept-Language: "en-US,en;q=0.9"
Accept-Encoding: "gzip, deflate, br, zstd"
Content-Type: "application/x-www-form-urlencoded"
Origin: "{{BaseURL}}"
Referer: "{{BaseURL}}/blog/example-post"
Sec-Fetch-Dest: "empty"
Sec-Fetch-Mode: "cors"
Sec-Fetch-Site: "same-origin"
# Form data.
body: "comment=Great+article!&author=John+Doe&post_id=123"
This will generate the following request:
POST /comments/new HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Connection: close
Content-Length: 50
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-US,en;q=0.9
Content-Type: application/x-www-form-urlencoded
Origin: https://example.com<
Referer: https://example.com/blog/example-post
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
comment=Great+article!&author=John+Doe&post_id=123