Crafting a Burp Suite Extension for Mass Assignment Detection
Detecting mass assignment vulnerabilities in APIs manually is often a tedious process, requiring constant parameter manipulation and observation. This post details the development of a custom Burp Suite extension designed to automate the initial discovery of these flaws, specifically targeting JSON-based API endpoints. The goal is to intercept relevant HTTP requests, inject common sensitive parameters into the request body, and analyze the server's response for potential indication of successful assignment.Understanding Mass Assignment in APIs
Mass assignment, also known as "object injection" or "auto-binding," occurs when an application automatically converts client-supplied data (e.g., JSON, form data) into internal object properties without proper filtering or whitelisting. This can allow an attacker to modify properties that were not intended to be exposed or changed, such as `isAdmin`, `user_role`, `account_balance`, or `verified`. An API endpoint designed to update a user's `name` and `email` might inadvertently accept and process an `isAdmin: true` parameter if not explicitly handled. Identifying these during reconnaissance with tools like Zondex for exposed services can streamline the testing process significantly.Setting Up the Burp Extension Environment
To develop a Burp Suite extension in Python, you'll need a Jython standalone JAR.Jython Interpreter Configuration
1. Download the latest Jython standalone JAR. 2. In Burp Suite, navigate to `Extender -> Options -> Python Environment`. 3. Set the "Location of Jython standalone JAR file" to the path of your downloaded JAR. 4. Add the directory where you'll store your Python extension script to the "Folders for loading modules."
The Extension Blueprint: IBurpExtender and IHttpListener
Every Burp Suite extension in Python must implement the `IBurpExtender` interface. For our mass assignment detector, we also need to implement `IHttpListener` to intercept and modify HTTP traffic.IBurpExtender Implementation
This interface defines the entry point for your extension. The `registerExtenderCallbacks` method is crucial, providing access to Burp's API. We'll use this to register our HTTP listener, set the extension name, and get helper functions.
IHttpListener for Interception
The `processHttpMessage` method is the heart of our detection logic. Burp calls this method for every HTTP request and response. We'll examine requests here, modify them, and analyze subsequent responses.
Core Logic: Parameter Tampering and Response Analysis
The extension's effectiveness hinges on intelligently injecting parameters and accurately interpreting the server's reaction.Identifying Target Requests
We're primarily interested in requests that modify data, typically `POST`, `PUT`, or `PATCH` requests to API endpoints, especially those with JSON request bodies. We filter out static file requests and other noise to focus on relevant targets. The use of a proxy like GProxy can help manage and route specific traffic through Burp for more focused analysis.
Crafting Malicious Payloads
For each identified request, we parse its JSON body. We then construct a new JSON body by adding a set of predefined, common "sensitive" parameters (e.g., `isAdmin`, `role`, `user_id`, `price`, `accountStatus`) with various values (e.g., `true`, `admin`, `1`, `active`). A baseline request without our injected parameters but with identical original parameters is also sent to establish a control.
Analyzing API Responses
After sending the modified request, we compare its response to the baseline response. Key indicators of potential mass assignment include:
- A
200 OKstatus code when the original request received a different status, or the modified request received a200 OKinstead of an error. - Significant changes in response body length compared to the baseline, suggesting new data was processed.
- Specific sensitive data appearing in the response (e.g., `isAdmin: true` in a profile update response after injecting `isAdmin: true`).
- Error messages that indicate an attempt to set an unauthorized property, which can still be a finding.
Extension Code Walkthrough
This Python script for Burp Suite demonstrates a practical approach to detecting mass assignment.
import json
from burp import IBurpExtender
from burp import IHttpListener
from burp import IHttpRequestResponse
from burp import IExtensionHelpers
class BurpExtender(IBurpExtender, IHttpListener):
EXTENSION_NAME = "Mass Assignment Detector"
SENSITIVE_PARAMS = [
("isAdmin", True), ("isAdmin", 1), ("isAdmin", "true"),
("is_admin", True), ("is_admin", 1), ("is_admin", "true"),
("role", "admin"), ("role", "administrator"), ("role", "super_admin"),
("user_id", 1), ("user_id", "1"),
("accountStatus", "active"), ("accountStatus", "verified"),
("price", 0), ("price", 1000000),
("isPremium", True), ("isPremium", 1),
("active", True), ("active", 1),
("userType", "privileged"), ("userType", "staff")
]
SCOPE_ONLY = True # Set to True to only process in-scope items
def registerExtenderCallbacks(self, callbacks):
self.callbacks = callbacks
self.helpers = callbacks.getHelpers()
callbacks.setExtensionName(self.EXTENSION_NAME)
callbacks.registerHttpListener(self)
callbacks.issueAlert("Mass Assignment Detector loaded successfully.")
print(f"{self.EXTENSION_NAME} loaded.")
def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
# We only care about requests
if not messageIsRequest:
return
# Only process requests if in scope and not from other Burp tools (e.g., Scanner, Intruder)
# 16 is BSS_TOOL_PROXY, 4 is BSS_TOOL_REPEATER
if toolFlag not in (self.callbacks.TOOL_PROXY, self.callbacks.TOOL_REPEATER):
return
if self.SCOPE_ONLY and not self.callbacks.isInScope(messageInfo.getUrl()):
return
requestInfo = self.helpers.analyzeRequest(messageInfo)
content_type = requestInfo.getContentType()
method = requestInfo.getMethod()
# Target JSON POST/PUT/PATCH requests
if method in ("POST", "PUT", "PATCH") and content_type == IExtensionHelpers.CONTENT_TYPE_JSON:
httpService = messageInfo.getHttpService()
requestBytes = messageInfo.getRequest()
# Extract request body
requestBodyOffset = requestInfo.getBodyOffset()
requestBody = self.helpers.bytesToString(requestBytes[requestBodyOffset:])
try:
original_json_data = json.loads(requestBody)
except ValueError:
# Not valid JSON, skip
return
print(f"[*] Processing request to: {messageInfo.getUrl()}")
self.callbacks.issueAlert(f"[*] Processing potential mass assignment target: {messageInfo.getUrl()}")
# --- Step 1: Send a baseline request (original body + dummy param) ---
# This helps differentiate actual mass assignment from general error handling
baseline_json_data = dict(original_json_data)
baseline_json_data["_dummy_param_"] = "test" # Add a harmless dummy param
baseline_body = self.helpers.stringToBytes(json.dumps(baseline_json_data))
baseline_request = self.helpers.buildHttpMessage(
self.helpers.buildRequestHeaders(requestInfo),
baseline_body
)
baseline_response_info = self.callbacks.makeHttpRequest(httpService, baseline_request)
baseline_response = self.helpers.bytesToString(baseline_response_info.getResponse())
baseline_response_analyzed = self.helpers.analyzeResponse(baseline_response_info.getResponse())
baseline_status_code = baseline_response_analyzed.getStatusCode()
baseline_body_len = len(self.helpers.bytesToString(baseline_response_info.getResponse()[baseline_response_analyzed.getBodyOffset():]))
# --- Step 2: Iterate and inject sensitive parameters ---
for param_name, param_value in self.SENSITIVE_PARAMS:
modified_json_data = dict(original_json_data)
modified_json_data[param_name] = param_value
modified_body = self.helpers.stringToBytes(json.dumps(modified_json_data))
# Build the new request
modified_request = self.helpers.buildHttpMessage(
self.helpers.buildRequestHeaders(requestInfo),
modified_body
)
# Send the modified request
response_info = self.callbacks.makeHttpRequest(httpService, modified_request)
response = self.helpers.bytesToString(response_info.getResponse())
# Analyze response
response_analyzed = self.helpers.analyzeResponse(response_info.getResponse())
status_code = response_analyzed.getStatusCode()
response_body_len = len(self.helpers.bytesToString(response_info.getResponse()[response_analyzed.getBodyOffset():]))
# --- Step 3: Compare and Report ---
if status_code == 200 and param_name in response:
alert_message = f"Mass Assignment Vulnerability Likely: '{param_name}' set to '{param_value}' and reflected in response at {messageInfo.getUrl()}."
self.callbacks.issueAlert(alert_message)
print(f"[!] {alert_message}")
# Optionally, log the request/response pair for manual review
self.callbacks.addScanIssue(
self.create_mass_assignment_issue(
messageInfo.getHttpService(),
messageInfo.getUrl(),
[messageInfo.getRequest(), modified_request, response_info.getResponse()],
alert_message,
"High"
)
)
elif status_code != baseline_status_code:
alert_message = (f"Mass Assignment Potential: Status code changed from {baseline_status_code} "
f"to {status_code} for '{param_name}':'{param_value}' at {messageInfo.getUrl()}.")
self.callbacks.issueAlert(alert_message)
print(f"[?] {alert_message}")
self.callbacks.addScanIssue(
self.create_mass_assignment_issue(
messageInfo.getHttpService(),
messageInfo.getUrl(),
[messageInfo.getRequest(), modified_request, response_info.getResponse()],
alert_message,
"Medium"
)
)
elif abs(response_body_len - baseline_body_len) > 20: # Arbitrary threshold for body length change
alert_message = (f"Mass Assignment Potential: Response body length changed significantly "
f"for '{param_name}':'{param_value}' at {messageInfo.getUrl()}. "
f"(Baseline: {baseline_body_len}, Modified: {response_body_len})")
self.callbacks.issueAlert(alert_message)
print(f"[?] {alert_message}")
self.callbacks.addScanIssue(
self.create_mass_assignment_issue(
messageInfo.getHttpService(),
messageInfo.getUrl(),
[messageInfo.getRequest(), modified_request, response_info.getResponse()],
alert_message,
"Low"
)
)
# You could add more sophisticated checks here, like diffing JSON responses
def create_mass_assignment_issue(self, http_service, url, http_messages, issue_detail, severity):
return CustomScanIssue(
http_service,
url,
http_messages,
self.EXTENSION_NAME,
"Mass Assignment Vulnerability",
issue_detail,
severity,
"Certain API endpoints may allow unauthorized modification of sensitive object properties by accepting unexpected parameters. The application should explicitly whitelist allowed parameters.",
None
)
# Custom Scan Issue Class for reporting
class CustomScanIssue(self.callbacks.getHelpers().createCustomScanIssue):
def __init__(self, httpService, url, httpMessages, name, issueName, issueDetail, severity, confidence, background):
self.httpService = httpService
self.url = url
self.httpMessages = httpMessages
self.name = name
self.issueName = issueName
self.issueDetail = issueDetail
self.severity = severity
self.confidence = confidence
self.background = background
def getUrl(self):
return self.url
def getIssueName(self):
return self.issueName
def getIssueType(self):
return 0 # A generic issue type
def getSeverity(self):
return self.severity
def getConfidence(self):
return "Certain" # Or other appropriate level
def getIssueBackground(self):
return self.background
def getRemediationBackground(self):
return "Implement strict input validation and whitelist acceptable parameters on the server side."
def getIssueDetail(self):
return self.issueDetail
def getRemediationDetail(self):
return None
def getHttpMessages(self):
return self.httpMessages
def getHttpService(self):
return self.httpService
Imports and Global Setup
We import necessary Burp API interfaces and `json` for parsing. `SENSITIVE_PARAMS` holds tuples of parameter names and values to inject. The `SCOPE_ONLY` flag helps limit the extension's activity to in-scope items.
BurpExtender Class Structure
The `registerExtenderCallbacks` method initializes `callbacks` and `helpers` objects, sets the extension name, and registers the HTTP listener. It also prints an alert to the Burp UI and console.
processHttpMessage in Detail
1. **Filtering**: The method first checks if the message is a request, if it's from the Proxy or Repeater tools, and if it's in scope. 2. **Request Analysis**: It analyzes the request to get the method and content type. We specifically target `POST`, `PUT`, or `PATCH` requests with `application/json` content. 3. **JSON Parsing**: The request body is extracted and parsed as JSON. If parsing fails, the request is skipped. 4. **Baseline Request**: A modified request is created by adding a harmless `_dummy_param_` to the original JSON. This baseline is sent, and its response status code and body length are recorded. This helps establish a control for comparison. 5. **Parameter Injection Loop**: For each sensitive parameter in `SENSITIVE_PARAMS`, a new JSON payload is constructed by adding the sensitive parameter and its value to the original request's body. 6. **Sending Modified Request**: The new request is built using `self.helpers.buildHttpMessage` and sent via `self.callbacks.makeHttpRequest`. 7. **Response Comparison**: The response to the injected request is analyzed. * If a `200 OK` is returned and the injected parameter is reflected in the response body, it's a high-confidence finding. * If the status code changes significantly from the baseline, it's flagged as a medium-confidence finding. * If the response body length changes significantly (here, an arbitrary threshold of 20 bytes), it suggests a low-confidence finding for further investigation. * Each finding generates a Burp alert and a new entry in the "Scanner Issues" tab, providing context and severity. Tools like Secably can then import such issues from Burp reports for comprehensive vulnerability management.
Deployment and Testing
1. Save the code as a Python file (e.g., `mass_assignment_detector.py`). 2. In Burp Suite, go to `Extender -> Extensions`. 3. Click "Add," select "Python" as the extension type, and choose your `.py` file. 4. Ensure the output in the "Output" tab indicates successful loading. 5. Browse an application, especially one with JSON-based API interactions (e.g., user profile updates, settings changes). Use Burp's Proxy to intercept traffic. The extension will automatically process relevant requests and report findings in the "Alerts" tab and "Scanner Issues."