I’ve used Burp for years, but for quick scripting tasks I’ve always needed to also chain it to mitmproxy. I’m not a big fan of that setup because it’s a bit clunky, and I don’t want to switch to mitmproxy completely because I’m just not leet enough for it. With the discovery of the GraalVM polyglot API and Burp’s new Montoya API, I’ve finally got around to solving this problem by integrating a scripting environment that supports hot reloading into Burp, and I’m actually pretty excited to share this project with everyone.

Overview

For those who just want to know the features and see the repo here is a quick overview. The extension is available on GitHub here. If you find it useful, or want to see some changes, give us a shout on GitHub.

The features include:

  • Python 3 and JavaScript support
  • Manipulate requests and responses from the Proxy or other tools such as the Repeater
  • Conditionally drop requests & responses, or send them to the Intercept tab for manual inspection
  • Hot reloading of scripts on file change
  • Built-in cryptographic utilities
  • Filter DSL for easily determining if the plugin’s registered handlers should handle a request or response

Example

A quick Python script example to showcase some of the features:

# Filter definition for requests to be passed to this script's `on_request`
# function
REQ_FILTER = """
(and
    (in-scope)
    (method-eq "GET")
    (not (has-query-param "__ivision"))
)
"""

# Filter definition for responses to be passed to this script's `on_response`
# function
RES_FILTER = """
(and
    (in-scope)
    (path-matches r"^/api/v[0-9]+/myPermissions$")
    (has-json-key "permission.isSuperAdmin")
)
"""


# Initialization called when the script is loaded/reloaded. The passed object
# is a MontoyaApi object so the script can do anything a normal plugin can do.
def initialize(api):
    # log is injected into the script as a global
    log.info("Script initialized")


# Teardown called when the script is removed.
def cleanup():
    log.info("Script cleaning up")


# Request handler, a `ScriptHttpRequest` is passed as req, this is a
# `HttpRequestToSend` with some extra functionality
def on_request(req):
    return req.withQueryParameter("__ivision", "true")

# Response handler, a `ScriptHttpResponse` is passed as res, this is a
# `HttpResponseReceived` with some extra functionality
def on_response(res):
    log.info("Setting `isSuperAdmin` to true")

    body = res.bodyAsJson()

    body["permission"]["isSuperAdmin"] = True
    # or, you can specify JSON paths using a dotted string notation
    body.putDotted("permission.isSuperAdmin", True)

    # You can also return None here if you don't want to change the response
    # at all
    return res.withJson(body)