암호 서명 검증 부적절 (JWT None 알고리즘)

Improper Verification of Cryptographic Signature

설명

인증용 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가 있으면 만료를 검증하지만 이 예시는 해당 클레임의 존재 자체를 요구하지 않습니다.

참조