JWT 알고리즘 ‘none’ 공격: 코드 한 줄에 숨은 취약점
발행: (2025년 12월 31일 오후 02:53 GMT+9)
3 min read
원문: Dev.to
Source: Dev.to
공격 시연
// ❌ This looks fine...
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256', 'none'], // 💀 The vulnerability
});
1. 공격자가 유효한 JWT를 획득
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTYiLCJyb2xlIjoidXNlciJ9.
signature_here
2. 헤더를 none 알고리즘으로 수정
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.
eyJzdWIiOiIxMjM0NTYiLCJyb2xlIjoiYWRtaW4ifQ.
// No signature needed!
3. "none"이 알고리즘 목록에 포함돼 있어 서버가 허용
공격자는 이제 관리자 권한을 가짐.
알려진 CVE
| CVE | Library | Issue |
|---|---|---|
| CVE-2015-2951 | jwt‑simple | 알고리즘 혼동 |
| CVE-2016-10555 | jose2go | none 알고리즘 우회 |
| CVE-2018-0114 | node‑jose | 키 혼동 |
안전한 설정 예시
// ✅ Explicitly whitelist algorithms
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'], // Only what you use!
});
// ❌ Dangerous: allowing "none"
jwt.verify(token, secret, { algorithms: ['none'] });
// ✅ Safe: explicit RS256 verification
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
// ❌ Dangerous: brute‑forceable secret
jwt.sign(payload, 'password123');
// ✅ Safe: strong secret (256+ bits)
jwt.sign(payload, process.env.JWT_SECRET);
// ❌ Dangerous: token never expires
jwt.sign({ userId: 123 }, secret);
// ✅ Safe: short expiration
jwt.sign({ userId: 123 }, secret, { expiresIn: '1h' });
// ❌ Dangerous: password in token (tokens can be decoded!)
jwt.sign({ userId: 123, password: 'secret' }, key);
// ✅ Safe: only IDs in payload
jwt.sign({ userId: 123 }, key);
JWT 보안을 위한 ESLint 플러그인
eslint-plugin-jwt 규칙 세트는 모범 사례를 강제하고 흔히 발생하는 실수를 잡아줍니다.
규칙 개요
| Rule | CWE | What it catches |
|---|---|---|
no-algorithm-none | CWE‑347 | 알고리즘 "none" 허용 |
no-algorithm-confusion | CWE‑327 | RS/HS 혼동 공격 |
no-weak-secret | CWE‑326 | 무차별 대입 가능한 비밀키 |
no-hardcoded-secret | CWE‑798 | 코드에 하드코딩된 비밀 |
no-sensitive-payload | CWE‑312 | 토큰에 포함된 개인식별정보(PII) |
require-expiration | CWE‑613 | exp 클레임 누락 |
require-algorithm-whitelist | CWE‑327 | 명시적 알고리즘 미지정 |
require-issuer-validation | CWE‑345 | iss 검증 누락 |
require-audience-validation | CWE‑345 | aud 검증 누락 |
no-decode-without-verify | CWE‑347 | jwt.decode() 오용 |
require-issued-at | CWE‑613 | iat 클레임 누락 |
require-max-age | CWE‑613 | verify에 maxAge 미설정 |
no-timestamp-manipulation | CWE‑345 | 시계 왜곡 공격 |
예시 Lint 오류
src/auth.ts
15:3 error 🔒 CWE-347 CVSS:9.8 | JWT algorithm 'none' is allowed
Risk: Attackers can forge tokens without a signature
Fix: Remove 'none' from algorithms: ['HS256']
설치 방법
npm install eslint-plugin-jwt
// .eslintrc.js
import jwtPlugin from 'eslint-plugin-jwt';
export default [jwtPlugin.configs.recommended];
13개의 규칙. 완전한 JWT 보안. 오탐 제로.
⭐ GitHub에 별을 달아 주세요.
행동에 옮기세요
🚀 지금 바로 JWT 설정을 점검해 보세요. 알고리즘 목록에 "none"이 포함돼 있나요?