Java | 再帰のセキュリティ上の注意点

Java Java
スポンサーリンク

Javaで考える「再帰のセキュリティ上の注意点」(前半)

再帰は、アルゴリズムを美しく書ける強力なテクニックです。 ツリー探索、ファイル走査、数列計算、パーサ、バックトラッキングなど、 多くの場面で再帰は自然な形で登場します。

しかし、セキュリティスペシャリストの視点で見ると、 再帰には「きれいさ」と同時に、いくつか危険な側面があります。 前半では、まずその危険性を直感的に理解し、 具体例を通して「どこが危ないのか」をかみ砕いて整理していきます。 後半では、対策や安全な設計の考え方を掘り下げます。

再帰の基本をセキュリティ目線で見直す

再帰は「自分自身を呼び出す関数」です。 Java では、次のような形が典型です。

public static int factorial(int n) {
    if (n == 0) return 1;
    return n * factorial(n - 1);
}
Java

このコードは、数学的な定義そのままに書かれていて、 読みやすく、理解しやすい形になっています。

しかし、セキュリティの観点では、 次の二つが重要なチェックポイントになります。

終了条件が正しく設計されているか 入力によって「深さ」が暴走しないか

この二つが崩れると、再帰は一気に「危険なコード」に変わります。

危険性1:終了条件が壊れたときの無限再帰

再帰で最も分かりやすい危険が「終了条件の欠落・バグ」です。 終了条件が正しく機能しないと、 メソッドは自分自身を延々と呼び続けることになります。

例えば、次のようなバグを考えてみます。

public static int factorial(int n) {
    if (n == 0) return 1;
    return n * factorial(n); // n-1 を書き忘れている
}
Java

このコードは、factorial(5) を呼ぶと factorial(5)factorial(5)factorial(5) → … という無限再帰になります。

Javaでは、再帰呼び出しの深さには上限があり、 一定回数を超えると StackOverflowError が発生します。

セキュリティの観点では、 「終了条件のバグ」が攻撃者に悪用される可能性があります。 外部入力から n が渡される場合、 攻撃者は意図的に終了条件を踏まない値を送り込むことで、 サービスを停止させることができます。

危険性2:入力による「深さの暴走」

終了条件が正しくても、 入力によって再帰の深さが極端に深くなることがあります。

例えば、ディレクトリ構造を再帰で走査するコードを考えてみます。

public static void scan(File dir) {
    File[] files = dir.listFiles();
    if (files == null) return;

    for (File f : files) {
        if (f.isDirectory()) {
            scan(f);
        } else {
            System.out.println(f.getPath());
        }
    }
}
Java

このコードは、一見すると問題なさそうです。 しかし、もし攻撃者が「異常に深いディレクトリ構造」を作った場合、 再帰の深さは数千・数万階層に達する可能性があります。

Java のコールスタックには上限があるため、 深さが限界を超えると StackOverflowError が発生します。

ここで重要なのは、 「再帰の深さは入力に依存する」という点です。 外部から渡されるデータ構造(JSON、XML、フォルダ、ツリーなど)に対して再帰を使うとき、 その深さは攻撃者にコントロールされ得ます。

危険性3:計算量の爆発と DoS攻撃

フィボナッチ数列のように、 「再帰が枝分かれする」タイプのアルゴリズムは、 計算量が指数関数的に増えることがあります。

例えば、次のようなフィボナッチ再帰を考えます。

public static int fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
}
Java

このコードは、n が少し大きくなるだけで 呼び出し回数が爆発的に増えます。

n = 30 で数十万回 n = 40 で数億回 n = 50 で数十億回

セキュリティの観点では、 「n を外部から受け取る API」がこの実装を使っていると非常に危険です。

攻撃者が意図的に大きな n を送ることで、 サーバーに膨大な計算を強制し、 サービスを事実上停止させることができます。

これは典型的な DoS(Denial of Service:サービス妨害)攻撃 の一形態です。

危険性4:スタック情報の漏洩とエラーメッセージ

再帰が原因で StackOverflowError やその他の例外が発生したとき、 エラーメッセージやスタックトレースがそのまま外部に返されると、 内部構造が攻撃者に漏洩する可能性があります。

例えば、スタックトレースには

クラス名 メソッド名 ファイル名 行番号

などが含まれます。

これらの情報は、攻撃者にとって 「システム内部を推測するためのヒント」になります。

セキュリティの観点では、 再帰による例外が発生したときに スタックトレースをそのまま返さないことが重要です。

再帰と「入力の信頼性」の関係

再帰の危険性は、 ほとんどの場合「入力の信頼性」と結びついています。

内部で固定値だけを扱う再帰(例えばテスト用のコード)なら、 深さや計算量は開発者が完全にコントロールできます。

しかし、外部入力から渡される値や構造に対して再帰を使う場合、 深さ・計算量・終了条件の成立は、 攻撃者に部分的にコントロールされることになります。

そのため、セキュリティスペシャリストは

どこまでが安全な深さか どこまでが許容できる計算量か どの入力が終了条件を踏まないか

といった点を常に意識します。

前半のまとめと後半への橋渡し

前半では、再帰のセキュリティ上の注意点として

終了条件の欠落・バグによる無限再帰 入力による深さの暴走(StackOverflowError) 計算量の爆発による DoS攻撃 スタックトレースによる内部情報の漏洩 入力の信頼性と再帰の関係

といったテーマを整理しました。

後半では、これらの危険に対して

深さ制限の設計 非再帰化(ループ・スタック利用) 計算量を意識したアルゴリズム選択 例外処理とエラーメッセージの設計 安全な再帰の書き方の具体的なパターン

を、Javaコードとともに詳しく解説していきます。

タイトルとURLをコピーしました