# SQLインジェクションとは？仕組みと対策をコード例で解説

SQLインジェクションの仕組みと起こりうる被害、プレースホルダを中心にした対策を、IPAとOWASPの資料をもとに解説します。ORMやストアドプロシージャの注意点、並び替えの列名の扱いもNode.jsのコード例で確認できます。

SQLインジェクションは、外部から渡された値がSQL文の一部として解釈され、データベースを想定外に操作されてしまう脆弱性です。原因は、入力値を文字列連結でSQL文に組み込むことにあります。対策の基本は、すべてのSQL文をプレースホルダで組み立てることです。

<Points>

- 原因は、データとして扱うべき入力値がSQLの命令として解釈されること
- 根本対策はプレースホルダ。列名など置き換えられない部分は、決められた選択肢からだけ選ぶ
- エラー表示の抑制や権限の最小化は被害を抑える保険で、根本対策の代わりにはならない

</Points>

## SQLインジェクションとは

アプリケーションがデータベースに問い合わせるとき、検索語やIDなどの入力値をSQL文に組み込みます。このとき入力値がデータではなく命令として解釈されると、開発者が意図していない処理が実行されます。

IPAは、データベースを使うWebアプリケーションであれば、運営主体や性質を問わず起こりうる問題だとしています。個人情報などの重要な情報をデータベースに置いているサイトは特に注意が必要です。

OWASP Top 10:2025では、SQLインジェクション（CWE-89）はA05「Injection」に含まれます。OWASPはXSSを「頻度が高く影響が小さい」、SQLインジェクションを「頻度が低く影響が大きい」ものとして対比しています。Top 10全体は[OWASP Top 10の解説](/owasp-top-10/)にまとめています。

<Stats source="OWASP Top 10:2025 A05 Injection">
<Stat value="1万4千件超" label="SQLインジェクションに関連するCVEの数" />
<Stat value="3万件超" label="XSSに関連するCVEの数（比較用）" />
</Stats>

## 起こりうる被害

IPAの「安全なウェブサイトの作り方」は、発生しうる脅威として次を挙げています。データの閲覧だけで終わらない点が重要です。

<Figure title="SQLインジェクションで起こりうる被害" source="IPA「安全なウェブサイトの作り方 1.1 SQLインジェクション」">

| 種類 | 内容 |
| --- | --- |
| 情報の漏えい | 非公開の情報の閲覧、個人情報の漏えい |
| データの破壊 | データベース内の情報の書き換えや削除 |
| サイトへの影響 | ページの改ざん、パスワードの変更、システムの停止 |
| なりすまし | 認証を回避した不正ログイン、ログイン中の利用者に許された操作の実行 |
| 乗っ取り | ストアドプロシージャなどを使ったOSコマンドの実行、システムの乗っ取り、他への攻撃の踏み台化 |

</Figure>

## 脆弱なコードと安全なコード

問題になるのは、入力値を文字列連結でSQL文に組み込むコードです。次の例では、入力値の中に特別な意味を持つ記号があると、SQL文の構造そのものが変わってしまいます。

```js
// 脆弱な例：入力値を文字列連結している
const text = "SELECT id, name FROM users WHERE email = '" + email + "'";
const result = await client.query(text);
```

プレースホルダを使うと、SQL文の形と値が分かれます。次はNode.jsのPostgreSQLクライアント（node-postgres）の例です。公式ドキュメントも、値を文字列連結でクエリに入れるのを避けるよう案内しています。

```js
// 安全な例：値はプレースホルダで渡す
const text = "SELECT id, name FROM users WHERE email = $1";
const result = await client.query(text, [email]);
```

<Compare>
<Column title="文字列連結">

- 値がSQL文の一部になる
- 値の中の記号で文の構造が変わりうる
- エスケープの漏れがそのまま脆弱性になる

</Column>
<Column title="プレースホルダ" tone="accent">

- SQL文の形を先に固定する
- 値はデータとして別に渡す
- 値の内容で文の構造が変わらない

</Column>
</Compare>

## 主な対策

IPAは、根本的な解決と、被害を抑える保険的な対策を分けて示しています。まず根本的な解決を済ませ、そのうえで保険的な対策を重ねます。

<Steps>
<Step title="SQL文はすべてプレースホルダで組み立てる">

IPAが最初に挙げる根本的な解決です。プレースホルダには、データベース側で値を割り当てる「静的プレースホルダ」と、接続ライブラリ内でエスケープしてはめ込む「動的プレースホルダ」があります。どちらでも脆弱性は解消できますが、原理的に可能性がなくなる点で静的プレースホルダが優るとIPAは説明しています。

</Step>
<Step title="パラメータにSQL文をそのまま持たせない">

hiddenパラメータなどにSQL文を入れて受け渡す実装は、値を改変されると不正に利用されるおそれがあります。

</Step>
<Step title="エラーの詳細を画面に出さない">

データベースの種類やSQL文を含むエラーメッセージは、攻撃の手がかりになります。ブラウザには表示せず、サーバー側のログに残します。

</Step>
<Step title="データベースアカウントの権限を絞る">

接続に使うアカウントには必要最小限の権限だけを与えます。OWASPは、これは攻撃を防ぐものではなく、被害の範囲を小さくするものだと説明しています。管理者権限でアプリケーションを接続しないでください。

</Step>
</Steps>

<Note tone="caution">

手作業のエスケープをプレースホルダの代わりにしないでください。OWASPは、エスケープはSQLの方言や設定に左右されるとして、強く非推奨としています。IPAも文字列連結を使う場合は、データベースエンジンのAPIで、SQL文を構成するすべてのリテラルを正しく作るよう求めています。

</Note>

## プレースホルダだけでは足りない場面

プレースホルダで置き換えられるのは値だけです。OWASPは、テーブル名、列名、並び順の指定にはパラメータを使えないとして、アプリケーション側で持つ固定の選択肢に対応づけ、それ以外は拒否する方法を示しています。

```js
// 画面からの指定を、固定の列名に対応づける
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);
```

ここで文字列に組み込んでいるのは入力値ではなく、コードの中に書かれた列名だけです。

<Figure title="安全に見えて注意が必要な書き方" source="OWASP SQL Injection Prevention Cheat Sheet、OWASP Top 10:2025 A05 Injection">

| 書き方 | 注意点 |
| --- | --- |
| ORM | OWASPは移行先の一つに挙げる一方、クエリ文字列に値を連結せずバインドするよう求めている |
| ストアドプロシージャ | 自動的に安全にはならない。内部で動的にSQL文を作るなら、その値もバインドする |
| 入力値の検証 | OWASPは推奨しているが、完全な防御ではないとしている |

</Figure>

既存のコードについては、OWASPが挙げるようにソースコードレビューと、静的解析（SAST）・動的解析（DAST）などのテストツールをCI/CDに組み込む方法があります。

## よくある質問

### Q. ORMを使っていれば安全ですか？

多くの場合、値は安全に渡されますが、ORMを使っていること自体は保証になりません。生のSQLを書く機能に文字列連結で値を入れると、同じ問題が起こります。生のSQLを書いている箇所と、列名を動的に決めている箇所を重点的に確認してください。

### Q. 入力値のチェックをしていればプレースホルダは不要ですか？

不要にはなりません。OWASPは入力値の検証を有効な対策としつつ、完全な防御ではないとしています。検証は形式の誤りを早く弾くためのものとして、プレースホルダと組み合わせてください。

### Q. 静的プレースホルダと動的プレースホルダはどちらを使うべきですか？

IPAは、どちらでも脆弱性を解消できるとしたうえで、原理的に可能性がなくなる点で静的プレースホルダが優るとしています。どちらの方式で動くかは使っているライブラリと設定によるため、公式ドキュメントで確認してください。

### Q. 自分のサイトに脆弱性があるか、どう確かめればよいですか？

まずソースコードでSQL文を組み立てている箇所を洗い出し、文字列連結が残っていないかを確認します。テストは自分が管理する環境で行ってください。他人のサイトに無断で試すと、不正アクセスなどの法的な問題になるおそれがあります。不正アクセスの考え方は[不正アクセス対策の解説](/unauthorized-access-prevention/)にまとめています。

出典: [IPA「安全なウェブサイトの作り方 1.1 SQLインジェクション」](https://www.ipa.go.jp/security/vuln/websecurity/sql.html)、[OWASP SQL Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html)、[OWASP Top 10:2025 A05 Injection](https://top10.owasp.org/2025/A05_2025-Injection/)、[node-postgres「Queries」](https://node-postgres.com/features/queries)
