SQL インジェクション
ユーザー入力を通じて不正な SQL 文を注入し、データベースを不正操作する攻撃手法
セキュリティデータベース
SQL インジェクションとは
SQL インジェクションは、ユーザー入力をそのまま SQL 文に埋め込む脆弱性を悪用し、攻撃者が任意の SQL を実行する攻撃手法である。OWASP Top 10 で常に上位にランクされ、データの窃取、改ざん、削除、認証バイパスに悪用される。
攻撃の仕組み
// ❌ 脆弱なコード: ユーザー入力を直接 SQL に埋め込み
const query = `SELECT * FROM users WHERE email = '${email}' AND password = '${password}'`;
// 攻撃者の入力: email = "' OR '1'='1' --"
// 生成される SQL:
// SELECT * FROM users WHERE email = '' OR '1'='1' --' AND password = ''
// → 全ユーザーが返る (認証バイパス)
-- は SQL のコメントで、以降の条件が無視される。'1'='1' は常に true なので、全レコードが返る。
攻撃の種類
| 種類 | 手法 | 危険度 |
|---|---|---|
| Classic | UNION SELECT で他テーブルのデータを取得 | 高い |
| Blind | true/false の応答差でデータを推測 | 高い |
| Time-based Blind | SLEEP() の応答時間でデータを推測 | 高い |
| Second-order | 保存されたデータが後で SQL に使われる | 中程度 |
対策: パラメータ化クエリ (最重要)
// ✅ パラメータ化クエリ: ユーザー入力は値として扱われ、SQL 構文として解釈されない
const result = await db.query(
'SELECT * FROM users WHERE email = $1 AND password_hash = $2',
[email, passwordHash]
);
// DynamoDB: パラメータ化が標準
await ddb.send(new QueryCommand({
TableName: 'Users',
KeyConditionExpression: 'email = :email',
ExpressionAttributeValues: { ':email': email },
}));
パラメータ化クエリでは、ユーザー入力が SQL の構文として解釈されることはない。' OR '1'='1' -- は単なる文字列値として扱われる。
ORM を使う
// Prisma: パラメータ化が自動
const user = await prisma.user.findUnique({ where: { email } });
// Drizzle ORM
const user = await db.select().from(users).where(eq(users.email, email));
ORM は内部的にパラメータ化クエリを生成するため、SQL インジェクションのリスクが大幅に低減される。ただし、生 SQL を書く $queryRaw などの機能を使う場合は注意が必要だ。
多層防御
パラメータ化クエリだけでなく、複数の防御層を組み合わせる。
- パラメータ化クエリ (必須)
- 入力バリデーション (メールアドレスの形式チェックなど)
- 最小権限の DB ユーザー (アプリ用ユーザーに DROP 権限を与えない)
- WAF (AWS WAF の SQL インジェクションルール)
- エラーメッセージの制限 (DB のエラー詳細をユーザーに返さない)
DynamoDB は SQL インジェクションに強い
DynamoDB は SQL を使わないため、従来の SQL インジェクションは発生しない。ただし、FilterExpression にユーザー入力を直接埋め込むと、NoSQL インジェクションのリスクがある。ExpressionAttributeValues で値をパラメータ化する。
実践的な知識は関連書籍でも得られる。