Authorization disclaimer. This walkthrough is limited to the intentionally vulnerable PortSwigger Web Security Academy lab linked below. Test only systems you own or are explicitly authorized to assess.

Overview

The product page calls GET /api/products/1/price, but the route also accepts an unused PATCH method. An ordinary user can submit {"price":0} and then order the jacket for $0.00.

The lab requires purchasing the jacket by finding the hidden `PATCH /api/products/1/price` operation.
The lab requires purchasing the jacket by finding the hidden `PATCH /api/products/1/price` operation.

Lab objective/setup

Sign in as wiener, browse product 1, and inspect the price request. The target route is /api/products/1/price; the objective is to identify its unadvertised write method, set the price to zero, and place the order.

What I observed

Burp history shows the storefront reading /api/products/1/price. Unsupported-method responses disclose Allow: GET, PATCH, while a bare PATCH reaches content-type validation instead of method rejection.

The storefront normally uses only `GET /api/products/1/price`.
The storefront normally uses only `GET /api/products/1/price`.
A `POST` probe is rejected with `405`, but the `Allow` header discloses the hidden `PATCH` method.
A `POST` probe is rejected with `405`, but the `Allow` header discloses the hidden `PATCH` method.
`PUT` is also rejected, confirming that the available methods are specifically `GET` and `PATCH`.
`PUT` is also rejected, confirming that the available methods are specifically `GET` and `PATCH`.

Why it is vulnerable

Removing a feature from the client does not remove the underlying API capability; authorization and validation must be enforced by the endpoint itself.

Walkthrough in the authorized PortSwigger lab

  1. Capture GET /api/products/1/price and send it to Repeater.
  2. Try POST and PUT; both return 405 with Allow: GET, PATCH.
  3. Try PATCH; the content-type error confirms that the handler exists and expects JSON.
  4. Send PATCH /api/products/1/price with Content-Type: application/json and {"price":0}.
  5. Verify the $0.00 product price, add the jacket to the cart, and place the order.
1
2
3
POST  /api/products/1/price → 405 Method Not Allowed
PUT /api/products/1/price → 405 Method Not Allowed
PATCH /api/products/1/price → 400 Bad Request ("Only 'application/json' Content-Type is supported")
1
2
3
Content-Type: application/json

{"price": 0}
The hidden `PATCH` handler is reachable and rejects only the missing JSON content type.
The hidden `PATCH` handler is reachable and rejects only the missing JSON content type.
The baseline API response reports the jacket price as `$1337.00`.
The baseline API response reports the jacket price as `$1337.00`.
An authenticated ordinary user sets `price` to `0`; the API returns `200 OK` with `$0.00`.
An authenticated ordinary user sets `price` to `0`; the API returns `200 OK` with `$0.00`.
The storefront now displays the Lightweight l33t Leather Jacket at `$0.00`.
The storefront now displays the Lightweight l33t Leather Jacket at `$0.00`.
The cart accepts the modified `productId` 1 price and shows a `$0.00` total ready for checkout.
The cart accepts the modified `productId` 1 price and shows a `$0.00` total ready for checkout.

Verification

After the zero-price jacket order is placed, the Academy changes the lab state to solved.

The Academy marks the unused API endpoint lab as solved after the zero-price order.
The Academy marks the unused API endpoint lab as solved after the zero-price order.

Impact and defensive lesson

An unused client feature can remain a live server-side privilege boundary. Inventory every method on every deployed route, require administrative authorization for catalog changes, validate writable fields, and test operations that the current UI does not call.

References