Cybersecurity · Deep dive · 30/09/2026

JWT alg:none: το bug που άνοιξε 17 τρισεκατομμύρια rows

Ένας 16χρονος ερευνητής μπήκε σε μια εσωτερική υπηρεσία analytics της Microsoft χωρίς κωδικό. Δεν έσπασε κρυπτογραφία και δεν έκλεψε credentials. Έστειλε ένα token χωρίς υπογραφή, και ο server το δέχτηκε.

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1cG4iOiJhZG1pbiJ9.  Το τρίτο κομμάτι, η υπογραφή, είναι κενό. Το πρώτο δηλώνει {"alg":"none"}. Αν ο server δεχτεί αυτό το token ως έγκυρο, ο καθένας μπορεί να γίνει admin.

Τι έγινε

Ο ερευνητής, με το ψευδώνυμο Faav, βρήκε ότι η Titan, μια εσωτερική υπηρεσία analytics της Microsoft, έλεγχε τα περιεχόμενα ενός JWT (tenant, audience, app ID, χρήστη) αλλά δεν επαλήθευε ποτέ την υπογραφή.

Έστειλε token με alg ίσο με none, κενή υπογραφή και claim χρήστη admin. Η υπηρεσία τον αντιμετώπισε ως διαχειριστή και δέχτηκε SQL queries προς 17 βάσεις ClickHouse.

Η εύρεση βοηθήθηκε από ένα εργαλείο που έφτιαξε ο ίδιος, το Antares, το οποίο έκανε τη δουλειά της επανάληψης και του ελέγχου του login για μέρες. Το τελικό βήμα ήταν ανθρώπινη διαίσθηση.

Χρονολόγιο: αναφορά στις 5 Σεπτεμβρίου, περιορισμός πρόσβασης από τη Microsoft στις 9 Σεπτεμβρίου, bounty 5.000$ στις 17 Σεπτεμβρίου. Δεν υπάρχουν δημόσια στοιχεία κακόβουλης εκμετάλλευσης πριν τη διόρθωση.

Τι σημαίνει πραγματικά το «17 τρισεκατομμύρια». Είναι εκτίμηση αποθηκευμένων rows από metadata των βάσεων και μπορεί να περιλαμβάνει ιστορικά, διπλά και παράγωγα δεδομένα. Δεν είναι 17 τρισ. πελάτες ούτε επιβεβαιωμένα κλεμμένες εγγραφές. Ο ερευνητής επιβεβαίωσε πρόσβαση σε metadata και σε δείγματα μίας γραμμής, ανάμεσά τους από ένα Bing analytics dataset.

Πώς είναι φτιαγμένο ένα JWT

Ένα JSON Web Token είναι τρία κομμάτια σε base64url, χωρισμένα με τελείες. Το base64url δεν είναι κρυπτογράφηση, οποιοσδήποτε το διαβάζει. Το μόνο που κάνει το token αξιόπιστο είναι η υπογραφή.

HeaderΟ αλγόριθμος υπογραφής (alg) και ο τύπος.
PayloadΟι ισχυρισμοί (claims): ποιος είσαι, για ποιον προορίζεται το token, πότε λήγει.
SignatureΗ απόδειξη ότι ο issuer έγραψε αυτά τα δύο πρώτα κομμάτια και ότι δεν άλλαξαν.

Η ρίζα του προβλήματος

Το πρότυπο JWT επιτρέπει «unsecured» tokens με alg: none. Κάποιες βιβλιοθήκες και εφαρμογές εμπιστεύονται το alg που δηλώνει το ίδιο το token. Αυτό είναι το λάθος: ο επιτιθέμενος γράφει το header, άρα διαλέγει ο ίδιος πώς θα ελεγχθεί.

Στην Titan υπήρχαν πολλοί έλεγχοι (tenant, audience, app ID, χρήστης), και όλοι ήταν άχρηστοι. Όταν ο επιτιθέμενος ελέγχει τα claims και δεν χρειάζεται να αποδείξει ποιος τα εξέδωσε, κάθε έλεγχος από κάτω δεν προστατεύει τίποτα. Το web interface ήταν μόνο πίσω από VPN, αλλά υπήρχε ξεχωριστό API endpoint με δημόσια τεκμηρίωση.

Δοκίμασέ το στον browser σου

Ο inspector τρέχει τοπικά, τίποτα δεν στέλνεται πουθενά. Επικόλλησε ένα token ή φτιάξε ένα δικό σου unsigned για να δεις τι εντοπίζεται.

Disclaimer inspector. Εκπαιδευτικό εργαλείο: αναλύει μόνο τη δομή του token που του δίνεις, δεν επαληθεύει υπογραφές και δεν συνδέεται σε καμία υπηρεσία. Μην επικολλάς πραγματικά tokens παραγωγής — αν διέρρευσαν, κάνε τα rotate.

Λάθος και σωστός κώδικας

Ευάλωτο

// Αποκωδικοποιεί και εμπιστεύεται
const p = JSON.parse(
  atob(token.split('.')[1])
);
if (p.aud === 'titan' && p.upn) {
  user = p.upn;   // ← κανείς δεν
}                 //   έλεγξε υπογραφή

Σωστό

// jsonwebtoken (Node)
const p = jwt.verify(token, publicKey, {
  algorithms: ['RS256'],   // allowlist
  issuer: 'https://login.example',
  audience: 'titan',
  clockTolerance: 30
});
user = p.sub;

Checklist άμυνας

  1. Επαλήθευε πάντα την υπογραφή πριν διαβάσεις οποιοδήποτε claim. Το decode() δεν είναι έλεγχος.
  2. Allowlist αλγορίθμων στον server. Ποτέ μην αφήνεις το token να επιλέξει. Απέρριψε το none ρητά.
  3. Έλεγξε issuer και audience μετά την υπογραφή, όχι αντί για αυτήν.
  4. Μην αντιστοιχίζεις claims που ελέγχει ο χρήστης απευθείας σε προνομιούχους λογαριασμούς (όπως ένα upn ίσο με admin).
  5. Fail closed: αν κάτι στην επαλήθευση αποτύχει ή λείπει, η απάντηση είναι 401.
  6. Το VPN δεν αντικαθιστά authentication. Έλεγξε ανεξάρτητα κάθε endpoint που φτάνει από έξω, ακόμα κι αν η εφαρμογή θεωρείται εσωτερική.
  7. Πρόσθεσε τεστ: στείλε unsigned token και token με αλλαγμένο payload και ίδια υπογραφή. Και τα δύο πρέπει να αποτυγχάνουν.

Δεν είναι καινούργιο: γνωστό από το 2015

Το 2015 ο ερευνητής Tim McLean δημοσίευσε ότι πολλές βιβλιοθήκες JWT δέχονταν alg: none και ότι μπορούσαν να εξαπατηθούν σε «σύγχυση αλγορίθμου». Οι περισσότερες διορθώθηκαν τότε. Το πρότυπο (RFC 7519) όμως εξακολουθεί να ορίζει τα «unsecured JWT», και το RFC 8725 (JWT Best Current Practices, 2020) συστήνει ρητά allowlist αλγορίθμων και απόρριψη ό,τι δεν αναμένεται.

Το ότι το ίδιο λάθος εμφανίζεται ξανά το 2026, σε εσωτερική υπηρεσία μεγάλης εταιρείας, δείχνει ότι το πρόβλημα δεν είναι τεχνικό. Είναι ζήτημα ροής εργασίας: ο κώδικας γράφεται, δοκιμάζεται με έγκυρα tokens και δεν δοκιμάζεται ποτέ με κακόβουλα.

Τρεις παραλλαγές της ίδιας ιδέας

Όλες βασίζονται στην ίδια αδυναμία: ο server αφήνει το token να επηρεάσει το πώς θα ελεγχθεί.

1. alg: none

Το token δηλώνει ότι δεν έχει υπογραφή και ο server δεν διαμαρτύρεται. Είναι η περίπτωση της Titan.

2. Σύγχυση αλγορίθμου (RS256 → HS256)

Ο server έχει δημόσιο κλειδί για RS256 (ασύμμετρη υπογραφή). Αν η βιβλιοθήκη ακολουθεί το alg του token, ο επιτιθέμενος το αλλάζει σε HS256 (συμμετρική υπογραφή) και υπογράφει με το δημόσιο κλειδί ως «μυστικό». Το δημόσιο κλειδί είναι δημόσιο, άρα ο server επαληθεύει επιτυχώς μια πλαστογραφία.

3. Header που δείχνει το κλειδί

Τα πεδία kid, jku, x5u και jwk λένε στον server ποιο κλειδί να χρησιμοποιήσει. Αν ο κώδικας τα εμπιστεύεται, ο επιτιθέμενος μπορεί να δείξει σε δικό του κλειδί ή, με ένα kid που μπαίνει ανεπεξέργαστο σε αναζήτηση αρχείου ή βάσης, να προκαλέσει path traversal ή injection.

ΕπίθεσηΤι ελέγχει ο επιτιθέμενοςΗ άμυνα
alg noneΤο alg στο header και την απουσία υπογραφήςAllowlist αλγορίθμων, απόρριψη του none
RS256 → HS256Το alg, και ξέρει το δημόσιο κλειδίΚλειδί δεμένο με αλγόριθμο, όχι με βάση το token
kid / jku / jwkΑπό πού φορτώνεται το κλειδίΣταθερή λίστα εμπιστευμένων κλειδιών και URL
Αδύναμο μυστικό HS256Τίποτα, σπάει offlineΜακρύ τυχαίο μυστικό ή ασύμμετρα κλειδιά
Χωρίς expΗ διάρκεια ζωήςΣύντομη λήξη και έλεγχος exp

Γιατί τέτοια bugs επιβιώνουν

Πώς το ψάχνει ένας ερευνητής

Στη Titan, η ένδειξη ήταν ότι το payload άλλαζε ενώ η υπογραφή έμενε ίδια, και ο server συνέχιζε να δέχεται τα νέα claims. Αυτό αρκεί για να καταλάβεις ότι η υπογραφή δεν ελέγχεται. Το επόμενο βήμα είναι το token με alg: none.

Το εργαλείο Antares έκανε το επαναλαμβανόμενο κομμάτι: εντοπισμός endpoints, δοκιμές του login και καταγραφή αποκρίσεων, σε διάστημα δέκα ημερών. Η ανακάλυψη δεν ήταν καθαρά αυτόματη, αφού το τελικό βήμα ήταν ανθρώπινη υπόθεση για το πώς η εφαρμογή ερμηνεύει το πεδίο του χρήστη. Το μοτίβο έχει σημασία: το AI κάνει την υπομονετική δουλειά, ο άνθρωπος βάζει την ιδέα.

Δοκίμασε τη δική σου εφαρμογή

Αν έχεις API με JWT, τρία τεστ σε δικό σου περιβάλλον αρκούν. Και τα τρία πρέπει να επιστρέφουν 401 ή 403.

# Μόνο για δικά σου συστήματα
import base64, json, requests

def b64(d): return base64.urlsafe_b64encode(json.dumps(d).encode()).rstrip(b"=").decode()

url   = "https://api.dikos-sou.example/me"
claims = {"sub": "test-user", "aud": "my-api"}

tests = {
  "unsigned":       b64({"alg":"none","typ":"JWT"}) + "." + b64(claims) + ".",
  "alg NONE":       b64({"alg":"NONE","typ":"JWT"}) + "." + b64(claims) + ".",
  "garbage sig":    b64({"alg":"HS256","typ":"JWT"}) + "." + b64(claims) + ".AAAA",
}
for name, tok in tests.items():
    r = requests.get(url, headers={"Authorization": "Bearer " + tok})
    print(name, r.status_code, "ΠΡΟΒΛΗΜΑ" if r.status_code == 200 else "ok")

Ανίχνευση και όριο ζημιάς

Συχνές ερωτήσεις

Είναι το JWT ανασφαλές;

Όχι από μόνο του. Είναι ένα φορμάτ, και η ασφάλεια εξαρτάται από το πώς ελέγχεται. Οι περισσότερες αστοχίες προέρχονται από την υλοποίηση, όχι από το πρότυπο.

Πρέπει να το αποφύγω και να χρησιμοποιώ sessions;

Όχι απαραίτητα, αλλά αν δεν χρειάζεσαι stateless tokens για συγκεκριμένο λόγο (πολλές υπηρεσίες, τρίτα μέρη), ένα απλό server-side session είναι λιγότερο επιρρεπές σε τέτοια λάθη.

Αρκεί να χρησιμοποιώ μια γνωστή βιβλιοθήκη;

Βοηθάει πολύ, αλλά όχι αν την καλείς λάθος. Δώσε πάντα ρητά τους αλγορίθμους, τον issuer και το audience, και μην περνάς αυτό που λέει το token ως παράμετρο επαλήθευσης.

Γιατί μας νοιάζει

Το ενδιαφέρον δεν είναι το exploit, που είναι γνωστό anti-pattern εδώ και χρόνια. Είναι ότι σε μια εταιρεία με τεράστιους πόρους ασφάλειας, ένα ξεχασμένο βήμα εξουδετέρωσε πολλά στρώματα ελέγχου, και το βρήκε ένας έφηβος με ένα εργαλείο αυτοματοποίησης. Οι ίδιες μέθοδοι είναι διαθέσιμες σε όποιον ψάχνει, από τις δύο πλευρές.

Σχετικά: Passkeys 2026 Passwordless & 2FA Phishing Trends Deepfake Detection