SQLインジェクションスキャナー・ツールの選び方

松山 雄太

TECHFUND Inc. セキュリティ研究者

更新日:

対象読者: SQLインジェクションの検査ツールを選ぶ開発・セキュリティ担当者。

まず押さえるポイント

SQLインジェクションを調べるなら、まずは検査したいものが『動いているWebアプリ』なのか『ソースコード』なのかを決めます。前者にはZAPやsqlmapなどの動的検査、後者にはコード解析を使い、重要な箇所は両方で確認するのが実用的です。

スキャナーを選ぶ前に、検査の入口を決める

例えば、検索フォームに入力した値が、そのままSQL文の一部として組み立てられているとします。動的検査は実際にリクエストを送り、応答の差などから問題を探します。コード解析は値がどこから入り、どんなクエリに渡されるかを追います。

動的検査は画面やAPIに到達できなければ調べられません。コード解析も、対応言語やフレームワーク、解析対象の設定によって見逃しがあります。一方の結果がゼロでも、もう一方が不要になったとは判断できません。

代表的なツールと使い分け

ZAP:Webアプリ全体を確認したいときのOSS候補です。SQLインジェクションの能動スキャンルールがあり、ほかのWeb脆弱性も合わせて調べられます。ただしZAP Baselineは既定では受動スキャンなので、これを動かしただけでSQLインジェクションの能動検査が済んだとは言えません。

sqlmap:特定の入力箇所に疑いがあり、SQLインジェクションを重点的に検証したいときのOSSです。検出だけでなく悪用の自動化機能もあるため、許可範囲を決め、影響を抑えられる環境で使います。

Burp Suite Professional:HTTPリクエストを確認しながら、スキャナーと手動検証を組み合わせたい担当者向けです。自動スキャナーは有償のProfessionalなどに含まれ、無料のCommunity Editionには含まれません。

Wapiti:コマンドラインからWebアプリをブラックボックスで調べるOSSです。公式資料ではエラー、真偽値、時間差を使うSQLインジェクション検査やOpenAPIファイルを使ったAPIスキャンを案内しています。SPAや複雑な認証を含む対象で必要な経路に到達できるかは個別に検証してください。

いずれも稼働中のアプリにリクエストを送るツールです。画面だけでなく認証後のAPI、管理画面、利用者ごとの権限を含め、どこまで到達したかを結果と一緒に記録します。ソースコードを使った静的解析は別の選択肢として組み合わせます。

導入時に比べたいのは、検出数と到達範囲の両方

同じステージング環境に、既知の問題がある入力と、安全にパラメーター化された入力を用意して比べます。前者を見つけられるかに加えて、後者を誤って問題としないかも確認します。認証が切れて途中で検査が止まっていないか、JSONのAPIを含め必要な入口を巡回できたかも確認してください。

スキャンは検証用データや可用性に影響することがあります。対象の所有者から明示的な許可を得て、対象範囲、実行時間、負荷、除外する操作を合意し、可能なら本番とは分離した環境で試します。

MOVEitの事例から、公開経路も確認する

NVDが記録するMOVEit TransferのCVE-2023-34362では、SQLインジェクションにより未認証の攻撃者がDBへアクセスできる可能性があり、2023年5〜6月の悪用も記載されています。開発時には、ログイン後の処理だけでなく公開経路も確認する必要があります。以下はTypeScriptとnode-postgresによる架空の教材例で、MOVEitの実装や当該CVEの再現コードではありません。検査は許可済みの対象に限定してください。

見つけた後は、値とSQLの構造を分ける

修正の基本は、外部からの値をSQL文字列に連結せず、パラメーターとして渡すことです。ただし、並び順やテーブル名などSQLの構造そのものは、値のバインドだけでは扱えない場合があります。その場合は、アプリ側で用意した固定の選択肢から選びます。

次の例では検索値をバインドし、並び順は二つの固定クエリから選択します。これに加えて、DBユーザーの権限を必要最小限にし、利用者がそのデータを見る権限も別に確認します。修正後は、元の指摘が解消したことと、通常の検索が壊れていないことを再検証します。

サーバー側のnode-postgres教材例です。値は$1にバインドし、ORDER BYは二つの固定クエリから選び、未知の並び順は拒否します。利用者の認可とDBの最小権限は別途必要です。引用符がデータとして扱われること、両方の並び順、権限のない利用者への拒否も確認します。
import type { Pool } from "pg";

export async function findProducts(
  db: Pool, name: string, sort: string,
) {
  const queries = {
    newest: "SELECT id, name FROM products WHERE name = $1 ORDER BY created_at DESC, id DESC LIMIT 50",
    oldest: "SELECT id, name FROM products WHERE name = $1 ORDER BY created_at ASC, id ASC LIMIT 50",
  } as const;
  if (sort !== "newest" && sort !== "oldest") {
    throw new Error("Invalid sort");
  }
  return db.query(queries[sort], [name]);
}
架空の例:検索値をSQLの構造と分けて渡す
  1. ユーザーが検索値を入力
  2. アプリが値を $1 にバインド
  3. 固定クエリと値をデータベースに渡して実行

ORDER BY などのSQL構造は許可した固定クエリから選びます。利用者の認可とDBの最小権限は別途必要で、この図だけで安全性が保証されるわけではありません。

よくある質問

スキャナーでSQLインジェクションがないと保証できますか?

できません。検査範囲、設定、アクセス可能な画面によって検出できる範囲が変わります。コードレビューも組み合わせてください。

本番アプリを検査してもよいですか?

明示的な許可と合意した範囲が必要です。動的検査はデータや可用性に影響する場合があるため、可能な限り管理された環境を使用します。

出典・関連資料

Hi AUDIT Agentなら

コードのデータの流れを確認し、検出箇所と修正案をレビューできます。SQLインジェクションの具体的な対応範囲は対象環境ごとに確認し、人が再検証してください。

Hi AUDIT Agentに登録する