Step 148. OWASP Juice Shop 1: Introduction to the Modern Web App — The Attack Stage Has Changed

Step 148. OWASP Juice Shop 1: Introduction to the Modern Web App — The Attack Stage Has Changed

Level 2 — Introduction to Security and Attack Skill Basics | Difficulty ★★★☆☆ | Estimated time: 4 hours

Prerequisites: Step 145 (information exposure) and Step 147 (DVWA wrap-up) complete. You know robots.txt and hidden-path discovery.

  • What you need: a machine that runs Docker (Juice Shop container), browser developer tools (F12), Python 3 + Flask (local reproduction)
  • ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.

DVWA was the world of 2000s PHP web apps. The OWASP Juice Shop you meet today is a modern web app built with Node.js and Angular — the work of drawing the screen happens not on the server but in your browser. This change redraws the attacker’s map: the page source is empty, and instead a giant JavaScript file holds the API addresses, hidden features, even the developer’s TODO comments. Today we understand this new stage’s structure, reproduce "the attack of reading JS files" ourselves, and then take on Juice Shop’s introductory challenges.


1. Learning Objectives

By the end of this chapter, you will be able to:

  • Explain the structural difference between an SPA (single-page application) and a traditional web app
  • Discover API paths and hints in frontend JS files
  • Collect /rest/ and /api/ calls in the developer tools Network tab
  • Launch Juice Shop with Docker and track progress on the score board
  • Practice "an undocumented API’s usage traces are its documentation"

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Docker + browser developer tools / Python 3 + Flask (local reproduction)
Today’s commands docker run -d -p 3000:3000 bkimminich/juice-shop, F12 → Sources/Network, in-JS search (Ctrl+F)
Concepts needed SPA, REST API, client-side logic exposure, hash routing (/#/...)
Today’s artifact An API path list + a score-board challenge record

2-1. SPA — The Screen Is Built in the Browser

A traditional web app (like DVWA) has the server complete the HTML and send it. View the page source and you saw the content. An SPA (Single Page Application) is different. The server sends one skeleton HTML and a giant JS bundle, and the browser’s JavaScript draws the screen. Open the page source and there’s just Loading.... Addresses like /#/login, /#/basket in the address bar, changing after the #, are called hash routing — no request goes to the server; the JS just swaps the screen.

2-2. What a Fat Frontend Means

The logic that draws the screen all downloads to your computer — which means part of the application’s blueprint is shipped wholesale to the attacker. Inside the JS bundle are the addresses of the APIs it calls, the paths of features not yet public in the menu, and comments the developer never deleted. This is why several of Juice Shop’s real challenges are designed as "read the JS file and there’s a hint."

2-3. REST API — An Undocumented API’s Usage Traces Are Its Documentation

An SPA’s server is a REST API server exchanging JSON data instead of HTML. Paths like /rest/products, /api/Users are exactly that. Open the developer tools Network tab and operate the screen, and you see every API the browser calls, with every parameter. Even without official docs, the usage traces themselves are the API specification — this is the reconnaissance method of modern web attacks.

2-4. Juice Shop — A Playground with Scoring

Juice Shop is an intentionally vulnerable web app made by OWASP, with over 100 challenges classified by difficulty. A hidden score board (/#/score-board) tracks your progress, and each challenge’s 💡 icon gives a hint. If DVWA was the textbook of "code comparison by difficulty," Juice Shop is the textbook of "modern web app reconnaissance."


3. Follow Along

3-1. Local Reproduction — An SPA’s Empty Source and Fat JS

Before launching Juice Shop, experience with a small simulation app why its structure is special (this book was measured on 2026-09-09).

Input (the core of mini_shop.py)

from flask import Flask, jsonify

app = Flask(__name__)

INDEX_HTML = """<!DOCTYPE html>
<html><head><title>Mini Juice Shop (Lab)</title></head>
<body>
<div id="app">Loading...</div>
<script src="/main.js"></script>
</body></html>"""

MAIN_JS = """// Mini Juice Shop frontend bundle (simulation)
// TODO: delete these comments before deploy!
// Admin section: /#/administration — no authorization check implemented yet
// Score board: /#/score-board (hidden page)
const API = {
  products: "/rest/products",
  user: "/api/Users",
  basket: "/rest/basket/",
  admin: "/rest/admin/application-version"
};
console.warn("Dev warning: debug mode is on");
"""

@app.route("/")
def index():
    return INDEX_HTML

@app.route("/main.js")
def mainjs():
    return MAIN_JS, 200, {"Content-Type": "application/javascript"}

@app.route("/robots.txt")
def robots():
    return "User-agent: *\nDisallow: /ftp/\n", 200, {"Content-Type": "text/plain"}

@app.route("/rest/products")
def products():
    return jsonify([{"id": 1, "name": "Apple Juice"}, {"id": 2, "name": "Orange Juice"}])

How to read it: the homepage HTML is empty (Loading...), and all the information is in main.js. A miniature of Juice Shop. Start the server, then move on to the next experiment.

3-2. Reading the JS File — Digging Out Comments and API Paths

The attacker’s first action is not sightseeing the screen but downloading the JS bundle. Opening main.js in the developer tools Sources tab and searching with Ctrl+F — here we mimic that with a script.

Output (measured 2026-09-09):

=== 1) Homepage HTML — no information in the body (SPA) ===
<div id="app">Loading...</div>
<script src="/main.js"></script>

=== 2) Extracting API paths/hints after downloading main.js ===
--- Comments (TODO/hints) ---
// TODO: delete these comments before deploy!
// Admin section: /#/administration — no authorization check implemented yet
// Score board: /#/score-board (hidden page)
--- /rest/ · /api/ paths ---
/api/Users
/rest/admin/application-version
/rest/basket/
/rest/products

How to read it: two kinds of loot came out. ① Comments — the addresses of a hidden admin section and a score board, verbatim from the developer’s memos. ② Four API paths — a list of what features the server has. In a browser, this is exactly the same work as developer tools → Sources → main.js → Ctrl+F searching for score-board and /rest/.

3-3. robots.txt and Calling the API Directly

Output (measured 2026-09-09):

=== 3) Checking robots.txt ===
User-agent: *
Disallow: /ftp/

=== 4) Calling the discovered API directly ===
GET /rest/products -> 200 [{"id":1,"name":"Apple Juice"},{"id":2,"name":"Orange Juice"}]
GET /ftp/ -> 404 (the path robots.txt told us — its existence itself is information)

How to read it: the robots.txt technique you learned in Step 145 works as-is in a modern web app. And the API discovered in the JS was callable even without a login — the JSON comes straight back. "Knowing the API path" and "being able to call it" are different, but without knowing the path you can’t even try. Discovery is the preliminary stage of every attack.

3-4. Launching Juice Shop and Finding the Score Board (wargame practice, output example)

Do this on your Docker machine. The output is an output example.

Input

docker run -d -p 3000:3000 --name juice bkimminich/juice-shop

Connect to http://localhost:3000 in a browser. The first task is finding the map — type http://localhost:3000/#/score-board directly in the address bar, or search for score-board in the JS bundle as in 3-2 to find the score board. This "discovering the hidden page" itself is recognized as the first challenge, and a congratulation banner appears at the top of the screen (screen example):

[Notification banner] You solved a challenge: Score Board
(Find the carefully hidden 'Score Board' page.)

How to read it: the score board shows the challenge list by difficulty (★1–6), and solved items are painted green. Today’s targets, the ★1–2 challenges, are mostly "reconnaissance" — finding hidden pages, reading hints in the JS, reading warning messages in the developer console.

3-5. Drawing the API Map with the Network Tab (screen example)

Take a lap through Juice Shop’s login screen, product list, and cart with the developer tools Network tab open. An example of what you collect (screen example):

GET  /rest/products/search?q=apple     → product search
POST /rest/user/login                  → login (JSON body)
GET  /api/Users/1                      → user info
GET  /rest/basket/6                    → shopping basket (the number differs per user!)

How to read it: write each request’s path, method, and parameters into a table. A path with a number baked in, like /rest/basket/6, is especially the seed of the question "what if I change that number?" Today we stop at collecting — changing it (IDOR) is a topic of its own, covered in depth next time. The longer your collected list grows, the wider your attack map becomes.


4. Missions & Exercises

Mission — A Modern Web App Reconnaissance Report

  1. Launch the 3-1 simulation SPA, confirm the homepage source holds no information, then discover and record the four API paths and the comment hints in main.js
  2. Call the discovered /rest/products directly and receive the JSON response
  3. Launch Juice Shop with Docker, find the score-board page, and solve your first challenge
  4. Collect 5+ /rest/ and /api/ calls with the developer tools Network tab and organize them into a table
  5. Solve a cumulative 15 score-board challenges (mostly ★1–2), recording one line each on "how I found it"

Exercises

Exercise 1. Explain why "view page source" has become useless in an SPA, and what the attacker reads instead.

Exercise 2. Explain why a developer’s TODO comment left in a frontend JS file is dangerous, using today’s measured example.

Exercise 3. Explain the meaning of "an undocumented API’s usage traces are its documentation," connecting it to using the Network tab.

Exercise 4. Explain why changing the part after # in the address (hash routing) sends no request to the server, and where you must look to find hidden screens like the score board.


5. Model Answers & Completion Criteria

Mission Model Answer

The loot discovered in the local reproduction (measured 2026-09-09): the API paths /api/Users, /rest/admin/application-version, /rest/basket/, /rest/products, and the hints in the comments (/#/administration, /#/score-board). An example challenge record from Juice Shop:

1. Score Board — discovered the hidden /#/score-board (JS search)
2. Error Handling — induced the error screen via a nonexistent path
3. Exposed Metrics — accessed the /metrics path
4. Missing Encoding — investigated the alt text of a broken image
...

How to verify: ① is there local JS-discovery output? ② is there the JSON from the direct API call? ③ is there a record of the score-board progress screen? ④ does the API table have 5+ rows? ⑤ does each challenge carry its "discovery path"?

Exercise Answers

Answer 1. An SPA’s server sends only empty skeleton HTML and the browser’s JS draws the screen, so the page source has no content. Instead, the attacker downloads and reads the entity that draws the screen — the JS bundle (main.js) — and observes the actual API calls with the developer tools Network tab.

Answer 2. A JS bundle is a public file downloaded to every visitor’s computer. As today’s measurement showed — one comment gave away the hidden admin section (/#/administration) and the score-board address — TODO and debug comments are attack hints, plain and simple. This is why comment removal and obfuscation before deploy are needed.

Answer 3. Even without official API docs, open the developer tools Network tab and operate the screen, and every request the browser actually sends — path, method, parameters — gets recorded. That record is the API’s instruction manual and the deliverable of reconnaissance.

Answer 4. Because the part after # (the fragment) is processed only inside the browser and is never sent to the server. So which hash paths exist cannot be learned via server requests — you must search the path list (route definitions) inside the JS bundle that handles screen transitions.

Completion Criteria Checklist

  • [ ] I confirmed the contrast between an SPA’s empty page source and its fat JS myself
  • [ ] I discovered API paths and comment hints in the JS file
  • [ ] I called a discovered API without a login and received JSON
  • [ ] I launched Juice Shop with Docker and found the score board
  • [ ] I collected 5+ API calls from the Network tab into a table
  • [ ] I solved 15 challenges and recorded the discovery paths
  • [ ] I can explain the structural difference of hash routing and SPAs

6. Common Pitfalls & Fixes

Wall 1. docker run doesn’t work

Symptom: Cannot connect to the Docker daemon, or the command itself doesn’t exist.
Cause: Docker Desktop is off or not installed.
Fix: start Docker Desktop first and retry after the status icon turns green. On Windows, you may need the WSL2 backend configured.

Wall 2. localhost:3000 won’t open

Symptom: the container is up but the browser refuses the connection.
Cause: the container hasn’t finished starting (the first run takes tens of seconds), or a port conflict.
Fix: check for a message like "Server listening" with docker logs juice, and if port 3000 is taken, relaunch with -p 3100:3000.

Wall 3. The JS file is too big to read

Symptom: main.js is tens of thousands of lines in a single-line minified blob.
Cause: modern frontends are minified at build time. Reading it whole is impossible — that’s normal.
Fix: don’t read — search. Restore line breaks with the developer tools’ Pretty print ({} button), then Ctrl+F for keywords like score-board, /rest/, admin. Search beats close reading.

Wall 4. I solved a challenge but it’s not counted

Symptom: the score board won’t turn green.
Cause: you satisfied only half the condition, or the browser cached an old state.
Fix: re-read the 💡 hint and match the condition exactly. Refreshing the score board (F5) often reflects it.

Wall 5. Nothing shows in the Network tab

Symptom: you operate the screen but the request list is empty.
Cause: requests are recorded only when you operate the screen after opening the developer tools. Requests before opening have already passed.
Fix: open the Network tab with F12 first, then refresh the screen. Also check whether the filter is narrowed to Fetch/XHR.


7. Summary

Today’s Concepts

Concept One-line explanation
SPA A modern web app structure where the server sends only the skeleton and the browser’s JS draws the screen
JS bundle A blueprint shipped to every visitor — a trove of API paths and hints
REST API Server feature units exchanging JSON (/rest/, /api/)
Hash routing The path after # — never reaches the server; JS just switches screens
Network tab A reconnaissance tool where usage traces are the API documentation
Score board Juice Shop’s hidden scoring page and map (/#/score-board)

Today’s Commands

Command What it does
docker run -d -p 3000:3000 bkimminich/juice-shop Launch Juice Shop
docker logs juice Confirm container startup
F12 → Sources → main.js → Ctrl+F Search keywords in the JS bundle
F12 → Network (Fetch/XHR) Collect API calls
http://localhost:3000/#/score-board Access the score board directly
curl address/robots.txt Check hidden-path hints

An Instinct More Important Than Commands

The move from DVWA to Juice Shop is not a change of tools but a shift of gaze. From eyes that read the HTML the server hands out, to eyes that read the JS and network conversations coming down to the browser. The attack surface hasn’t disappeared — it has moved inside your computer.

The API list you collected today is not a mere list of addresses. The number baked into each path, the endpoints that open without a login, the unfinished features in the comments — all are "handles to shake next." The saying that reconnaissance is half the attack is literally true in the modern web.


Once every box is checked, Step 148 is complete.