ACME Corporation
Customer Portal & API Security Engagement
- Test window
- 23 Mar – 16 Apr 2026
- Test type
- Grey-box web & API
- Targets
- portal.acme.example · api.acme.example
- Standards
- OWASP WSTG · PTES · OSSTMM
- Specialists
- 2 (certified)
- Classification
- TLP:AMBER+STRICT
1Executive summary
Background
OffSeq performed a grey-box security assessment of the ACME Customer Portal and its public API. Testing combined automated scanning with manual exploitation; the authenticated portion was reached via temporary portal credentials provisioned by the client.
Key findings
- A critical, unauthenticated SQL injection in the login API allows full authentication bypass and read access to the user store.
- A broken access control flaw (IDOR) lets any authenticated user download every customer’s invoices — a mass data-exposure path.
- Defence-in-depth gaps: no Content Security Policy, HSTS below one year, and an end-of-life React bundle in production.
Priority recommendations
- Parameterise all database access and add least-privilege DB accounts; treat the SQL injection as a P1 incident.
- Enforce object-level authorization on every API resource and switch to non-enumerable identifiers.
- Roll out a CSP (report-only → enforce), raise HSTS to ≥ 1 year with preload, and plan the React migration.
At a glance
Six findings were identified: one (1) Critical, one (1) High, one (1) Medium, two (2) Low and one (1) Informational. The Critical and High findings chain directly into mass exposure of customer PII and financial records.
2Vulnerability summary
| Severity | Found | Acceptable | Verdict |
|---|---|---|---|
| Critical | 1 | 0 | Unacceptable |
| High | 1 | ≤ 1 | Within tolerance |
| Medium | 1 | ≤ 3 | Within tolerance |
| Low | 2 | ≤ 7 | Within tolerance |
| Info | 1 | — | — |
One Critical finding exceeds the acceptable threshold, driving an overall “remediation required” verdict regardless of the other categories.
3OWASP Top 10 (2025) coverage
- A01:2025 Broken Access Control IDOR on /v1/invoices Fail
- A02:2025 Security Misconfiguration Missing CSP, short HSTS Fail
- A03:2025 Software Supply Chain Failures End-of-life React 16.13.1 Fail
- A04:2025 Cryptographic Failures Pass
- A05:2025 Injection SQL injection in auth API Fail
- A06:2025 Insecure Design Pass
- A07:2025 Authentication Failures Pass
- A08:2025 Software or Data Integrity Failures Pass
- A09:2025 Logging & Alerting Failures Out of scope this engagement N/A
- A10:2025 Mishandling of Exceptional Conditions Pass
4Reconnaissance
Passive reconnaissance catalogued the portal’s public attack surface. Three subdomains resolve to the portal origin behind CloudFlare; one administrative host resolves directly, bypassing the WAF entirely.
| Host | IP | Note |
|---|---|---|
| portal.acme.example | 203.0.113.10 | Primary portal · behind CloudFlare |
| api.acme.example | 203.0.113.11 | Public API |
| admin.acme.example | 203.0.113.12 | Direct origin — bypasses WAF |
| status.acme.example | 203.0.113.13 | Status page · verbose build metadata |
- The status page publishes verbose build metadata (git SHA, build date).
- Two staging subdomains (stg-*) were live during the test window.
5Findings
Six representative findings, ordered by severity. Each is reproducible and mapped to a fix.
No findings match this filter.
- OS-01
Unauthenticated SQL injection in the portal authentication API
Critical Open A05:2025 CWE-899.8CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HCVSS 3.1The `email` field of the login endpoint is concatenated directly into a SQL query with no parameterisation. A time-based blind injection confirms the flaw and allows both authentication bypass and arbitrary read access to the `users` table — including e-mail addresses, roles and bcrypt password hashes.
Affected
- POST https://api.acme.example/v1/auth/login
Impact
An unauthenticated attacker can log in as any user (including administrators) and exfiltrate the entire customer identity store. This is a single-request path to full account takeover and a reportable personal-data breach under the GDPR.
Technical detail
A 5-second response delay on the payload below, versus an immediate response on the control, confirms time-based blind injection:
HTTP request
POST /v1/auth/login HTTP/2 Host: api.acme.example Content-Type: application/json {"email":"x' OR (SELECT 1 FROM (SELECT SLEEP(5))a)-- -","password":"x"}HTTP response
HTTP/2 200 content-type: application/json x-response-time: 5024ms {"token":"eyJhbG...","role":"admin"}Remediation
Replace all string-built queries with parameterised statements / prepared queries. Run the application under a least-privilege database account, add a positive-validation input filter, and deploy a WAF rule as a compensating control until the fix ships. Rotate all credentials that may have been exposed.
sqlmap identified the following injection point: Parameter: email (JSON) Type: time-based blind Title: MySQL >= 5.0.12 AND time-based blind (SLEEP) available databases [2]: acme_portal, information_schema Database: acme_portal Table: users [48,213 entries]sqlmap confirmation (redacted) - OS-02
Broken access control exposes every customer’s invoices (IDOR)
High Open A01:2025 CWE-6398.1CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:NCVSS 3.1Invoice records are addressed by a sequential integer ID and served without any ownership check. Any authenticated user can increment the identifier to enumerate and download invoices belonging to other customers, each containing names, addresses and payment summaries.
Affected
- GET https://api.acme.example/v1/invoices/{id}
Impact
A single low-privileged account can harvest the full invoice history of the customer base — a large-scale exposure of personal and financial data, and a GDPR-reportable breach.
HTTP request
GET /v1/invoices/100245 HTTP/2 Host: api.acme.example Authorization: Bearer <attacker-session>HTTP response
HTTP/2 200 content-type: application/pdf content-disposition: attachment; filename="invoice-100245.pdf" %PDF-1.7 (invoice for a *different* customer)Remediation
Enforce object-level authorization on every request — verify the authenticated principal owns the requested resource server-side. Replace sequential IDs with non-enumerable identifiers (UUIDv4) and apply deny-by-default access control.
for id in $(seq 100240 100260); do curl -s -o "inv-$id.pdf" -w "%{http_code}\n" \ -H "Authorization: Bearer $T" \ https://api.acme.example/v1/invoices/$id done # 21/21 returned HTTP 200 — none belonged to the test account.Enumeration loop (proof of scale) - OS-03
Content Security Policy (CSP) is not implemented
Medium Hardening required A02:2025 CWE-6934.0CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:NCVSS 3.1The application returns no `Content-Security-Policy` header on any page observed during testing.
Affected
- https://portal.acme.example/
- https://portal.acme.example/users/sign_in
- https://portal.acme.example/admin/*
Impact
Without a CSP the browser cannot constrain reflected or stored XSS, third-party script loading from a compromised CDN, or data exfiltration after a successful injection. It removes a key layer of defence-in-depth.
HTTP response
HTTP/2 200 content-type: text/html; charset=utf-8 strict-transport-security: max-age=15552000 x-frame-options: DENY x-content-type-options: nosniff (no Content-Security-Policy header)Remediation
Roll out a strict CSP starting on the sign-in and admin pages, beginning in `Content-Security-Policy-Report-Only` mode, then enforce. Avoid `unsafe-inline` for scripts; prefer nonces or hashes.
- OS-04
Outdated JavaScript library — React 16.13.1
Low Recommended update A03:2025 CWE-1104The production bundle ships React 16.13.1 (March 2020), well past its supported lifecycle. While it has no known direct CVEs, end-of-life frameworks tend to drag older transitive dependencies with known issues.
Affected
- https://portal.acme.example/assets/application-90283b17.js
Impact
Running an unsupported framework increases the likelihood of an unpatched vulnerability entering production via a transitive dependency.
Remediation
Adopt continuous dependency review (Dependabot / Renovate), add software composition analysis (SCA) to CI, and plan a migration to React 18/19.
References
- OS-05
HSTS max-age is below one year
Low Hardening required A02:2025 CWE-16The `Strict-Transport-Security` header is sent with `max-age=15552000` (180 days), below the one-year minimum recommended for HSTS preload.
Affected
- https://portal.acme.example/
Impact
A short `max-age` shortens the window in which the browser refuses plain HTTP, marginally widening the opportunity for an SSL-stripping attack.
Remediation
Set `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload` and submit the domain to the HSTS preload list.
References
- OS-06
Publicly accessible recursive DNS resolver
Info InformationalThe portal host answers recursive DNS queries for foreign zones from unauthorised source addresses (RD+RA flags set).
Affected
- 203.0.113.10:53
Impact
Open resolvers can be abused as amplifiers in reflected DDoS attacks against third parties. No direct risk to ACME data, recorded for completeness.
Remediation
Restrict UDP/TCP 53 at the perimeter to authorised clients, or disable recursion for the public interface.
Attack path
How the findings chain into a breach
Individual findings matter less than what they enable together. This is the sequence we demonstrated — from mapping the surface to mass data exposure.
Reconnaissance
Map the surface
Catalogued the portal, public API and an admin host resolving directly — bypassing the CloudFlare WAF.
admin.acme.example exposed
Initial access
Bypass authentication
A blind SQL injection in the login API returns a valid admin session with no credentials.
Auth bypass · CVSS 9.8
Credential access
Dump the user store
The same injection reads the users table — e-mails, roles and password hashes — straight from the database.
48k records exposed
Broken access control
Walk the invoices
Sequential invoice IDs with no ownership check let any session enumerate and download every customer’s invoice.
Mass PII / financial data
Impact
Exfiltrate at scale
Combined, these give a single-request foothold and a scripted path to exfiltrate the customer base.
GDPR-reportable breach
Demonstrated chain · OS-01 → OS-02 · grey-box engagement, April 2026
6Social engineering (phishing campaign)
A pretexted portal-login phishing campaign was run against all ACME employees. The landing page cloned the corporate sign-in flow; submitted credentials were discarded server-side.
A 22% credential-submission rate makes awareness training viable and recommended on a quarterly cadence, with repeat clickers enrolled in targeted follow-up.
Representative excerpt — the full deliverable runs 30–60+ pages