Crafting Custom Nuclei Templates for Rapid Vulnerability Detection
Rapid vulnerability detection often hinges on specialized tooling and custom logic tailored to specific application contexts. Nuclei, a fast and customizable scanner, provides this capability through its template engine. Instead of relying solely on public signatures, crafting custom Nuclei templates allows pentesters to codify unique detection heuristics, specific exploit patterns for zero-days or N-days, or to test for configurations unique to an engagement. This direct approach enables quick deployment of targeted checks, significantly accelerating the identification phase within a pentest or bug bounty workflow.
The Anatomy of a Nuclei Template
Understanding the fundamental structure of a Nuclei template is critical before building anything complex. Every template is a YAML file, defining a request (or a series of requests) and the conditions under which a match indicates a vulnerability or interesting finding.
Template Metadata: id and info
The
id field is a unique identifier for your template. It's crucial for managing and referencing templates. The
info block provides metadata such as the template's name, author, severity, and tags, which help categorize and filter templates during scans.
id: custom-admin-panel-detect
info:
name: Custom Admin Panel Detection
author: your-pentester-name
severity: info
description: "Detects a specific custom admin panel based on unique page title."
reference:
- https://example.com/docs/admin_panel_signature
classification:
cvss-metrics: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N
cvss-score: 0.0
cwe-id: CWE-200
tags: custom, admin, panel, discovery
Defining Requests: http and network
The core of most Nuclei templates lies within the
http or
network sections, defining the actual interactions with the target. For web applications,
http is predominant. This section specifies the HTTP method, path, headers, and body content.
A simple GET request to check for a specific path:
requests:
- method: GET
path:
- "{{BaseURL}}/custom/admin/login.php"
headers:
User-Agent: "Nuclei-Custom-Scanner/1.0"
matchers:
- type: status
status:
- 200
- type: word
words:
- "Custom Admin Login"
part: body
For POST requests, include the
body directive. Variables like
{{BaseURL}} are automatically resolved by Nuclei from your target list.
requests:
- method: POST
path:
- "{{BaseURL}}/api/v1/auth"
headers:
Content-Type: "application/json"
body: |
{
"username": "admin",
"password": "password123"
}
matchers:
- type: status
status:
- 401
- type: word
words:
- "Invalid Credentials"
part: body
Precision Matching: Identifying Vulnerabilities
After sending a request, Nuclei evaluates the response against defined
matchers. These matchers are the intelligence layer, determining if the response indicates the presence of the vulnerability or condition you're looking for.
Response Matchers: status, word, regex
status: Matches against the HTTP response status code. Useful for checking for successful page loads (200), redirects (3xx), or errors (4xx, 5xx).
word: Searches for specific strings or keywords within the response body, headers, or other parts. Supports case-insensitive matching and exclusion.
regex: Provides powerful pattern matching using regular expressions. Essential for dynamic content or specific data formats.
Combining these matchers allows for precise detection. The
type: or or
type: and directives within the
matchers block control the logic.
matchers:
- type: status
status:
- 200
- type: word
words:
- "Welcome to the Dashboard"
- "Admin Area"
condition: or
part: body
- type: regex
regex:
- "version: v[0-9]+\\.[0-9]+\\.[0-9]+"
part: header
case-insensitive: true
In the example above, a match occurs if the status is 200 AND (either "Welcome to the Dashboard" or "Admin Area" is found in the body) AND a version string is found in the header.
Advanced Matching with DSL
Nuclei's Domain Specific Language (DSL) matchers allow for complex, programmatic checks against various parts of the HTTP response. This includes evaluating response sizes, specific header values, or even performing logical operations on extracted data.
Example using DSL to check for a specific header value and body length:
matchers:
- type: dsl
dsl:
- 'contains(all_headers, "X-Powered-By: Custom-Framework")'
- 'len(body) > 1000'
condition: and
This matcher will trigger only if the
X-Powered-By header contains "Custom-Framework" and the response body is longer than 1000 characters.
Data Extraction for Further Analysis
Sometimes, merely detecting a condition isn't enough; you need to extract specific data from the response for further analysis or to chain into subsequent requests. Nuclei provides
extractors for this purpose.
Extracting Information: regex, json, xpath
regex: Extracts data based on regular expressions, often used with capturing groups.
json: Extracts values from JSON responses using JSONPath expressions.
xpath: Extracts values from XML/HTML responses using XPath expressions.
Extracting a CSRF token using regex from the body:
extractors:
- type: regex
name: csrf_token
regex:
- 'name="_csrf" value="([a-zA-Z0-9]{32})"'
group: 1
part: body
Extracting a session ID using JSONPath:
extractors:
- type: json
name: session_id
json:
- '$.data.session'
part: body
Chaining Requests and Variables
One of Nuclei's powerful features is the ability to chain requests, where data extracted from one request can be used as input for a subsequent request. This is critical for multi-step vulnerabilities like authentication bypasses or information disclosure through chained actions.
The
set directive within an extractor populates a variable that can then be referenced in later requests using
{{variable_name}}.
id: chained-auth-bypass
info:
name: Chained Authentication Bypass
author: your-pentester-name
severity: high
description: "Demonstrates a two-step authentication bypass."
tags: auth, bypass, chain
requests:
- raw:
- |
GET /login HTTP/1.1
Host: {{Hostname}}
User-Agent: Nuclei-Custom-Scanner/1.0
matchers:
- type: status
status:
- 200
extractors:
- type: regex
name: auth_token
regex:
- 'name="auth_token" value="([a-zA-Z0-9]+)"'
group: 1
part: body
set: auth_token
- raw:
- |
POST /verify HTTP/1.1
Host: {{Hostname}}
Content-Type: application/x-www-form-urlencoded
User-Agent: Nuclei-Custom-Scanner/1.0
Content-Length: {{len("auth_token=" + auth_token)}}
auth_token={{auth_token}}
matchers:
- type: status
status:
- 200
- type: word
words:
- "Authentication Successful"
part: body
In this template, the first request fetches an
auth_token, which is then dynamically used in the body of the second POST request.
Interacting with External Services: Interactsh Integration
For out-of-band (OOB) vulnerability detection, such as blind SSRF, XSS, or RCE, Nuclei integrates seamlessly with Interactsh. By embedding an Interactsh URL in your request, you can detect if the target application makes an external call, confirming the vulnerability.
To use Interactsh, simply include
{{interactsh-url}} in your request where you expect an OOB interaction. Nuclei automatically manages Interactsh server interaction.
id: blind-ssrf-detection
info:
name: Blind SSRF via Image URL
author: your-pentester-name
severity: high
description: "Detects blind SSRF by attempting to load an image from Interactsh."
tags: ssrf, oob, interactsh
requests:
- method: GET
path:
- "{{BaseURL}}/load_image?url={{interactsh-url}}"
matchers:
- type: word
words:
- "image loaded successfully" # Optional, if the app responds
part: body
- type: dsl
dsl:
- 'interactsh_protocol("http")' # Verifies an HTTP interaction occurred
condition: and
The
interactsh_protocol DSL function is crucial here, confirming that an HTTP request was made to the Interactsh server by the target.
Testing and Debugging Your Custom Templates
Effective template development requires rigorous testing and debugging. Nuclei provides several command-line flags to assist in this process.
To test a template against a specific target and get verbose output, use the
-debug and
-validate flags:
nuclei -t custom-admin-panel-detect.yaml -u https://example.com -debug -validate
-debug: Shows the full HTTP requests and responses, including any internal variables or extracted data. This is invaluable for understanding why a matcher isn't triggering or an extractor isn't capturing data as expected.
-validate: Checks the template YAML for syntax errors and structural issues before execution.
-silent: Can be useful in conjunction with -debug to suppress normal Nuclei output and focus solely on debug messages.
-lfa: (List Field Attributes) Displays all available fields that can be accessed via DSL for a given template and target.
When debugging, pay close attention to:
- **Request formation**: Does the generated HTTP request match what you intended? Check headers, paths, and body.
- **Response content**: Does the target's response contain the expected keywords, regex patterns, or JSON structure?
- **Matcher logic**: Is your `condition: and` or `condition: or` correctly implemented? Are all parts (
body, header, all_headers) specified correctly?
- **Extractor output**: Are the extracted variables being populated correctly? Use `debug` to see their values.
A common pitfall is incorrect regular expression syntax or JSONPath expressions. Always test these independently before embedding them in a Nuclei template. If a template isn't firing, start by simplifying your matchers and extractors, gradually adding complexity back in.