SQLインジェクション対策で「何を書けば正解か」と迷ったら、答えはほぼ一つ、プレースホルダ(プリペアドステートメント)です。情報処理安全確保支援士試験(SC)の午後で「実装レベルの対策を述べよ」と問われたとき、プレースホルダを利用すると書けるかどうかで得点が決まります。逆に「エスケープ処理を行う」とだけ書くと、部分点止まり、あるいは減点される可能性があります。
私は2005年から2008年までCIOとしてIT統制とセキュリティ投資の判断を担いましたが、当時WAFは高価で運用のハードルも高く、導入を見送りました。代わりにWebサーバー側のアクセス制御設定(ハードニング)を工夫して泥臭く守っていた記憶があります。だからこそ実感するのですが、WAFのような「保険」に頼る前に、アプリケーション側でSQLインジェクションを原理的に成立させない作り込みこそが本丸です。その本丸がプレースホルダです。
本記事では、SQLインジェクションのメカニズムを基礎から紐解いたうえで、プレースホルダがなぜ根本対策になるのか、静的プレースホルダと動的プレースホルダは何が違うのか、エスケープ処理では何が足りないのかを、SC試験の記述問題に耐えるレベルまで掘り下げます。読み終えたとき、「なぜ防げるのか」を自分の言葉で説明できる状態を目指します。
なお、実際の午前II・午後の過去問(「SQLインジェクション攻撃を防ぐ方法はどれか」など)の解法演習は、別記事SQLインジェクション攻撃を防ぐ方法はどれか|午前・午後の過去問で解法と対策を完全攻略にまとめています。本記事で対策技術の理屈を固めたら、そちらで問題演習に進むのがおすすめです。
SQLインジェクション対策の正解はプレースホルダ(プリペアドステートメント)
結論から入ります。SQLインジェクションの根本的対策は、IPA「安全なウェブサイトの作り方」でも第一の推奨とされているプレースホルダによる実装です。SQL文の組み立てを全てプレースホルダで行えば、機械的な処理でSQL文が構成されるため、そもそも脆弱性が発生しません。まずこの一点を押さえてから、仕組みへ進みます。
プレースホルダの仕組み:Prepare・Bind・Execute
プレースホルダとは、SQL文のひな形(テンプレート)をあらかじめ作っておき、後から変化する値(ユーザー入力など)をそこに「埋め込む」技術です。プレースホルダ(Placeholder)は「場所取り」という意味で、SQL文の中に ? や :id といった記号で値の場所を確保しておきます。処理は次の3段階で進みます。
- 1. Prepare(準備):データベースに「これから実行するSQLの形」を伝えます。例:
SELECT * FROM users WHERE id = ? AND pass = ?;この時点でデータベースはSQLの構造(SELECT命令である・WHERE句がある・条件は2つ、など)を解析し確定させます。 - 2. Bind(割り当て):確保しておいた
?の場所に実際の値を割り当てます。例:1つ目の?に「yamada」、2つ目の?に「' OR '1'='1」を入れる。 - 3. Execute(実行):値を埋め込んだ状態でSQLを実行します。
なぜプレースホルダで防げるのか:構造とデータの分離

ここが最大のポイントです。Prepareの段階でSQL文の構造(命令としての意味)が確定しているため、後からBindされる値は、どんな文字列であっても単なる文字データ(リテラル)として扱われます。
認証回避で使われる攻撃文字列 ' OR '1'='1 を入力した場合を考えてみましょう。プレースホルダを使用すると、データベースはこの文字列全体を「パスワードという値そのもの」として解釈します。つまり「パスワードが『' OR '1'='1』という変な文字列であるユーザーを探せ」という検索になります。当然、そんなパスワードを設定しているユーザーはいないので検索結果は0件となり、攻撃は失敗します。
SQLの構文として解釈されるタイミングと、データが渡されるタイミングを明確に分離することで、入力値が命令として悪用される余地を完全に排除しているのです。SC試験の記述で問われる「なぜ防げるのか」への模範解答は、この「構造を先に確定させ、入力値をリテラルとしてバインドするため、SQLの意味が変わらない」という一文に集約されます。
学習の全体像=進むべき道筋がひと目で分かる
〈セキュリティ学習メール〉
静的プレースホルダと動的プレースホルダの違い

SC試験のレベルでは、さらに一歩踏み込んで「静的プレースホルダ」と「動的プレースホルダ」の違いを理解しておく必要があります。どちらもSQLインジェクション対策として有効ですが、堅牢さに差があり、IPAは静的プレースホルダの方が優れるとしています。
静的プレースホルダ(サーバサイド・プリペアドステートメント)
これは、データベースエンジン側でSQLの構文解析とコンパイルを事前に行う方式です。流れは次のとおりです。
- アプリからDBへ、
?付きのSQLを送る - DBがそれを解析し、実行計画を作る(Prepare)
- アプリからDBへ、値を送る(Bind)
- DBが実行する
この方式では、値がDBに届く前にSQLの形が完全に確定しているため、原理的にSQLインジェクションが発生する余地がありません。最も推奨される方式です。
動的プレースホルダ(クライアントサイド・プリペアドステートメント)
これは、アプリケーションのライブラリ(データベースドライバ)側で擬似的にプレースホルダ処理を行う方式です。ドライバが入力値を適切にエスケープ処理(無害化)したうえでSQL文に組み立ててから、DBへ送信します。DB側から見ると、通常のSQL文が一回送られてくるのと同じ挙動になります。
ライブラリの実装が正しければ安全ですが、過去には特定の文字コード(Shift_JISなど)を使用した際にエスケープ処理をすり抜ける脆弱性が発見されたこともあります。そのため、可能な限り「静的プレースホルダ」を使用することが望ましいとされています。学び始めの人がつまずきやすいのは「プレースホルダを使えばどちらでも同じく安全」と思い込む点です。動的方式はエスケープに依存する分だけ理論上の弱点が残る、という区別を必ず押さえてください。
エスケープ処理との違いと、なぜ主対策にならないのか
古い解説記事では「入力値のシングルクォート(')をエスケープする」という対策が紹介されていることがありますが、現代の開発でこれを主たる対策とすることは推奨されません。プレースホルダとエスケープは、そもそも守り方の発想が違います。
エスケープ処理とは何か(サニタイジングとの混同に注意)
エスケープ処理とは、SQLにおいて特別な意味を持つ記号(シングルクォートなど)を、意味を打ち消した形に変換して文字列リテラルとして正しく構成する処理です。IPAの用語では、文字列連結でSQLを組み立てざるを得ない場合の代替策として位置づけられています。なお、古い記事では入力の無害化を意味する「サニタイジング」という言葉と混同して使われることもありますが、厳密には異なる概念です。
エスケープ処理の3つの限界
手作業でのエスケープ処理には、次のような問題点があります。
- 実装漏れの可能性:開発者がすべての入力箇所で手動でエスケープ関数を呼び出す必要があり、ヒューマンエラーによる抜け漏れが発生しやすい
- 複雑さ:データベースの種類や使用する文字コードによって、エスケープすべき文字やルールが微妙に異なるため、完璧に実装するのが難しい
- 可読性の低下:コードがエスケープ処理だらけになり、メンテナンス性が下がる
プレースホルダを使えば、これらの面倒な処理をデータベースやドライバに任せられるため、安全かつスマートに開発できます。ここでよくある誤解は「エスケープは遅いから使わない」というものですが、これは誤りです。エスケープ処理自体の負荷は軽微で、速度が理由で避けるわけではありません。避ける理由はあくまで実装の抜け漏れ・複雑さ・可読性にあります。SC試験の記述式回答で「エスケープ処理を行う」と書くと部分点止まりになりがちなのは、こうした信頼性の差が背景にあります。「プレースホルダを利用する」と書くのが鉄則です。
前提知識:SQLインジェクションのメカニズムと認証回避
対策の理屈を腹落ちさせるために、攻撃がどう成立するのかを整理しておきます。SQLインジェクション(SQL Injection)は、データベースを操作する言語である「SQL」の文中に、悪意のある命令(コード)を注入(Injection)する攻撃手法です。
データベースとWebアプリの関係
通常、Webアプリケーションは次の手順でデータベースと対話します。
- ユーザー入力:利用者が検索フォームやログイン画面に文字を入力する
- SQL構築:アプリケーション(Webサーバ側)が、その入力値を含んだSQL文を作成する
- 送信:作成したSQL文をデータベースサーバへ送信する
- 実行:データベースがSQL文を解釈し、検索や更新を実行する
- 応答:結果をアプリケーションに返す
SQLインジェクションは、この「SQL構築」の段階で、入力値が単なる「データ」ではなく「命令の一部」として組み込まれてしまうことで発生します。プレースホルダが解決するのは、まさにこの構築段階です。
認証回避の事例:' OR '1'='1
最も有名な「ログイン認証回避」を見てみましょう。内部で次のようなSQL文が作られるとします。
SELECT * FROM users WHERE id = '入力されたID' AND pass = '入力されたPASS';
(※注:現代の実際の開発現場では、パスワードは必ずハッシュ化して保存し、SQLのWHERE句ではなくアプリケーション側で照合するのが常識ですが、ここではSQLインジェクションの原理を分かりやすく解説するため、あえて平文での比較を例としています。)
ここで攻撃者がパスワード欄に ' OR '1'='1 を入力すると、SQL文は次のようになります。
SELECT * FROM users WHERE id = 'yamada' AND pass = '' OR '1'='1';
SQLにおける OR は、どちらか片方が真(True)であれば全体として真になります。'1'='1' は常に真なので、パスワードが合っているかに関係なくWHERE句の条件は「常に真」となり、データベースは条件に一致するデータを返してしまいます。これにより攻撃者はパスワードを知らなくてもログインに成功します。これがSQLインジェクションによる認証回避の基本原理です。
情報漏洩・改ざん・OSコマンド実行への発展
認証回避は氷山の一角です。SQLインジェクションが可能ということは、攻撃者がデータベースに対して任意の命令を実行できる可能性を意味します。情報漏洩では、UNION 句を悪用して別テーブルのデータを結合表示させます。UNIONを成功させるには元のSELECT文とカラム数やデータ型を一致させる必要がありますが、攻撃者は NULL を埋め込むなどの手法で条件を突破し、顧客管理テーブルの内容を検索結果画面に表示させます。
改ざん・消去では、セミコロン(;)で複数のSQL文を連続実行できる「複文(マルチクエリ)」が許可された環境で UPDATE や DROP TABLE を注入され、データ書き換えやテーブル破壊が起こりえます。さらにデータベースの権限次第では、SQLを通じてサーバ上のファイル読み書きやOSコマンド実行にまで発展し、サーバ自体の乗っ取りと内部ネットワークへの侵入を許すことになります。いずれの被害も、根本原因は同じ「入力値がSQL命令として解釈されること」であり、プレースホルダで断ち切れます。
補足:ブラインドSQLインジェクションでも対策は変わらない
画面上にエラーやデータが表示されない場合でも攻撃が成立するのが「ブラインドSQLインジェクション」です。発見が難しいぶん、対策の一貫性を理解しておくと記述問題に強くなります。
ブーリアンベースとタイムベース
ブーリアンベース(Boolean-based)は、「YESかNOか」の質問をデータベースに投げ、その反応の違いでデータを1文字ずつ特定する手法です。「もしパスワードの1文字目が 'a' なら検索結果を表示せよ」という入力を送り、結果が出れば1文字目は 'a'、出なければ違う文字、と絞り込みます。
タイムベース(Time-based)は、画面表示すら変わらない場合に「時間」を使います。「1文字目が 'a' なら10秒待機(SLEEP)してから返せ」という命令を注入し、応答がすぐ返ればハズレ、10秒以上待たされたらアタリと判断します。レスポンスの遅延で情報を盗む手法です。
対策はやはりプレースホルダ
手口こそ巧妙ですが、根本原因は通常のSQLインジェクションと同じ「入力値がSQL命令として解釈されてしまうこと」です。したがって対策も変わりません。プレースホルダを使用して入力値が命令に干渉しないようにすれば、ブーリアン型もタイム型も完全に防げます。「画面に何も表示されないから安全」という思い込みは危険だと肝に銘じておきましょう。
多層防御:WAF・最小権限・エラーメッセージ抑制
プレースホルダは強力ですが、セキュリティに「絶対」はありません。プレースホルダが使えない特殊な箇所や、改修が難しい古いシステムに備え、「多層防御」の考え方が重要です。ここは根本対策の代わりではなく、あくまで保険という位置づけを外さないでください。
WAF(Web Application Firewall)
WAFは、Webアプリケーションへの通信を監視し、攻撃と思われるパターン(シグネチャ)を含むリクエストを遮断する防御システムです。SQLインジェクションの特徴的な文字列(SELECT・UNION・' OR など)を検知してブロックします。ただしWAFはあくまで対症療法で、攻撃パターンを少し変えられるとすり抜けられる可能性があります(WAFバイパス)。根本対策(プレースホルダ)を行ったうえで、保険として導入するものです。WAFは「あれば理想だが、それだけに頼るものではない」という位置づけを外さないでください。
権限の最小化(最小権限の原則)
Webアプリケーションがデータベースに接続する際のアカウント権限を必要最小限に絞ります。記事を表示するだけの機能なら SELECT 権限のみを与え、DROP や DELETE 権限を与えません。こうすれば、万が一SQLインジェクションが発生しても、テーブル削除などの壊滅的な被害を防げます。新卒エンジニアが安易に「全部の権限を付けておけば動くから」とやりがちですが、その油断が被害を最大化します。
エラーメッセージの抑制
攻撃者はエラーメッセージの内容(「カラム数が合いません」「構文エラーです」など)からデータベースの構造やSQLの形を推測します。この情報漏洩は防ぐべきですが、これとは別に「エラーベースSQLインジェクション」という攻撃手法も存在します。攻撃者が意図的にデータベースエラーを引き起こし、そのエラーメッセージの中に機密データそのものを含めさせる手法です。「エラーメッセージから構造を推測する行為」と「エラーメッセージからデータを直接抽出する攻撃手法」を区別して理解しておくことが、SC試験では重要です。ユーザーには「システムエラーが発生しました」といった定型文だけを見せ、詳細は内部ログにのみ記録するよう設定しましょう。
言語別のプレースホルダ実装(Java・PHP・Python)
概念を理解したら、実装イメージも押さえておきましょう。言語は違っても、やっていることは「Prepare」と「Bind」である点に注目してください。
Java(JDBC)
// SQLのひな形を定義(?がプレースホルダ)
String sql = "SELECT * FROM users WHERE id = ? AND pass = ?";
// 1. Prepare: SQL文をコンパイル
PreparedStatement pstmt = connection.prepareStatement(sql);
// 2. Bind: ? に値をセット(型を指定してセットできるのが強み)
pstmt.setString(1, inputUserId);
pstmt.setString(2, inputPassword);
// 3. Execute: 実行
ResultSet rs = pstmt.executeQuery();
Javaでは Statement クラスを使うと文字列連結によるSQL実行が可能ですが、セキュリティの観点からは原則 PreparedStatement を使用すべきです。
PHP(PDO)
// SQLのひな形(名前付きプレースホルダ :id なども使える)
$sql = 'SELECT * FROM users WHERE id = :id AND pass = :pass';
// 1. Prepare
$stmt = $pdo->prepare($sql);
// 2. Bind & 3. Execute(配列で渡すことでバインドと実行を同時に行う)
$stmt->execute([
':id' => $inputUserId,
':pass' => $inputPassword
]);
PHPは歴史的に mysql_query などの古い関数で文字列連結を行うコードが多く出回っていましたが、現在はPDOを使用してプリペアドステートメントを活用するのが標準です。また、PDOの設定で PDO::ATTR_EMULATE_PREPARES を false に設定することで、静的プレースホルダ(DB側でのPrepare)を強制できます。動的プレースホルダの弱点を避けたい場合の重要設定です。
Python(sqlite3 など)
# SQLのひな形
sql = "SELECT * FROM users WHERE id = ? AND pass = ?"
# カーソルを取得
cur = connection.cursor()
# 1. Prepare, 2. Bind, 3. Execute を1行で書くことが多い
# executeメソッドの第2引数にタプルとして値を渡す
cur.execute(sql, (input_user_id, input_password))
Pythonでは f-string や % 演算子を使った文字列フォーマットが便利ですが、SQL文の構築にこれらを使ってはいけません。必ずライブラリが提供するプレースホルダ機能(第2引数に値を渡す方法)を使います。なお、PostgreSQL用の psycopg2 など、ライブラリによってはプレースホルダの記号が ? ではなく %s になる点には注意が必要です。
SC試験での出題パターンと解答の型
SC試験の午後問題では、SQLインジェクションは「セキュアプログラミング」や「Webサイトの脆弱性診断」のテーマで出題されます。典型的な問われ方は次の3つです。
1. 脆弱性の指摘
ソースコードの一部が提示され、「このコードにはどのような脆弱性があるか、具体的に述べよ」と問われます。文字列連結でSQLを組み立てている箇所を見つけるのがポイントです。
2. 攻撃手法の理解
攻撃リクエストのログが提示され、「攻撃者は何を意図してこの文字列を入力したか」を問われます。'--(コメントアウト)や UNION SELECT の意味を読み解く力が試されます。
3. 修正案の提示
「この脆弱性を解消するための実装レベルの対策を述べよ」と問われます。ここで「静的プレースホルダを使用する」と答えられるかが合否を分けます。過去問での具体的な出題(「SQLインジェクション攻撃を防ぐ方法はどれか」など)の選択肢の切り分けは、SQLインジェクション攻撃を防ぐ方法はどれか|午前・午後の過去問で解法と対策を完全攻略で実際の問題に沿って演習できます。
【演習】SQLインジェクション対策 理解度チェック(全10問)
SC試験では、攻撃手法だけでなく「プレースホルダを使うとなぜ防げるのか(構造とデータの分離)」や「静的プレースホルダと動的プレースホルダの違い」といった深い理解が問われます。午前IIでは対策手法の選択、午後では実装レベルの記述で問われるのが定番です。定番のひっかけは、根本対策(プレースホルダ)と保険的対策(WAF・最小権限・エラー抑制)の取り違え、そしてエスケープを主対策と誤認する点です。以下の練習問題で本記事の理解度を確認してみましょう。
まとめ:SQLインジェクション対策はプレースホルダを軸に説明できるようにする
SQLインジェクションは、Webアプリケーションの信頼を根底から覆す脆弱性ですが、その発生原理は「データと命令の混同」というシンプルなもので、対策も「プレースホルダの使用」という確立された手法があります。重要ポイントを振り返ります。
- 仕組み:入力値を操作して、開発者の意図しないSQL命令を実行させる攻撃
- 被害:情報漏洩、データ改ざん・消失、認証回避、OSコマンド実行への発展
- 根本対策:プレースホルダ(プリペアドステートメント)を使用し、入力値をリテラルとして処理させ、命令としての実行を防ぐ
- 推奨:可能な限り、データベース側で処理する「静的プレースホルダ」を選ぶ
- 多層防御:WAFの導入やDB接続アカウントの権限最小化を保険として組み合わせる
私がCIOだった時代、WAFという「保険」に頼れない中でアプリケーションとサーバ設定を地道に固めるしかありませんでした。新卒エンジニアを教えていた頃も、安易に「Allow All(全面許可)」で動かして根本を崩す悪癖を何度も見てきました。だからこそ強調したいのは、SQLインジェクション対策は保険ではなくプレースホルダという根本対策を軸に据えることです。SC試験では「なぜプレースホルダで防げるのか?」に対し、「SQL文の構造を先に確定させ、入力値をリテラル(データ)としてバインドするため、SQLの意味を変えさせないから」と論理的に説明できるようにしておきましょう。理屈を固めたら、過去問演習の記事で実際の問題に挑戦し、得点源に変えてください。
本記事は情報処理安全確保支援士(SC)試験対策を目的として作成しています。
参考資料
学習の全体像=進むべき道筋がひと目で分かる
〈セキュリティ学習メール〉