Webアプリケーションの脆弱性の中で、最も有名かつ危険度が高いものの一つが「SQLインジェクション」です。ニュースで「数万件の個人情報が流出」といった報道を目にすることがありますが、その原因がこの脆弱性にあるケースは後を絶ちません。
情報処理安全確保支援士試験(SC)においても、SQLインジェクションは午後試験で頻出のトピックです。特に、攻撃の仕組みだけでなく、「なぜ発生するのか」「どうすれば根本的に防げるのか」という理屈を正確に理解しておく必要があります。
本記事では、SQLインジェクションのメカニズムを基礎から紐解き、試験で必ず問われる「プレースホルダ(プリペアドステートメント)」による対策について徹底的に解説します。これを読めば、なぜ「エスケープ処理」だけでは不十分なのか、その理由が明確になるはずです。
SQLインジェクションとは何か?
SQLインジェクション(SQL Injection)は、その名の通り、データベースを操作する言語である「SQL」の文中に、悪意のある「命令(コード)」を「注入(Injection)」する攻撃手法です。
Webアプリケーションの多くは、利用者からの入力データを受け取り、それをデータベースへの問い合わせ(クエリ)に組み込んで処理を行います。例えば、ログイン画面で入力されたIDとパスワードを使って、「このユーザーが存在するか?」をデータベースに問い合わせるといった処理です。
もし、このアプリケーションが入力データを適切に処理せずにそのままSQL文として組み立ててしまっていたらどうなるでしょうか。攻撃者が入力欄に特殊な記号を含む文字列を入力することで、開発者が意図していない「別のSQL命令」としてデータベースに解釈させてしまうことが可能になります。
これが成功すると、本来見る権限のないデータを盗み見られたり、データを改ざん・消去されたり、場合によっては認証を回避して管理者になりすまされたりと、システムにとって致命的な被害をもたらします。

データベースとWebアプリの関係
攻撃の仕組みを理解する前に、正常な処理の流れを整理しておきましょう。通常、Webアプリケーションは以下のような手順でデータベースと対話します。
- ユーザー入力: 利用者が検索フォームやログイン画面に文字を入力する
- SQL構築: アプリケーション(Webサーバー側)が、その入力値を含んだSQL文を作成する
- 送信: 作成したSQL文をデータベースサーバーへ送信する
- 実行: データベースがSQL文を解釈し、検索や更新を実行する
- 応答: 結果をアプリケーションに返す
SQLインジェクションは、この「SQL構築」の段階で、入力値が単なる「データ」ではなく「命令の一部」として組み込まれてしまうことで発生します。
攻撃のメカニズム:認証回避の事例
では、具体的にどのようにして攻撃が成立するのか、最も有名な「ログイン認証回避」の例を見てみましょう。
想定するのは、IDとパスワードを入力してログインする一般的なシステムです。内部では、以下のようなSQL文が作られるとします。
SELECT * FROM users WHERE id = '入力されたID' AND pass = '入力されたPASS';
このSQL文の意味は、「usersテーブルから、idが『入力されたID』かつ、passが『入力されたPASS』であるレコード(行)を全て取り出せ」というものです。レコードが見つかればログイン成功、見つからなければ失敗、というロジックです。
正常な入力の場合
例えば、正規のユーザーである「yamada」さんが、パスワード「pass123」を入力した場合、SQL文は以下のようになります。
SELECT * FROM users WHERE id = 'yamada' AND pass = 'pass123';
データベースはこれを実行し、条件に一致するユーザーがいればそのデータを返します。これは問題ありません。
攻撃者が悪用した場合
ここで、攻撃者がパスワードの入力欄に、以下のような文字列を入力したとします。
' OR '1'='1
これをそのままSQL文に埋め込むと、どうなるでしょうか。
SELECT * FROM users WHERE id = 'yamada' AND pass = '' OR '1'='1';
このSQL文をデータベースが解釈すると、条件式は以下のようになります。
id = 'yamada'かつpass = ''であるか?- または(OR)
'1' = '1'であるか?
SQLにおける OR は、どちらか片方が真(True)であれば、全体として真となります。ここで 1=1 は、数学的に常に正しい(真)です。つまり、パスワードが合っているかどうかに関係なく、この WHERE 句の条件は「常に真(True)」となってしまうのです。
その結果、データベースは「条件に一致するデータ(つまり全ユーザー、またはテーブルの先頭のユーザー)」を返してしまいます。これにより、攻撃者はパスワードを知らなくてもログインに成功してしまいます。これがSQLインジェクションによる認証回避の基本原理です。

SQLインジェクションが招く深刻な被害
認証回避は氷山の一角に過ぎません。SQLインジェクションが可能であるということは、攻撃者がデータベースに対して「任意の命令」を実行できる可能性があることを意味します。被害は大きく分けて以下の3つに分類されます。
1. 情報漏洩(機密性の侵害)
最も頻繁に狙われるのが、データベース内の情報の窃取です。UNION 句というSQLの命令を悪用することで、本来表示されるはずのない別のテーブルのデータを結合して表示させることができます。
例えば、商品検索の結果を表示する画面でSQLインジェクションがあるとします。攻撃者は商品データの代わりに、顧客管理テーブル(customer)のデータを検索結果画面に表示させるようにSQLを操作します。これにより、クレジットカード番号、住所、氏名、パスワードハッシュなどが大量に流出する事故が発生します。
2. データの改ざん・消去(完全性・可用性の侵害)
データの読み取りだけでなく、書き込みや削除も可能です。UPDATE 文を注入されれば、全てのユーザーのパスワードを攻撃者が知る値に書き換えたり、Webサイト上のコンテンツを改ざんしてフィッシングサイトへ誘導したりすることができます。
さらに最悪の場合、DROP TABLE などの命令によって、データベースそのものを破壊・消去される恐れもあります。これは事業継続に関わる致命的なダメージとなります。
3. OSコマンドインジェクションへの発展
データベースの設定や権限によっては、SQLを通じてデータベースサーバー上のファイルを読み書きしたり、OSのコマンドを実行したりできる場合があります。これが成功すると、データベースサーバー自体が乗っ取られ、そこを足がかりに内部ネットワークへの侵入を許すことになります。
根本的対策:プレースホルダ(Prepared Statement)
SQLインジェクションを防ぐための方法はいくつか知られていますが、最も効果的で、かつSC試験で「正解」として求められるのが「プレースホルダ(プリペアドステートメント)」の使用です。
なぜこれが「根本的対策」と呼ばれるのか、その仕組みを深く理解しましょう。
プレースホルダの仕組み
プレースホルダとは、SQL文のひな形(テンプレート)をあらかじめ作っておき、後から変化する値(ユーザー入力など)をそこに「埋め込む」技術です。プレースホルダ(Placeholder)は「場所取り」という意味です。SQL文の中に ? や :id といった記号で場所を確保しておきます。
処理の流れは以下のようになります。
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インジェクション対策として有効ですが、セキュリティの堅牢さに若干の差があります。
静的プレースホルダ(サーバーサイド・プリペアドステートメント)
これは、データベースエンジン側でSQLの構文解析とコンパイルを事前に行う方式です。
- アプリからDBへ、
?付きのSQLを送る - DBがそれを解析し、実行計画を作る(Prepare)
- アプリからDBへ、値を送る(Bind)
- DBが実行する
この方式では、値がDBに届く前にSQLの形が完全に確定しているため、原理的にSQLインジェクションが発生する余地がありません。最も推奨される方式です。
動的プレースホルダ(クライアントサイド・プリペアドステートメント)
これは、アプリケーションのライブラリ(データベースドライバ)側で擬似的にプレースホルダ処理を行う方式です。ドライバが入力値を適切にエスケープ処理(無害化)した上で、SQL文に組み立ててから、DBへ送信します。DB側から見ると、通常のSQL文が一回送られてくるのと同じ挙動になります。
ライブラリの実装が正しければ安全ですが、過去には特定の文字コード(Shift_JISなど)を使用した際にエスケープ処理をすり抜ける脆弱性が発見されたこともあります。そのため、可能な限り「静的プレースホルダ」を使用することが望ましいとされています。
エスケープ処理(サニタイジング)の限界
昔の解説記事などでは、「入力値のシングルクォート(')をエスケープする」という対策が紹介されていることがありますが、現代の開発においてこれを主たる対策とすることは推奨されません。
これを「エスケープ処理」や「サニタイジング」と呼びますが、以下のような問題点があります。
- 実装漏れの可能性: 開発者がすべての入力箇所で手動でエスケープ関数を呼び出す必要があり、ヒューマンエラーによる抜け漏れが発生しやすい
- 複雑さ: データベースの種類や使用する文字コードによって、エスケープすべき文字やルールが微妙に異なるため、完璧に実装するのが難しい
- 可読性の低下: コードがエスケープ処理だらけになり、メンテナンス性が下がる
プレースホルダを使えば、これらの面倒な処理をデータベースやドライバに任せることができるため、安全かつスマートに開発ができます。SC試験の記述式回答で「エスケープ処理を行う」と書くと、部分点しかもらえない、あるいは減点される可能性があります。「プレースホルダを利用する」と書くのが鉄則です。
多層防御のアプローチ:WAFと最小権限
プレースホルダは強力ですが、セキュリティに「絶対」はありません。万が一、プレースホルダが使えない特殊な箇所があったり、古いシステムで改修が難しかったりする場合に備え、「多層防御」の考え方が重要です。
WAF(Web Application Firewall)
WAFは、Webアプリケーションへの通信を監視し、攻撃と思われるパターン(シグネチャ)を含むリクエストを遮断する防御システムです。SQLインジェクションの特徴的な文字列(SELECT, UNION, ' OR など)を検知してブロックします。
ただし、WAFはあくまで「対症療法」です。攻撃パターンを少し変えられるとすり抜けられる可能性があります(これをWAFバイパスといいます)。根本対策(プレースホルダ)を行った上で、保険として導入するものです。
権限の最小化(Least Privilege)
Webアプリケーションがデータベースに接続する際のアカウント権限を、必要最小限に絞る対策です。例えば、記事を表示するだけの機能なら SELECT 権限のみを与え、DROP や DELETE 権限を与えないようにします。
こうすれば、万が一SQLインジェクションが発生しても、テーブルの削除などの壊滅的な被害は防ぐことができます。
エラーメッセージの抑制
データベースのエラーメッセージをそのままブラウザに表示しないことも重要です。攻撃者はエラーメッセージの内容(「カラム数が合いません」「構文エラーです」など)から、データベースの構造やSQLの形を推測します。これを「エラーベースSQLインジェクション」と呼びます。
ユーザーには「システムエラーが発生しました」といった定型文だけを見せ、詳細は内部ログにのみ記録するように設定しましょう。
補足解説:ブラインドSQLインジェクションの脅威
基本的なSQLインジェクションに加え、さらに陰湿で発見が難しい「ブラインドSQLインジェクション」についても知識を深めておきましょう。これは、画面上にエラーやデータが表示されない場合でも攻撃が可能という手法です。
画面に結果が出なくても情報は盗める
通常のSQLインジェクションでは、UNION 句などを使って盗み出したデータを画面上に表示させます。しかし、アプリケーションによっては、SQLの結果を画面に表示せず、単に「処理が成功しました」「失敗しました」という画面遷移しかしないものがあります。また、エラーメッセージも完全に抑制されている場合もあります。
一見すると安全そうに見えますが、攻撃者は「応答の違い」を利用して情報を盗み出します。これを「ブラインド(盲目)SQLインジェクション」と呼びます。
ブーリアンベース(Boolean-based)
これは「YESかNOか」の質問をデータベースに投げかけ、その反応の違いでデータを1文字ずつ特定する手法です。
例えば、ある会員IDのパスワードを盗みたいとします。攻撃者は以下のようなSQLになるような入力を送信します。「もしパスワードの1文字目が 'a' なら、検索結果を表示せよ。違うなら表示するな」
- もし画面に検索結果が出れば → 1文字目は 'a' だとわかります
- もし検索結果が出なければ → 1文字目は 'a' ではない。次は 'b' で試そう
このように、データベースへの質問(クエリ)と、アプリの挙動(表示される/されない)を繰り返すことで、時間はかかりますが確実にデータを特定できてしまいます。
タイムベース(Time-based)
さらに手ごわいのが、画面表示すら変わらない場合です。この場合、攻撃者は「時間」を使います。「もしパスワードの1文字目が 'a' なら、10秒間待機(SLEEP)してから結果を返せ」という命令を注入します。
Webブラウザの応答がすぐに返ってくればハズレ、10秒以上待たされたらアタリ(1文字目は 'a')と判断できます。このように、レスポンスの遅延を利用して情報を盗む手法がタイムベースのブラインドSQLインジェクションです。
対策は変わらない
ブラインドSQLインジェクションは、手口こそ巧妙ですが、根本的な原因は通常のSQLインジェクションと同じ「入力値がSQL命令として解釈されてしまうこと」です。したがって、対策も変わりません。プレースホルダを使用して、入力値が命令に干渉しないようにすれば、ブーリアン型もタイム型も完全に防ぐことができます。
「画面に何も表示されないから安全」という思い込みは危険であることを肝に銘じておきましょう。
開発現場での実装ポイント:言語別のプレースホルダ
概念としてのプレースホルダは理解できたと思いますが、実際にコードを書く際にはどのように実装するのでしょうか。主要なプログラミング言語での一般的な書き方を比較してみます。言語は違っても、やっていることは「Prepare」と「Bind」であることに注目してください。
Java (JDBC) の場合
Javaの標準的なデータベース接続APIであるJDBCでは、PreparedStatement クラスを使用します。
// 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) の場合
Web開発で人気のPHPでは、PDO(PHP Data Objects)という拡張モジュールを使うのが一般的です。
// 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 / psycopg2 など)
PythonでDB操作を行う際も同様です。ここでは標準ライブラリの 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引数に値を渡す方法)を使う必要があります。
セキュリティ診断とツール活用
開発段階での対策に加え、運用フェーズや納品前のテストフェーズでは、実際に脆弱性がないかを検査することも重要です。
自動脆弱性スキャンツール
SQLインジェクションの脆弱性を手動ですべてチェックするのは、大規模なサイトでは不可能です。そこで、脆弱性スキャナ(Web Application Scanner)を利用します。有名なツールには以下のようなものがあります。
- OWASP ZAP (Zed Attack Proxy): 無料で使えるオープンソースの高機能スキャナ。SC試験対策としても、実際の挙動を確認するために触ってみる価値があります
- Burp Suite: プロのセキュリティ診断員も利用する業界標準ツール。リクエストをキャプチャして書き換えるプロキシ機能が強力です
これらのツールは、フォームやURLパラメータに対して、自動的に大量の攻撃パターン(シングルクォート、SQLキーワードなど)を送信し、サーバーの応答(エラーメッセージが出るか、表示が変わるかなど)を解析して脆弱性を報告してくれます。
sqlmapの使用
攻撃者が使うツールを知ることも防御の役に立ちます。sqlmap はSQLインジェクションの検出と悪用を自動化する強力なペネトレーションテストツールです。コマンド一つで、DBの種類判別、テーブル一覧の取得、データのダンプまでを自動で行います。
許可を得ていないサーバーに対して実行することは犯罪になります。必ず自身の管理下にあるローカル環境での学習に使用してください。
SC試験では具体的なツール名まで問われることは稀ですが、「脆弱性検査ツールを用いて、既知の攻撃パターンに対する耐性を確認する」というプロセスは、セキュリティマネジメントの観点からも重要です。
データベースの設定による緩和策
アプリ側の対策が最優先ですが、DBサーバー側の設定で被害を最小限に抑える工夫も覚えておきましょう。
ストアドプロシージャの利用
ストアドプロシージャは、一連のSQL処理をあらかじめデータベース上に保存しておき、アプリからそのプロシージャを呼び出す機能です。プレースホルダと同様に、SQLの構造が事前に定義されるため、インジェクションのリスクを低減できます。
また、アプリ側から直接テーブルを操作する権限を剥奪し、「プロシージャの実行権限」のみを与えることで、不正なクエリによるデータ流出を防ぐ効果も期待できます。
不要な機能の無効化
多くのデータベース製品には、便利な拡張機能が標準で搭載されていますが、これらが攻撃の足がかりになることがあります。例えば、SQL Serverの xp_cmdshell は、SQLからOSコマンドを実行する強力な機能ですが、インジェクション攻撃を受けた際にサーバー乗っ取りの直接的な原因になります。
セキュリティの原則「不要なサービス・機能は停止する」に従い、これらの機能は無効化しておくべきです。
情報処理安全確保支援士試験での出題パターン
情報処理安全確保支援士試験の午後問題において、SQLインジェクションは「セキュアプログラミング」や「Webサイトの脆弱性診断」のテーマで出題されます。典型的な出題パターンとしては、以下のようなものがあります。
1. 脆弱性の指摘
ソースコードの一部が提示され、「このコードにはどのような脆弱性があるか、具体的に述べよ」と問われます。文字列連結でSQLを組み立てている箇所を見つけるのがポイントです。
2. 攻撃手法の理解
攻撃リクエストのログが提示され、「攻撃者は何を意図してこの文字列を入力したか」を問われます。'-- (コメントアウト)や UNION SELECT の意味を読み解く力が試されます。
3. 修正案の提示
「この脆弱性を解消するための実装レベルの対策を述べよ」と問われます。ここで「静的プレースホルダを使用する」と答えられるかが合否を分けます。
「なぜ防げるのか」を説明できますか?SQLインジェクション完全攻略クイズ
情報処理安全確保支援士試験(SC)では、SQLインジェクションの攻撃手法だけでなく、「プレースホルダを使うとなぜ防げるのか(構造とデータの分離)」や「静的プレースホルダと動的プレースホルダの違い」といった深い理解が問われます。 本記事で解説した攻撃のメカニズム、根本対策の実装、そしてブラインドSQLインジェクションなどの応用知識を含めた全10問の演習問題を作成しました。 単なる用語の暗記ではなく、試験の記述問題にも対応できる「論理的な理解」ができているか、ぜひチェックしてみてください。
まとめ
SQLインジェクションは、Webアプリケーションの信頼を根底から覆す恐ろしい脆弱性です。しかし、その発生原理は「データと命令の混同」という非常にシンプルなものであり、対策も「プレースホルダの使用」という確立された手法が存在します。
今回の重要ポイントを振り返ります。
- 仕組み: 入力値を操作して、開発者の意図しないSQL命令を実行させる攻撃
- 被害: 情報漏洩、データ改ざん・消失、認証回避
- 根本対策: プレースホルダ(プリペアドステートメント)を使用する。これにより、入力値を「ただの文字列」として処理させ、命令としての実行を防ぐ
- 推奨: 可能な限り、データベース側で処理する「静的プレースホルダ」を選ぶ
- 多層防御: WAFの導入や、DB接続アカウントの権限最小化を組み合わせる
試験対策としては、「なぜプレースホルダで防げるのか?」という問いに対し、「SQL文の構造を先に確定させ、入力値をリテラル(データ)としてバインド処理するため、SQLの意味を変えさせないから」と論理的に説明できるようにしておきましょう。
Webセキュリティの基本にして奥義であるこの対策をマスターし、安全なシステム構築と試験合格の両方を目指してください。