Black-box web application penetration test. Full attack chain from unauthenticated visitor to application owner.
Black-box penetration test against OWASP Juice Shop, an intentionally vulnerable Node.js e-commerce application. Objective: gain unauthorized administrative access and demonstrate the real-world blast radius of every finding. The assessment identified 9 vulnerabilities across 7 OWASP Top 10 (2021) categories, including two Critical-severity issues enabling complete session takeover. Every finding is backed by a working exploit, a captured Burp Suite request/response, a root cause analysis, and a remediation recommendation. In a production environment, this attack chain would expose all customer data, enable arbitrary account takeover, and grant persistent administrative control with no detectable footprint.
Mapped the full attack surface before running a single exploit. Enumerated all API endpoints via Burp Suite passive crawl, discovered an unauthenticated /ftp/ directory exposing backup files, and extracted every REST route from main.js via static analysis. No automated scanner. All manual.
Findings did not exist in isolation. They chained. The SQLi bypass produced the admin JWT. The FTP exposure yielded the seed phrase and OAuth secret. IDOR gave access to every user basket. Together they formed a single kill chain from unauthenticated visitor to full application owner.
Every finding is backed by a working exploit, a captured Burp Suite request/response, a root cause analysis, and a remediation recommendation. 45 evidence screenshots documented across all phases. Full report on GitHub.
email: ' OR 1=1--
password: anything
→ Admin JWT extracted. Session hijacked.
Root cause: User input concatenated directly into SQL string with no parameterisation. Any raw query without a prepared statement is this bug.
Fix: Replace with parameterised queries or an ORM. Input validation is secondary: the query itself must never accept literal user input.
GET /ftp/package.json.bak HTTP/1.1
→ BIP-39 seed phrase exposed.
→ OAuth client_secret in plaintext.
Root cause: Backup files deployed to a publicly accessible directory with no access controls. A forgotten file is all it takes.
Fix: Remove /ftp/ from the public webroot. Enforce authentication on all non-public paths. Add backup file patterns to .gitignore and deployment exclusion lists.
GET /api/BasketItems/1 → user A cart
GET /api/BasketItems/2 → user B cart
→ Zero ownership validation.
Root cause: API returns objects by ID alone with no ownership check. The server trusts the client to only request its own data.
Fix: Verify req.user.id matches the resource owner on every object request server-side. Use UUIDs instead of sequential integers to raise the enumeration cost.
Attempt 1: admin123 → 401
Attempt 2: password → 401
Attempt 3: admin12345 → 200 ✓
→ No lockout. No rate limit.
Root cause: Login endpoint accepts unlimited attempts with no throttling or lockout. Password complexity is the only defence.
Fix: Implement account lockout after 5 failed attempts, IP-based rate limiting via express-rate-limit, and exponential backoff. Add CAPTCHA on repeated failures.
<iframe src="javascript:alert('XSS')">
→ Executes in victim's browser context.
→ Escalates to session cookie theft.
Root cause: User-controlled input reflected into HTML without output encoding. As a developer, I have written this exact pattern.
Fix: Encode all user input before rendering into the DOM. Use DOMPurify or framework-level encoding. Set a strict Content-Security-Policy to block inline script execution.
1. UI blocks non-image files → JS only
2. Intercept POST in Burp Suite
3. Rename payload.php → avatar.jpg
→ Server accepted. No server check.
Root cause: File type enforcement happens only in JavaScript. Any intercepting proxy bypasses it in seconds. The server performs zero validation.
Fix: Validate file type and MIME server-side using an extension allowlist. Scan file content, not just the header. Store uploads outside the webroot and serve through a controlled handler.