SQL インジェクション
入力欄に書いた文字列を SQL の一部として実行させてしまう攻撃。ログイン回避やデータ全件漏洩につながる。文字列連結をやめ、プレースホルダ(プリペアドステートメント)で値を「データ」として渡すのが本質的対策。
- 1.ユーザー入力を文字列連結で SQL に埋め込むと、入力が「データ」ではなく「命令」として実行される。これが SQL インジェクション。
- 2.結果はログイン回避・テーブル全件漏洩・改ざん・削除まで。Web 攻撃の中でも被害が桁違いに大きい古典的かつ現役の脅威。
- 3.本質的対策はただ一つ、プレースホルダ(プリペアドステートメント)で値とクエリを分離すること。エスケープや入力検証は補助でしかない。
なぜ起きるのか:データと命令が混ざる
問題の根は「文字列連結で SQL を組み立てている」一点に尽きます。次のコードは、ユーザー名で会員を1件引くつもりの典型的な脆弱コードです。
// ❌ 脆弱:入力をそのまま文字列に連結している
const name = req.query.name; // 例: ユーザーが入力した値
const sql = "SELECT * FROM users WHERE name = '" + name + "'";
db.query(sql);
name が alice なら、組み上がる SQL は ... WHERE name = 'alice' で問題ありません。ところが攻撃者が name に ' OR '1'='1 を入れると、SQL はこう変わります。
-- 攻撃者の入力で組み上がってしまう SQL
SELECT * FROM users WHERE name = '' OR '1'='1'
'1'='1' は常に真なので、WHERE の条件が無効化され、全ユーザーが返ります。プログラムは「名前という値を渡した」つもりでも、DB から見れば「条件式を書き換える命令」が届いている。入力(データ)のはずのものが、SQL の構文(命令)として効いてしまう——これが SQL インジェクションの正体です。
何が危険か:3つの攻撃パターン
「ただ1件多く返るだけ」では済みません。混ぜ込める断片次第で、被害の質が変わります。
| 手口 | やられること | 入力の例(イメージ) |
|---|---|---|
| 認証回避 | WHERE 条件を常に真にしてログインを突破 | ' OR '1'='1' -- |
| UNION 攻撃 | 別テーブルの中身を結果に連結して抜き出す | ' UNION SELECT card_no, cvv FROM cards -- |
| スタッククエリ / 破壊 | ; で2文目を追加し更新・削除を実行 | '; DROP TABLE users; -- |
| ブラインド | 真偽や応答時間の差から1ビットずつ推測 | ' AND (SELECT ...) -- |
特にログイン回避は、認証の議論で前提にしている「パスワード照合」そのものを飛び越えます。次は典型的な脆弱ログインです。
// ❌ 脆弱:ログイン判定の SQL に入力を連結
const sql =
"SELECT * FROM users WHERE id = '" + id + "' AND pw = '" + pw + "'";
// 攻撃者が id 欄に admin' -- を入力すると…
id に admin' -- を入れると、-- 以降はコメント扱いになり、AND pw = ... の条件が丸ごと消えます。
SELECT * FROM users WHERE id = 'admin' --' AND pw = '(なんでも)'
パスワードを一切知らなくても admin でログインできてしまう。Cookie やセッションで認証状態をどれだけ厳密に管理しても、入口の SQL が破れていれば意味がありません。
「シングルクォート ' を消す」「危険そうなキーワードを正規表現でブロックする」といった自前フィルタは、回避手段が無数にあり破られます。クォートが不要な数値カラム(WHERE id = ' + id)では 1 OR 1=1 だけで成立し、キーワードは大小文字・コメント挿入・URL/16進エンコードなどで容易にすり抜けます。ブラックリスト方式は SQL インジェクション対策として信頼してはいけません。
本質的対策:プレースホルダで「値」として渡す
唯一にして最強の対策は、SQL の構文と、後から差し込む値を、最初から分離すること。これを実現するのが**プレースホルダ(プリペアドステートメント)**です。SQL 文には? や $1 といった「穴」だけを書いておき、値は別経路で渡します。
// ✅ 安全:プレースホルダで値を「データ」として渡す
const sql = "SELECT * FROM users WHERE id = ? AND pw = ?";
db.query(sql, [id, pw]); // 値は SQL とは別に渡される
ポイントは、DB は ? の中身を「構文」として解釈しないこと。id に admin' -- が来ても、それは「admin' -- という名前の文字列」として WHERE 条件の値に収まるだけで、コメントや OR として効くことはありません。文字列連結による「値が命令に化ける」経路そのものが断たれます。
プレースホルダは ORM やクエリビルダを使えば多くの場合自動で効きます。ただし生 SQL に文字列を埋め込む口(多くの ORM が持つ raw / whereRaw / テンプレートリテラルでの組み立て)を使った瞬間、同じ穴が開きます。「ORM だから安全」ではなく、「値は必ずバインド経由」「SQL 文に外部入力を連結しない」が守るべき原則です。
| 対策 | 立ち位置 | 効果と注意 |
|---|---|---|
| プレースホルダ / プリペアドステートメント | 本命(これが対策の核) | 値と構文を分離。ほぼ全ての注入を原理的に防ぐ |
| 最小権限(least privilege) | 被害の封じ込め | 万一漏れても、アプリ用ユーザーに DROP / 他テーブル権限を与えない |
| 入力検証(型・長さ・許可リスト) | 補助・多層防御 | 数値は数値・列名は許可リスト化。単体では不十分 |
| エスケープ | 最後の手段 | プレースホルダが使えない箇所のみ。自前実装は避ける |
| エラーメッセージを返さない | 情報源を絞る | DB エラー全文の露出はブラインド攻撃の手掛かりになる |
プレースホルダで値は守れますが、テーブル名や列名、ORDER BY の対象はプレースホルダで渡せません(構文の一部だから)。これらを動的に変えたい場合は、あらかじめ用意した許可リスト(ホワイトリスト)に照合し、一致したものだけを使います。入力をそのまま列名に流してはいけません。
// ✅ 列名のような“構文部分”は許可リストで固定する
const allowed = { name: 'name', date: 'created_at' };
const col = allowed[req.query.sort] ?? 'created_at'; // 一致しなければ既定値
const sql = `SELECT * FROM users ORDER BY ${col}`; // col は安全な定数のみ
多層防御:万一に備える最小権限
プレースホルダを徹底しても、コードのどこか1箇所で漏れる可能性はゼロにできません。そこで「破られても被害を最小化する」二段目として、最小権限が効きます。アプリが接続する DB ユーザーに、業務に必要な権限だけを与えるのです。
読み取り中心の画面用ユーザーに INSERT/UPDATE/DELETE を与えない、まして DROP TABLE や他スキーマへのアクセスを許さない——こうしておけば、仮に注入されても '; DROP TABLE users; -- は権限エラーで弾かれます。アプリは管理者権限(root / superuser)で DB に繋がない、が鉄則。プレースホルダ(注入を防ぐ)と最小権限(被害を抑える)は、役割が違うので両方やるものです。
まとめ:覚えておくべき一行
SQL インジェクションは「入力を文字列連結で SQL に混ぜる」から起きる、古典的かつ今も多発する攻撃です。フィルタやエスケープで小手先に防ごうとすると必ず穴が残ります。
外部入力は、SQL 文に連結せず、必ずプレースホルダ(プリペアドステートメント)で「値」として渡す。 これが本質的対策。加えて、列名など構文部分は許可リスト、DB ユーザーは最小権限——この3点で守ります。同じ「入力をそのまま信用した」系の攻撃として、ブラウザ側で起きる XSS も合わせて押さえておくと、Web の入力検証の勘所がつかめます。
セキュリティ Article
SQL インジェクションを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
セキュリティ
比較で見る軸
難易度: intermediate / カテゴリ: セキュリティ / タグ数: 4
導入後に効く点
結果はログイン回避・テーブル全件漏洩・改ざん・削除まで。Web 攻撃の中でも被害が桁違いに大きい古典的かつ現役の脅威。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- intermediate
- カテゴリ
- セキュリティ
- タグ数
- 4
判断チェックリスト
- 自社の用途が「セキュリティ / SQL」に近いか確認する。
- 強みである「ユーザー入力を文字列連結で SQL に埋め込むと、入力が「データ」ではなく「命令」として実行される。これが SQL インジェクション。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。