What the report looks like

The document you receive at the end of an engagement, shown on a fictional target. Every name, host and finding on this page is made up.

Sample report on a fictional target. Not a client, not a real finding.

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.

Findings by severity
HighMediumLow
121

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

All findings with severity and retest result
IDFindingSeverityMaps toRetest
F-01Org A user can edit Org B invoices (IDOR)HighOWASP API1:2023Closed
F-02No attempt limit on the login endpointMediumOWASP API2:2023Closed
F-03Session cookie sent without the Secure flagMediumOWASP A05:2021Closed
F-04Server version shown in response headersLowOWASP A05:2021Accepted 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
  1. Sign in as user-b (Org B) and note the id of one of its invoices, here 8812.
  2. Sign in as user-a (Org A).
  3. 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.

A real report covers your own scope and test window. A critical finding is raised with you as soon as it is confirmed, not saved for the report. How an engagement runs

Want a report like this for your system?

A short call, then a signed scope. No testing before that.

Request a scoping call