← コラム一覧

SQLインジェクションとは|プレースホルダによる根本対策とエスケープとの違い

SQL インジェクションが成立する仕組みを、SQL 文の組み立て方の違いから解説。プレースホルダ(バインド機構)が根本対策とされる理由、エスケープや入力値検証との位置づけの違い、最小権限による被害の限定、情報処理安全確保支援士試験での問われ方まで。

SQL インジェクション(CWE-89)は、アプリが SQL 文を文字列として組み立てるときに、利用者から受け取った値をそのまま繋いでしまうことで成立する脆弱性です。値として扱われるはずの文字列が SQL の構文の一部として解釈され、本来の条件を書き換えたり、別の表を読み出したりできてしまいます。情報処理安全確保支援士試験では、根本対策としてプレースホルダを書かせる設問が定番で、エスケープ処理や入力値検証との位置づけの違いまで問われます。

なぜ成立するのか — 骨組みと値の境目

問題の本質は「SQL の骨組み(構文)」と「値」の境目が、文字列連結では失われることにあります。データベースは受け取った一本の文字列を解釈するだけなので、どこまでが開発者の意図した構文で、どこからが利用者の入力だったかを知る術がありません。引用符を閉じる文字が値の中に紛れ込めば、そこから先は構文として読まれます。

文字列として組み立てる "SELECT ... WHERE name = '" + 入力 + "'" 入力が SQL 文の一部として解釈される。骨組みと値の境目が無い 値が構文になる プレースホルダに渡す "SELECT ... WHERE name = ?" + 値を別に渡す 骨組みを先に決めてから値を当てはめる。値は値のまま扱われる エスケープの書き漏らしが起こりえない構造になる 値は値のまま
文字列連結とプレースホルダの違い。値が構文になりうるかどうかが分かれ目。
# 【危険】値を文字列として繋いでいる
sql = "SELECT id, subject FROM inquiries WHERE subject LIKE '%" + keyword + "%'"
rows = conn.execute(sql).fetchall()

# 【安全】骨組みを先に決め、値は別に渡す
sql = "SELECT id, subject FROM inquiries WHERE subject LIKE %s"
rows = conn.execute(sql, [f"%{keyword}%"]).fetchall()

プレースホルダを使うと、データベースは先に SQL 文の構造を解析し、後から値を当てはめます。値の中に引用符やセミコロンが入っていても、それは値の一部として扱われるだけで、構文にはなりません。「エスケープを書き漏らす」という失敗が構造的に起こりえなくなる点が、根本対策と呼ばれる理由です。

静的プレースホルダと動的プレースホルダ

方式値を当てはめる場所特徴
静的プレースホルダデータベース側構文解析が先に確定する。原理的に構文へ影響しない
動的プレースホルダアプリ(ライブラリ)側ライブラリがエスケープして組み立てる。実装の品質に依存する

どちらもプレースホルダと呼ばれますが、値を当てはめる場所が違います。可能であれば静的プレースホルダ(プリペアドステートメント)を選びます。なお、表名や列名、ORDER BY の並び順といった「構文そのもの」はプレースホルダで置き換えられません。これらを利用者に選ばせる必要がある場合は、許可リスト(あらかじめ決めた選択肢との照合)で扱います。

エスケープと入力値検証の位置づけ

エスケープ処理は次善の策です。文字の種類やデータベースの設定(文字コードなど)によって必要な処理が変わり、書き漏らしや解釈の食い違いが残ります。入力値検証(数値であることを確かめる、長さを制限するなど)は補助であって、根本対策ではありません。午後問題で「根本対策を述べよ」と問われたときに入力値検証を書くと、点になりません。

権限を絞ることで何が変わるか

アプリが接続するデータベースアカウントに、全表への SELECT/INSERT/UPDATE/DELETE を与えていると、検索処理に一つ穴があるだけで、会員名簿や請求情報まで読み出されます。逆に、問い合わせ表への SELECT と INSERT しか持たないアカウントで接続していれば、同じ穴でも届く範囲は問い合わせ表までに限られます。脆弱性そのものは無くなりませんが、被害の大きさが変わります。

同じ穴でも、届く範囲が変わる 広い権限のアカウント アプリ 問い合わせ表 会員名簿 請求情報 穴が突かれると、全部に届く 必要な権限だけのアカウント アプリ 問い合わせ表 会員名簿(不可) 請求情報(不可) 穴は残っていても、届く範囲は狭い
同じ脆弱性でも、接続アカウントの権限で被害の範囲が変わる。

大事なのは、これが「根本対策の代わり」にはならないことです。プレースホルダは穴そのものを塞ぎ、最小権限は穴が残っていても届く範囲を狭めます。役割が違うので、対策を二つ挙げるときはこの二つを並べるのが自然です。緩和策を二つ並べても、根本対策を書いたことにはなりません。

エラーメッセージと、痕跡の残り方

データベースのエラーをそのまま画面に返すと、表名・列名・SQL 文の断片といった内部情報が漏れ、攻撃の手がかりになります。利用者には定型のメッセージを返し、詳細はサーバのログにだけ残します。一方で、エラーを返さなくても、応答の違い(結果が返る/返らない、応答時間)から情報を得る手法があるため、エラーを隠すことは対策ではなく、あくまで手がかりを減らすだけである点にも注意します。

調査では、データベースの監査記録が決め手になります。「どのアカウントが」「いつ」「どの表を」「何行返したか」が残っていれば、平常時と比べて異常を判断できます。返した行数が業務の量を大きく超えている操作は、それだけで有力な手がかりです。

情報処理安全確保支援士 試験での問われ方

午後問題では、脆弱な SQL の組み立てを抜粋で示し、「脆弱性の名称」「根本対策」を書かせ、続けて「アカウントの権限設定の問題点」や「ログから異常と判断した根拠」を問う構成が多く見られます。対策を書くときは、根本対策(プレースホルダ)と被害の限定(最小権限)を混ぜないこと、そして「文字列で組み立てない」という否定形まで書けると観点を落としにくくなります。午前Ⅱでは、SQL インジェクション・OS コマンドインジェクション・ディレクトリトラバーサルの識別が繰り返し問われます。いずれも「利用者からの文字列を、解釈される場所へそのまま渡した」点は同じで、渡した先が何か(SQL か、シェルか、ファイルパスか)で名前が変わる、と整理すると迷いません。

IPA 公式 PDF:令和5年度 秋期 午前Ⅱ問題(問1 が脆弱性の識別) ↗本文はこの公式 PDF でご覧ください(アプリ内には転載していません)。

IPA 公式:過去問題(情報処理安全確保支援士試験) ↗各年度の問題冊子・解答例・採点講評の PDF を、公式サイトで確認できます。

確認しておきたいこと

演習(支援士日誌)で手を動かす →