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

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:

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.

View both basic and advanced examples of each protocol and type by navigating through these documentation pages.

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:

  1. status: Matches against status codes.
  2. size: Matches against the response size in bytes.
  3. word: Matches against strings.
  4. regex: Matches patterns using regular expressions.
  5. binary: Matches hexadecimal patterns in binary responses.
  6. dsl: Enables complex matching using dynamic expressions and helper functions.
  7. 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:

  1. regex: Extracts data matching regular expressions.
  2. kval: Extracts key-value pairs from response headers or cookies.
  3. json: Extracts JSON data using syntax similar to that used in jq.
  4. dsl: Extracts data based on expressions.
  5. 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