A Burp Suite extension (Montoya API) that enables active scanning of nested or encoded parameters by providing an interactive:
unwrap → discover → scan
It is designed for cases where the “real” parameters are buried inside multiple encoding layers (Base64 / URL / HTML / Unicode, etc.) and formats like JSON, XML, or x-www-form-urlencoded.
Prerequisites: Java 17+, Apache Maven 3.8+
mvn package -DskipTestsThe fat JAR is written to:
target/param-unwrapper-1.0.0.jar
To also run the unit tests:
mvn verify- Open Burp Suite (Pro or Community ≥ 2023.x).
- Go to Extensions → Installed → Add.
- Extension type: Java.
- Select
target/param-unwrapper-1.0.0.jar. - Click Next. The extension loads and adds a "Param Unwrapper" tab to the Burp Suite window.
Click Param Unwrapper in the Burp Suite tab bar.
The tab is split horizontally:
| Left panel | Right panel |
|---|---|
| Rules list, rule editor, Parse / Clear / Delete controls, Candidates table | Large HTTP request editor |
Populate the right-side request editor via the context menu:
- Right-click any request in Proxy history, Repeater, Logger, etc.
- Choose "Send to Param Unwrapper" (this acts as the “load” action)
What “load” does:
- Copies the selected request into the Param Unwrapper request editor (right side)
- Leaves your rules/candidates untouched until you "Parse"
- Click Add on the left panel to create a new rule.
- Set a descriptive name and leave Enabled checked.
| Option | Description |
|---|---|
| Burp parameter by name | A specific query/body/cookie parameter (e.g. data) |
| Whole request body | The entire raw request body |
The codec chain is a sequence of decode steps applied top-to-bottom when parsing/unwrapping.
When the scanner injects payloads, Param Unwrapper automatically re-encodes using the reverse chain (bottom-to-top), so the outbound request preserves the original encoding layers.
Common steps:
- Base64 Decode (decode) / Base64 encode (re-encode)
- URL Decode (decode) / URL encode (re-encode)
- HTML Decode
- Unicode Escape
You can chain them, for example:
- URL Decode → Base64 Decode
(useful when the Base64 text itself is URL-encoded inside a parameter)
4 — Parse and build a profile (Parse / Load list / Clear / Delete behavior)
With a request loaded and a rule selected, click "Parse".
"Parse" automatically searches the decoded container for injection points. The extension:
- Extracts the container (parameter value or body)
- Decodes it via the codec chain
- Parses the decoded content
- Populates the "Insertion points" table with up to 1024 entries
| Insertion points type | Description |
|---|---|
| Value | A scalar leaf field; payload replaces its value |
| Key rename | An object/form key; payload becomes the new key name |
| Whole body | The entire decoded container; payload replaces it entirely |
If you prefer not to auto-discover candidates, you can build a manual "Include list" and click "Load list".
"Load list" populates the "Insertion points" table only from the items currently in the Include list. It does not run automatic discovery.
Typical uses:
- You already know the exact fields/paths you want to target
- You want a minimal, deterministic set of insertion points
- You want to avoid “extra” candidates found by parsing
The "Insertion points" table is automatically saved on every change (selection, edits, add/remove entries).
You can:
- Use the "✓ checkbox" to include/exclude candidates
- Edit an "Identifier" cell directly (e.g. adjust a JSON Pointer, key name, etc.)
- Use "Add entry" to add candidates manually
"Clear" resets the current parse/list results in the UI (i.e., the Candidates/profile view) so you can start fresh.
Typical uses:
- You changed the rule configuration and want to rebuild the profile cleanly
- You loaded a new request and don’t want to keep prior candidates visible
"Delete" removes the currently selected rule (and its associated stored candidates/profile) from the rules list.
- If you want to keep a rule but temporarily ignore it, use "Enabled" instead of deleting.
When Burp Scanner actively scans a request that matches an enabled rule, Param Unwrapper:
- Builds insertion points from the current (auto-saved) "Insertion points" selection
- Applies payload mutations (VALUE / KEY / WHOLE_BODY)
- Re-encodes using the reverse codec chain
- Sends modified requests so Burp can detect issues inside previously-wrapped content
When a request is displayed in an HTTP editor (Repeater, Proxy history, etc.) a "Param Unwrapper" tab will appear automatically if a rule matches.
The tab shows:
- Which rule matched
- Pretty-printed decoded content
- All detected inner parameters and their current values
- The matched rule during automated scan and how the payload was injected (useful for validating that unwrapping worked)
Some wrapped formats are not pure JSON/XML/form, or you may need to extract/unwrap values from custom “function-call-like” formats.
Custom regex rules are intended for cases where:
- The container is a string that needs parsing via patterns
- You need to capture specific parts and treat them like candidates for injection
- You need predictable extraction and reconstruction behavior
A custom regex rule:
- Matches the wrapped container string
- Extracts one or more groups (captures) as “inner values”
- Treats those inner values as candidates for injection
- When injecting, it rebuilds the container string by replacing the appropriate capture group(s) while preserving the rest
- Prefer anchored patterns (
^...$) to avoid unexpected partial matches. - Use "non-greedy" quantifiers where needed (
.*?) to avoid over-capturing. - The named capture groups must be called "value" -
(?<value>...), to capture the parameters. - Always test regex rules against realistic traffic (see examples below).
Examples (trial website from ParamUnwrapperTests - https://github.com/misiungs/ParamUnwrapperTests/tree/main)
Request:
POST /endpoint1/execute HTTP/1.1
Host: localhost:8090
Content-Length: 77
sec-ch-ua-platform: "Linux"
Accept-Language: en-US,en;q=0.9
sec-ch-ua: "Chromium";v="145", "Not:A-Brand";v="99"
Content-Type: application/json
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36
Accept: */*
Origin: http://localhost:8090
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:8090/endpoint1.html
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
{"username":"user_234","data":"eyJrZXkxIjoidmFsdWUxIiwia2V5MiI6InZhbHVlMiJ9"}
data contains a Base64-encoded JSON document (nested parameters).
Figures:
- Endpoint 1 — send request to Param Unwrapper

- Endpoint 1 — properly configured unwrapping rule

- Endpoint 1 — Message editor tab shows matched rule during scan with injected payload

- Endpoint 1 — detected by scanner: XSS inside unwrapped parameter

Request:
GET /endpoint2/execute?test=a2V5MT12YWx1ZTEma2V5Mj1oZWxsbw%3D%3D HTTP/1.1
Host: localhost:8090
sec-ch-ua-platform: "Linux"
Accept-Language: en-US,en;q=0.9
sec-ch-ua: "Chromium";v="145", "Not:A-Brand";v="99"
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36
sec-ch-ua-mobile: ?0
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:8090/endpoint2.html
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
test contains a Base64-encoded x-www-form-urlencoded payload.
Figures:
- Endpoint 2 — properly configured unwrapping rule

- Endpoint 2 — detected by scanner: command injection and XSS inside unwrapped parameter

Request:
POST /endpoint3/execute?data=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPG5vdGU%2BCiAgPGhlYWRpbmc%2BVGVzdCBIZWFkaW5nPC9oZWFkaW5nPgogIDxib2R5PlRlc3QgQm9keTwvYm9keT4KPC9ub3RlPg%3D%3D HTTP/1.1
Host: localhost:8090
Content-Length: 0
sec-ch-ua-platform: "Linux"
Accept-Language: en-US,en;q=0.9
sec-ch-ua: "Chromium";v="145", "Not:A-Brand";v="99"
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36
sec-ch-ua-mobile: ?0
Accept: */*
Origin: http://localhost:8090
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:8090/endpoint3.html
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
data contains a Base64-encoded XML payload.
Figures:
This example demonstrates a case where the wrapped value looks like a function call, and must be parsed using regex rules.
Request:
GET /endpoint4/execute?call=magicFunction%28test1%2Ctest2%29 HTTP/1.1
Host: localhost:8090
sec-ch-ua-platform: "Linux"
Accept-Language: en-US,en;q=0.9
sec-ch-ua: "Chromium";v="145", "Not:A-Brand";v="99"
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36
sec-ch-ua-mobile: ?0
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:8090/endpoint4.html
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
After URL-decoding, the call parameter becomes:
magicFunction(test1,test2)
Goal: treat test1 and/or test2 as injectable candidates while preserving:
- the function name
magicFunction - parentheses and commas
A typical strategy is:
- Match the full string
- Capture each argument separately
Example conceptual pattern:
- test1 ->
^magicFunction\((?<value>[^,)]*) - test2 ->
^magicFunction\([^,)]*,(?<value>[^)]*)
Then:
- Candidate identifiers can represent "test1" and "test2"
- Injecting into
?<value>replaces only that capture group while keeping the rest intact - Re-encoding URL encoding preserves the original transport encoding
Figures:
GPL-3.0 — see LICENSE.



