SQLインジェクションは、Webアプリケーションの脆弱性を悪用し、不正なSQL文を実行させる攻撃手法です。これにより、不正ログインやデータの改ざん・削除、個人情報の漏えいといった深刻な被害が発生する可能性があります。
結論から言うと、SQLインジェクション対策の基本は「プリペアドステートメント(パラメータ化クエリ)を使い、SQL文を文字列連結で組み立てないこと」です。そのうえで、入力値のチェック、データベース権限の最小化、エラーメッセージの非表示、WAFなどを重ねて守ります。
本記事では、SQLインジェクションの仕組み、攻撃の種類、実際の被害事例、言語別のコード例を交えた防止策を解説し、安全なシステム構築のための実践的な対策を紹介します。
- SQLインジェクションが起こる仕組みと、代表的な攻撃パターン
- 国内外の実際の被害事例と、被害を受けた企業が負う責任
- Java・PHP・Pythonでの安全な書き方と、プレースホルダが使えない箇所の対処法
- WAF・ORM・NoSQLインジェクションなど関連する対策と、開発時のチェックリスト
SQLインジェクションとは
SQLインジェクション(SQL Injection)は、ユーザー入力を介して不正なSQL文を注入し、データベースを意図しない形で操作されてしまう攻撃手法です。
これにより、不正ログイン、データの改ざん・削除(DROP TABLEなど)、情報漏えいなど、企業の存続に関わる重大な被害が発生する恐れがあります。
例えば、ログイン画面のユーザー名入力欄に admin' OR '1'='1 などの特殊な文字列を入力すると、システムが本来意図しないSQLクエリを実行し、認証をバイパスしてしまう可能性があります。
これは OR '1'='1' の部分が常に「真」となり、本来必要な認証をバイパスしてしまうためです。
※プリペアドステートメント(パラメータ化クエリ)を使用することで、このような攻撃は防ぐことができます。
SQLインジェクションは今も主要な脅威
SQLインジェクションは2000年前後から知られている古い攻撃ですが、今も現役の脅威です。
- OWASP Top 10:2025(Webアプリの重大リスクをまとめた国際的な指標)では、SQLインジェクションを含む「Injection」が5位に入っています(2021年版は3位)。順位は下がったものの、関連する脆弱性(CVE)の数は最も多いカテゴリーとされています。
- 株式会社EGセキュアソリューションズの「SiteGuard セキュリティレポート」によると、2025年4〜6月に観測された攻撃の64.5%がSQLインジェクションで、ECサイトへの攻撃に限ると85%以上を占めました。
- 国内でも、2025年に教育関連企業のWebサイトがSQLインジェクションを受け、最大28万件超の個人情報が漏えいした可能性があると公表されるなど、被害は続いています。
攻撃ツールの自動化が進み、知名度の低い小さなサイトも機械的に狙われます。「うちは小規模だから大丈夫」とは言えないのが現状です。
SQLインジェクションが起こる仕組み
なぜ文字列連結が問題なのか
Webアプリケーションがデータベースへアクセスする際、以下のように文字列を直接連結してSQLを生成していると危険です。
-- 【脆弱な例】
query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
ユーザーからの入力にシングルクォート ' や、SQL構文を破壊する文字が混ざっていると、本来意図しない形でクエリの構造を変化させてしまいます。
たとえば、' OR '1'='1 という文字列をユーザー名として連結すると、'1'='1' は常に真となり、全ユーザー情報を取得される可能性があります。
図解イメージ(テキストベースの例)
- 想定していたクエリ:
SELECT * FROM users WHERE username = '正常な入力' AND password = '正常な入力' - 悪意ある入力を連結:
SELECT * FROM users WHERE username = '' OR '1'='1' -- ' AND password = '' OR '1'='1'により条件式が常に真となり、--以降(パスワードの条件)はコメントとして無視されるため、全レコードが取得される。
文字列連結によって「データ」と「命令」の境界が曖昧になり、攻撃者の意図通りにクエリ構造が改変されることが、SQLインジェクションの本質的な問題です。
SQLインジェクションの主な種類
攻撃にはいくつかのパターンがあります。どれも原因は同じ「入力がSQL文の一部として解釈されること」なので、対策も共通です。種類を知っておくと、「画面に結果が出ないから安全」といった誤解を防げます。
| 種類 | 概要 | 特徴 |
|---|---|---|
| 認証回避型 | 常に真になる条件を注入し、ログインを突破する | 上の例の典型パターン |
| UNIONベース | UNION句で別テーブルの検索結果を結合し、画面に表示させる | 検索結果を表示する画面が狙われやすい |
| エラーベース | わざとSQLエラーを起こし、エラーメッセージからテーブル名などを探る | エラー内容を画面に出していると成立しやすい |
| ブラインド型 | 結果は表示されないが、ページの表示の違いや応答時間の差から1文字ずつ情報を推測する | 画面に何も出なくても情報を抜かれる |
| セカンドオーダー型 | 一度保存された悪意ある文字列が、後で別の処理のSQLに使われたときに発動する | 「DBから取り出した値は安全」という思い込みを突く |
特にブラインド型とセカンドオーダー型は、見た目では気づきにくいのが厄介な点です。「入力がどこから来たか」に関係なく、SQLに値を埋め込むときは必ずパラメータとして渡すことが重要です。
狙われやすい入力箇所
入力フォームだけが入口ではありません。サーバーが受け取ってSQLに使う値は、すべて攻撃者が書き換えられると考えます。
- ログイン・会員登録・問い合わせフォーム:もっとも代表的な入口
- サイト内検索の入力欄:LIKE検索などで文字列を連結しがち
- URLのパラメータ(例:
?id=123、?sort=price):数値やカラム名をそのまま埋め込むと危険 - Cookie・HTTPヘッダー(User-Agentなど):アクセスログをDBに保存する処理などで使われる
- APIに送られるJSON:スマホアプリ向けAPIも同じく対象
hiddenフィールドやプルダウンの値も、ブラウザの開発者ツールを使えば簡単に書き換えられます。「画面上で選択式だから安全」とは言えません。
SQLインジェクションの主な被害例
不正ログイン
- 管理者アカウントへの侵入
常に真(TRUE)となる条件を注入することで、正しいパスワードを入力しなくてもログインできてしまう。 - セッション情報の悪用
セッションIDやログイン用トークンをデータベースに保存している場合、それらを盗まれたり書き換えられたりして、他人になりすまされる可能性もあります。
データの改ざん・削除
- UPDATE文での改ざん
例えば商品価格やポイント情報などを攻撃者が意図的に変更。 - DROP TABLEなど破壊的なコマンド
一部または全テーブルを削除され、システムそのものが停止する危険性。 - Webページの改ざん
記事やお知らせをDBで管理しているサイトでは、内容を書き換えられ、悪意あるサイトへの誘導やマルウェア配布に使われることもあります。
情報漏えい
- 個人情報・顧客情報の流出
クレジットカード情報や住所・電話番号など、機密性の高いデータが閲覧・取得される可能性。 - パスワードの流出と使い回し被害
パスワードを平文や弱いハッシュで保存していると、盗まれた情報で他のサービスにも不正ログインされる二次被害につながります。
SQLインジェクションによる実際の被害事例
SQLインジェクションが原因と公表・認定された主な事例です。
| 時期 | 企業 | 被害の内容 |
|---|---|---|
| 2007〜2008年 | Heartland Payment Systems(米国・決済処理会社) | SQLインジェクションを入口に侵入され、1億3,000万件以上のカード情報が盗まれた。カード会社への賠償など多額の負担が発生 |
| 2015年 | TalkTalk(英国・通信会社) | 約15万7,000人分の顧客データが流出。英国の個人情報保護当局(ICO)から当時過去最高額の40万ポンドの制裁金 |
| 2025年 | 学悠出版(国内・模試運営) | Webサイトへの不正アクセスにより、最大28万件超の個人情報が漏えいした可能性があると公表 |
TalkTalkの事例では、ICOの調査で、本件の前にも同じ脆弱性を狙ったSQLインジェクション攻撃が2回あったのに、監視が不十分で対応できていなかったことが指摘されています。脆弱性を放置し、攻撃の兆候を見逃すと、被害は大きくなるという教訓です。
被害を受けた企業が負う責任
日本では、2022年4月施行の改正個人情報保護法により、個人データの漏えい等が起きたときの個人情報保護委員会への報告と本人への通知が義務化されました。不正アクセスなど不正の目的による漏えい(またはそのおそれ)は、件数にかかわらず報告の対象です。
- 速報:事態を知ってから速やかに(目安は概ね3〜5日以内)
- 確報:原則30日以内。不正の目的によるおそれがある場合は60日以内
このほか、原因調査やシステム改修の費用、顧客への補償、サービス停止による売上の損失、取引先からの信用低下など、金銭面・信用面の負担は長く続きます。対策にかかるコストは、被害を受けたときのコストに比べればはるかに小さいと考えるべきです。
SQLインジェクションの防止策
パラメータ化クエリ(プリペアドステートメント)の使用
なぜ安全か?
- 「データ(入力)」と「命令(SQL構文)」を明確に分離して扱える。
- SQL文の構造を先に確定させ、入力値は後から「ただの値」として渡すため、入力にどんな記号が含まれていてもSQL構文として解釈されない。
? や :name のような値の置き場所をプレースホルダと呼びます。エスケープのように「危険な文字を変換する」のではなく、そもそも構文と値を混ぜないので、エスケープ漏れのようなミスが起きにくいのが最大の利点です。
コード例(Java風)
String sql = "SELECT * FROM users WHERE username = ? AND password_hash = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, hashPassword(inputPassword)); // ハッシュ化したパスワードを比較
ResultSet rs = pstmt.executeQuery();
パスワードはハッシュ化して保存し、認証時は同じハッシュアルゴリズムで変換した値を比較します(例: bcrypt, Argon2)。
※bcryptやArgon2はソルト(ランダムな値)を含むため、実際には「ユーザー名で1件取得し、ライブラリの検証関数(PHPなら password_verify())で照合する」形が一般的です。上の例は、プレースホルダの使い方を示すための簡略版です。
コード例(PHP・PDO)
$pdo = new PDO('mysql:host=localhost;dbname=app;charset=utf8mb4', $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false, // DB側の本物のプリペアドステートメントを使う
]);
$stmt = $pdo->prepare('SELECT id, password_hash FROM users WHERE username = :username');
$stmt->execute([':username' => $username]);
$row = $stmt->fetch();
if ($row && password_verify($inputPassword, $row['password_hash'])) {
// ログイン成功
}
DSNで文字コード(charset=utf8mb4)を指定しておくことも大切です。文字コードの食い違いは、エスケープをすり抜ける原因になることがあります。
コード例(Python・sqlite3)
# 【脆弱な例】f文字列で組み立てている
cur.execute(f"SELECT * FROM users WHERE username = '{username}'")
# 【安全な例】プレースホルダ(?)で値を渡す
cur.execute("SELECT * FROM users WHERE username = ?", (username,))
Pythonでは、f文字列や format() でSQLを組み立てるのが典型的な失敗例です。値は必ず execute() の第2引数で渡しましょう。
プレースホルダが使えない箇所はホワイトリストで制御
プレースホルダで渡せるのは「値」だけです。テーブル名・カラム名・ORDER BYの並び順(ASC/DESC)などSQLの構文要素は、プレースホルダにできません。並び替え機能などでこれらを入力から決める場合は、許可した値の一覧(ホワイトリスト)と照合して選びます。
// 並び替えに使ってよいカラムだけを許可する
$allowed = ['created_at', 'price', 'name'];
$sort = in_array($_GET['sort'] ?? '', $allowed, true) ? $_GET['sort'] : 'created_at';
$order = (($_GET['order'] ?? '') === 'asc') ? 'ASC' : 'DESC';
$sql = "SELECT * FROM products ORDER BY {$sort} {$order}";
入力された文字列をそのまま埋め込むのではなく、あらかじめ用意した安全な値の中から選ぶのがポイントです。同様に、LIKE検索で使う % や _ は、値そのものはプレースホルダで渡しつつ、必要に応じてワイルドカード文字をエスケープします。
入力値のバリデーション
- ホワイトリスト方式(英数字や特定記号のみ受け付ける)で、想定外の文字を排除。
- 適切な文字数制限を行い、過剰に長い入力を防ぐ。
- IDや個数などは型(整数かどうか)と範囲をチェックする。
- チェックは必ずサーバー側で行う。JavaScriptによるブラウザ側のチェックは利便性のためのもので、簡単に回避できる。
ただし、名前や住所のように記号を含みうる項目もあるため、バリデーションだけで攻撃を完全に防ぐことはできません。あくまでプリペアドステートメントと組み合わせる対策です。バリデーション・エスケープ・サニタイズの違いは、次の記事で詳しく解説しています。
サニタイズとは?エスケープ・バリデーションとの違いと実装方法を解説エスケープ処理(補助的対策)
- データベースごとに異なるエスケープ方法を利用して、シングルクォートなどを無害化。PHPの場合、かつての
mysql_real_escape_string()はPHP 7.0で削除済みのため、現在はmysqli_real_escape_string()やPDO::quote()が該当します。 - ただし、パラメータ化クエリの導入が最優先で、エスケープ処理はあくまで補助的。構文ミスや適用漏れがあると脆弱性を残す原因になります。
- 数値として扱う箇所(
WHERE id = 123など)はクォートで囲まないため、文字列用のエスケープでは防げない点にも注意が必要です。
データベースの権限設定
- アプリケーション専用のDBユーザーを用意し、最低限の操作権限(SELECT, INSERTなど)に制限する。
- 万一クエリを改変されても、DROPなどの危険な操作ができないようにする。
- 管理者用(root)アカウントをアプリから使わない。参照専用の画面には読み取り専用ユーザーを使うなど、用途ごとに分けるとより安全です。
エラーメッセージを画面に表示しない
SQLのエラー内容(テーブル名やSQL文の一部など)をそのまま画面に出すと、攻撃者にとって重要なヒントになります(前述のエラーベース型)。本番環境では、利用者には「エラーが発生しました」のような汎用メッセージだけを表示し、詳細はサーバーのログに記録しましょう。PHPなら本番で display_errors をオフにします。
脆弱性テストやスキャンの実施
- 定期的に脆弱性検査を行い、潜在的な問題を早期発見。
- 主なツール例:
- OWASP ZAP(オープンソースの脆弱性スキャナー)
- SQLMap(SQLインジェクション検出・攻撃テストに特化)
- Burp Suite(総合的なWebセキュリティテストツール)
- コードレビューで「SQLを文字列連結している箇所がないか」を確認する。静的解析ツールをCIに組み込むと、見落としを減らせます。
スキャンは自分が管理する環境、または許可を得た環境だけで行うこと。他人のサイトに無断で試すと、不正アクセス禁止法などに触れるおそれがあります。本番ではなく検証環境で行うのが基本です。
CMS・プラグイン・ライブラリを最新に保つ
自分でコードを書いていなくても、WordPressのプラグインやテーマ、フレームワーク、ライブラリにSQLインジェクションの脆弱性が見つかることは珍しくありません。修正版が出たら早めに更新し、更新が止まったプラグインは使わないようにしましょう。修正が出る前の脆弱性を狙う攻撃については、こちらの記事で解説しています。
ゼロデイ攻撃とは?意味・仕組み・被害事例・対策をわかりやすく解説補足トピック
ORM(Object-Relational Mapping)の活用
近年のWebフレームワークでは、ActiveRecordやHibernateなどのORMによってSQLを直接書かずに済むケースが増えています。ORMは内部的にパラメータ化クエリを用いるため、手動でSQLを組み立てるリスクが減少します。
ただし、ORMでもカスタムクエリを書かなければならない場面では、引き続きSQLインジェクションに注意が必要です。
たとえばLaravelの whereRaw() や DB::raw()、Djangoの raw() などの「生SQL」を書ける機能に、入力値を文字列連結で渡すと、ORMを使っていても脆弱になります。生SQLを使うときも、各機能が用意しているバインド(whereRaw('price > ?', [$price]) など)を使いましょう。
WordPressでプラグインやテーマを自作する場合は、$wpdb->prepare() を使います。%s(文字列)・%d(整数)などのプレースホルダで値を渡す仕組みです。
global $wpdb;
$sql = $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_author = %d AND post_status = %s",
$author_id,
'publish'
);
$results = $wpdb->get_results($sql);
NoSQLインジェクション(具体例)
SQL以外のデータベース(MongoDB、Firebaseなど)に対しても注入攻撃は存在します。
たとえばMongoDBでは、以下のようにユーザー入力をそのままfindクエリに渡すと危険です。
// 【脆弱なコード】ユーザーの入力をそのまま MongoDB の find に渡してしまう
db.users.find({ username: userInput, password: passInput });
もし攻撃者が次のようなJSONを入力として渡した場合:
{
"username": { "$ne": null },
"password": "password123"
}
"$ne": null は「null以外ならすべて該当する」という意味になるため、ユーザー名の条件が事実上なくなり、パスワードが一致するアカウントを、ユーザー名を知らなくても探し当てられる形になります。パスワード側にも同じ手口を使えば、条件はさらに緩くなります。
NoSQLの場合も、SQLインジェクションと同様にデータとクエリ(命令)を分離し、パラメータを正しくバインド・検証する対策が重要です。
具体的には、受け取った値が文字列であることを確認する(オブジェクトが来たら拒否する)、$ で始まるキーを含む入力を受け付けない、スキーマ検証ライブラリで入力の形を固定する、といった対策が有効です。
WAF(Webアプリケーションファイアウォール)の導入
WAF(Web Application Firewall)は、SQLインジェクションやXSS(クロスサイトスクリプティング)などの攻撃を検知・ブロックする仕組みです。
特に、Webサイト運営者やクラウド環境を利用している企業にとって、手軽に追加できる防御策として有効です。
WAFの導入方法
1. クラウド型WAF(簡単に導入可能)
- AWS WAF(Amazon Web Servicesを利用する場合)
- Cloudflare WAF(Webサイト向け、無料プランあり)
- Google Cloud Armor(Google Cloud環境向け)
このほか、国内のレンタルサーバーでは、WAFを標準機能として提供していることが多くあります。まずは契約中のサーバーの管理画面で、WAFが有効になっているか確認してみましょう。
2. オンプレミス型WAF(自社で管理・カスタマイズ可能)
- ModSecurity(オープンソース、Apache/Nginx/IISに導入可能)
2024年1月に開発元のTrustwaveからOWASPへ管理が移り、現在はOWASPのプロジェクトとして開発が続いています。ルールには同じくOWASPの「Core Rule Set(CRS)」を組み合わせて使うのが一般的です。 - Imperva WAF(エンタープライズ向け、細かいカスタマイズが可能)
WAFのメリット
- SQLインジェクションの攻撃パターンを事前にブロック
- アプリケーションのコードを修正しなくても、防御を追加できる
- 特にクラウド型WAFなら設定が簡単で、初心者でも導入しやすい
WAFの限界と注意点
WAFは万能ではありません。既知の攻撃パターンには強いものの、新しいSQLインジェクション手法には対応できない場合があります。また、正常なアクセスを攻撃と誤判定してブロックしてしまう「誤検知」が起きることもあり、導入後はログを見ながら調整が必要です。
そして、WAFだけではアプリケーション内部の脆弱性は解決できないため、以下の基本対策と組み合わせることが重要です。
- パラメータ化クエリ(プリペアドステートメント)の使用(SQLインジェクション防止の基本)
- 入力値のバリデーション(異常なデータの入力を防ぐ)
- 最小限のデータベース権限設定(不要な権限を持たせない)
WAFは「補助的な防御策」であり、アプリケーションコードの安全性を高めることが最優先!
レバテックキャリアの評判は?ITエンジニア転職のメリット・注意点【2026年】SQLインジェクション対策チェックリスト
開発時やコードレビューのときに確認したい項目をまとめました。上から順に優先度が高い項目です。
- SQLに値を入れる箇所は、すべてプレースホルダ(プリペアドステートメント)を使っている
- 文字列連結・f文字列・テンプレート文字列でSQLを組み立てている箇所がない
- テーブル名・カラム名・ORDER BYを入力から決める箇所は、ホワイトリストで照合している
- ORMの生SQL機能(whereRaw・raw()など)でも、値はバインドで渡している
- 入力値の型・長さ・形式をサーバー側でチェックしている
- アプリ用のDBユーザーは必要最小限の権限だけを持っている
- 本番環境でSQLエラーの詳細を画面に表示していない
- パスワードはbcryptやArgon2などでハッシュ化して保存している
- CMS・プラグイン・ライブラリを最新版に更新している
- 定期的に脆弱性スキャンを行い、WAFやアクセスログで攻撃の兆候を監視している
SQLインジェクションに関するよくある質問
SQLインジェクションとXSSの違いは?
どちらも「入力値がプログラムの一部として解釈されてしまう」点は同じですが、狙われる場所が違います。SQLインジェクションはサーバー側のデータベースを不正に操作する攻撃、XSS(クロスサイトスクリプティング)は利用者のブラウザで不正なスクリプトを実行させる攻撃です。対策も、SQLインジェクションはプレースホルダ、XSSは出力時のHTMLエスケープが基本で、別々に行う必要があります。
エスケープ処理だけでは不十分ですか?
不十分です。エスケープは「すべての箇所で、正しい方法で、漏れなく」行って初めて効果があり、1か所の書き忘れや文字コードの設定ミスが脆弱性になります。また、数値を埋め込む箇所やカラム名など、エスケープでは守れない箇所もあります。基本はプリペアドステートメントで、エスケープは補助と考えましょう。
WAFを入れればコードの対策は不要ですか?
不要にはなりません。WAFは既知のパターンを止めるのは得意ですが、検知をすり抜ける攻撃もあります。WAFは「コードの対策が万一漏れていたときの保険」として位置づけ、根本対策はアプリケーション側で行います。
WordPressサイトでも被害を受けますか?
受ける可能性があります。WordPress本体は $wpdb->prepare() などで対策されていますが、プラグインやテーマに脆弱性が見つかるケースは珍しくありません。本体・プラグイン・テーマの更新、使っていないプラグインの削除、レンタルサーバーのWAFの有効化が基本の対策です。
被害に気づいたら、まず何をすべきですか?
被害の拡大を防ぐため、該当機能の停止やサイトの一時公開停止を検討し、アクセスログやDBのログを保全します(ログを消さないことが原因調査の前提になります)。そのうえで脆弱な箇所を特定・修正し、DBのパスワードなど認証情報を変更します。個人データが漏えいした(おそれがある)場合は、前述のとおり個人情報保護委員会への報告と本人への通知が必要です。自社だけで対応が難しい場合は、セキュリティ専門会社への調査依頼も検討しましょう。
まとめ
- SQLインジェクションとは、ユーザー入力を利用して意図しないSQL文を実行させる攻撃手法。OWASP Top 10:2025でも上位に入る、今も現役の脅威。
- 文字列連結によるSQL組み立てが最大のリスク。パラメータ化クエリの導入が必須。
- 被害例として、不正ログイン、データの改ざん・削除、情報漏えいなど多岐にわたり、被害規模は極めて大きい。日本では漏えい時の報告・本人通知も義務。
- 具体的な対策は下記の通り。
- パラメータ化クエリ(プリペアドステートメント)の利用
- プレースホルダが使えない箇所はホワイトリストで制御
- 入力値のバリデーションとエスケープ処理
- DBユーザーの権限を最小限に抑え、エラー詳細を画面に出さない
- 定期的な脆弱性スキャン(OWASP ZAP, SQLMap, Burp Suite など)と、CMS・ライブラリの更新
- ORM活用・NoSQLインジェクション・WAFの導入など、補足的な対策や関連領域にも注意を払う。
SQLインジェクションは発見されてから長い歴史を持つ攻撃手法ですが、いまだに多くのシステムで深刻な被害をもたらしています。基本的な対策を怠らず、「データと命令を混ぜない」設計を常に意識することが重要です。
CSRF攻撃とは?仕組みやXSSとの違い、最新の対策をわかりやすく解説








