Penetration test report, version 1.1 (updated after retest)
Invoicing app: web application and API
- Target
- app.invoicing.test and /api/*
- Accounts
- user-a (Org A), user-b (Org B), both test accounts
- Out of scope
- Denial of service, third-party services, other hosts
- Authorization
- Signed by the system owner before any test traffic
1. Summary for management
We tested app.invoicing.test and its API with two test accounts in different organisations. We found four issues. The serious one let a user in one organisation change another organisation's invoices by changing a number in the request. Fix that first.
After the client applied the fixes we tested again. Three findings are closed. The fourth is low risk and the client has accepted it for now.
| High | Medium | Low |
|---|---|---|
| 1 | 2 | 1 |
2. Scope and method
Manual testing backed by tools, as user-a and user-b and without a session. Every finding was confirmed by hand. Nothing was changed or deleted beyond the two test accounts, and no denial-of-service test was run. Findings are mapped to the OWASP Top 10 or the OWASP API Security Top 10 where they apply.
3. Findings
| ID | Finding | Severity | Maps to | Retest |
|---|---|---|---|---|
| F-01 | Org A user can edit Org B invoices (IDOR) | High | OWASP API1:2023 | Closed |
| F-02 | No attempt limit on the login endpoint | Medium | OWASP API2:2023 | Closed |
| F-03 | Session cookie sent without the Secure flag | Medium | OWASP A05:2021 | Closed |
| F-04 | Server version shown in response headers | Low | OWASP A05:2021 | Accepted risk |
4. F-01 in full
Each finding in the report has these parts. The other three are left out here.
- Severity
- High
- Where
PATCH /api/invoices/{id}- Maps to
- OWASP API1:2023, Broken Object Level Authorization
- Issue
- The API loads an invoice by its id without checking which organisation the caller belongs to. Any signed-in user can read and edit another organisation's invoice by changing the id.
- Impact
- A user of one organisation can change amounts, due dates and status on another organisation's invoices.
- Reproduce
- Sign in as user-b (Org B) and note the id of one of its invoices, here 8812.
- Sign in as user-a (Org A).
- Send the request below with user-a's session.
- Evidence
PATCH /api/invoices/8812 HTTP/1.1 Host: app.invoicing.test Cookie: session=<user-a> Content-Type: application/json {"status": "paid"} HTTP/1.1 200 OK {"id": 8812, "org": "Org B", "status": "paid"}- Fix
- Load the invoice with both the id and the caller's organisation in the query, and return 404 when no row matches. Apply the same check to every route that takes an invoice id.
- Retest
- Same request repeated after the fix:
404 Not Found, invoice unchanged. Closed
5. Retest
Each finding was tested again with the original request after the fix. The report was updated to version 1.1: F-01, F-02 and F-03 closed, F-04 recorded as accepted by the client.