Sanction - An mitmproxy addon that tests authorisation controls

Posted on Feb 15, 2022
tl;dr: Replicating the BurpSuite Autorize Addon in mitmproxy
💡 The latest version on GitHub varies drastically compared to this version, please visit the GitHub Page for the latest update.

When it comes to web security testing, BurpSuite is my go to. PortSwigger have done a great job in building and maintaining the tool. While it comes with a variety of modules and components out of the box, I often find myself relying heavily on 2 built in components and one addon:

  1. Intercept
  2. Repeater
  3. Autorize

The workflow I follow is to interact and use the target application while keeping note of what is happening in the background. When required, I would toggle the interceptor or send requests to the repeater to test specific endpoints / flows. Meanwhile, in the background Autorize is automating the manual work of checking for authorisation related issues.

When one of the other BurpSuite components are needed, they work with minimal setup. This is where I find value in BurpSuite, it works out of the box and provides the extensibility to address specific use cases.

mitmproxy

I have been looking at mitmproxy for a while to add it to my toolkit. It seemed to allow for heavy customisation using Python and the UI was keyboard driven which was an added benefit. So I set out to see how much of my workflow can I rely on mitmproxy for.

It worked out of the box for repeating and intercepting requests, the keybindings made it easy to find, select, edit and send any request within seconds. For example, to edit the body of a request I would only have to do the following:

  1. e to bring up the edit menu.
  2. a to select the request body as an edit target.
  3. :wq to save my changes.
  4. r to send the edited request

For something like cookies, I would only change the 2nd step where I am selecting my edit target. You can see this in action on the mitmproxy docs.

Sanction

Replicating the functionality of Autorize would have to be done through an addon. In mitmproxy addons are python scripts that implement predefined methods that hook into mitmproxy events. You can see some examples in the docs.

To replicate the basic functionality of Autorize I would have to do 2 things:

  1. Specify which cookies / headers should be replaced.
  2. Replay the request with the modified values of those cookies / headers.
  3. Replay the request with those cookies / headers removed.

The full source code for the Sanction can be found on GitHub

Configuration

Addons in mitmproxy can be configured using the load method that can add options to the global options store.

In order to use this addon, you will have to pass 3 flags:

  1. place - cookies / headers
  2. names - list of comma separated cookies / headers to be replaced
  3. values - list of comma separated values to be used for replacement

For example, the place flag can be added like this:

# Snippet setting "place" as an addon option
class Sanction:
    def __init__(self):
        ctx.log.info("Initialising")
    def load(self, loader):
        loader.add_option(
            name="place", # Name of the option
            typespec = str, # Type
            default = 'cookies', # Default value
            help = "cookies / headers" # Help text
        )

Validation on the user supplied options can be done in the configure method, this will also be called during runtime as the options can be updated while mitmproxy is running.

# Snippet checking if the supplied names and values are an equal number
class Sanction:
    def __init__(self):
        ctx.log.info("Initialising")
    def configure(self, updates):
        if "names" in updates or "values" in updates:
            self.names = parse_list(ctx.options.names)
            ctx.log.info("Names %s" % self.names)
            self.values = parse_list(ctx.options.values)
            ctx.log.info("Values %s" % self.values)
            if len(self.names) != len(self.values):
                raise exceptions.OptionsError("Number of Keys and Values must be equal")

Event Hooks

A set of methods are available to addons that hook into mitmproxy events. We have already used two of these methods when implementing the load and configure methods above.

For our use case we need to implement the request method. Two copies of each of the unmodified requests are made, one will have the cookies / headers replaced, and the other one will have them removed.

Both copies are then replayed along with the original request.

class Sanction:
    def __init__(self):
        ctx.log.info("Initialising")
    def request(self, flow: http.HTTPFlow):
        # Avoid applying this on a replayed request (will lead to an infinite loop)
        if flow.is_replay == "request":
            return 
        if ctx.options.place == "cookies": # Check if the user wants to replace cookies / headers
            alt_auth = flow.copy() # One copy of the request to modify the values
            no_auth = flow.copy() # One copy of the request with the values removed
            for cookie,value in zip(self.names, self.values):
                if cookie in flow.request.cookies: 
                    del no_auth.request.cookies[cookie] # Remove it 
                    alt_auth.request.cookies[cookie] = value # Replace it
            ctx.master.commands.call("replay.client", [alt_auth]) # Replay first copy
            ctx.master.commands.call("replay.client", [no_auth]) # Replay second copy
        else: 
            alt_auth = flow.copy()
            no_auth = flow.copy()
            for header,value in zip(self.names, self.values):
                if header in flow.request.headers:
                    del no_auth.request.headers[cookie]
                    alt_auth.request.headers[header] = value
            ctx.master.commands.call("replay.client", [alt_auth])
            ctx.master.commands.call("replay.client", [no_auth])

Using Sanction

To load the addon script with mitmproxy you need to add it with the -s option. In this case we are replacing the preferredlocale cookie with the value “esaddon

For each request the addon will make two additional ones with modified and removed values

Original

original

Modified

modified

Removed

removed

UI and filters

By default mitmproxy displays an arrow on the right of the request to indicate if it is replayed, this can be seen in the main screen of mitmproxy: ui In order to narrow down to the interesting requests we can use the built-in filter functionality in mitmproxy. By pressing f on the keyboard you get the option to apply a filter. mitmproxy provides an extensive list of filter expressions that can be used.

For example, we can apply “! ~a” to remove all the asset requests, like this: filter_assets Or we can extend this filter and apply “! ~a & ~replay” to remove all assets and select only the replay requests: filter_replays

With this filter applied we can get get an overview of our replayed requests and selectively choose which ones to analyse in detail.

What is next?

In its current form Sanction is very barebones and needs to support a few things before it can be readily be used.

  1. Filter Support - To apply this addon on a specified filter
  2. Detection Capabilities - To identify whether authorisation is enforced or not on an endpoint.
  3. Better UI - Currently nothing prompts me in the UI if a replayed request is successful or not other than the basic HTTP Response Code / Size. Having a better indicator combined with point 2 would ehance the usability of the addon.

Feel free to have a look at the code and submit any pull requests with improvements. I am sure there are many at this stage!