Introduction: Template-Based Scanning with Nuclei

Welcome to the third lesson of our DAST course. In the previous lesson, we used OWASP ZAP to perform broad, "heavy" scans that crawled every link and threw thousands of generic attacks at the application. While powerful, this approach can be slow and noisy. Today, we are shifting our focus to Nuclei, a modern vulnerability scanner that takes a radically different approach. Instead of blindly attacking everything, Nuclei is highly targeted and customizable, relying on community-powered templates to identify specific threats.

Nuclei operates on a simple but powerful concept: Template-Based Scanning. A template is a YAML file that describes exactly what request to send and exactly what response indicates a vulnerability. This allows security engineers to write custom checks for application-specific logic, such as a unique misconfiguration in your company's login flow or a newly discovered exploit that general scanners haven't added yet. In this lesson, we will move away from generic scanning and learn how to write precise, custom detection logic to secure our Pastebin application.

Anatomy of a Nuclei Template

To use Nuclei effectively, you must understand the structure of its templates. A Nuclei Template is a structured YAML file that acts as a blueprint for the scanner. It tells the engine three things: who wrote the check (metadata), what to send to the server (requests), and how to verify whether the vulnerability exists (matchers). Without this structure, the scanner would not know how to interact with the target or interpret the results.

The first section of any template is the Metadata Block. This includes the id, which acts as a unique identifier for the scan, and the info block, which contains details like the name, severity, and tags. This organization is critical when you have thousands of templates; tags allow you to run specific subsets of scans, such as only running "critical" severity checks or only checking for "misconfigurations."

Here is what the top of a template looks like:

id: pastebin-debug-endpoint

info:
  name: Pastebin Debug Endpoint Exposed
  author: security-team
  severity: high
  description: Checks if debug endpoints are accessible in production
  tags: pastebin,misconfig

The second and most important part is the HTTP Block. This defines the actual network traffic Nuclei will generate. You can specify the HTTP method (GET, POST, etc.), the path, and any headers. Nuclei uses dynamic variables like {{BaseURL}}, which allows you to write a template once and run it against any target domain. Finally, the Matchers tell Nuclei what to look for in the response. If the response matches your criteria — for example, finding the word password in the body — Nuclei reports a vulnerability.

Detecting Exposed Debug Endpoints

One of the most common security mistakes in web development is leaving Debug Endpoints exposed in a production environment. Developers often create routes like /api/debug or /api/config to help with testing, but if these are accessible to the public, they can leak sensitive database credentials or internal system paths. A general scanner might miss these if they aren't linked anywhere in the application, but with Nuclei, we can explicitly hunt for them.

To detect these, we define a request that attempts to access a list of known sensitive paths. We use the http: block to specify the paths we want to guess. Notice that we don't need separate templates for each path; Nuclei allows us to provide a list, and it will automatically generate requests for each one.

http:
  - method: GET
    path:
      - "{{BaseURL}}/api/debug"
      - "{{BaseURL}}/api/health/detailed"
      - "{{BaseURL}}/api/config"

However, simply getting a response isn't enough; we need to verify that the response actually contains sensitive data. We do this using Matchers. In the example below, we use a word matcher to look for specific dangerous strings like database or secret. We also check that the HTTP status code is 200 (OK). By using matchers-condition: or, we tell Nuclei to report an issue if any of these sensitive words appear in the response.

    matchers-condition: or
    matchers:
      - type: word
        words:
          - "database"
          - "password"
          - "secret"
        condition: or
        
      - type: status
        status:
          - 200

This logic is highly effective. If the server returns a 404 Not Found, the status matcher fails, and no alert is triggered. If the server returns a 200 OK, but the page is just a generic "Welcome" screen without sensitive keywords, the word matcher fails. An alert is only generated when the scanner finds actual evidence of a leak.

Testing for Default Credentials

Another critical check is ensuring that default accounts, like admin/admin, have been disabled. Unlike simple GET requests, testing a login form usually requires sending a POST request with a JSON body. General scanners often struggle to guess the correct JSON structure required by a specific API, which causes them to fail even if the vulnerability exists. Nuclei solves this by allowing Raw Requests, where you define the exact HTTP message structure.

In a raw request, you manually construct the headers and the body. This gives you complete control over the Content-Type and the payload format. Below, we are crafting a specific login attempt targeting the /api/login endpoint. We explicitly tell the server we are sending JSON data and provide the default credentials we want to test.

http:
  - raw:
      - |
        POST /api/login HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json
        
        {"username":"admin","password":"admin"}

To confirm whether the login was successful, we cannot simply rely on a status code, as some APIs return 200 OK even for failed logins (with a specific error message in the body). Instead, we look for positive indicators of success. In most modern applications, a successful login returns an Authentication Token or a "success" message. We configure our matchers to look specifically for these indicators.

    matchers-condition: and
    matchers:
      - type: word
        words:
          - "token"
          - "success"
        condition: or
        
      - type: status
        status:
          - 200

Here, we use matchers-condition: and. This ensures that Nuclei only reports a vulnerability if both conditions are met: the status code is 200 AND the response body contains a success indicator. This reduces false positives significantly compared to blindly checking for status codes alone.

Checking for Missing Security Headers

Security is not just about finding bugs; it is also about ensuring proper defense mechanisms are in place. Security Headers like Content-Security-Policy (CSP) and Strict-Transport-Security (HSTS) tell the browser how to behave securely. Identifying missing headers requires a different approach: we are not looking for the presence of a string, but rather its absence.

One way to achieve this is with Regex Matchers (Regular Expressions). Specifically, we can use a technique called a "negative lookahead." The regex ^(?!.*Content-Security-Policy).*$ effectively translates to: "Scan the text, and match successfully only if the string Content-Security-Policy is NOT found." We specify part: header to tell Nuclei to only analyze the HTTP response headers, ignoring the body content entirely.

    matchers:
      - type: regex
        name: missing-csp
        regex:
          - "^(?!.*Content-Security-Policy).*$"
        part: header

However, Nuclei also provides a simpler, more readable built-in alternative: the negative: true field. You can add negative: true to any matcher to invert its result, meaning the matcher succeeds only when the pattern is not found. This achieves exactly the same goal as the regex negative lookahead above, but with far less complexity:

    matchers:
      - type: word
        name: missing-csp
        words:
          - "Content-Security-Policy"
        part: header
        negative: true

This matcher tells Nuclei: look in the response headers for the word Content-Security-Policy, and report a finding only if it is absent. For most missing-header checks, negative: true is the preferred approach over a complex regex, as it avoids potential regex pitfalls and keeps your template intentions explicit and easy to read.

In addition to finding missing headers, we often want to gather intelligence about the server. Nuclei provides Extractors for this purpose. Unlike matchers, which return a boolean "found/not found," extractors pull specific data out of the response and display it in the output. This is useful for Fingerprinting, such as identifying the specific server version or technology stack being used.

    extractors:
      - type: kval
        kval:
          - x_powered_by
          - server

In this snippet, kval (Key-Value) extraction is used. Nuclei looks for headers named x_powered_by or server and extracts their values. If the server responds with Server: nginx/1.18.0, Nuclei will display that version number in the scan results — helping you identify if the server software is outdated.

Running Nuclei Scans with Custom Templates

Now that we have created our templates, we need to run them against our target. Nuclei is a command-line tool, similar to ZAP CLI, but it is generally much faster to execute. To run a scan, we need to specify two primary arguments: the Target URL (-u) and the Template Source (-t).

In the CodeSignal environment, we will save our templates in a folder named templates/. We can then instruct Nuclei to load all checks from that directory and run them against our local Pastebin application running on port 3001.

nuclei -u http://localhost:3001 -t templates/

When you run this command, Nuclei compiles the templates, connects to the target, and iterates through the requests. The output is color-coded and highly readable. It displays the template id, the protocol, the severity level, and the specific URL that triggered the match.

Example Output:

[INF] Current nuclei version: v2.9.0
[INF] Loading templates...
[high] [pastebin-debug-endpoint] http://localhost:3001/api/debug
[critical] [pastebin-default-credentials] http://localhost:3001/api/login
[medium] [missing-security-headers] http://localhost:3001/

This output tells us immediately what went wrong. We see a High severity issue where the debug endpoint was accessed, a Critical issue where the admin login succeeded with default credentials, and a Medium issue regarding missing headers. This concise reporting makes it easy to prioritize fixes.

Summary and Practice Preview

In this lesson, you learned how to harness the power of Nuclei for targeted vulnerability scanning. We explored the anatomy of a Nuclei template, including the metadata, request definitions, and matchers. You learned how to detect exposed debug endpoints using word matchers, how to test for default credentials using raw POST requests, and how to identify missing security headers using both regex negative lookaheads and the cleaner negative: true matcher field.

This shift from "broad scanning" to "precise templating" gives you the ability to verify specific security requirements and regression tests. In the upcoming practice exercises, you will write these YAML templates from scratch, run them against the Pastebin application, and interpret the results to uncover hidden vulnerabilities. Let's get started writing some code!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal