Using Burp Suite’s Cookie Jar for JSON Web Tokens

I was going over an application I wrote prepping for my upcoming class and realized the shortcoming’s of Burp’s default session handling mechanisms. Not to knock Burp, but working with a Single Page Application (SPA) that makes calls to several APIs using a JSON Web Token (JWT) doesn’t mix well with Burp’s macros and Cookie Jar. Granted, this isn’t a problem if your JWT’s expiration date is sufficiently far out in the future and none of the APIs you’re working with de-authenticate your “session” (revoke or blacklist your token). JWTs aren’t necessarily meant to be linked to any real session state. They grant access to things and expire at a certain date.

In wanting to show students how applications often pose problems when faced with malicious request activity, I made it such that my application features a de-authentication mechanism that blacklists JWTs when malicious activity is witnessed. As such, I had to work up a way (in Burp Suite) to check my session and re-authenticate once a token has been blacklisted. Before jumping into how I solved this I should probably explain how my application works. Again, it’s a SPA that reaches out to various APIs and a content distribution “network” (CDN). The origins at play here are:

https://books2.store.com (hosts the SPA – HTML and Javascript)
https://api.store.com (does all the things for the “store”)
https://auth.foobar.com (provides authentication/autherization for everything)
https://cdn1.foo.com (serves up various files – no token required)

Pretty straight forward. JWTs get handed out by auth.foobar.com to books2.store.com and ultimately get passed back and forth between all hosts but cdn1.foo.com. The JWT is stored as a cookie by books2.store.com and sent in a header when ajax requests are made to auth.foobar.com and api.store.com (from the books2.store.com origin).

The following figure details a login request made to auth.foobar.com.

Using Burp's Cookie Jar for JWTs - Request to Auth API

This figure details the response handed back by auth.foobar.com with a JWT.

Using Burp's Cookie Jar for JWTs - Response from Auth API

This figure details a request to api.store.com which includes the JWT placed in a header.

Using Burp's Cookie Jar for JWTs - Request to Store API

This figure details the response received from api.store.com.

Using Burp's Cookie Jar for JWTs - Response from Store API

Again, the JWT is stored as a cookie by the user agent and is associated with books2.store.com.

Using Burp's Cookie Jar for JWTs - JWT stored as a cookie

To add a little more context, make sure to understand the maintenance of an authenticated state is necessary when automating attacks (Intruder) and performing vulnerability scanning (Scanner).

Without any de-authentication or blacklisting you could just set a token via the “Add Custom Header” extension and go about your business given that your token has a far away expiration date.

When faced with de-authentication and blacklisting mechanisms you can create a macro that fires off a login request, grabs the token using the “Add Custom Header” extension, and include it in the current request. Granted, this will happen for EVERY request. This may be completely acceptable given that the authentication API isn’t a production asset and your client doesn’t necessarily mind you beating up their infrastructure. Fire away and go about your business! But if this isn’t the case, or you just want to be more efficient and less cumbersome, you’ll need to come up with a more elegant solution than acquiring a new token for EVERY request.

This is where Burp Extensions come in. I created a way to mimic the Cookie Jar for JWTs (or header values, in general). In the end, two small extensions were necessary to pull this off.

1) “Store & Set” – Grab a JWT from a login macro when the current request is deemed “invalid”, store the value in the cookie jar, and then insert it into a request header.
2) “Set” – Grab the stored token value from the cookie jar and insert it into a request header.

Both extensions contained logic that only inserts the token value (taken from the cookie jar) into a header if the specified header was already included in the request.

Only a single session handling rule with two associated actions is required for this endeavor.

Using Burp's Cookie Jar for JWTs - Burp Session Handling Interface

The first action will use the “Set” extension to update the current request’s JWT with the one that’s been stored in the Cookie Jar. This action behaves very much like Burp’s builtin “Use cookies from the session handling cookie jar” action.

Using Burp's Cookie Jar for JWTs - Session Handling Settings for JWT Set Extension

The second will be a “check session is valid” action that will issue the current request, test its validity, if invalid, run a login macro, followed by our “Store & Set” extension which will parse the new JWT from the macro’s response, store the JWT in the cookie jar, and then update the current request’s JWT with the newly acquired one.

The following figure details the settings for the session check portion of the session handling rule. We know that when a JWT is revoked or has expired the string “Expired Token” will be present in responses. We will use this to invalidate the session and trigger a macro.

Using Burp's Cookie Jar for JWTs - Session Check settings for JWT Store & Set Extension

The following figure details the settings used where a macro is triggered due to an invalid session which is then followed by our “Store & Set” extension being called.

Using Burp's Cookie Jar for JWTs - Session Check settings for JWT Store & Set Extension

This is all very similar to how one would utilize Burp session handling rules and macros to provide state maintenance when an application makes use of traditional session cookies. The difference being that we’re not using cookies and, instead, are relegated to request headers.

The following code comprises the “Store & Set” extension. Take note of the “Cookie” class and its associated functions. These allow you to work with Burp’s Cookie Jar.

# python imports
import re
import sys

# Burp specific imports
from burp import IBurpExtender
from burp import ISessionHandlingAction
from burp import ICookie

# For using the debugging tools from
# https://github.com/securityMB/burp-exceptions
try:
    from exceptions_fix import FixBurpExceptions
except ImportError:
    pass

class Cookie(ICookie):
	
	def getDomain(self):
		return self.cookie_domain

	def getPath(self):
		return self.cookie_path

	def getExpiration(self):
		return self.cookie_expiration

	def getName(self):
		return self.cookie_name

	def getValue(self):
		return self.cookie_value

	def __init__(self, cookie_domain=None, cookie_name=None, cookie_value=None, cookie_path=None, cookie_expiration=None):
		self.cookie_domain = cookie_domain
		self.cookie_name = cookie_name
		self.cookie_value = cookie_value
		self.cookie_path = cookie_path
		self.cookie_expiration = cookie_expiration

class BurpExtender(IBurpExtender, ISessionHandlingAction):

	#
	# Define config and gui variables
	#

	cookieName = 'jwt'
	cookieDomain = 'dummy.com'

	#
	# Define some cookie functions
	#

	def deleteCookie(self, domain, name):
		cookies = self.callbacks.getCookieJarContents()
		for cookie in cookies:
			#self.stdout.println("%s = %s" % (cookie.getName(), cookie.getValue()))	
			if cookie.getDomain() == domain and cookie.getName() == name:
				cookie_to_be_nuked = Cookie(cookie.getDomain(), cookie.getName(), None,  cookie.getPath(), cookie.getExpiration())
				self.callbacks.updateCookieJar(cookie_to_be_nuked)
				break

	def createCookie(self, domain, name, value, path=None, expiration=None):
		cookie_to_be_created = Cookie(domain, name, value,  path, expiration)
		self.callbacks.updateCookieJar(cookie_to_be_created)

	def setCookie(self, domain, name, value):
		cookies = self.callbacks.getCookieJarContents()
		for cookie in cookies:
			if cookie.getDomain() == domain and cookie.getName() == name:
				cookie_to_be_set = Cookie(cookie.getDomain(), cookie.getName(), value,  cookie.getPath(), cookie.getExpiration())
				self.callbacks.updateCookieJar(cookie_to_be_set)
				break

	def getCookieValue(self, domain, name):
		cookies = self.callbacks.getCookieJarContents()
		for cookie in cookies:
			if cookie.getDomain() == domain and cookie.getName() == name:
				return cookie.getValue()


	#
	# implement IBurpExtender
	#

	def	registerExtenderCallbacks(self, callbacks):
		# keep a reference to our callbacks object
		self.callbacks = callbacks

		# obtain an extension helpers object
		self.helpers = callbacks.getHelpers()

		# set our extension name
		callbacks.setExtensionName("JWT - Store & Set")

		# register ourselves a Session Handling Action
		callbacks.registerSessionHandlingAction(self)

		# Used by the custom debugging tools
		sys.stdout = callbacks.getStdout()

		print("DEBUG: JWT - Store & Set - Enabled!")

		return

	#
	# Implement ISessionHandlingAction
	#

	def getActionName(self):
		return "JWT - Store & Set"

	def performAction(self, current_request, macro_items):
		if len(macro_items) >= 0:
			# grab some stuff from the current request
			req_text = self.helpers.bytesToString(current_request.getRequest())

			current_macro = macro_items[0]
			macro_resp = current_macro.getResponse()
			macro_resp_info = self.helpers.analyzeResponse(macro_resp)

			# parse the response & search for jwt
			if macro_resp:
				macro_resp_body = macro_resp[macro_resp_info.getBodyOffset():]
				macro_resp_text = self.helpers.bytesToString(macro_resp_body)
				search_re = '"%s":"(.*?)"' % self.cookieName
				search = re.search(search_re, macro_resp_text, re.IGNORECASE)

				# we have a jwt in the macro response
				if search:
					jwt = search.group(1)

					# set the cookie value in the cookie jar
					self.createCookie(self.cookieDomain, self.cookieName, jwt)

					# replace the old token with the stored value
					header_replace = "%s: %s" % (self.cookieName, jwt)
					req_text = re.sub(r"\r\n" + self.cookieName + ": .*\r\n", "\r\n" + header_replace + "\r\n" , req_text)

					# set the current request
					current_request.setRequest(self.helpers.stringToBytes(req_text))

try:
    FixBurpExceptions()
except:
    pass

The following code comprises the “Set” extension. It’s almost identical to the “Store & Set” extension’s code aside from checking for an initial cookie (JWT) value and leaving out the macro response parsing.

The only difference between the two scripts are contained in the “performAction” function and the extension naming portions of the code. For the sake of brevity, I will only post that function to illustrate the differences. Swap one version out for the other.

def performAction(self, current_request, macro_items):
	# grab some stuff from the current request
	req_text = self.helpers.bytesToString(current_request.getRequest())

	# grab jwt from cookie jar
	jwt = self.getCookieValue(self.cookieDomain, self.cookieName)

	# does a value exist yet?
	if jwt != None:
		# replace the old token with the stored value
		header_replace = "%s: %s" % (self.cookieName, jwt)
		req_text = re.sub(r"\r\n" + self.cookieName + ": .*\r\n", "\r\n" + header_replace + "\r\n" , req_text)

		# set the current request
		current_request.setRequest(self.helpers.stringToBytes(req_text))

Putting all of this into play and verifying the results proves successful. The following figure details where the application revoked the current token causing the session handling rules detailed above to re-authenticate and update subsequent tokens.

Using Burp's Cookie Jar for JWTs - Verificaton of session rescue via re-authentication.

So altogether, nothing too mind-bending here but definitely useful and a fun exercise to work through. Hopefully this post will provide you with some insight into using Burp Extensions to interact with the Cookie Jar.

One bit of criticism I have for this exercise is that the act of setting the current request to the updated value seems to be pretty resource intensive. Running a scan with 40 threads making use of this pair of extensions seems to be pretty slow. I am going to revisit this code in Java and see if I can improve the performance. Stay tuned…

Tagged , , , ,