Administration panel reached by rewriting a 403 in the response
The administration endpoint returned 403 with the full invoice list in the body. Rewriting the status code to 200 was enough to read it.
Summary
The portal enforced the boundary between user and administrator entirely in the browser. The administration view asked the server for the invoice list and, on a 403 Forbidden, showed an insufficient permissions notice.
The body of that 403 carried the full invoice list. The control sat in the status code, not in what the server chose to send.
Technical details
Observed behaviour
The invoice listing request returned 403, but with a body of several hundred kilobytes:
GET /api/admin/invoices?company=[REDACTED] HTTP/2
Cookie: [UNPRIVILEGED USER SESSION]
HTTP/2 403 Forbidden
Content-Type: application/json
Content-Length: 412874
{"error":"forbidden","data":{"invoices":[ ... 3,100 records ... ]}}Confirming the flaw
We intercepted the response and changed only the status line, leaving the body untouched. The application rendered the full panel:
HTTP/2 200 OK # the only change
Content-Type: application/json
{"error":"forbidden","data":{"invoices":[ ... ]}}
[+] Administration panel rendered
[+] Full billing visible, including other companiesScope of access
The company parameter was never checked against the session either. The listing was not limited to the authenticated user’s organisation:
GET /api/admin/invoices?company=[DIFFERENT IDENTIFIER]
HTTP/2 403 Forbidden -> body carrying another company’s invoices
[!] The company identifier was not checked against the sessionWe worked with two accounts provided by the client and a company identifier linked to them. We did not download or retain any third-party documents. We reported the finding the same day. We publish this analysis anonymised, with the client’s prior authorisation.
Impact
- Administration panel access from an unprivileged account
- Read access to the organisation’s full billing history from that same account
- Exposure of other companies’ data on the portal, because the identifier was not checked against the session
- Sensitive business information: billing records, contract terms and the client list
- GDPR exposure from processing third-party billing data
Remediation
- Authorise on the server, not in the browser. If the user has no permission, the response must not contain the resource, not even inside an error body.
- Return empty bodies on authorisation errors. A 403 should carry a generic message and nothing else.
- Check every identifier in the request against the session. None of them can be trusted as it arrives from the browser.
- Review the remaining administrative endpoints for the same pattern: this kind of flaw usually lives in a shared layer.
- Add authorisation tests to the automated suite, with an unprivileged account per role.
References
More write-ups
Check whether your administrative endpoints authorise on the server
Tell us which assets you want tested. We reply in under 24 hours.