Introduction to Deserialization Attacks  

Identifying a Vulnerability (Python)


Scenario (HTBooks)

For this next scenario, let's imagine another company named HTBooks GmbH & Co KG hired us to perform a white-box test of their website. We are given the URL, the source code, and the credentials: franz.mueller:bierislekker


Initial Recon

Looking at the main page, we see nothing interesting, just some placeholder text.

If we click on the Sign up link in the navbar, we will find that user registrations have been temporarily disabled. Fortunately for us, we were given a set of credentials, so this doesn't matter too much.

Heading on over to /login, we can log into the website using the credentials franz.mueller:bierislekker given to us.

Now that we are logged in, we can navigate to the catalog, where we quickly realize nothing is interesting. It is just a static list of books in stock and their corresponding statuses.

If we hover over More in the navbar, we can see a link to the Admin Panel, which is interesting to us.

However, attempting to visit it will result in an Access Denied message, presumably since franz.mueller is not an administrator.

Checking the source of templates/admin.html confirms this theory:

{% if user.isAdmin() %}
...
{% else %}
<div class="notification is-danger">
    Access denied.
</div>
{% endif %}

With nothing else to look at on the website, we might start looking through the cookies and notice this one called auth_8bH3mjF6n9 which holds a base64-encoded value:

If we take the value and decode it locally, we can see that it starts with the bytes 80 04 95 and ends with a period. If you recall from the Introduction to Deserialization Attacks section, this very likely means it is a serialized Python object, and specifically that it was serialized with Pickle (protocol version 4).

[!bash!]$ echo gASVSgAAAAAAAACMCXV0aWwuYXV0aJSMB1Nlc3Npb26Uk5QpgZR9lCiMCHVzZXJuYW1llIwNZnJhbnoubXVlbGxlcpSMBHJvbGWUjAR1c2VylHViLg== | base64 -d | xxd

00000000: 8004 954a 0000 0000 0000 008c 0975 7469  ...J.........uti
00000010: 6c2e 6175 7468 948c 0753 6573 7369 6f6e  l.auth...Session
00000020: 9493 9429 8194 7d94 288c 0875 7365 726e  ...)..}.(..usern
00000030: 616d 6594 8c0d 6672 616e 7a2e 6d75 656c  ame...franz.muel
00000040: 6c65 7294 8c04 726f 6c65 948c 0475 7365  ler...role...use
00000050: 7294 7562 2e                             r.ub.

Since this is a white-box pentest, we should check the source code to see exactly what this cookie is. By grepping for the cookie name, we can see that it is defined in util/config.py:

[!bash!]$ grep 'auth_8bH3mjF6n9' -rn .
./util/config.py:5:AUTH_COOKIE_NAME = "auth_8bH3mjF6n9"

And with a follow-up grep, we can see that the cookie is set in app.py...

[!bash!]$ grep 'AUTH_COOKIE_NAME' -rn .

./util/config.py:5:AUTH_COOKIE_NAME = "auth_8bH3mjF6n9"
./app.py:13:    if util.config.AUTH_COOKIE_NAME in request.cookies:
./app.py:14:        user = util.auth.cookieToSession(request.cookies.get(util.config.AUTH_COOKIE_NAME))
./app.py:21:    if util.config.AUTH_COOKIE_NAME in request.cookies:
./app.py:23:        user = util.auth.cookieToSession(request.cookies.get(util.config.AUTH_COOKIE_NAME))
./app.py:30:    if util.config.AUTH_COOKIE_NAME in request.cookies:
./app.py:31:        user = util.auth.cookieToSession(request.cookies.get(util.config.AUTH_COOKIE_NAME))
./app.py:38:    if util.config.AUTH_COOKIE_NAME in request.cookies:
./app.py:45:    if util.config.AUTH_COOKIE_NAME in request.cookies:
./app.py:53:            resp.set_cookie(util.config.AUTH_COOKIE_NAME, auth)
./app.py:61:    resp.set_cookie(util.config.AUTH_COOKIE_NAME, '', expires=0)

... specifically in the login() function:

...
@app.route("/login", methods = ['GET', 'POST'])
def login():
    if util.config.AUTH_COOKIE_NAME in request.cookies:
        return redirect("/")

    if request.method == 'POST':
        if util.auth.checkLogin(request.form['username'], request.form['password']):
            resp = make_response(redirect("/"))
            sess = util.auth.Session(request.form['username'])
            auth = util.auth.sessionToCookie(sess).decode()
            resp.set_cookie(util.config.AUTH_COOKIE_NAME, auth)
            return resp
    
    return render_template("login.html")
...

In the code snippet from login() we saw that the value of this cookie is generated by util.auth.sessionToCookie(), so taking a look inside util/auth.py we can see exactly what util.auth.Session and util.auth.sessionToCookie() are:

...
class Session:
    def __init__(self, username):
        con = sqlite3.connect(config.DB_NAME)
        cur = con.cursor()
        res = cur.execute("SELECT username, role FROM users WHERE username = ?", (username,))
        self.username, self.role = res.fetchone()
        con.close()

    def getUsername(self):
        return self.username

    def getRole(self):
        return self.role

    def isAdmin(self):
        return self.role == 'admin'

def sessionToCookie(session):
    p = pickle.dumps(session)
    b = base64.b64encode(p)
    return b

def cookieToSession(cookie):
    b = base64.b64decode(cookie)
    for badword in [b"nc", b"ncat", b"/bash", b"/sh", b"subprocess", b"Popen"]:
        if badword in b:
            return None
    p = pickle.loads(b)
    return p
...

Reading through the source code, we can confirm that this authentication cookie is a serialized (pickled) object.

We can see in app.py that the cookieToSession is called when the user tries to access any page with the auth_8bH3mjF6n9 cookie set. For example, /admin:

...
@app.route("/admin")
def admin():
    if util.config.AUTH_COOKIE_NAME in request.cookies:
        user = util.auth.cookieToSession(request.cookies.get(util.config.AUTH_COOKIE_NAME))
        return render_template("admin.html", user=user)

    return redirect("/login")
...

VPN Servers

Warning: Each time you "Switch", your connection keys are regenerated and you must re-download your VPN connection file.

All VM instances associated with the old VPN Server will be terminated when switching to a new VPN server.
Existing PwnBox instances will automatically switch to the new VPN server.

Switching VPN...

PROTOCOL

/ 1 spawns left

Waiting to start...

Questions

Answer the question(s) below to complete this Section and earn cubes!

Click here to spawn the target system!

Target: Click here to spawn the target system!

+10 Streak pts

Previous

+10 Streak pts

Next