Featured image of post SQLインジェクション対策:最新のデータベースセキュリティガイド Featured image of post SQLインジェクション対策:最新のデータベースセキュリティガイド

SQLインジェクション対策:最新のデータベースセキュリティガイド

パラメータ化クエリ、ORM保護、ストアドプロシージャ、WAF回避技術、NoSQLインジェクション亜種、自動スキャンツールまで網羅したSQLインジェクション対策の最新ガイド。

SQLインジェクションは数十年の認識がありながら、今なおOWASP Top 10にランクインしています。2023〜2024年には医療、eコマース、政府機関でSQLiを悪用した重大な情報漏洩が発生しました。古典的な' OR 1=1 --攻撃はよく知られていますが、二次注入、ブラインドSQLi(時間ベース・真偽値ベース)、帯域外抽出などの最新亜種も増加しています。予防策は周知されているにもかかわらず、レガシーコード、ORMの誤用、テスト自動化の不足により実行が不十分なのが現状です。

パラメータ化クエリとプリペアドステートメント

プリペアドステートメントはSQLインジェクション対策の黄金基準です。SQLロジックとデータをデータベースエンジンレベルで分離し、ユーザー入力がクエリ構造を改変することを防ぎます。

// Node.js (pg) — 安全
const result = await client.query(
  "SELECT * FROM users WHERE email = $1 AND status = $2",
  [userEmail, "active"]
);

// Python (psycopg2) — 安全
cursor.execute(
  "INSERT INTO orders (user_id, amount) VALUES (%s, %s)",
  [user_id, amount]
);

文字列補間は、入力が「エスケープ」されていても決して安全ではありません。パラメータ化クエリはクエリプランキャッシュを損なわず、むしろ多くのデータベースでアドホックSQLよりも効率的にキャッシュされます。

言語安全なAPI危険なパターン
Node.js (pg)client.query(sql, params)文字列補間によるSQL
Python (psycopg2)cursor.execute(sql, params)f-strings や % 書式
Java (JDBC)PreparedStatementStatement + 文字列結合
C# (SqlClient)SqlCommand + ParametersインラインSQL
PHP (PDO)PDO::prepare() + execute()mysqli::query() + 連結

動的テーブル名や可変長IN句は注意が必要です。テーブル名は許可リストで検証し、動的リストにはANY配列構文を使用します。


ORM保護と落とし穴

Prisma、TypeORM、Django ORM、HibernateなどのORMはデフォルトでインジェクションを防止します。しかし、生クエリ、LIKE句、ORDER BY句でリスクが再発します。

// Prisma — 生クエリの落とし穴
const users = await prisma.$queryRawUnsafe(
  `SELECT * FROM users WHERE name LIKE '%${searchTerm}%'` // 脆弱
);

// 安全な代替:パラメータバインディング
const users = await prisma.$queryRaw`
  SELECT * FROM users WHERE name LIKE ${"%" + searchTerm + "%"}
`;

ORDER BY句はパラメータ化できません。カラム名を許可リストで検証してください:

const allowedColumns = ["name", "email", "created_at"];
if (!allowedColumns.includes(sortBy)) {
  throw new Error("無効なソートカラム");
}
const result = await client.query(
  `SELECT * FROM users ORDER BY ${sortBy} ${sortDir}`,
);

ORMは銀の弾丸ではありません — 開発者は基盤となるSQLの振る舞いを理解する必要があります。


ストアドプロシージャとデータベース強化

適切にパラメータ化されたストアドプロシージャは安全です。しかし、内部でEXECEXECUTE IMMEDIATEを使用した動的SQLはリスクを再導入します。

-- 安全
CREATE PROCEDURE get_user(IN user_id INT)
BEGIN
  SELECT * FROM users WHERE id = user_id;
END;

-- 脆弱
CREATE PROCEDURE get_user(IN table_name VARCHAR(64))
BEGIN
  SET @sql = CONCAT('SELECT * FROM ', table_name);
  PREPARE stmt FROM @sql;
  EXECUTE stmt;
END;

最小権限の原則を適用します。アプリケーションのDBユーザーには必要最小限の権限のみ付与し、DROPALTERCREATEは許可しません。PostgreSQLの行レベルセキュリティ、SQL ServerのEXECUTE AS、MySQLのSQL SECURITYを活用してデータベース層でアクセスを制御します。


入力検証とWAF

入力検証だけでは不十分ですが、第二層として不可欠です。ブラックリスト方式よりホワイトリスト方式を優先します。型強制(parseIntによる整数変換)、長さ制限、Unicode正規化(NFC/NFD)により多くのバイパス試行を防げます。

WAFは遅延戦術であり、解決策ではありません。攻撃者はコメント難読化(/**/)、エンコーディング亜種(URL、Unicode、Hex、二重URLエンコード)、HTTPパラメータ汚染、SLEEP()の代わりにBENCHMARK()を使用するなどの手法でWAFを回避します。

-- WAF回避:コメント難読化
' UNION/**/SELECT/**/password/**/FROM/**/users--

-- WAF回避:別の関数を使用
1 AND BENCHMARK(5000000, MD5('test'))

NoSQLインジェクションと自動スキャン

NoSQLデータベースもインジェクションに対して脆弱です。MongoDBの$where句注入、$regex注入、JSONクエリオブジェクト操作は全てリスクです。原則は普遍的です:ユーザー入力を連結するデータベース言語はすべて脆弱です。

データベースインジェクションベクター防御策
MongoDB$where, $regex, $ne$eqの使用、演算子の検証
CouchbaseN1QL文字列連結パラメータ化N1QL
CassandraCQL文字列連結プリペアドステートメント
RedisEVAL/EVALSHA (Lua)パラメータ化EVAL

シフトレフトで自動スキャンを導入します。Semgrep、ESLintセキュリティプラグイン、CodeQLなどのSASTツールがプルリクエストでインジェクションパターンを検出します。OWASP ZAPなどのDASTツールが実行中のアプリケーションを動的にスキャンします。

name: Security Scan
on: pull_request
jobs:
  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npx semgrep --config=auto

防止が失敗した場合に備えて、異常なクエリパターン、予期しないエラー、データ送信量の急増を監視します。資格情報を即座にローテーションし、クエリログ分析で注入箇所を特定し、データ漏洩を封じ込め、フォレンジック証拠を保存します。

SQLインジェクションは、確立されたパターンを一貫して適用することで完全に防止可能です。パラメータ化クエリを一次防御とし、ORM(生クエリ認識あり)を構造的防御とし、入力検証とWAFを二次層とし、CI/CDの自動スキャンを検証手段とします。これらの層を一貫して適用することで、脆弱性のクラス全体を排除できます。