불충분한 난수 사용

보안용 값에 부적합한 의사 난수 사용

설명

비밀번호 재설정 토큰, 세션 ID, OTP나 CSRF 토큰을 Math.random()으로 만들면 공격자의 예측을 막는 암호학적 보장을 얻을 수 없습니다. 구현과 관측 가능한 출력에 따라 다음 값을 추정할 수 있으며, 값의 범위가 작으면 무차별 대입도 쉬워집니다. 보안용 난수에는 충분한 길이와 암호학적 난수 생성기가 필요합니다.

잠재적 영향

  • 인증/세션 탈취: 예측 가능한 token 또는 session ID를 추측해 타인의 계정으로 로그인하거나 세션을 가로챌 수 있습니다.
  • 비밀번호 재설정 하이재킹: reset 토큰을 맞춰 비밀번호를 변경, 계정을 탈취할 수 있습니다.
  • 2FA/OTP 우회: 예측 가능한 OTP/코드를 생성할 경우, 추가 인증을 무력화할 수 있습니다.
  • CSRF 방어 우회: CSRF token이 예측 가능하면 위조 요청을 방어하지 못합니다.
  • 암호화 안전성 약화: 키의 예측이나 알고리즘이 요구하는 nonce·IV 조건 위반으로 기밀성 또는 무결성 보장이 약해질 수 있습니다.

해결 방법

  • CSPRNG 사용: 보안 민감 값 생성에는 반드시 암호학적 난수 생성기(CSPRNG)를 사용하세요.
    • Browser: window.crypto.getRandomValues
    • Node.js: crypto.randomBytes, (숫자 코드가 필요하면) crypto.randomInt
  • 충분한 길이 확보: 추측을 막아야 하는 무작위 토큰은 충분한 엔트로피(예: 128비트 이상)를 확보하세요. 암호 키와 nonce·IV는 사용 알고리즘의 길이·유일성·예측 불가능성 요구를 따르세요. 짧은 OTP에는 만료와 시도 횟수 제한도 필요합니다.
  • 안전한 인코딩: hex 또는 base64url로 인코딩하세요. 임의 문자셋으로 매핑할 때는 나머지(%) 연산으로 인한 bias가 생기지 않도록 rejection sampling 등을 사용하세요.
  • Math.random 금지: 보안 목적(비밀번호/토큰/키/nonce/OTP 등)에는 Math.random을 절대 사용하지 마세요.

예시

변경 전

javascript
// BAD: 보안 민감 토큰을 Math.random()으로 생성
function createResetToken() {
  // 예측 가능하며 엔트로피가 낮음
  const token =
    Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
  return token; // reset, session, apiKey 등에 사용하면 위험
}

변경 후

javascript
// GOOD (Node.js): CSPRNG 사용, 256비트 토큰
const crypto = require("crypto");
function createResetToken() {
  return crypto.randomBytes(32).toString("hex"); // 64자 hex, 편향 없음
}

// GOOD (Browser): Web Crypto API
function createCsrfToken() {
  const bytes = crypto.getRandomValues(new Uint8Array(32)); // 256비트
  // base64url 인코딩
  let b64 = btoa(String.fromCharCode(...bytes))
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
  return b64;
}

변경 전에는 Math.random()의 출력을 문자열로 바꾸지만, 인코딩은 암호학적 보안을 추가하지 않습니다. 변경 후의 Node.js와 브라우저 부분은 각각의 환경에서 별도로 실행하는 예시입니다. 각 API에서 생성한 32바이트를 hex 또는 base64url로 인코딩합니다. 실제 토큰에는 용도, 만료, 사용 횟수와 저장·전송 보호도 적용하세요.

참조