13 KiB
13 KiB
Nuclei Template Development Guide
Table of Contents
- Template Structure
- Template Types
- Matchers and Extractors
- Advanced Techniques
- Testing and Validation
- Best Practices
Template Structure
Basic Template Anatomy
id: unique-template-id
info:
name: Human-readable template name
author: your-name
severity: critical|high|medium|low|info
description: Detailed description of what this template detects
reference:
- https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-XXXXX
- https://nvd.nist.gov/vuln/detail/CVE-2024-XXXXX
tags: cve,owasp,misconfig,custom
# Template type: http, dns, network, file, etc.
http:
- method: GET
path:
- "{{BaseURL}}/vulnerable-endpoint"
matchers:
- type: status
status:
- 200
- type: word
words:
- "vulnerable signature"
Required Fields
- id: Unique identifier (kebab-case, organization-scoped for custom templates)
- info.name: Clear, descriptive name
- info.author: Template author
- info.severity: One of: critical, high, medium, low, info
- info.description: What vulnerability this detects
- info.tags: Searchable tags for filtering
Optional but Recommended Fields
- info.reference: Links to CVE, advisories, documentation
- info.classification: CWE, CVE, OWASP mappings
- info.metadata: Additional metadata (max-request, verified, etc.)
Template Types
HTTP Templates
Most common template type for web application testing:
id: http-example
info:
name: HTTP Template Example
author: security-team
severity: high
tags: web,http
http:
- method: GET
path:
- "{{BaseURL}}/api/users"
- "{{BaseURL}}/api/admin"
headers:
Authorization: "Bearer {{token}}"
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
part: body
words:
- "\"role\":\"admin\""
- "sensitive_data"
extractors:
- type: regex
name: user_ids
regex:
- '"id":([0-9]+)'
DNS Templates
Test for DNS misconfigurations and subdomain takeovers:
id: dns-takeover-check
info:
name: DNS Subdomain Takeover Detection
author: security-team
severity: high
tags: dns,takeover
dns:
- name: "{{FQDN}}"
type: CNAME
matchers:
- type: word
words:
- "amazonaws.com"
- "azurewebsites.net"
- "herokuapp.com"
Network Templates
TCP/UDP port scanning and service detection:
id: exposed-redis
info:
name: Exposed Redis Instance
author: security-team
severity: critical
tags: network,redis,exposure
network:
- inputs:
- data: "*1\r\n$4\r\ninfo\r\n"
host:
- "{{Hostname}}"
- "{{Hostname}}:6379"
matchers:
- type: word
words:
- "redis_version"
Matchers and Extractors
Matcher Types
Status Matcher
matchers:
- type: status
status:
- 200
- 201
condition: or
Word Matcher
matchers:
- type: word
part: body # body, header, all
words:
- "error"
- "exception"
condition: and
case-insensitive: true
Regex Matcher
matchers:
- type: regex
regex:
- "(?i)password\\s*=\\s*['\"]([^'\"]+)['\"]"
part: body
Binary Matcher
matchers:
- type: binary
binary:
- "504B0304" # ZIP file signature (hex)
part: body
DSL Matcher (Dynamic Expressions)
matchers:
- type: dsl
dsl:
- "status_code == 200 && len(body) > 1000"
- "contains(tolower(body), 'admin')"
Matcher Conditions
- and: All matchers must match
- or: At least one matcher must match (default)
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "admin"
Extractors
Extract data from responses for reporting or chaining:
Regex Extractor
extractors:
- type: regex
name: api_keys
part: body
regex:
- 'api[_-]?key["\s:=]+([a-zA-Z0-9_-]{32,})'
group: 1
JSON Extractor
extractors:
- type: json
name: user_data
json:
- ".users[].email"
- ".users[].id"
XPath Extractor
extractors:
- type: xpath
name: titles
xpath:
- "//title"
Advanced Techniques
Request Chaining (Workflows)
Execute templates in sequence, passing data between them:
id: workflow-example
info:
name: Multi-Step Authentication Test
author: security-team
workflows:
templates:
- template: login.yaml
- template: fetch-user-data.yaml
login.yaml:
id: login-template
info:
name: Login and Extract Token
author: security-team
severity: info
http:
- method: POST
path:
- "{{BaseURL}}/api/login"
body: '{"username":"test","password":"test"}'
extractors:
- type: json
name: auth_token
json:
- ".token"
internal: true # Pass to next template
Variables and Helpers
Use dynamic variables and helper functions:
http:
- method: GET
path:
- "{{BaseURL}}/api/users/{{username}}"
# Available variables:
# {{BaseURL}}, {{Hostname}}, {{Host}}, {{Port}}, {{Path}}
# {{RootURL}}, {{Scheme}}, {{username}} (from previous extractor)
matchers:
- type: dsl
dsl:
# Helper functions: len(), contains(), regex_match(), etc.
- 'len(body) > 500'
- 'contains(tolower(header), "x-api-key")'
- 'status_code >= 200 && status_code < 300'
Payloads and Fuzzing
Use payload files for fuzzing:
id: sqli-fuzzing
info:
name: SQL Injection Fuzzing
author: security-team
severity: critical
http:
- method: GET
path:
- "{{BaseURL}}/api/users?id={{payload}}"
payloads:
payload:
- "1' OR '1'='1"
- "1' UNION SELECT NULL--"
- "'; DROP TABLE users--"
matchers:
- type: word
words:
- "SQL syntax"
- "mysql_fetch"
- "ORA-01756"
Or use external payload file:
payloads:
payload: payloads/sql-injection.txt
attack: clusterbomb # pitchfork, clusterbomb, batteringram
Rate Limiting and Threads
Control request rate to avoid overwhelming targets:
id: rate-limited-scan
info:
name: Rate-Limited Vulnerability Scan
author: security-team
severity: medium
metadata:
max-request: 50 # Maximum requests per template execution
http:
- method: GET
path:
- "{{BaseURL}}/api/endpoint"
threads: 5 # Concurrent requests (default: 25)
Testing and Validation
Local Testing
Test templates against local test servers:
# Test single template
nuclei -t custom-templates/my-template.yaml -u http://localhost:8080 -debug
# Validate template syntax
nuclei -t custom-templates/my-template.yaml -validate
# Test with verbose output
nuclei -t custom-templates/my-template.yaml -u https://target.com -verbose
Template Validation
Use the bundled validation script:
python3 scripts/template_validator.py custom-templates/my-template.yaml
Test Lab Setup
Create a vulnerable test application for template development:
# Use DVWA (Damn Vulnerable Web Application)
docker run -d -p 80:80 vulnerables/web-dvwa
# Or OWASP Juice Shop
docker run -d -p 3000:3000 bkimminich/juice-shop
Best Practices
1. Accurate Severity Classification
- Critical: RCE, authentication bypass, full system compromise
- High: SQL injection, XSS, significant data exposure
- Medium: Missing security headers, information disclosure
- Low: Minor misconfigurations, best practice violations
- Info: Technology detection, non-security findings
2. Minimize False Positives
# Use multiple matchers with AND condition
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "admin"
- "dashboard"
condition: and
- type: regex
regex:
- '<title>.*Admin.*Panel.*</title>'
case-insensitive: true
3. Clear Naming Conventions
- id:
organization-vulnerability-type-identifier- Example:
acme-api-key-exposure-config
- Example:
- name: Descriptive, clear purpose
- Example: "ACME Corp API Key Exposure in Config Endpoint"
4. Comprehensive Documentation
info:
name: Detailed Template Name
description: |
Comprehensive description of what this template detects,
why it's important, and how it works.
References:
- CVE-2024-XXXXX
- Internal ticket: SEC-1234
reference:
- https://nvd.nist.gov/vuln/detail/CVE-2024-XXXXX
- https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-XXXXX
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
cvss-score: 9.8
cve-id: CVE-2024-XXXXX
cwe-id: CWE-89
metadata:
verified: true
max-request: 10
shodan-query: 'http.title:"Admin Panel"'
tags: cve,owasp,sqli,high-severity,verified
5. Responsible Testing Parameters
# Avoid aggressive fuzzing in default templates
info:
metadata:
max-request: 10 # Limit total requests
http:
- method: GET
threads: 5 # Limit concurrent requests
# Use specific, targeted payloads
payloads:
test: ["safe-payload-1", "safe-payload-2"]
6. Error Handling
http:
- method: GET
path:
- "{{BaseURL}}/api/test"
# Handle various response scenarios
matchers:
- type: dsl
dsl:
- "status_code == 200 && contains(body, 'vulnerable')"
- "status_code == 500 && contains(body, 'error')"
condition: or
# Negative matchers (must NOT match)
matchers:
- type: word
negative: true
words:
- "404 Not Found"
- "403 Forbidden"
7. Template Organization
custom-templates/
├── api/
│ ├── api-key-exposure.yaml
│ ├── graphql-introspection.yaml
│ └── rest-api-misconfig.yaml
├── cves/
│ ├── 2024/
│ │ ├── CVE-2024-12345.yaml
│ │ └── CVE-2024-67890.yaml
├── exposures/
│ ├── sensitive-files.yaml
│ └── backup-exposure.yaml
└── misconfig/
├── cors-misconfiguration.yaml
└── debug-mode-enabled.yaml
8. Version Control and Maintenance
- Use Git to track template changes
- Tag templates with version numbers in metadata
- Document changes in template comments
- Regularly test templates against updated applications
info:
metadata:
version: 1.2.0
last-updated: 2024-11-20
changelog: |
1.2.0 - Added additional matcher for new vulnerability variant
1.1.0 - Improved regex pattern to reduce false positives
1.0.0 - Initial release
Example: Complete Custom Template
id: acme-corp-api-debug-exposure
info:
name: ACME Corp API Debug Endpoint Exposure
author: acme-security-team
severity: high
description: |
Detects exposed debug endpoint in ACME Corp API that leaks
sensitive configuration including database credentials,
API keys, and internal service URLs.
reference:
- https://internal-wiki.acme.com/security/SEC-1234
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
cvss-score: 7.5
cwe-id: CWE-200
metadata:
verified: true
max-request: 3
version: 1.0.0
tags: acme,api,exposure,debug,high-severity
http:
- method: GET
path:
- "{{BaseURL}}/api/v1/debug/config"
- "{{BaseURL}}/api/v2/debug/config"
- "{{BaseURL}}/debug/config"
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
part: body
words:
- "database_url"
- "api_secret_key"
condition: or
- type: regex
part: body
regex:
- '"(password|secret|token)":\s*"[^"]+"'
extractors:
- type: regex
name: exposed_secrets
part: body
regex:
- '"(database_url|api_secret_key|jwt_secret)":\s*"([^"]+)"'
group: 2
- type: json
name: config_data
json:
- ".database_url"
- ".api_secret_key"