Cyber Apocalypse 2025 Writeup - Trial by Fire
Table of Contents
Trial by Fire
For this exercise, we were given a url with a webapp and a zip file containing the source code.
Looking at the source code, we see it's a flask webapp inside of a docker image managed by uwsgi and supervisord
When we visit the site, we are greeted by this screen:
The last line stood out to me. Checking the jinja template for this page in the source code shows us the obvious solution here: template injection:
So, I now know that template injection is likely the way to victory. What I need now is a field that I can inject a payload into that is not escaped. Luckily enough, there is only one real field that we actually enter into the application- the warrior's username at the start. Searching the source for templates, we see another instance:
So, this seems like an obvious direction to go in. Let's set our username to
{{ url_for.__globals__ }}
After playing out the game, we are greeted with this screen-
Looking at the data, there are several interesting pieces of information:
- The application path:
/app/venv/lib/python3.12/site-packages/flask/app.py
- Access to dangerous functions like
eval
andexec
in the__builtins__
dictionary - Session data showing the injection worked:
'warrior_name': '{{ url_for.__globals__ }}'
- Access to the current application context:
current_app': <HTB 'application.app'>
So, to find the flag we can:
open
function via__builtins__
os
module- Various Python execution functions
I ended up with the SSTI payload {{ url_for.__globals__.__builtins__.open('/app/flag.txt').read() }}
, which simply just reads the flag. Unfortunately, the web form limits the character name length, so we need to actually use burpsuite to intercept the request and replace our name with the payload:
Then, after playing out the sequence we are greeted with the flag :)