Nuclei is powerful out of the box, but the real value comes from writing custom templates for application-specific vulnerabilities. This guide covers the full template syntax so you can automate detection of any vulnerability you discover during pentests.
Template Anatomy id: template-name info: name: Human Readable Name author: yourhandle severity: critical description: | What this template detects and why it matters. reference: - https://cve.mitre.org/cve/CVE-2024-XXXX tags: cve,rce,apache requests: - method: GET path: - "{{BaseURL}} /endpoint" matchers: - type: status status: - 200
HTTP Request Basics requests: - method: POST path: - "{{BaseURL}} /api/login" headers: Content-Type: application/json Accept: application/json body: '{"username":"admin","password":"admin"}' matchers-condition: and matchers: - type: status status: - 200 - type: word words: - '"role":"admin"' - '"authenticated":true' condition: or
Matcher Types Status matchers: - type: status status: - 200 - 201
Word matchers: - type: word part: body words: - "root:x:0:0" - "[MySQL]" condition: or negative: false
Regex matchers: - type: regex part: body regex: - 'uid=\d+\(.*\)' - 'AWS_ACCESS_KEY_ID=AKIA[0-9A-Z]{16}'
DSL (Dynamic conditions) matchers: - type: dsl dsl: - 'status_code == 200 && contains(body, "error")' - 'len(body) > 1000' - 'contains(header, "X-Powered-By: PHP")'
Size matchers: - type: size size: - 1337 part: body
Binary matchers: - type: binary binary: - "504b0304" part: body
Multiple Requests (Multi-Step) Chain requests together — extract values from one and use in the next:
requests: - method: GET path: - "{{BaseURL}} /login" extractors: - type: regex name: csrf_token regex: - 'name="csrf_token" value="([^"]+)"' group: 1 - method: POST path: - "{{BaseURL}} /login" body: "username=admin&password=admin&csrf_token={{csrf_token}} " headers: Content-Type: application/x-www-form-urlencoded matchers: - type: word words: - "Dashboard" - "Welcome"
extractors: - type: regex name: extracted_value part: body regex: - '"token":"([^"]+)"' group: 1 - type: json name: user_id json: - '.data.user.id' - type: xpath xpath: - '//input[@name="token"]/@value' - type: kval kval: - Set-Cookie
Variables and Helper Functions Built-in variables {{BaseURL }} {{RootURL }} {{Host }} {{Path }} {{Scheme }} {{Port }} {{Username }} {{Password }} {{randstr }} {{rand_int(0 ,100 ) }} {{unix_time() }}
DSL helpers contains(body, "error" ) startswith(body, "HTTP" ) endswith(header, "PHP" ) len(body) to_lower(body) to_upper(header) trim(body) replace(body, "old" , "new" ) regex("pattern", body) base64(body) base64_decode(body) url_encode("string") url_decode("string") md5("string") sha256("string") hex_encode("string") date("2006-01-02") unix_time()
OOB (Out-of-Band) Interaction Use {{interactsh-url}} for blind vulnerability detection (DNS/HTTP callbacks):
id: blind-ssrf-example info: name: Blind SSRF via URL param severity: high requests: - method: GET path: - "{{BaseURL}} /fetch?url=http://{{interactsh-url}} /test" matchers: - type: word part: interactsh_protocol words: - "http" - "dns" condition: or
Nuclei automatically handles the Interactsh server — results appear in output when the callback fires.
Path Traversal Template id: lfi-path-traversal info: name: Local File Inclusion - /etc/passwd author: yourhandle severity: high tags: lfi,traversal requests: - method: GET path: - "{{BaseURL}} /{{path}} ?page=../../../../etc/passwd" - "{{BaseURL}} /{{path}} ?file=../../../../etc/passwd" - "{{BaseURL}} /{{path}} ?include=../../../../etc/passwd" - "{{BaseURL}} /{{path}} ?lang=../../../../etc/passwd" payloads: path: - "" - "index.php" - "page.php" attack: clusterbomb matchers: - type: regex regex: - "root:.*:0:0:" part: body
Credential Stuffing Template id: default-creds-check info: name: Default Credentials Check severity: medium requests: - method: POST path: - "{{BaseURL}} /login" body: "username={{username}} &password={{password}} " headers: Content-Type: application/x-www-form-urlencoded payloads: username: - admin - administrator - root password: - admin - password - 1234 - "" attack: pitchfork matchers: - type: dsl dsl: - 'status_code == 302 || contains(body, "dashboard")'
DNS Template id: dns-mx-record info: name: MX Record Lookup severity: info dns: - name: "{{FQDN}} " type: MX matchers: - type: word words: - "google.com" - "mailchimp" part: answer
Running Templates nuclei -u https://target.com -t templates/mytemplate.yaml nuclei -u https://target.com -t custom-templates/ nuclei -l urls.txt -t templates/ nuclei -u https://target.com -tags lfi,ssrf,rce nuclei -u https://target.com -severity critical,high nuclei -validate -t mytemplate.yaml nuclei -update-templates nuclei -u https://target.com -t mytemplate.yaml -debug nuclei -u https://target.com -t templates/ -rate-limit 50
Template Writing Tips
Test your matchers carefully — false positives waste client time. Use -debug to see raw responses.
Use DSL conditions for complex logic instead of stacking multiple simple matchers.
Always include a negative test — run against a URL that shouldn’t match and verify it doesn’t fire.
OOB over in-band for blind vulnerabilities — more reliable than timing-based detection.
Tag templates properly — good tags make templates reusable across different scopes.
Version your templates with metadata.max-request to document expected request count.
No comments yet. Be the first.