CSAW Qualifiers 2017- “Shia LaBeouf-Off!” Writeup

Thomas Crain
5 min readSep 28, 2017

--

I recently competed in the CSAW CTF Qualification Round 2017 with the UT Dallas Computer Security Group. Overall, we placed 123 out of 1432 teams with 1826 points. This is a writeup going over how I solved “Shia LaBeouf-Off”, a 150-point web challenge.

Problem

Do it
Just do it
Don’t let your dreams be dreams
Yesterday you said tomorrow
So just do it
Make your dreams come true
Just do it
Pick 1: http://web.chal.csaw.io:5487 http://web.chal.csaw.io:5488 http://web.chal.csaw.io:5489 http://web.chal.csaw.io:5490"

Solution

This description doesn’t give us much to go off of. Upon clicking the link, the title of “| Django + Docker Example” tells us this server uses Django, a popular web framework for Python. Looking further into the site, there are two parts: a polling app and an ad-lib app.

Ignore those! Let’s take a look at a 404 page for this service. Going to a nonexistent page such as “http://web.chal.csaw.io:5487/w0w” gives us more than just a standard 404 page- we get a nice debug printout!

Using the URLconf defined in django_docker.urls, Django tried these URL patterns, in this order:1. ^$ [name='home']
2. ^polls/
3. ^ad-lib/
The current path, w0w, didn't match any of these.You're seeing this error because you have DEBUG = True in your Django settings file. Change that to False, and Django will display a standard 404 page.

So from this we get a couple of pieces of information: 1) crashing the app is a great way to enumerate attack vectors, and 2) the website isn’t hiding anything in the URLconf- our attack surfaces are in the polling and ad-lib apps. Delving into the ad-lib app, we are met with the following prompt:

Give me an ad lib and I will Shia Labeouf it up for you!Where you want a noun, just put: “{{ noun }}”, for a verb: “{{ verb }}”, and for an adjective: “{{ adjective }}”!

The usage of the {{ }} construct is part of the Django template language. Templating in Django works roughly like this: Django will feed in a text file with variables (something in between a two sets of curly braces, example above) to a template engine and the template engine will replace that variable with some value given in a context dictionary when the web page is rendered. For an example, enter {{ noun }} into the text box and hit enter- on the output page, it looks like our variable was replaced with an image of Shia LaBeouf. Templates can also contain tags, which look like {% tag %} and are used to do a wide variety of programmatic things.

This is our likely attack vector- unsanitized user input being fed into the templating engine. Let’s see if we can leak the context dictionary through a debug page. The documentation for the template language tells us that “Variable names consist of any combination of alphanumeric characters and the underscore”. Entering some Q*bert swearing as a variable ({{ @!#?@! }}) raises an error and gives us a useful debug page.

In the traceback, we can peek at the local variables for each function call in the stack as well as click on the function calls to see code around each function call. Expanding the “./ad-lib/views.py” function call, we can see the following data:

./ad-lib/views.py in index18. 
19. def index(request):
20. global obj
21.
22. if request.method == "POST":
23. data = request.POST.get('formatdata', '')
24. template_data = TEMP.format(data.replace("noun", "noun|safe").replace("verb", "verb|safe").replace("adjective", "adjective|safe"))
25. template = Template(template_data)
26. context = RequestContext(request, {
27. 'noun': '<img src="https://media0.giphy.com/media/arNexgslLkqVq/200.webp#70-grid1" />',
28. 'verb': '<img src="https://media3.giphy.com/media/R0vQH2T9T4zza/200.webp#165-grid1" />',
29. 'adjective': '<img src="https://media1.giphy.com/media/TxXhUgEUWWL6/200.webp#129-grid1" />',
30. 'mrpoopy': obj
31. })
▼ Local vars
Variable | Value
data | u'{{ @!#?@! }}'
request | <WSGIRequest: POST '/ad-lib/'>
template_data | "\n{% extends 'base.html' %}\n{% load pools_extras %}\n\n{% block breadcrumb-page %}Format{% endblock breadcrumb-page %}\n{% block content %}\n{{ @!#?@! }}\n{% endblock content %}\n"

In the context dictionary in lines 26–31, we see the variables that the ad-lib instructions give us- we can insert a noun, a verb, and an adjective- as well as another key-value pair. What’s that mrpoopy object?

Let’s try to do some introspection on this object! If we insert {{ mrpoopy }} into the ad-lib form, we are presented with:

<ad-lib.someclass.Woohoo instance at 0x7fa795934638>

That’s not too helpful. Brainstorming ways to introspect the attributes of mrpoopy, we have a couple of methods we can try- we can try calling the __dict__ attribute of mrpoopy, or we can call dir(mrpoopy). However, neither of these will work. Submitting {{ mrpoopy.__dict__ }} returns an error because Django templates don’t accept variables and attributes with underscores. Submitting {{ dir(mrpoopy) }} returns an error because variables aren’t designed to pass arguments to method calls within templates.

Let’s explore the polls part of the website for this. Clicking on a poll gives a URL of the form /polls/[ID]/. The IDs of each poll are “1” and “2”, respectively. Sequential IDs like these are bad ideas as they allow easy enumeration of the resource being accessed. Case in point: navigating to /polls/3/ gives us the following exception:

Our infrastructure can't support that many Shias!

The exception is raised near the bottom of the traceback stack in a file called pools_extras.py. Expanding the code around the check(value) function call exposes some important functions:

./polls/templatetags/pools_extras.py in checknum5.
6. @register.filter(name='getme')
7. def getme(value, arg):
8. return getattr(value, arg)
9.
10. @register.filter(name='checknum')
11. def checknum(value):
12. check(value) ...
13.
14. @register.filter(name='listme')
15. def listme(value):
16. return dir(value)

Another construct of the Django template system is the filter. Filters are used to modify variables in a template. Django has a wide variety of built-in filters, but one can create their own filters (as we can see in the above code). The @register.filter(name=…) registers the function that follows it as a filter. The filters from this file can then be loaded into a template and be used there. Filters are used as such: {{ variable|filter_name:arg2,arg3,… }} will call the function associated with filter_name with the first argument being variable followed by any arguments listed after the filter name.

You might recognize the pools_extras name from our traceback in the ad-lib app. The local variables of ./ad-lib/views.py included the template_data variable, which contains the template being rendered after we submit the ad-libs form:

"\n{% extends 'base.html' %}\n{% load pools_extras %}\n\n{% block breadcrumb-page %}Format{% endblock breadcrumb-page %}\n{% block content %}\n{{ @!#?@! }}\n{% endblock content %}\n"

The template includes the {% load pools_extras %} tag, which loads the filters registered in pools_extras.py. There are two filters of interest in that file- getme and listme which are essentially filter aliases for getattr(value,arg) and dir(value), respectively. We can use them to introspect the mrpoopy object in our ad-lib app!

Going back to the ad-lib app, we can submit {{ mrpoopy|listme }} to see its attributes. This filter returns the result of dir(mrpoopy):

['Woohoo', '__doc__', '__flag__', '__module__']

Awesome! The __flag__ attribute of mrpoopy probably holds our flag. The attribute’s name keeps us from directly accessing it, but we can use the getme filter to call getattr(mrpoopy,“__flag__”). Submit {{ mrpoopy|getme:“__flag__”}} to finally reveal our flag:

flag{wow_much_t3mplate}

--

--