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).
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
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/
nuclei -update-templates
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
You can view the command line option flags with: nuclei -h
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:
robots-txt.yaml
template
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:
https://example.com has no robots.txt
However, if your target does have a robots.txt file, the output will reflect the discovery:
robots.txt
file
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:
extractor
[template-id] [protocol] [severity] <impacted target> [extracted data]
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:
-l
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.
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:
-tl
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
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:
critical
high
AND
ninjeeter:
nuclei -u https://example.com -severity critical,high -author ninjeeter
To learn more about advanced filtering using complex conditional expressions, view the official documentation.
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:
miscellaneous
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.
~/.config/nuclei/.nuclei-ignore
-include-templates
-include-tags
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?
-markdown-export
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:
-include-rr
.md
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:
Through integration with ProjectDiscovery’s Interactsh tool, you can discover “blind” vulnerabilities that do not produce immediate responses or outcomes.
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.
The id acts as a unique identifier used to reference the template. The only restriction on its value is it cannot contain spaces.
The info block contains key-value pairs that provide general information about the template including its:
A metadata block can be nested within the info block to provide additional template information and configuration rules:
A metadata block can also integrate ProjectDiscovery’s uncover tool, to run the template against targets returned from search engine queries.
The next block/s in the template defines the protocol or template type. The available protocols that start the network request block are:
The available types are:
View both basic and advanced examples of each protocol and type by navigating through these documentation pages.
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:
By default, these matching expressions will search in response bodies. However, you can specify the element/s to be parsed:
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.
DSL expressions used to extract and display response data are referred to as extractors. There are five types of extractors:
regex
kval
json
dsl
xpath
View examples of both basic and complex extractors here.
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.
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.
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}}"
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"
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