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 で値をパラメータ化する。

実践的な知識は関連書籍でも得られる。

関連用語