설명
인증용 JWT에서 서명 없는 none 알고리즘을 허용하면 공격자가 조작한 페이로드를 유효한 토큰처럼 받아들일 수 있습니다. jsonwebtoken 9.x에서는 빈 키만 전달한다고 자동으로 서명 검증이 생략되지는 않습니다. 아래 예시는 빈 키와 함께 algorithms에 none을 명시적으로 허용하는 위험한 구성입니다. 서명 검증 없이 권한이나 사용자 식별자를 신뢰하면 인증·인가를 우회할 수 있습니다.
잠재적 영향
- 인증 우회: 서명이 검증되지 않아 위조 토큰으로 로그인 상태를 가장할 수 있습니다.
- 권한 상승: 페이로드의 role/admin 값을 임의로 조작해 관리자 권한을 획득할 수 있습니다.
- 데이터 유출: 보호된 API 엔드포인트에 접근하여 민감 정보를 조회할 수 있습니다.
- 세션 하이재킹: 합법적 사용자로 가장해 지속적으로 서비스 자원에 접근할 수 있습니다.
해결 방법
- 항상 유효한 비밀키(secret) 또는 공개키(public key)를 verify()에 전달하세요.
- 알고리즘 허용 목록을 명시하고 'none'은 절대 허용하지 마세요. 예: { algorithms: ["HS256"] } 또는 { algorithms: ["RS256"] }.
decode()는 파싱만 수행하므로 인증·인가에 그 결과를 신뢰하지 마세요. 검증에는verify()와 신뢰한 키를 사용하세요.iss(issuer),aud(audience),exp만료 등 클레임을 검증하세요. 만료가 필수인 토큰이라면exp가 있는지도 별도로 확인하세요.
예시
변경 전
javascript
const express = require("express");
const jwt = require("jsonwebtoken");
const app = express();
// 취약: 키 없이(또는 빈 문자열 등 falsy) 검증 수행
app.get("/profile", (req, res) => {
const auth = req.headers.authorization || "";
const token = auth.replace(/^Bearer\s+/i, "");
// BAD: falsy 키 + none 허용 -> 서명 검증 우회 가능
const payload = jwt.verify(token, "", { algorithms: ["HS256", "none"] });
// 위조된 payload로도 접근 가능해짐
res.json({ user: payload.sub, admin: payload.admin });
});
app.listen(3000);
변경 후
javascript
const express = require("express");
const jwt = require("jsonwebtoken");
const app = express();
const JWT_SECRET = process.env.JWT_SECRET; // 안전한 보관 필수
app.get("/profile", (req, res) => {
const m = (req.headers.authorization || "").match(/^Bearer\s+(.+)$/i);
if (!m) return res.status(401).send("Missing token");
try {
const payload = jwt.verify(m[1], JWT_SECRET, {
algorithms: ["HS256"], // none 미허용, 필요한 알고리즘만
issuer: "auth.example.com", // iss 검증
audience: "my-api", // aud 검증
});
return res.json({ user: payload.sub });
} catch (e) {
return res.status(401).send("Invalid token");
}
});
app.listen(3000);
설명:
- 변경 전: 빈 키와 함께
algorithms에none을 허용하여 서명 없는 토큰을 받아들일 수 있습니다. 따라서 조작된 페이로드를 인증된 사용자 정보로 신뢰할 위험이 있습니다. - 변경 후: verify() 호출 시 신뢰 가능한 비밀키를 제공하고, 허용할 알고리즘을 명시적으로 제한하여 none을 차단합니다. 추가로
iss,aud를 검증하고 실패한 요청을 거부합니다.JWT_SECRET은 충분히 강한 비밀값으로 구성해야 합니다.exp가 있으면 만료를 검증하지만 이 예시는 해당 클레임의 존재 자체를 요구하지 않습니다.