Hack Your First Web App with Juice Shop

Exploit real web vulnerabilities using OWASP Juice Shop in a local lab.

Introduction

30 Second Summary

Every time you log in to a website or search for a product, there is code running behind the scenes that trusts your input. Hackers exploit that trust to steal data, hijack accounts, and take over entire systems.

In this project, you will hack into a deliberately vulnerable web application to learn how real-world attacks work. You will exploit SQL Injection, Cross-Site Scripting, and Broken Access Control vulnerabilities in a safe, legal environment.

What You'll Build

A local penetration testing lab using Docker and OWASP Juice Shop where you perform real attacks against a modern web application.

By the end of this project, you'll have:

  • A working Docker container running OWASP Juice Shop as your personal hacking lab.
  • Successful exploits of three critical vulnerability types from the OWASP Top 10:2025 framework.
  • A Score Board inside Juice Shop tracking your solved challenges as proof of work.
  • Secret Mission: discover confidential documents on the server through Sensitive Data Exposure.

Are there any prerequisites?

You should have a basic understanding of HTML and how HTTP requests work. No prior hacking or cybersecurity experience is needed.

Before We Start

Before We Start...

Install Docker and Launch Juice Shop

Every security professional needs a safe, isolated environment to practice real attacks without affecting production systems. In this step, you'll build your own penetration testing lab by installing Docker and launching OWASP Juice Shop, a deliberately vulnerable web application.

Docker lets you run Juice Shop inside an isolated container on your machine. Nothing you do inside this container can affect your host system, and it leaves no trace when stopped.

In this step, get ready to:
  • Install Docker Engine on your Ubuntu machine.
  • Configure Docker to run without requiring sudo.
  • Pull and launch the OWASP Juice Shop container.
Install Docker Engine on Ubuntu

Docker Engine is the runtime that lets you run containers on Linux. You'll install it from Docker's official apt repository to get the latest stable version.

  • Open a terminal window by pressing Ctrl+Alt+T.
  • Set up Docker's official apt repository by running these commands:

Sudo password prompt

The first time you run a sudo command, your terminal will ask for your user password. Type it and press Enter. The characters won't appear on screen as you type.

# Add Docker's official GPG key:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update

What do these commands do?

  • The first group downloads Docker's official GPG key and stores it in /etc/apt/keyrings/. This lets your system verify that packages actually come from Docker.
  • The tee block creates a new repository source file so apt knows where to find Docker packages.
  • The final sudo apt update refreshes the package list to include Docker's repository.
  • When prompted to confirm package installations, type Y and press Enter.
  • The final sudo apt update should complete without errors. This confirms the Docker repository was added correctly.
  • Install the Docker packages by running:
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

What does this install?

This installs five packages: the Docker Engine (docker-ce), the Docker CLI (docker-ce-cli), the container runtime (containerd.io), and build/compose plugins.

  • When prompted to confirm, type Y and press Enter.

Seeing a package not found error?

  • Make sure the repository setup commands from the previous block ran without errors. If any command failed, re-run the full block.
  • Check your internet connection. The apt update command needs to reach Docker's servers.

Help me troubleshoot my Docker installation on Ubuntu.

Configure Docker to run without sudo and verify installation

By default, Docker requires sudo for every command. Adding your user to the docker group removes that requirement.

  • Add your user to the Docker group and verify Docker works by running these commands:
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker
docker run hello-world

What do these commands do?

  • sudo groupadd docker creates the docker group. If you see a message saying the group already exists, that is fine.
  • sudo usermod -aG docker $USER adds your current user to that group.
  • newgrp docker activates the new group membership in your current terminal session without needing to log out and back in.
  • docker run hello-world downloads and runs a tiny test container that confirms Docker is working correctly.

You should see a message starting with Hello from Docker! followed by an explanation of what just happened. This confirms Docker Engine is installed and running without sudo.

Permission denied or command not found?

  • If you see a permission error, try closing your terminal completely and opening a new one. Then run docker run hello-world again.
  • If docker is not found, make sure the install step completed without errors. Re-run the sudo apt install command from the previous substep.

Help me fix Docker permission issues on Ubuntu.

Pull and run OWASP Juice Shop

Now that Docker is working, you'll download and launch OWASP Juice Shop. This is the vulnerable web application you'll attack throughout the rest of this project.

  • Pull the Juice Shop image and start the container by running:
docker pull bkimminich/juice-shop
docker run --rm -p 3000:3000 bkimminich/juice-shop

What do these commands do?

  • docker pull bkimminich/juice-shop downloads the Juice Shop image from Docker Hub. This may take a few minutes depending on your internet speed.
  • docker run --rm -p 3000:3000 bkimminich/juice-shop starts the container. The --rm flag automatically removes the container when you stop it. The -p 3000:3000 flag maps port 3000 inside the container to port 3000 on your machine.

Keep this terminal open

The docker run command keeps running in the foreground. You will see log output appearing as the server starts.

Leave this terminal open for the rest of the project. Open a new terminal window (Ctrl+Alt+T) for any future commands.

  • Open your web browser and navigate to http://localhost:3000 in the address bar.

You should see the Juice Shop storefront with products like Apple Juice and Orange Juice displayed on the page.

What is OWASP Juice Shop?

OWASP Juice Shop is a modern web application (Angular + Node.js + SQLite) intentionally riddled with security flaws. It covers all categories from the OWASP Top 10:2025 and contains 113 challenges ranging from 1-star to 6-star difficulty.

Because it runs inside a Docker container, it is completely isolated from your host system. You can attack it freely without any risk to your machine or network.

Page not loading at localhost:3000?

  • Check the terminal where you ran docker run. Wait until you see log output indicating the server has started before refreshing the browser.
  • Make sure you typed http://localhost:3000 (not https). Juice Shop does not use HTTPS locally.
  • If the container exited immediately, check if another service is already using port 3000. Stop that service and re-run the docker run command.

Help me get Juice Shop running in Docker.

Your penetration testing lab is live. Next up, you'll use your browser's Developer Tools to perform reconnaissance and discover hidden pages that the developers never meant you to find.

Reconnaissance - Discover the Hidden Score Board

Your Juice Shop lab is up and running. Now it's time to think like an attacker and explore what this application accidentally reveals.

Before launching any exploit, real penetration testers perform reconnaissance. This means gathering information about the target to find hidden pages, API endpoints, or configuration details that developers left exposed. In this step, you'll dig into Juice Shop's compiled JavaScript to uncover routes the developers never meant you to see.

In this step, get ready to:
  • Inspect the application's JavaScript source code for hidden route paths.
  • Discover and access the hidden Score Board page.
  • Identify the admin panel's URL for future exploitation.
Open Browser Developer Tools and inspect the application

Juice Shop is built with Angular, a JavaScript framework that compiles all its route paths, component names, and API endpoints into a single main.js file. This means hidden pages are still discoverable if you know where to look.

  • Press F12 to open Browser Developer Tools.
  • Click the Sources tab at the top of the Developer Tools panel.
  • In the left sidebar, expand the file tree and locate the main.js file. This is Juice Shop's compiled Angular code containing all route definitions.
  • Click on main.js to open it in the editor pane.
  • Press Ctrl+F to open the search bar inside the file.
  • Type score and press Enter to step through the results.
  • Keep pressing Enter to iterate through the results until you find a route mapping that contains a path like score-board.

Why does this work?

When developers compile a single-page application, the route definitions (URLs the app responds to) get bundled into the JavaScript file. Even though the Score Board page has no visible link anywhere in the UI, its route path still lives inside main.js.

This is why security through obscurity never works. Hiding a page from the navigation menu does not prevent someone from finding it in the source code.

Access the hidden Score Board

You found the route path. Now navigate directly to the hidden page to confirm it exists and is accessible.

  • In your browser's address bar, navigate to http://localhost:3000/#/score-board.

The Score Board page loads, showing all 113 challenges organized by category and difficulty (1-6 stars). You just solved your first challenge.

  • Look for the challenge titled "Find the carefully hidden Score Board page" in the list. It now shows a green checkmark, confirming it is solved.

What is Security Misconfiguration?

This demonstrates OWASP A02:2025 Security Misconfiguration. The Score Board exists in production with no proper access control. Client-side routing information leaks the existence of hidden pages.

In a real application, sensitive admin pages, debug endpoints, or internal tools could be discovered this same way.

Score Board not loading?

Make sure you include the # in the URL. Juice Shop uses Angular's hash-based routing, so /#/score-board is required.

Also confirm your Juice Shop container is still running by checking that the homepage loads at http://localhost:3000.

help me figure out why the Score Board page is not loading at localhost:3000/#/score-board

Search for the admin route in main.js

You proved that hidden routes are discoverable. Now use the same technique to find an even more sensitive page.

  • Press F12 to open Developer Tools again (if you closed it).
  • Navigate back to the Sources tab and open main.js.
  • Press Ctrl+F and search for administration.
  • You will find a route mapping that contains path: "administration". This confirms an admin panel exists.

Now try accessing this route directly.

  • In your browser's address bar, navigate to http://localhost:3000/#/administration.

You get a 403 Forbidden error because you are not logged in as an admin. The page exists, but it requires elevated privileges to access.

  • Remember this URL for later. You now know the admin panel exists and where it lives. You just need admin privileges to get in.

Why is reconnaissance so valuable?

In just a few minutes, you discovered two hidden routes that the application never links to in its UI. A real attacker builds a map of an application before attempting any exploits.

You now know what to target (the admin panel) and what blocks you (authentication). The next step is finding a way past that gate.

You have mapped out the target. You know hidden pages exist and that the admin panel is locked behind authentication. Next up, you will use SQL Injection to break through that gate and log in as the administrator.

SQL Injection - Break Into the Admin Account

In the previous step, you discovered two hidden routes by digging through the application's JavaScript source code. You found the admin panel at /administration, but the server returned a 403 Forbidden error because you are not logged in as an admin.

Now you need admin credentials. Instead of guessing passwords, you will exploit a flaw in how the login form talks to the database. This technique is called SQL Injection, and it is one of the most dangerous vulnerability types in the OWASP Top 10:2025.

In this step, get ready to:
  • Understand how login forms interact with databases at the SQL level.
  • Exploit a SQL Injection vulnerability to bypass authentication entirely.
  • Verify admin access and confirm the challenge on the Score Board.
Understand how login forms work at the database level

Before you attack the login form, you need to understand what happens behind the scenes when someone submits their email and password.

  • Navigate to http://localhost:3000/#/login in your browser.

You should see a login form with Email and Password fields. When you submit this form, the backend runs a SQL query against the SQLite database that looks like this:

SELECT * FROM Users WHERE email = '<input>' AND password = '<hashed input>';

What does this query do?

This SQL query asks the database to find a user whose email matches what you typed AND whose password matches what you typed. If both conditions are true, the database returns that user's record and the app logs you in.

The critical problem is that Juice Shop pastes your input directly into this query as a string. It does not separate "instructions to the database" from "values provided by the user." This broken boundary is what makes SQL Injection possible.

Exploit the login form with SQL Injection

Since the application treats your input as code instead of data, you can inject SQL syntax that changes the logic of the query. You will craft a payload that makes the query always return true, bypassing the password check entirely.

  • Click the Email field on the login form.
  • Type the following payload into the Email field:
' or 1=1--
  • Click the Password field and type test (or anything at all).
  • Click Log in.

You are now logged in as the administrator. Look at the top-right corner of the page. You should see the admin's email: admin@juice-sh.op.

How did this payload work?

Your injected payload transformed the SQL query into this:

SELECT * FROM Users WHERE email = '' or 1=1--' AND password = '...';

The single quote (') closes the email string early. or 1=1 adds a condition that is always true, so the database returns ALL users. -- is a SQL comment that tells the database to ignore everything after it, including the password check.

The database returns the first user in the table, which happens to be the administrator. This works because the developer concatenated user input directly into the SQL string instead of using parameterized queries (prepared statements).

What is a parameterized query?

A parameterized query (also called a prepared statement) keeps user input strictly separated from SQL instructions. The database treats the input as a data value only, never as executable code. If Juice Shop used prepared statements, your payload would be treated as a literal email address and the login would simply fail.

Still seeing an error after clicking Log in?

Make sure the payload starts with a single quote character. Copy it exactly as shown, including the single quote at the beginning and the two dashes at the end.

Check that you pasted the payload into the Email field, not the Password field. The injection must go where the SQL query inserts user input for the email column.

Help me troubleshoot my SQL Injection payload in Juice Shop

Verify your admin access
  • Confirm the top-right corner of the page shows admin@juice-sh.op as the logged-in user.
  • Navigate to http://localhost:3000/#/score-board in your browser.
  • Look for the challenge titled "Log in with the administrator's user account." It should now show a green checkmark.

Your Score Board now shows 2 solved challenges. You have proven that a single unsanitized input field can give an attacker full admin access to an application.

Why is this classified under OWASP A05:2025 Injection?

SQL Injection falls under the A05:2025 Injection category because the attack works by injecting hostile data into an interpreter (the SQL database engine). The OWASP Top 10:2025 groups SQL Injection together with XSS and other injection types because they all exploit the same fundamental flaw: untrusted data sent to an interpreter without proper validation or escaping.

You now have admin credentials in Juice Shop. Next up, you will use a completely different attack vector. Instead of targeting the database, you will inject malicious JavaScript directly into the browser through a Cross-Site Scripting (XSS) attack.

Cross-Site Scripting (XSS) - Inject Malicious JavaScript

In the last step, you broke into the admin account using SQL Injection. That attack targeted the server's database layer. Now you are going to target the browser itself.

Cross-Site Scripting (XSS) lets an attacker inject scripts into web pages that other users view. In this step, you will execute a DOM-based XSS attack that proves you can run arbitrary JavaScript inside the Juice Shop browser session.

In this step, get ready to:
  • Learn what XSS is and why it is one of the most common web vulnerabilities.
  • Execute a DOM XSS attack by injecting a payload into the search field.
  • Confirm the challenge completion on the Score Board.
Understand what XSS is and why it matters

When a website takes user input and renders it directly into the page without sanitizing it, an attacker can inject HTML or JavaScript that executes in the victim's browser.

What is Cross-Site Scripting (XSS)?

XSS is dangerous because injected scripts execute in the context of the victim's browser session. An attacker could steal session cookies, redirect users to phishing pages, or perform actions on behalf of the user.

There are three types of XSS. DOM-based XSS manipulates the browser's DOM directly. Reflected XSS bounces the payload off the server via a URL or request. Stored XSS saves the payload so it is served to every visitor. You will perform a DOM-based attack.

Why is XSS classified under OWASP A05:2025 Injection?

The OWASP Top 10:2025 groups XSS under A05 Injection because the root cause is the same. The application fails to separate user-supplied data from executable instructions. In SQL Injection the instructions go to a database. In XSS the instructions go to a browser.

Execute a DOM XSS attack via the search field

Juice Shop's search functionality takes your input and renders it directly into the DOM without sanitizing it. The Angular code uses a function called bypassSecurityTrustHtml() on the search value, which disables the framework's built-in protection.

  • Make sure you are on the Juice Shop homepage at http://localhost:3000. Click the Juice Shop logo in the top-left corner if you are on a different page.
  • Find the search field in the navigation bar (magnifying glass icon in the top-left area).
  • Click the magnifying glass icon to open the search input.
  • Paste the following XSS payload into the search field:
<iframe src="javascript:alert(`xss`)">
  • Press Enter to submit the search.

An alert box pops up displaying the text "xss". This proves that arbitrary JavaScript can be executed in the browser through this input field.

What does this payload do?

The <iframe> tag with a javascript: protocol URI tells the browser to execute the code inside it. The alert(`xss`) function creates a popup box, proving that script execution occurred.

In a real attack, instead of a harmless alert(), an attacker would use document.cookie to steal session tokens, or redirect users to a phishing site. The underlying vulnerability exists because Angular's sanitization was explicitly bypassed for the search value.

No alert box appeared?

Make sure you pasted the payload into the search field in the navigation bar, not into any other input on the page. The DOM XSS challenge specifically requires the navigation bar search.

Check that you copied the entire payload including the opening < and closing >. A missing character will break the HTML tag structure.

If your browser has a popup blocker enabled, it may suppress the alert. Try disabling popup blocking for localhost.

Help me troubleshoot my DOM XSS payload in Juice Shop

Observe the challenge completion
  • Close the alert box by clicking OK.
  • Navigate to the Score Board at http://localhost:3000/#/score-board.
  • Confirm that the "Perform a DOM XSS attack" challenge (1 star) is now marked as solved with a green checkmark.

Your Score Board now shows 3 solved challenges. You have proven that user input can execute code in the browser when the application fails to sanitize it.

What is DOM-based XSS specifically?

DOM-based XSS happens entirely in the browser. The server never sees the malicious payload in the response. Instead, client-side JavaScript reads user input and inserts it into the page's Document Object Model without escaping. This makes it harder to detect with traditional server-side security tools because the attack never crosses the network boundary.

You have now exploited two of the three OWASP Top 10:2025 vulnerability categories covered in this project. Next up, you will combine your admin credentials with the hidden route you discovered earlier to access the restricted admin panel.

Broken Access Control - Access the Admin Panel

You have already injected SQL to steal admin credentials and executed JavaScript in the browser through XSS. Now it is time to exploit the most critical vulnerability category in modern web security.

In Step 2, you discovered the /administration route hiding in the JavaScript source code. In Step 3, you gained admin access via SQL Injection. In this step, you will combine those two wins to access restricted functionality that no normal user should ever reach.

In this step, get ready to:
  • Understand why Broken Access Control is the #1 risk in the OWASP Top 10:2025.
  • Access the restricted admin panel using your admin session.
  • Explore admin capabilities and verify the challenge completion on the Score Board.
Understand Broken Access Control (OWASP A01:2025)

Broken Access Control is the number one risk in the OWASP Top 10:2025. It occurs when users can access resources or perform actions beyond their intended permissions.

You already discovered the /administration path in Step 2 and gained admin credentials in Step 3. Now you will chain these vulnerabilities together to access restricted functionality.

What is Broken Access Control?

Access control enforces policy so that users cannot act outside of their intended permissions. When this fails, it can lead to unauthorized information disclosure, modification, or destruction of data.

The Juice Shop has a client-side route guard that checks if you are an admin. The problem is that client-side checks are trivial to bypass because the attacker controls the browser. Proper access control requires server-side verification on every request.

Navigate to the admin panel

Because you are already logged in as the administrator, the client-side route guard will let you through. All you need is the URL.

  • Confirm you are still logged in as admin@juice-sh.op by checking the top-right corner of the page for the admin email address.
  • If you are logged out, return to http://localhost:3000/#/login and repeat the SQL Injection from Step 3 (enter ' or 1=1-- in the Email field with any password).
  • Navigate to http://localhost:3000/#/administration in your browser's address bar.

You should now see the Administration panel with a list of all registered users and a Customer Feedback section.

Why does this work?

The admin route was hidden (security through obscurity), and the access check relied on a client-side Angular route guard. You discovered the path via JavaScript inspection in Step 2 and obtained admin credentials via SQL Injection in Step 3.

Chaining vulnerabilities like this is how real-world attacks work. One weakness opens the door to another. Proper access control requires server-side verification on every request, not just a client-side check.

Seeing a blank page or error?

If you see a 403 error or an empty page, you are likely logged out. Check the top-right corner for the admin email. If it is missing, go back to the login page and repeat the SQL Injection payload.

Make sure the URL is exactly http://localhost:3000/#/administration with the # symbol included. Without it, the Angular router cannot find the route.

Why can't I access the admin panel?

Explore the admin capabilities

Now that you are inside the admin panel, take a moment to observe the sensitive data and powerful controls that are exposed.

  • Look at the Registered Users section. You can see all user email addresses and creation dates.
  • Look at the Customer Feedback section. Each review has a trash can icon that lets you delete it.
  • Navigate to the Score Board at http://localhost:3000/#/score-board.
  • Confirm that the "Access the administration section of the store" challenge is now marked as solved with a green checkmark.

Your Score Board should now show 4 solved challenges.

Why is this so dangerous in the real world?

In a real application, the admin panel might let an attacker delete user accounts, export customer databases, change product prices, or escalate privileges further. Broken Access Control is ranked #1 in the OWASP Top 10:2025 because it is both common and devastating.

The fix is straightforward in principle. Every request to a restricted resource must be verified on the server side, not just in client-side JavaScript that the user can inspect or bypass.

Secret mission

Sensitive Data Exposure - Find Confidential Documents

You have injected SQL, executed scripts, and accessed the admin panel. But what about the files the developers left lying around? In this mission, you will hunt for exposed directories and uncover confidential documents that should never be publicly accessible.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally via Docker, so there are no ongoing cloud costs.

Resources you used:

  • OWASP Juice Shop Docker container (running on port 3000).
  • Juice Shop Docker image (bkimminich/juice-shop).
  • Docker Engine (installed on Ubuntu).

Keep everything running

No action needed. Choose this if you are still actively exploring Juice Shop and want to try more challenges.

  • Your Juice Shop container continues running at http://localhost:3000.
  • Docker Engine remains installed and available for future projects.
  • Your Score Board progress is preserved as long as the container keeps running.

Pause - I'll come back to this later

Stop the running container to free up memory and CPU. Your Docker images stay on disk so you can restart quickly later.

  • Switch to the terminal where Juice Shop is running and press Ctrl+C to stop it.
  • The container was started with the --rm flag, so it automatically removes itself when stopped.
  • To restart later, run the same command again:
docker run --rm -p 3000:3000 bkimminich/juice-shop

What about my Score Board progress?

Because the container uses the --rm flag, your challenge progress resets each time you restart. This is normal for a testing lab. You can re-solve the challenges for extra practice.

Delete - I don't want to use this again

Remove all project resources from your system completely.

  • Stop the Juice Shop container by pressing Ctrl+C in the terminal where it is running.
  • Remove the Juice Shop Docker image by running:
docker rmi bkimminich/juice-shop
  • Remove the hello-world image that was pulled during verification:
docker rmi hello-world
  • To completely uninstall Docker Engine from your system, run the following commands:
# Uninstall Docker Engine packages
sudo apt purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras

# Remove all images, containers, and volumes
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd

# Remove the Docker apt source list and GPG key
sudo rm /etc/apt/sources.list.d/docker.sources
sudo rm /etc/apt/keyrings/docker.asc

What do these commands do?

The apt purge command removes all Docker packages you installed. The rm -rf commands remove stored images, containers, and volumes that persist even after uninstalling the packages.

The last two rm commands remove the apt repository configuration and GPG key that you added during installation.

Nice Work!

Nice Work!

You just hacked a web application. You set up a real penetration testing lab and exploited three of the most critical vulnerability categories in the OWASP Top 10:2025.

You've learned how to:

  • Exploit SQL Injection to bypass authentication and log in as the administrator of a web application.
  • Execute a DOM-based Cross-Site Scripting (XSS) attack, proving that arbitrary JavaScript can run in a victim's browser.
  • Chain reconnaissance with Broken Access Control to discover hidden routes and access a restricted admin panel.
  • Secret Mission: Discovered confidential documents via Sensitive Data Exposure by finding a publicly accessible directory on the server.

Ready to quiz yourself?