安全でない乱数の使用

安全でない乱数の使用

説明

セッションID、トークン、一時パスワードなどに、java.util.Random、Math.random()、ThreadLocalRandom、SplittableRandom のような暗号用途に適さない疑似乱数生成器(PRNG)を使うと、値を予測されるおそれがあります。攻撃者は観測した値からシードや内部状態を推定し、後続の値を予測したり推測の候補を絞ったりして、セッションやアカウントの乗っ取りを試みる可能性があります。

想定される影響

  • セッションの乗っ取り: 予測可能なセッションIDやCookieにより、正規ユーザーになりすまされるおそれがあります。
  • アカウントの乗っ取り: 一時パスワード、ログイントークン、メール確認用トークンを推測され、アカウントを奪われる可能性があります。
  • 権限の悪用や昇格: アクセストークンやAPIキーを推測され、権限のない操作を実行されるおそれがあります。
  • CSRF対策の回避: CSRFトークンが予測可能だと、偽造したリクエストが通る可能性があります。
  • 機密情報の漏えい: 認証や認可の回避によって、保護された情報にアクセスされるおそれがあります。

対処方法

  • セキュリティに関わる値には java.security.SecureRandom を使います。
    • 例: SecureRandom sr = new SecureRandom(); byte[] b = new byte[32]; sr.nextBytes(b);
  • Random、Math.random()、ThreadLocalRandom、SplittableRandom をセッション、トークン、認証コードの生成に使わないでください。
  • 固定シードを指定せず、new SecureRandom() に既定のエントロピーを取得させます。
  • 少なくとも128ビット、できれば192~256ビットの乱数を生成し、URL-safe Base64でエンコードします。
  • パスワード再設定やメール確認用のトークンには短い有効期限を設け、1回だけ使用できるようにします。
  • 推測による攻撃を抑えるため、レート制限や試行回数の制限を適用します。

例

変更前

java
import javax.servlet.http.*;
import java.io.IOException;
import java.util.concurrent.ThreadLocalRandom;

public class WeakCookieServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        long id = ThreadLocalRandom.current().nextLong(); // 暗号用途に適さないPRNG
        String value = Long.toHexString(id);
        resp.addHeader("Set-Cookie", "SID=" + value + "; HttpOnly; Secure");
        resp.getWriter().println("ok");
    }
}

変更後

java
import javax.servlet.http.*;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Base64;

public class StrongCookieServlet extends HttpServlet {
    private static final SecureRandom SR = new SecureRandom();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        byte[] token = new byte[32]; // 256ビットのエントロピー
        SR.nextBytes(token);
        String value = Base64.getUrlEncoder().withoutPadding().encodeToString(token);
        resp.addHeader("Set-Cookie", "SID=" + value + "; HttpOnly; Secure; SameSite=Strict");
        resp.getWriter().println("ok");
    }
}

説明:

  • 変更前: ThreadLocalRandom は性能を重視したPRNGであり、暗号用途に必要な予測困難性を提供しません。この方法で作ったCookieは予測や推測が容易になり、セッションの乗っ取りにつながる可能性があります。
  • 変更後: SecureRandom で256ビットの乱数を生成し、URL-safe Base64でエンコードします。固定シードを使わず、十分なエントロピーを確保することで推測のリスクを抑えます。トークンの有効期限やサーバー側の検証は、別途実装する必要があります。

参考資料