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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
#!/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() |
Thank you! This was really useful