SQLインジェクションは、外部から渡された値がSQL文の一部として解釈され、データベースを想定外に操作されてしまう脆弱性です。原因は、入力値を文字列連結でSQL文に組み込むことにあります。対策の基本は、すべてのSQL文をプレースホルダで組み立てることです。
SQLインジェクションとは
アプリケーションがデータベースに問い合わせるとき、検索語やIDなどの入力値をSQL文に組み込みます。このとき入力値がデータではなく命令として解釈されると、開発者が意図していない処理が実行されます。
IPAは、データベースを使うWebアプリケーションであれば、運営主体や性質を問わず起こりうる問題だとしています。個人情報などの重要な情報をデータベースに置いているサイトは特に注意が必要です。
OWASP Top 10:2025では、SQLインジェクション(CWE-89)はA05「Injection」に含まれます。OWASPはXSSを「頻度が高く影響が小さい」、SQLインジェクションを「頻度が低く影響が大きい」ものとして対比しています。Top 10全体はOWASP Top 10の解説にまとめています。
起こりうる被害
IPAの「安全なウェブサイトの作り方」は、発生しうる脅威として次を挙げています。データの閲覧だけで終わらない点が重要です。
SQLインジェクションで起こりうる被害
| 種類 | 内容 |
|---|---|
| 情報の漏えい | 非公開の情報の閲覧、個人情報の漏えい |
| データの破壊 | データベース内の情報の書き換えや削除 |
| サイトへの影響 | ページの改ざん、パスワードの変更、システムの停止 |
| なりすまし | 認証を回避した不正ログイン、ログイン中の利用者に許された操作の実行 |
| 乗っ取り | ストアドプロシージャなどを使ったOSコマンドの実行、システムの乗っ取り、他への攻撃の踏み台化 |
脆弱なコードと安全なコード
問題になるのは、入力値を文字列連結でSQL文に組み込むコードです。次の例では、入力値の中に特別な意味を持つ記号があると、SQL文の構造そのものが変わってしまいます。
// 脆弱な例:入力値を文字列連結している
const text = "SELECT id, name FROM users WHERE email = '" + email + "'";
const result = await client.query(text);
プレースホルダを使うと、SQL文の形と値が分かれます。次はNode.jsのPostgreSQLクライアント(node-postgres)の例です。公式ドキュメントも、値を文字列連結でクエリに入れるのを避けるよう案内しています。
// 安全な例:値はプレースホルダで渡す
const text = "SELECT id, name FROM users WHERE email = $1";
const result = await client.query(text, [email]);
文字列連結
- 値がSQL文の一部になる
- 値の中の記号で文の構造が変わりうる
- エスケープの漏れがそのまま脆弱性になる
プレースホルダ
- SQL文の形を先に固定する
- 値はデータとして別に渡す
- 値の内容で文の構造が変わらない
主な対策
IPAは、根本的な解決と、被害を抑える保険的な対策を分けて示しています。まず根本的な解決を済ませ、そのうえで保険的な対策を重ねます。
SQL文はすべてプレースホルダで組み立てる
IPAが最初に挙げる根本的な解決です。プレースホルダには、データベース側で値を割り当てる「静的プレースホルダ」と、接続ライブラリ内でエスケープしてはめ込む「動的プレースホルダ」があります。どちらでも脆弱性は解消できますが、原理的に可能性がなくなる点で静的プレースホルダが優るとIPAは説明しています。
パラメータにSQL文をそのまま持たせない
hiddenパラメータなどにSQL文を入れて受け渡す実装は、値を改変されると不正に利用されるおそれがあります。
エラーの詳細を画面に出さない
データベースの種類やSQL文を含むエラーメッセージは、攻撃の手がかりになります。ブラウザには表示せず、サーバー側のログに残します。
データベースアカウントの権限を絞る
接続に使うアカウントには必要最小限の権限だけを与えます。OWASPは、これは攻撃を防ぐものではなく、被害の範囲を小さくするものだと説明しています。管理者権限でアプリケーションを接続しないでください。
プレースホルダだけでは足りない場面
プレースホルダで置き換えられるのは値だけです。OWASPは、テーブル名、列名、並び順の指定にはパラメータを使えないとして、アプリケーション側で持つ固定の選択肢に対応づけ、それ以外は拒否する方法を示しています。
// 画面からの指定を、固定の列名に対応づける
const sortColumns = new Map([
["newest", "created_at"],
["name", "name"],
]);
const column = sortColumns.get(sort);
if (column === undefined) {
return res.status(400).send("Bad Request");
}
const text = `SELECT id, name FROM users ORDER BY ${column}`;
const result = await client.query(text);
ここで文字列に組み込んでいるのは入力値ではなく、コードの中に書かれた列名だけです。
安全に見えて注意が必要な書き方
| 書き方 | 注意点 |
|---|---|
| ORM | OWASPは移行先の一つに挙げる一方、クエリ文字列に値を連結せずバインドするよう求めている |
| ストアドプロシージャ | 自動的に安全にはならない。内部で動的にSQL文を作るなら、その値もバインドする |
| 入力値の検証 | OWASPは推奨しているが、完全な防御ではないとしている |
既存のコードについては、OWASPが挙げるようにソースコードレビューと、静的解析(SAST)・動的解析(DAST)などのテストツールをCI/CDに組み込む方法があります。
よくある質問
ORMを使っていれば安全ですか?
多くの場合、値は安全に渡されますが、ORMを使っていること自体は保証になりません。生のSQLを書く機能に文字列連結で値を入れると、同じ問題が起こります。生のSQLを書いている箇所と、列名を動的に決めている箇所を重点的に確認してください。
入力値のチェックをしていればプレースホルダは不要ですか?
不要にはなりません。OWASPは入力値の検証を有効な対策としつつ、完全な防御ではないとしています。検証は形式の誤りを早く弾くためのものとして、プレースホルダと組み合わせてください。
静的プレースホルダと動的プレースホルダはどちらを使うべきですか?
IPAは、どちらでも脆弱性を解消できるとしたうえで、原理的に可能性がなくなる点で静的プレースホルダが優るとしています。どちらの方式で動くかは使っているライブラリと設定によるため、公式ドキュメントで確認してください。
自分のサイトに脆弱性があるか、どう確かめればよいですか?
まずソースコードでSQL文を組み立てている箇所を洗い出し、文字列連結が残っていないかを確認します。テストは自分が管理する環境で行ってください。他人のサイトに無断で試すと、不正アクセスなどの法的な問題になるおそれがあります。不正アクセスの考え方は不正アクセス対策の解説にまとめています。
出典: IPA「安全なウェブサイトの作り方 1.1 SQLインジェクション」、OWASP SQL Injection Prevention Cheat Sheet、OWASP Top 10:2025 A05 Injection、node-postgres「Queries」



