BurpLisp is a powerful Burp Suite extension that allows you to dynamically inspect and modify outgoing HTTP requests using Clojure (Lisp) expressions.
With BurpLisp, you can write expressive Lisp code directly inside a dedicated tab in Burp Suite to alter headers, body payload, URLs, and target services on the fly. The extension packages the Clojure runtime inside, making it fully self-contained.
To compile the project and build the self-contained fat JAR, run the following command in the project root:
gradle jarThis will generate a self-contained JAR file at:
build/libs/burplisp-1.0-SNAPSHOT.jar
- Open Burp Suite.
- Go to the Extensions tab (formerly Extender).
- Under Installed, click Add.
- Choose Extension type: Java.
- Select the path to the compiled JAR:
build/libs/burplisp-1.0-SNAPSHOT.jar(within the project folder). - Click Next to load the extension. You should see "BurpLisp extension initialized successfully" in the output tab.
The Clojure function takes two arguments: request (a Map representing the HTTP Request) and state (an Atom holding dynamic shared state). It must return a modified version of the request Map.
{:method "POST"
:url "https://api.example.com/v1/users?id=123"
:path "/v1/users"
:query "id=123"
:body "{\"username\": \"test\"}"
:headers {"Host" "api.example.com"
"Content-Type" "application/json"
"User-Agent" "ClojureClient"}
:service {:host "api.example.com"
:port 443
:secure true}}:method(String) - Changes the request method (e.g.GET,POST,PUT).:path(String) - Changes the URL path.:query(String) - Changes the URL query string parameters.:body(String or byte[]) - Changes the request body. Supports JSON/XML strings or raw binaries.:headers(Map) - Completely replaces the HTTP headers list.:service(Map::host,:port,:secure) - Modifies destination host, port, and SSL/TLS secure setting.
The state parameter is a standard Clojure atom (which starts as an empty map {}). It allows you to persist data across multiple HTTP requests safely (e.g. tracking session states, auto-incrementing serial indices, or propagating CSRF tokens dynamically).
(fn [request state]
;; Update counter in state atom
(swap! state update :counter (fnil inc 0))
(let [count (:counter @state)]
(assoc-in request [:headers "X-Request-Index"] (str count))))To prevent unwanted browser noise, you can filter which requests pass through the Clojure runtime:
- Target Scope Only: When enabled, BurpLisp ignores requests whose URL does not match Burp's target scope (
api.scope().isInScope(...)). - Applicable Tools: Filter interception using checkboxes for Proxy, Repeater, Intruder, and Scanner.
- Automatic Preferences Storage: Configurations (Clojure code, main toggle, scope toggles, and tool checkboxes) are saved automatically to Burp Suite's preferences, ensuring they persist between Burp Suite reboots or extension reloads.
- Asynchronous Non-Blocking Compilation: Compiling Lisp code is offloaded from Swing's Event Dispatch Thread (EDT) into a
SwingWorkerbackground thread. The Burp Suite UI remains responsive during compilation. - Interception Timeout Protection: Modified requests run inside a cached daemon thread pool. If a user script crashes or hangs (e.g., infinite loop
(while true ...)), the request times out after 500 milliseconds and is safely forwarded unmodified, preventing Burp Suite from freezing.
Use the Preset Snippets dropdown inside the UI to instantly load common configurations:
(fn [request state]
(assoc-in request [:headers "X-Burp-Lisp"] "Active"))Inject custom Authorization headers strictly when matching target domains:
(fn [request state]
(let [host (get-in request [:service :host])]
(if (= host "api.example.com")
(-> request
(assoc-in [:headers "Authorization"] "Bearer SECRET-LISP-TOKEN")
(assoc-in [:headers "X-Lisp-Applied"] "true"))
request)))Add extra debugging parameters to the URL query string dynamically:
(fn [request state]
(let [current-query (:query request)
new-query (if (empty? current-query)
"debug=true&test=1"
(str current-query "&debug=true&test=1"))]
(assoc request :query new-query)))Directly inspect and patch string values inside the JSON body payload:
(fn [request state]
(let [body (:body request)]
(if (and (string? body) (.contains body "\"role\""))
(assoc request :body (.replace body "\"role\":\"user\"" "\"role\":\"admin\""))
request)))- BurpLispExtension.java: Extension initialization and registering Montoya scope APIs.
- BurpLispTab.java: Preferences storage, Swing-based layout, and SwingWorker compilation.
- LispHttpHandler.java: Intercepts HTTP requests, handles thread pool futures with 500ms timeouts, maps Ring-like variables, and registers filters.