Navigating HackTheBox's 'Templated': An intro to SSTI
Recently, my schedule has become quite packed with school and training commitments. However, I understand the value of maintaining my skills and techniques, which is why I have chosen to participate in CTFs as a way to stay connected with the community and keep my abilities in top form.
BACKGROUND
To provide some context, I recently came across a web-based CTF challenge called “Templated” on HackTheBox. As an easy level challenge, I decided to give it a shot and spun up the server to start testing. Upon accessing the main web page, I was presented with the following:
My initial approach was to uncover any hidden content that wasn’t linked on the page. I started by viewing the page source, but couldn’t find anything useful. Next, I checked the robots.txt file, but that didn’t yield any results either. Finally, I attempted to use Burp’s content-discovery feature, but that too was unsuccessful. Based on these findings, it became clear to me that the challenge probably didn’t revolve around the web content itself.
Now, let’s take a moment to summarize what we’ve learned about the website thus far. First off, it’s clear that the site is built using Flask and Jinja2. In addition, we’ve discovered that the site also employs Werkzeug
. With this new information in mind, I decided to conduct some further research on this technology stack.
After conducting a Google search, I discovered that Werkzeug’s common method for Remote Code Execution relies on its debugger. Typically, the console for the debugger is located at “/console”. However, when I attempted to access the endpoint, it seemed that the console was not stored in the default location.
It’s worth noting that the error message I received actually included the exact endpoint I was targeting. Since the challenge was named “Templated,” it immediately occurred to me to test for SSTI using the payload {{5*5}}.
Upon receiving the response, it became apparent that the application was interpreting our code, which is a strong indication of SSTI.
Context: Virtualising Backend
Before delving further into the specifics, it’s helpful to first provide some context on how and why this code executes on backend. Essentially, the website copies the content directly and prints the resulting HTML back to the user, based on the user’s URL. This setup allows for the reuse of HTML templates and the ability to render based on variables.
<h1>Welcome to the page!</h1> <u>This page {{page_searched}} is not available</u>
Given that we know the server is running on Flask, we can leverage the Method Resolution Order to navigate up the Flask request library and import the “os” library. This grants us access to the “os” library, effectively giving us shell access to the server, allowing us to execute commands which will be printed on the template and rendered to us.
We could exploit the SSTI by calling os.popen().read()
payload: {{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
Running id
command shows that we are root user
Since we are able to run os commands we can navigate directories and locate the flag.txt file.
final payload: {{self.__init__.__globals__.__builtins__.__import__('os').popen('cat flag.txt').read()}}
Flag obtained: HTB{t3mpl4t3s_4r3_m0r3_p0w3rfu1_th4n_u_th1nk!}
CONCLUSION
In conclusion, this challenge was relatively straightforward, as there were no WAFs or filtered protections in place.