Problem Description
I heard cookies and string formatting are safe in 2019?
http://challenges.fbctf.com:8083
(This problem does not require any brute force or scanning.
We will ban your team if we detect brute force or scanning).
Application Overview
From the problem description it looks like it's going to be about Cookie Forgery and Server Side Template Injection (SSTI).
On opening the page we are greeted with a login/register form, similar the other web challenges. After logging in, we are greeted with this page:
On submission, the 3 form fields are sent to the server.
The homepage then displays the event, according to the property we chose.
Template Injection
After inserting some basic SSTI payloads to the name and address field with no success. I decided to mess around with the event_important
field because it looked suspicious enough. And sure enough, after setting event_important
to "id" something happened.
Because the problem description mentions about string formatting, this is most likely an SSTI vulnerability just to be sure I also tried to set the event_important
to "__dict__" to check if we are actually accessing an object's property. It turned out, not only did this verify that it is actually a template injection but also how it is being done.
fmt
property which uses argument numbers and single curly braces, the server most likely uses Python's built-in format
method to create the template. There is also the show
property which is also a possible template string, so I used the payload "id}{0.id" and confirmed that the property that's actually used is fmt
.
Exploitation
There several ways we can further exploit this vulnerability:
We have references to SQL Alchemy objects and classes, so maybe it's somehow possible access the database that way. Unfortunately, Python's format
method can only do property accesses and not method calls so it's probably going to be really hard if not impossible to query the database. (I tried lol)
Another way worth trying is to access the global objects and modules in the application and hope that there's something interesting inside them. One way to access globals is through the __globals__
property of a function. One function we can access is the __init__
method of the event object. And voila! there's lots of interesting stuff for us to traverse. One of particular interest is the Flask app instance.
We can further inspect the Flask app instance by accessing its __dict__
. You might notice that __globals__
is a dictionary. We cannot use the dot property access syntax to access a dictionary's value in a Python format string, instead we have to use a weird new syntax dictionary[key_string]
(notice that there are no quotes). Sending __init__.__globals__[app].__dict__
allows us to see many of the app's settings and properties.
There are definitely many properties and methods that are interesting, such as the view methods and login manager as well as the SECRET_KEY which is always very interesting.
At this point I didn't realize that by default Flask uses signed cookies, which means the server signs the session data and sends it back to the client. Signed cookies sessions are meant to reduce server load by off-loading session data to the client while keeping it secure by signing it with a secret. Unfortunately this makes it very easy for an attacker with a secret key to forge session data by creating the session data and then signing it using the secret key. This why you should as keep your secret key, secret.
Cookie Forgery
After learning that Flask uses signed cookies by default (thanks to Flask's awesome documentation) I became certain that the solution was to craft a signed cookie using the retrieved secret_key. I ran a Flask app to forge signed cookies.
from flask import Flask
from flask.sessions import SecureCookieSessionInterface
app = Flask(__name__)
app.secret_key = b'fb+wwn!n1yo+9c(9s6!_3o#nqm&&_ej$tez)$_ik36n8d7o6mr#y'
session_serializer = SecureCookieSessionInterface().get_signing_serializer(app)
@app.route('/')
def index():
print(session_serializer.dumps("admin"))
return "lol"
There were two cookies events_sesh_cookies
and user
. I tried forging the events_sesh_cookies
first, setting the id in the session data to 1 (most likely to be the admin). It successfully authenticated me as another user but didn't grant me admin authorization. So I looked to the other cookie, user
. Decoding it reveals that it is actually storing the user's username. Then I thought that changing the user
cookie data to "admin" will probably authorize me as an admin. After forging the user
cookie, accessing /flag
gives the flag:
Flag: fb{e@t_aLL_th0s3_c0oKie5}