Decode Django session data without the infrastructure

I recently had to access Django session data from a cookie in a third party application (based on Tomcat) with no access to the Django infrastructure. This example shows how I did it. The key is understanding that the session data is composed of two components: a unique token and the data separated by a colon (:). The unique token is composed of various parts including the SECRET_KEY from settings.py. It is built using the data and the SHA1 hash algorithm. The token can be used to guarantee that the data is associated with the correct Django session. The data is simply a pickled dictionary of plaintext data so it can be decoded without regard to the token and, by implication, the SECRET_KEY. With this in mind, it is easy to see that the session data can be decoded by a third party application by simply unpickling it. The data is secure because it is not available in the session cookie. The session cookie only contains the encrypted session key which references the session data in the context of the Django application. To access the data the 3rd party must have access to that context. In my case it would be done through the database.
#!/usr/bin/env python
'''
Decode (and encode) Django session data.
'''
import base64
import hmac
import hashlib
import pickle


def session_utoken(msg, secret_key, class_name='SessionStore'):
    '''
    Get the unique session token.

    @param msg          The message string.
    @param secret_key   The SECRET_KEY from the Django settings.py file.
    @param class_name   The class name. This is emulates the SessionBase
                        _hash() function in Django.
    @returns The unique session token.
    '''
    key_salt = "django.contrib.sessions" + class_name
    sha1 = hashlib.sha1((key_salt + secret_key).encode('utf-8')).digest()
    utoken = hmac.new(sha1, msg=msg, digestmod=hashlib.sha1).hexdigest()
    return utoken


def encode(session_dict, secret_key, class_name='SessionStore'):
    '''
    Encode Django session data using the secret key from the settings.py file.

    This code is completely independent of Django so it can be used by
    third party tools.

    @param session_dict The session data as a dictionary from Django.
    @param secret_key   The SECRET_KEY from the Django settings.py file.
    @param class_name   The class name. This is emulates the SessionBase
                        _hash() function in Django.
    @returns the session data as a dictionary
    '''
    pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL)
    utoken = session_utoken(pickled, secret_key, class_name)
    cipher = base64.b64encode(utoken.encode() + b':' + pickled)
    return cipher.decode()


def decode(session_data, secret_key, class_name='SessionStore'):
    '''
    Decode Django session data using the secret key from the
    settings.py file and verifying it.

    This code is completely independent of Django so it can be used by
    third party tools.

    @param session_data The session data from Django.
    @param secret_key   The SECRET_KEY from the Django settings.py file.
    @param class_name   The class name. This is emulates the SessionBase
                        _hash() function in Django.
    @returns the session data as a dictionary
    '''
    encoded_data = base64.b64decode(session_data)
    utoken, pickled = encoded_data.split(b':', 1)
    expected_utoken = session_utoken(pickled, secret_key, class_name)
    if utoken.decode() != expected_utoken:
        raise BaseException('Session data corrupted "%s" != "%s"',
                            utoken.decode(),
                            expected_utoken)
    return pickle.loads(pickled)


def _test():
    '''
    Internal test of the encode/decode functions.
    '''
    # Some sample data.
    SECRET_KEY = r'spdB<HnIQxod4/2pRkjo_<buak@^DVykOf8~bI]@N.cF\~b)'

    sdx = ['NWQ0NGFmNTRjZmNhYWI4ZDIwZmE2ODk4MmVlNjI4NWM1M2IyODQ1NzqAAn1xAChVBHRoaXNxAVUEdGhhdHECVQZzZWNyZXRxA1UEaHVzaHEEdS4=',
           'YzZkZmM0N2JkZjIwZjA3ZjUyODZiNDMzOTQwYTZmZTA3Y2RjYzhmMzqAAn1xAC4=',
           ]
    passed = 0
    failed = 0
    total = 0
    for data in sdx:
        print
        print 'TEST'
        print '  data = %d %s' % (len(data), data)
        dec = decode(data, SECRET_KEY)
        print '  decode  = %r' % (repr(dec))
        enc = encode(dec, SECRET_KEY)
        print '  encode  = %r' % (repr(enc))
        decx = decode(enc, SECRET_KEY)
        print '  decode1 = %r' % (repr(decx))

        total += 1
        if dec == decx:
            passed += 1
            print '  status  = PASSED'
        else:
            failed += 1
            print '  status  = FAILED'

    print
    print 'SUMMARY total=%d, passed=%d, failed=%d' % (total, passed, failed)
    if failed:
        print 'RESULT: FAILED'
    else:
        print 'RESULT: PASSED'
    print


if __name__ == '__main__':
    _test()

One thought on “Decode Django session data without the infrastructure”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.