SQLインジェクションは、Webアプリの代表的な脆弱性です。フォームやURLから受け取った値をSQLに直接混ぜると、攻撃者が意図しないSQLを実行できる場合があります。
一言でいうと
SQLインジェクションは、ユーザー入力をSQL文字列として混ぜてしまい、SQLの意味を攻撃者に書き換えられる問題です。
危険な考え方
ログイン処理で、次のようにSQLを文字列連結で作ると危険です。
const sql =
"SELECT * FROM users WHERE email = '" + email + "' AND password = '" + password + "'";
ユーザーが普通のメールアドレスを入力する前提なら動くかもしれません。しかし、入力値にSQLの一部として解釈される文字列が入ると、条件が変わる可能性があります。
何が問題なのか
SQLでは、文字列、条件、コメントなどに意味があります。入力値をそのままSQLに連結すると、データとして扱うべき文字列が、SQLの命令として解釈されることがあります。
攻撃の具体的な文字列を覚える必要はありません。初心者がまず覚えるべきことは、次の一点です。
ユーザー入力をSQL文字列に直接連結しないことが基本です。
プレースホルダー
安全な基本は、プレースホルダーやパラメータ化クエリを使うことです。
const sql = "SELECT * FROM users WHERE email = $1";
const values = [email];
この形では、SQLの構造と入力値を分けて渡します。データベースドライバは、email をSQL文の一部ではなく値として扱います。
データベースやライブラリによって記法は異なります。
| 系統 | プレースホルダー例 |
|---|---|
| PostgreSQL系 | $1, $2 |
| MySQL系 | ? |
| SQLite系 | ?, :name |
| ORM | ライブラリのAPIで値を渡す |
ORMを使えば絶対安全か
ORMやクエリビルダーを使うと、プレースホルダーを自然に使いやすくなります。ただし、raw SQLを文字列連結で組み立てると危険は残ります。
// よい方向性: 値を別に渡す
const user = await db.user.findUnique({
where: { email }
});
一方で、ORMでも生SQLを直接書く機能を使うときは注意が必要です。
入力チェックとの違い
入力チェックも重要ですが、SQLインジェクション対策の中心はプレースホルダーです。
| 対策 | 役割 |
|---|---|
| 入力チェック | 想定外の形式を早めに弾く |
| プレースホルダー | SQL構造と値を分離する |
| 権限分離 | 被害範囲を小さくする |
| ログ監視 | 異常なアクセスに気づく |
入力チェックだけに頼ると、抜け漏れが起きる可能性があります。
実務で守ること
- SQLを文字列連結で作らない
- プレースホルダーを使う
- ORMのraw SQL機能を使うときは値の渡し方を確認する
- DBユーザーに過剰な権限を与えない
- エラーメッセージにSQLや内部情報を出しすぎない
SQLインジェクション対策は、入力を頑張って無害化するより、SQL構造と値を分ける設計にすることが重要です。
よくある誤解
| 誤解 | 実際 |
|---|---|
| 個人開発なら関係ない | 公開すれば攻撃対象になります |
| 入力チェックすれば十分 | プレースホルダーが基本です |
| ORMなら何を書いても安全 | raw SQLの使い方次第で危険です |
| SELECTだけなら安全 | 情報漏えいにつながる可能性があります |
まとめ
SQLインジェクションは、ユーザー入力がSQLの命令として解釈されることで起きます。基本対策は、ユーザー入力をSQL文字列に直接連結せず、プレースホルダーやパラメータ化クエリで値として渡すことです。