「Webアプリケーションのデータが不正に抜き取られた」「ログイン画面を突破された」――こうした被害の原因として、長年にわたり報告され続けているのがSQLインジェクションです。
SQLインジェクションは、Webアプリケーションへの攻撃手法としては古典的でありながら、いまだに被害件数の多い脅威です。IPA(情報処理推進機構)の「ソフトウェア等の脆弱性関連情報に関する届出状況」でも、毎年上位に報告されています。
この記事では、SQLインジェクションの仕組み・攻撃パターン・具体的な防御策について、現場で実践できるレベルで解説します。「SQLインジェクションという言葉は聞いたことがあるけれど、何をどう防げばいいのか分からない」という方にも分かりやすくまとめました。

SQLインジェクションとは?なぜ今でも危険なのか
SQLインジェクション(SQL Injection)とは、Webアプリケーションの入力フォームなどに不正なSQL文(データベースへの命令文)を注入(インジェクション)し、本来アクセスできないデータを盗んだり、改ざん・削除したりする攻撃手法です。
なぜ20年以上前から知られている手法が今でも深刻なのか。理由は主に3つあります。
・被害の影響範囲が大きい: データベースには顧客情報・認証情報・決済データなど機密性の高い情報が集中しているため、1回の攻撃成功で大量のデータが漏洩する可能性がある
・自動化ツールの存在: SQLインジェクションの脆弱性を自動で検出・悪用するツールが広く出回っており、スキルの低い攻撃者でも実行できる
・レガシーシステムの残存: 古い設計のWebアプリケーションがメンテナンスされないまま稼働しているケースが多く、脆弱性が放置されている
OWASP(Open Worldwide Application Security Project)が公表する「OWASP Top 10」でも、SQLインジェクションを含むインジェクション系の脆弱性は常に上位にランクインしています。
SQLインジェクションの攻撃の仕組み
SQLインジェクションの仕組みを理解するには、Webアプリケーションがデータベースとどのようにやり取りしているかを知る必要があります。
通常、ユーザーがログインフォームにIDとパスワードを入力すると、アプリケーションは以下のようなSQL文を内部で組み立ててデータベースに問い合わせます。
# 通常のSQL文の例(概念的なイメージ) SELECT * FROM users WHERE user_id = '入力されたID' AND password = '入力されたパスワード'
ここで問題になるのは、ユーザーの入力値がそのままSQL文に埋め込まれるケースです。攻撃者が入力欄に特殊な文字列を入れると、SQL文の構造自体が書き換えられてしまいます。
1. 認証バイパス(ログインの突破)
攻撃者がユーザーID欄に「’ OR ‘1’=’1」のような文字列を入力すると、SQL文の条件式が常に「真」となり、パスワードを知らなくてもログインできてしまう場合があります。これが最も基本的なSQLインジェクションのパターンです。
2. データの不正取得
UNION句(複数のSELECT文の結果を結合するSQL構文)を悪用して、本来表示されるべきでないテーブルの情報を画面上に表示させる手法です。たとえば、商品一覧を表示するページの検索パラメータを操作し、ユーザーテーブルのメールアドレスやパスワードハッシュを取得するケースがあります。
3. データの改ざん・削除
INSERT文やUPDATE文、DELETE文を注入することで、データベースの内容を書き換えたり削除したりすることも可能です。ECサイトの商品価格を不正に変更されるケースや、テーブルごと削除される最悪のケースも報告されています。
4. ブラインドSQLインジェクション
画面にエラーメッセージやデータが直接表示されない場合でも、「条件が真のときと偽のときでレスポンスに差がある」ことを利用して、1ビットずつ情報を推測する手法です。時間のかかる攻撃ですが、自動化ツールにより効率的に実行されます。
これらの攻撃はすべて、アプリケーションが「ユーザーの入力を信頼してSQL文に埋め込んでいる」ことが根本的な原因です。
具体的な防御手順
SQLインジェクションの防御は、「入力をSQL文にそのまま埋め込まない」ことが大原則です。以下の対策を組み合わせて多層防御を構築しましょう。
1. プリペアドステートメント(パラメータ化クエリ)を使う
SQLインジェクション対策として最も効果的かつ基本的な方法です。プリペアドステートメント(準備済み文)を使えば、SQL文の構造とパラメータ(値)が分離されるため、ユーザー入力がSQL文の構造を変えることができなくなります。
# Pythonでの安全なSQL実行例(概念的なコード) # NG: 文字列結合でSQL文を組み立てる(絶対にやってはいけない) # cursor.execute("SELECT * FROM users WHERE id = '" + user_input + "'") # OK: プリペアドステートメントを使用する cursor.execute("SELECT * FROM users WHERE id = %s", (user_input,))
PHP、Java、Python、Ruby、Node.jsなど主要な言語・フレームワークには、プリペアドステートメントの仕組みが標準で用意されています。新規開発ではもちろん、既存コードのリファクタリングでも最優先で対応すべきポイントです。
2. 入力値のバリデーション(検証)を行う
ユーザーからの入力は、期待する形式かどうかを厳密にチェックします。
・型チェック: 数値を期待する項目には数値のみ許可する
・文字種制限: 英数字のみの項目にシングルクォートやセミコロンが含まれていたら拒否する
・長さ制限: 入力値の最大長を設定し、異常に長い値を弾く
・ホワイトリスト方式: 許可する値のリストを事前に定義し、それ以外はすべて拒否する
ただし、バリデーションだけではSQLインジェクションを完全には防げません。あくまでプリペアドステートメントと組み合わせる「追加の防御層」として位置づけてください。
3. エラーメッセージを制御する
データベースのエラーメッセージがそのままユーザーに表示されると、攻撃者にデータベースの種類やテーブル構造のヒントを与えてしまいます。
・本番環境: ユーザーには「システムエラーが発生しました」等の汎用メッセージだけを表示する
・詳細エラー: サーバーのログファイルにのみ記録し、画面には出力しない
# Apache設定でのエラー表示制御例 # php.iniの設定 # display_errors = Off(本番環境では必ずOff) # log_errors = On # error_log = /var/log/php/error.log
4. データベースの権限を最小化する
Webアプリケーションがデータベースに接続する際のアカウントには、必要最小限の権限だけを付与します。
・参照しかしないアプリケーションにはSELECT権限のみ付与する
・DROP TABLE、GRANT、FILE権限など管理者権限は絶対に付与しない
・テーブル単位でアクセス権限を設定し、不要なテーブルへの接続を遮断する
# MySQLでのアプリケーション用アカウント作成例 # 読み取り専用ユーザーの作成 CREATE USER 'webapp_readonly'@'localhost' IDENTIFIED BY 'パスワード'; GRANT SELECT ON app_database.* TO 'webapp_readonly'@'localhost'; # 必要なテーブルへの書き込みだけ許可する例 GRANT SELECT, INSERT, UPDATE ON app_database.orders TO 'webapp_write'@'localhost';
5. WAF(Web Application Firewall)を導入する
WAF(Webアプリケーションファイアウォール)は、Webアプリケーションへの不正なリクエストを検知・遮断する仕組みです。SQLインジェクションの典型的なパターンをフィルタリングできます。
ただし、WAFは「最後の防衛線」であり、根本的な対策(プリペアドステートメント)の代替にはなりません。WAFをすり抜ける巧妙な攻撃パターンも存在するため、WAFだけに頼るのは危険です。
中小企業でも今日からできること
「開発チームがいない」「外注で作ったシステムの中身が分からない」という中小企業でも、以下のステップで対策を進められます。
| 対策 | 内容 | コスト |
|---|---|---|
| 脆弱性診断の実施 | 外部の診断サービスやオープンソースの診断ツールで自社Webサイトをチェック | 無料〜数十万円 |
| CMSのアップデート | WordPress等のCMS・プラグインを最新版に更新する | 無料 |
| WAFの導入 | クラウド型WAFなら導入が容易。月額数千円から利用可能 | 月額数千円〜 |
| エラーメッセージの確認 | 自社サイトでDBエラーが画面に表示されていないか目視確認 | 無料 |
| 開発会社への確認 | プリペアドステートメントを使用しているか、開発会社・保守担当に確認する | 無料 |
まず取りかかるべきは「エラーメッセージの確認」と「CMSのアップデート」の2つです。どちらもコストがかからず、すぐに着手できます。
自社で開発したWebアプリケーションがある場合は、ソースコード中にSQL文の文字列結合がないかを確認してもらうことが重要です。古いシステムほど、この書き方が残っている可能性が高くなります。
よくある誤解と注意点
【注意】「入力値をエスケープすれば安全」は不十分
シングルクォートなどの特殊文字をエスケープ(無害化)する処理だけでは、すべてのSQLインジェクションを防ぎきれません。文字エンコーディングの違いや数値型パラメータなど、エスケープが効かないケースがあります。エスケープ処理よりもプリペアドステートメントを使うほうが確実で安全です。
【注意】「社内システムだから大丈夫」は危険な思い込み
インターネットに公開していない社内システムでも、SQLインジェクションのリスクは存在します。社内ネットワークに侵入した攻撃者が内部のWebアプリケーションを足がかりにデータベースへアクセスするケースや、内部犯行のリスクもあります。社内システムであっても、外部公開システムと同じレベルのセキュア・コーディングが求められます。
【注意】「フレームワークを使っていれば安全」とは限らない
最近のWebフレームワーク(Laravel、Django、Ruby on Rails等)はORM(Object Relational Mapping)という仕組みでSQL文を自動生成してくれるため、基本的にはSQLインジェクションに強い設計です。しかし、パフォーマンス改善などの理由でORMを迂回して生のSQL文を書く場面もあり、そこに脆弱性が生まれることがあります。フレームワークを使っていても、生SQL文の箇所には注意が必要です。
本記事のまとめ
SQLインジェクションは、Webアプリケーションの入力欄に不正なSQL文を注入し、データベースを不正に操作する攻撃手法です。古くから知られていながら、今でも多くの被害が報告されています。
防御の最も確実な方法は「プリペアドステートメントの使用」です。これに加えて入力値のバリデーション、エラーメッセージの制御、データベース権限の最小化、WAFの導入を組み合わせた多層防御が理想です。
| 脅威 | 対策 | 優先度 |
|---|---|---|
| SQL文の構造書き換え | プリペアドステートメントの使用 | 最優先 |
| 不正な入力値 | 入力値バリデーション(型・長さ・文字種) | 最優先 |
| DB構造の情報漏洩 | エラーメッセージを画面に表示しない | 高 |
| 被害範囲の拡大 | DB接続アカウントの権限最小化 | 高 |
| 既知の攻撃パターン | WAF導入(根本対策の補助として) | 中 |
Webアプリケーションを動かすLinuxサーバー自体のセキュリティ設定については、姉妹サイトLinuxMaster.JPで詳しく解説しています。ファイル権限やSELinuxの設定と組み合わせることで、より堅牢なシステムを構築できます。
また、クラウド環境でのWebアプリケーション保護については、CloudMasters.TOKYOでIAMポリシーやセキュリティグループの設定方法を紹介しています。あわせてご確認ください。
Webアプリケーションの脆弱性対策、自信はありますか?
SQLインジェクションをはじめとするWebアプリケーションの脆弱性は、正しい知識があれば確実に防御力を高められます。
正しいセキュリティ知識を体系的に身につけたい方へ、メルマガで実践的なセキュリティ対策ノウハウをお届けしています。


コメント