Javaアプリケーションにおけるセキュアコーディング完全攻略|Java Gold頻出の脆弱性対策を徹底解説

Java Gold試験対策 セキュアコーディング(SQLインジェクション対策、機密データ保護)の解説と実戦例題10問の図解アイキャッチ画像

Java Gold試験において「Javaアプリケーションにおけるセキュアコーディング」は、出題数こそ多くないものの、知っているか知らないかで得点が大きく変わる分野です。「インジェクション攻撃って何?」「入力検証はどう書くの?」という疑問に、現役文系エンジニアの視点で、試験で狙われるポイントを図解とクイズ形式で徹底解説します。

目次

1. セキュアコーディングとは:なぜJavaエンジニアに必要か

セキュアコーディングとは、悪意のある攻撃や意図しない動作を防ぐことを意識してコードを書くことです。

文系出身の私は、セキュアコーディングを「泥棒が入れない家を設計する」イメージで理解しています。鍵をかける(入力検証)、窓を強化する(エスケープ処理)、合鍵を最小限にする(権限の最小化)。これらを最初から設計に組み込むのがセキュアコーディングの考え方です。

試験では「このコードにはどんな脆弱性があるか」「どう修正すべきか」という形で出題されます。

2. 【最重要】試験で狙われる3大攻撃パターン

① インジェクション攻撃

悪意のある入力値をプログラムに混入させて、意図しない処理を実行させる攻撃です。

脆弱なコード例(SQLインジェクション):

// ❌ 危険:ユーザー入力を直接SQL文に連結している
String query = "SELECT * FROM users WHERE name = '" + userName + "'";
Statement stmt = conn.createStatement();
stmt.executeQuery(query);

上記のコードでは、`userName` に `’ OR ‘1’=’1` のような文字列を入れられると、全件取得されてしまいます。

修正後(PreparedStatementを使う):

// ✅ 安全:プレースホルダーで入力値を分離する
String query = "SELECT * FROM users WHERE name = ?";
PreparedStatement pstmt = conn.prepareStatement(query);
pstmt.setString(1, userName);
pstmt.executeQuery();

試験のポイント: `Statement` ではなく `PreparedStatement` を使うことがセキュアコーディングの基本。プレースホルダー(`?`)によって入力値がSQL文として解釈されなくなります。

② 機密データの漏洩

パスワードや個人情報などの機密データを、意図せず外部に公開してしまう問題です。

// ❌ 危険:パスワードをStringで保持するとメモリに残り続ける
String password = "secret123";

// ✅ 安全:char[]で保持し、使用後にゼロクリアする
char[] password = {'s','e','c','r','e','t','1','2','3'};
// 使用後
Arrays.fill(password, '\0');

試験のポイント: `String` はイミュータブル(変更不可)のためGCされるまでメモリに残ります。パスワードなどは `char[]` で管理し、使用後に `Arrays.fill()` でゼロクリアすることが推奨されます。

③ 入力検証の不備

外部からの入力値を検証せずに使用することで発生する問題です。

// ❌ 危険:入力値をそのまま使用
public void processAge(int age) {
    System.out.println("年齢:" + age);
}

// ✅ 安全:入力値を検証してから使用
public void processAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("不正な年齢値: " + age);
    }
    System.out.println("年齢:" + age);
}

試験のポイント: 外部からの入力は「信頼しない」が基本。範囲外の値、null、空文字列などを適切にチェックし、不正な値には `IllegalArgumentException` などの例外をスローします。

3. セキュアコーディングの主要原則まとめ

原則内容よく使う手段
入力検証外部入力を信頼しない範囲チェック・null チェック・正規表現
最小権限の原則必要最小限の権限のみ付与するprivateフィールド・アクセス修飾子
機密データ保護パスワード等をメモリに残さないchar[]使用・Arrays.fill()でクリア
インジェクション対策入力値をコードとして実行させないPreparedStatement・エスケープ処理
エラー情報の制限詳細なエラー情報を外部に出さない例外メッセージを汎用化する
不変オブジェクトの活用オブジェクトの予期せぬ変更を防ぐfinalフィールド・防御的コピー

4. 防御的コピーとイミュータブル設計

セキュアコーディングの重要な技法として「防御的コピー」があります。オブジェクトを外部から変更されないようにするための手法です。

// ❌ 危険:配列の参照をそのまま返す(外部から変更可能)
public class UserData {
    private int[] scores;
    
    public int[] getScores() {
        return scores; // 呼び出し元から変更できてしまう
    }
}

// ✅ 安全:防御的コピーを返す
public class UserData {
    private int[] scores;
    
    public int[] getScores() {
        return scores.clone(); // コピーを返すので元データは安全
    }
    
    // コンストラクタでも防御的コピーを使う
    public UserData(int[] scores) {
        this.scores = scores.clone();
    }
}

試験のポイント: ミュータブル(変更可能)なオブジェクトを返すときは `.clone()``Collections.unmodifiableList()` を使って防御的コピーを返すことが推奨されます。コンストラクタでの受け取り時も同様です。

5. アクセス修飾子と最小権限の原則

セキュアコーディングの基本中の基本が「必要最小限の公開範囲にする」ことです。

// ❌ 危険:フィールドをpublicにすると外部から直接変更可能
public class BankAccount {
    public double balance; // 外部から自由に変更できてしまう
}

// ✅ 安全:privateにしてメソッド経由でのみアクセスを許可
public class BankAccount {
    private double balance;
    
    public double getBalance() {
        return balance;
    }
    
    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("金額は正の値で指定してください");
        balance += amount;
    }
}

試験のポイント: フィールドは原則 `private`。アクセスはgetterとsetterを通じて行い、setterで入力検証を行うことで不正な値の混入を防ぎます。

6. 実戦演習10問:クリックしてチェック!

【例題1】SQLインジェクション対策

次のコードの問題点として正しいものはどれですか?

String query = "SELECT * FROM users WHERE id = " + userId;
Statement stmt = conn.createStatement();
stmt.executeQuery(query);

A. Statementの代わりにConnectionを使うべきである
B. userIdを直接SQL文に連結しているためSQLインジェクションの危険がある
C. executeQuery()ではなくexecute()を使うべきである
D. このコードに問題はない

【解説】正解:B
ユーザー入力をSQL文に直接連結するとSQLインジェクション攻撃を受ける危険があります。PreparedStatementとプレースホルダー(?)を使って入力値を分離するのが正しい対策です。

【例題2】パスワードの安全な管理

パスワードを安全に扱う方法として最も適切なものはどれですか?

A. String型で保持し、使用後にnullを代入する
B. char[]型で保持し、使用後にArrays.fill()でゼロクリアする
C. byte[]型で保持し、使用後にnullを代入する
D. String型で保持し、System.gc()を呼び出してGCを促す

【解説】正解:B
StringはイミュータブルなためGCされるまでメモリに残り続けます。char[]はArrays.fill()でゼロクリアすることで即座にメモリから機密情報を消去できます。nullの代入やSystem.gc()はGCのタイミングを保証しません。

【例題3】防御的コピー

次のコードについて正しい説明はどれですか?

public class Config {
    private List<String> settings;
    
    public List<String> getSettings() {
        return settings;
    }
}

A. settingsフィールドがprivateなので安全である
B. getSettings()が内部のリストの参照を返すため、外部から変更可能である
C. List型はイミュータブルなので変更される心配はない
D. このコードに問題はない

【解説】正解:B
フィールドがprivateでも、getterがミュータブルなオブジェクトの参照を直接返すと外部から変更できます。Collections.unmodifiableList(settings)を返すか、new ArrayList<>(settings)でコピーを返すのが正しい対応です。

【例題4】入力検証

publicメソッドの引数に対する入力検証として最も適切なものはどれですか?

A. アサーション(assert)で検証する
B. if文で検証し、不正な値には例外をスローする
C. 入力値はそのまま使用し、問題が起きてから対処する
D. tryブロックで囲み、例外が発生したら握りつぶす

【解説】正解:B
アサーションはデフォルトで無効のため、publicメソッドの入力検証には使いません。if文で明示的に検証し、不正な値にはIllegalArgumentExceptionなどの例外をスローするのが正しい実装です。

【例題5】エラー情報の漏洩

次のコードの問題点として正しいものはどれですか?

try {
    // DB処理
} catch (SQLException e) {
    response.getWriter().println("エラー: " + e.getMessage());
}

A. catchブロックでSQLExceptionを捕捉してはいけない
B. 例外の詳細メッセージを外部に出力するとDB構造などの機密情報が漏洩する危険がある
C. response.getWriter()の使用が問題である
D. このコードに問題はない

【解説】正解:B
SQLExceptionのメッセージにはSQLクエリやテーブル名など、攻撃者に有益な情報が含まれることがあります。外部に返すエラーメッセージは「処理中にエラーが発生しました」のように汎用化し、詳細はログにのみ出力するのが正しい実装です。

【例題6】最小権限の原則

セキュアコーディングにおける「最小権限の原則」の説明として正しいものはどれですか?

A. すべてのフィールドをpublicにして透明性を高める
B. 必要最小限のアクセス権限のみを付与し、不要な公開を避ける
C. メソッドはすべてstaticにして共有を最小化する
D. クラスはすべてfinalにして継承を禁止する

【解説】正解:B
最小権限の原則とは、必要な処理に必要な権限だけを与えることです。フィールドはprivate、メソッドは必要なものだけpublicにするなど、アクセス修飾子を適切に使い分けることで不正アクセスのリスクを下げます。

【例題7】イミュータブルオブジェクト

イミュータブル(不変)なクラスを設計する際に必要な要素として正しいものをすべて選んでください。(2つ選択)

A. クラスをfinalにする
B. すべてのフィールドをpublicにする
C. すべてのフィールドをprivate finalにする
D. セッターメソッドを必ず提供する

【解説】正解:A・C
イミュータブルクラスの設計には「クラスをfinalにして継承を禁止する」「フィールドをprivate finalにして変更不可にする」ことが必要です。セッターを提供するとフィールドが変更できてしまい、イミュータブルではなくなります。

【例題8】PreparedStatementの使い方

PreparedStatementを使ったSQLインジェクション対策について正しいものはどれですか?

A. プレースホルダー(?)に入力値をセットすることで、入力値がSQL文として解釈されなくなる
B. PreparedStatementを使えばSQL文に直接文字列連結しても安全である
C. PreparedStatementはStatementより遅いため実務では使わない
D. プレースホルダーはWHERE句にしか使えない

【解説】正解:A
PreparedStatementのプレースホルダー(?)にsetString()などでセットされた値は、DBドライバーがエスケープ処理を行うためSQL文として解釈されません。PreparedStatementを使っても文字列連結をすれば意味がない点も覚えておきましょう。

【例題9】コンストラクタでの防御的コピー

次のコードの問題点として正しいものはどれですか?

public class Period {
    private final Date start;
    
    public Period(Date start) {
        this.start = start; // 渡された参照をそのまま保持
    }
}

A. Dateクラスはイミュータブルなのでこのコードは安全である
B. コンストラクタに渡したDateオブジェクトを後から変更するとフィールドも変わってしまう
C. finalフィールドなので外部から変更されることはない
D. このコードに問題はない

【解説】正解:B
DateはミュータブルなクラスなのでnewDate(date.getTime())のように防御的コピーを使うべきです。finalフィールドでも、参照先のオブジェクト自体がミュータブルなら変更可能です。

【例題10】総合問題

次のうちセキュアコーディングの観点から正しい実装はどれですか?

// A案
public class UserService {
    public String[] getRoles() {
        return roles;
    }
}

// B案
public class UserService {
    public String[] getRoles() {
        return roles.clone();
    }
}

A. A案:参照を直接返す方がパフォーマンスが良く推奨される
B. B案:clone()で防御的コピーを返すことで外部からの変更を防ぐ
C. どちらも同じ結果になる
D. 配列の場合はclone()を使っても効果がない

【解説】正解:B
配列はミュータブルなため、直接参照を返すと外部から要素を変更できます。clone()による防御的コピーを返すことでオリジナルの配列を保護できます。セキュリティとパフォーマンスはトレードオフですが、試験ではセキュリティ重視の選択肢を選びましょう。

まとめ:合格へのチェックリスト

  • SQLインジェクション対策はPreparedStatement+プレースホルダーが基本
  • パスワードはchar[]で管理し、使用後はArrays.fill()でゼロクリア
  • 外部入力は必ずif文で検証し、不正値にはIllegalArgumentExceptionをスロー
  • publicメソッドの入力検証にアサーションは使わない
  • ミュータブルなオブジェクトを返すときは防御的コピー(clone()・unmodifiableList())
  • エラーメッセージは汎用化して詳細情報を外部に漏らさない
  • フィールドは原則private、最小権限の原則を徹底する

セキュアコーディングの詳細な仕様についてはOracle公式:Secure Coding Guidelines for Java SEも参考にしてください。

関連記事はこちら:アノテーション完全攻略ガイド|Java Gold対策例外処理とアサーション完全攻略ガイド

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

tibiyaのアバター tibiya ITエンジニア

文系卒あほエンジニア
趣味はゲームとギャンブルとテニスっぽいスポーツと釣りです
Java javascript angular react C#

コメント

コメントする

目次