【実務・中級編】SQLインジェクションを防ぐプリペアドステートメントの言語別実装パターン – アプリケーションセキュリティ & 安全な開発防御ガイド

SQLインジェクションは「過去の遺物」か?――現場で戦うエンジニアへ贈る、真の防御戦略

「SQLインジェクション? 今どきフレームワーク使ってるし、大丈夫でしょ」

もしあなたが心のどこかでそう思っているなら、今日の話を最後まで聞いてほしい。私はこれまで数多くのインシデントレスポンスを担当してきたが、その多くは「ORMがあるから大丈夫」「一部のクエリだけ手書きしていた」という油断から始まっている。

攻撃者は、あなたの書いたコードの「わずかな隙間」を狙う。彼らにとってSQLインジェクションは、金庫の鍵を物理的に破壊するような手間のかかる作業ではない。「正しいはずの鍵穴に、別の形の鍵を差し込んで強引に回す」、それだけのシンプルな行為なのだ。

今日は、教科書に載っているようなお題目ではなく、現場で通用する「プリペアドステートメントによる絶対防御」の実装を、言語別の具体的なコードと共に解説する。

1. なぜ「文字列結合」が死を招くのか(PoCの視点)

まず、敵が何をしているかを理解しよう。例えば、こんなコードを書いていないだろうか。

— 攻撃者が入力欄に ‘ OR ‘1’=’1 を入力すると…
SELECT FROM users WHERE username = ” OR ‘1’=’1′ AND password = ‘…’;

このクエリが実行された瞬間、認証プロセスは無意味になる。データベースは「1=1」という真(TRUE)の結果を返し、攻撃者は管理者権限でログインする。これがSQLインジェクションの基本だ。

「動的なクエリの組み立て」は、悪魔の所業である。

2. 言語別:プリペアドステートメントの「正解」実装

プリペアドステートメントの核心は、「SQLの構造(命令)」と「データ(値)」を分離してDBエンジンに送ることにある。DB側が値を命令の一部として解釈する余地を、物理的に奪うのだ。

Python (psycopg2) の場合

PostgreSQLを使うなら、`psycopg2`のバインド変数機能を使いこなすこと。

NG: query = “SELECT FROM users WHERE id = ” + user_id
OK: パラメータを個別に渡す
query = “SELECT FROM users WHERE id = %s”
cursor.execute(query, (user_id,)) # 辞書やタプルで渡すことで安全に処理される

Node.js (pg) の場合

`pg`ライブラリを使う際、`$1`, `$2`といったプレースホルダーを使用する。

// $1 は第一引数の配列値と置換される
const query = ‘SELECT FROM users WHERE email = $1 AND status = $2’;
const values = [‘hacker@example.com’, ‘active’];

try {
const res = await client.query(query, values);
console.log(res.rows);
} catch (err) {
// ログには詳細を出さない(エラー情報から構造が漏れるため)
console.error(‘Database query error’);
}

Java (JDBC) の場合

Javaエンジニアなら、`PreparedStatement`を使わない理由は存在しない。

String sql = “SELECT FROM users WHERE username = ? AND password = ?”;
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.setString(1, inputUsername);
pstmt.setString(2, inputPassword);

try (ResultSet rs = pstmt.executeQuery()) {
// …処理
}
}

3. コードだけで安心するな:インフラ層の多重防御

アプリケーションが完璧でも、ゼロデイ脆弱性や設定ミスは起こりうる。我々のような「守る側」のエンジニアは、常に多層防御(Defense in Depth)を意識する必要がある。

Nginx/WAFでのフィルタリング

SQLインジェクションのシグネチャを検知するWAFは必須だ。特にAWS WAFなどを使用している場合、以下のルールセットを必ず有効化してほしい。

  • SQL injection protection rule group: よくあるSQLインジェクションパターン(`UNION SELECT`, `SLEEP()`, `information_schema`等)を自動的にブロックする。

最小権限の原則(IAM・DBユーザー)

Webアプリが使うDBユーザーに、`DROP TABLE`や`GRANT`の権限を与えていないだろうか?

設定すべきDBユーザーの権限ルール:
1. アプリ用ユーザーは、必要なテーブルへの `SELECT`, `INSERT`, `UPDATE` のみ許可する。
2. `schema` の変更権限は持たせない。
3. ストアドプロシージャの実行権限も、必要なものだけに絞る。

最後に:セキュリティは「態度」である

どれほど高度なツールを使っても、セキュリティの最終防衛ラインは「エンジニアの態度」だ。

  • 「動けばいい」というコードレビューをしない。
  • どんなに小さなSQL文でも、文字列結合の匂いがしたら指摘する。
  • ORMの便利機能に頼り切らず、実行されている生のクエリがどうなっているのかを確認する癖をつける。

あなたの書いたコードが、誰かの人生を狂わせる脆弱性の温床にならないように。それが、私たちがプロのエンジニアとして守るべき、最も大切な矜持ではないだろうか。

今日紹介したコードは、明日から現場でそのまま使えるはずだ。まずは君のプロジェクトのコードベースを `grep` して、`+` や `$` で文字列連結している箇所がないか、確認することから始めてほしい。

健闘を祈る。

コメント

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