Overview

The template flow engine was introduced in nuclei v3, and brings two significant enhancements to Nuclei:

These features are implemented using JavaScript (ECMAScript 5.1) via the goja backend.

Conditional Execution

Many times when writing complex templates we might need to add some extra checks (or conditional statements) before executing certain part of request.

An ideal example of this would be when bruteforcing wordpress login with default usernames and passwords, but if we carefully re-evaluate this template, we can see that template is sending 276 requests without even checking, if the url actually exists or the target site is actually a wordpress site.

With addition of flow in Nuclei v3 we can re-write this template to first check if the target is a wordpress site, if yes then bruteforce login with default credentials and this can be achieved by simply adding one line of content i.e flow: http(1) && http(2) and nuclei will take care of everything else.

id: wordpress-bruteforce

info:
  name: WordPress Login Bruteforce
  author: pdteam
  severity: high

flow: http(1) && http(2)

http:
  - method: GET
    path:
      - "{{BaseURL}}/wp-login.php"

    matchers:
      - type: word
        words:
          - "WordPress"

  - method: POST
    path:
      - "{{BaseURL}}/wp-login.php"

    body: |
        log={{username}}&pwd={{password}}&wp-submit=Log+In

    attack: clusterbomb 
    payloads:
      users: helpers/wordlists/wp-users.txt
      passwords: helpers/wordlists/wp-passwords.txt

    matchers:
      - type: dsl
        dsl:
          - status_code == 302
          - contains_all(header, "/wp-admin","wordpress_logged_in")
        condition: and

The update template now seems straight forward and easy to understand. we are first checking if the target is a wordpress site and then executing bruteforce requests. This is just a simple example of conditional execution and flow accepts any Javascript (ECMAScript 5.1) expression/code so you are free to craft any conditional execution logic you want.

Request Execution Orchestration

Flow is a powerful Nuclei feature that provides enhanced orchestration capabilities for executing requests. The simplicity of conditional execution is just the beginning. With flow, you can:

  • Iterate over a list of values and execute a request for each one
  • Extract values from a request, iterate over them, and perform another request for each
  • Get and set values within the template context (global variables)
  • Write output to stdout for debugging purposes or based on specific conditions
  • Introduce custom logic during template execution
  • Use ECMAScript 5.1 JavaScript features to build and modify variables at runtime
  • Update variables at runtime and use them in subsequent requests.

Think of request execution orchestration as a bridge between JavaScript and Nuclei, offering two-way interaction within a specific template.

Practical Example: Vhost Enumeration

To better illustrate the power of flow, let’s consider developing a template for vhost (virtual host) enumeration. This set of tasks typically requires writing a new tool from scratch. Here are the steps we need to follow:

  1. Retrieve the SSL certificate for the provided IP (using tlsx)
    • Extract subject_cn (CN) from the certificate
    • Extract subject_an (SAN) from the certificate
    • Remove wildcard prefixes from the values obtained in the steps above
  2. Bruteforce the request using all the domains found from the SSL request

You can utilize flow to simplify this task. The JavaScript code below orchestrates the vhost enumeration:

ssl();
for (let vhost of iterate(template["ssl_domains"])) {
    set("vhost", vhost);
    http();
}

In this code, we’ve introduced 5 extra lines of JavaScript. This allows the template to perform vhost enumeration. The best part? You can run this at scale with all features of Nuclei, using supported inputs like ASN, CIDR, URL.

Let’s break down the JavaScript code:

  1. ssl(): This function executes the SSL request.
  2. template["ssl_domains"]: Retrieves the value of ssl_domains from the template context.
  3. iterate(): Helper function that iterates over any value type while handling empty or null values.
  4. set("vhost", vhost): Creates a new variable vhost in the template and assigns the vhost variable’s value to it.
  5. http(): This function conducts the HTTP request.

By understanding and taking advantage of Nuclei’s flow, you can redefine the way you orchestrate request executions, making your templates much more powerful and efficient.

Here is working template for vhost enumeration using flow:

id: vhost-enum-flow

info:
  name: vhost enum flow
  author: tarunKoyalwar
  severity: info
  description: |
    vhost enumeration by extracting potential vhost names from ssl certificate.

flow: |
  ssl();
  for (let vhost of iterate(template["ssl_domains"])) {
    set("vhost", vhost);
    http();
  }

ssl:
  - address: "{{Host}}:{{Port}}"

http:
  - raw:
      - |
        GET / HTTP/1.1
        Host: {{vhost}}

    matchers:
      - type: dsl
        dsl:
          - status_code != 400
          - status_code != 502

    extractors:
      - type: dsl
        dsl:
          - '"VHOST: " + vhost + ", SC: " + status_code + ", CL: " + content_length'

JS Bindings

This section contains a brief description of all nuclei JS bindings and their usage.

Protocol Execution Function

In nuclei, any listed protocol can be invoked or executed in JavaScript using the protocol_name() format. For example, you can use http(), dns(), ssl(), etc.

If you want to execute a specific request of a protocol (refer to nuclei-flow-dns for an example), it can be achieved by passing either:

  • The index of that request in the protocol (e.g.,dns(1), dns(2))
  • The ID of that request in the protocol (e.g., dns("extract-vps"), http("probe-http"))

For more advanced scenarios where multiple requests of a single protocol need to be executed, you can specify their index or ID one after the other (e.g., dns(“extract-vps”,“1”)).

This flexibility in using either index numbers or ID strings to call specific protocol requests provides controls for tailored execution, allowing you to build more complex and efficient workflows. more complex use cases multiple requests of a single protocol can be executed by just specifying their index or id one after another (ex: dns("extract-vps","1"))

Iterate Helper Function

Iterate is a nuclei js helper function which can be used to iterate over any type of value like array, map, string, number while handling empty/nil values.

This is addon helper function from nuclei to omit boilerplate code of checking if value is empty or not and then iterating over it

iterate(123,{"a":1,"b":2,"c":3})

// iterate over array with custom separator
iterate([1,2,3,4,5], " ")

Set Helper Function

When iterating over a values/array or some other use case we might want to invoke a request with custom/given value and this can be achieved by using set() helper function. When invoked/called it adds given variable to template context (global variables) and that value is used during execution of request/protocol. the format of set() is set("variable_name",value) ex: set("username","admin").

for (let vhost of myArray) {
  set("vhost", vhost);
  http(1)
}

Note: In above example we used set("vhost", vhost) which added vhost to template context (global variables) and then called http(1) which used this value in request.

Template Context

A template context is nothing but a map/jsonl containing all this data along with internal/unexported data that is only available at runtime (ex: extracted values from previous requests, variables added using set() etc). This template context is available in javascript as template variable and can be used to access any data from it. ex: template["dns_cname"], template["ssl_subject_cn"] etc.

template["ssl_domains"] // returns value of ssl_domains from template context which is available after executing ssl request 
template["ptrValue"]  // returns value of ptrValue which was extracted using regex with internal: true

Lot of times we don’t known what all data is available in template context and this can be easily found by printing it to stdout using log() function

log(template)

Log Helper Function

It is a nuclei js alternative to console.log and this pretty prints map data in readable format

Note: This should be used for debugging purposed only as this prints data to stdout

Dedupe

Lot of times just having arrays/slices is not enough and we might need to remove duplicate variables . for example in earlier vhost enumeration we did not remove any duplicates as there is always a chance of duplicate values in ssl_subject_cn and ssl_subject_an and this can be achieved by using dedupe() object. This is nuclei js helper function to abstract away boilerplate code of removing duplicates from array/slice

let uniq = new Dedupe(); // create new dedupe object
uniq.Add(template["ptrValue"]) 
uniq.Add(template["ssl_subject_cn"]);
uniq.Add(template["ssl_subject_an"]); 
log(uniq.Values())

And that’s it, this automatically converts any slice/array to map and removes duplicates from it and returns a slice/array of unique values

Similar to DSL helper functions . we can either use built in functions available with Javscript (ECMAScript 5.1) or use DSL helper functions and its upto user to decide which one to uses.

Skip Internal Matchers in MultiProtocol / Flow Templates

Before nuclei v3.1.4 , A template like CVE-2023-43177 which has multiple requests/protocols and uses flow for logic, used to only return one result but it conflicted with logic when for loop was used in flow to fix this nuclei engine from v3.1.4 will print all events/results in a template and template writers can use internal: true in matchers to skip printing of events/results just like dynamic extractors.

Note: this is only relevant if matchers/extractors are used in previous requests/protocols

Example of CVE-2023-6553 with new internal: true logic would be

id: CVE-2023-6553

info:
  name: Worpress Backup Migration <= 1.3.7 - Unauthenticated Remote Code Execution
  author: FLX
  severity: critical

flow: http(1) && http(2)

http:
  - method: GET
    path:
      - "{{BaseURL}}/wp-content/plugins/backup-backup/readme.txt"

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'contains(body, "Backup Migration")'
        condition: and
        internal: true  # <- updated logic (this will skip printing this event/result)

  - method: POST
    path:
      - "{{BaseURL}}/wp-content/plugins/backup-backup/includes/backup-heart.php"
    headers:
      Content-Dir: "{{rand_text_alpha(10)}}"

    matchers:
      - type: dsl
        dsl:
          - 'len(body) == 0'
          - 'status_code == 200'
          - '!contains(body, "Incorrect parameters")'
        condition: and