🔒 SQLインジェクションを理解して対策する
このページで学ぶこと
- SQLインジェクションとは何かがわかる
- 実際に攻撃を体験して危険性を実感できる
- Prisma を使えば自動的に安全である理由がわかる
SQLインジェクションとは
SQLインジェクション は、ユーザーの入力を通じて悪意のある SQL を実行させる攻撃です。
攻撃者が入力: ' OR '1'='1
↓
実行されるSQL: SELECT * FROM users WHERE email = '' OR '1'='1'OR '1'='1' は常に true なので、すべてのユーザーのデータが取得されてしまいます。
攻撃の前に、usersテーブルへシードデータを入れる
このあとの攻撃デモで結果がわかりやすいように、先に users テーブルへ10件ほどデータを入れます。
prisma/seed.mjs を作成して、次のコードを書きましょう。
// prisma/seed.mjs
import 'dotenv/config' // .env を読み込む
import { PrismaClient } from '../generated/prisma/client.js' // ESM では拡張子 .js まで書く
import { PrismaPg } from '@prisma/adapter-pg'
// Prisma 7 では adapter を渡して PrismaClient を作る
const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL,
})
const prisma = new PrismaClient({ adapter })
async function main() {
// 入れたいユーザーデータを配列でまとめる
const users = [
{ name: 'Seed User 1', email: 'test1@example.com' },
{ name: 'Seed User 2', email: 'test2@example.com' },
{ name: 'Seed User 3', email: 'test3@example.com' },
{ name: 'Seed User 4', email: 'test4@example.com' },
{ name: 'Seed User 5', email: 'test5@example.com' },
{ name: 'Seed User 6', email: 'test6@example.com' },
{ name: 'Seed User 7', email: 'test7@example.com' },
{ name: 'Seed User 8', email: 'test8@example.com' },
{ name: 'Seed User 9', email: 'test9@example.com' },
{ name: 'Seed User 10', email: 'test10@example.com' },
]
// createMany: 複数件をまとめて作成する
await prisma.user.createMany({
data: users,
skipDuplicates: true, // 同じemailがあれば重複作成しない
})
console.log('usersテーブルにシードデータを約10件入れました')
}
main()
.catch((error) => {
console.error(error)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect() // 接続を閉じる
})実行コマンド:
npx prisma generate
node prisma/seed.mjsSupabase の Table Editor で users テーブルに test1@example.com 〜 test10@example.com が入っていれば準備完了です。
攻撃を体験してみよう
脆弱なコードを書く
まずわざと脆弱なコードを書いて、SQLインジェクションを体験しましょう。
app/api/injection/route.js を作成して、次のコードを書いてください。
// ⚠️ app/api/injection/route.js(脆弱なコード — 絶対に本番で使わない)
import { NextResponse } from 'next/server'
import { prisma } from '../../../lib/prisma'
export async function GET(request) {
const { searchParams } = new URL(request.url)
const email = searchParams.get('email')
// ❌ 危険:ユーザー入力をSQLに直接埋め込んでいる
const result = await prisma.$queryRawUnsafe(
`SELECT * FROM users WHERE email = '${email}'`
)
return NextResponse.json(result)
}以下のURLにアクセスしてみましょう(localhost:3000 で開発サーバーが動いている状態で)。
# 通常のリクエスト
http://localhost:3000/api/injection?email=test1@example.com
# 攻撃リクエスト(シングルクォートを入れる)
http://localhost:3000/api/injection?email=' OR '1'='12番目のURLで全ユーザーのデータが返ってきたら、攻撃成功です。
実際の攻撃では、パスワードのハッシュや個人情報が丸ごと盗まれます。
Prisma は自動で安全
Prisma の通常の操作は**プレースホルダー(パラメータ化クエリ)**を使うため、SQLインジェクションが起きません。
app/api/injection/route.js を次のように書き換えて、http://localhost:3000/api/injection?email=' OR '1'='1にアクセスし、攻撃が成功しないことを確認してみましょう。
// ✅ 安全(Prismaの通常操作)
const result = await prisma.user.findFirst({
where: { email: email } // email がどんな文字列でも安全
})内部では以下のようなSQLが実行されます。
-- プレースホルダーを使ったSQL(安全)
SELECT * FROM users WHERE email = $1
-- $1 に email の値が別途渡される$1 と値を分離して渡すため、値の中に SQL の記号が含まれていても、SQL として解釈されません。
$queryRaw を使うときの注意
複雑な条件で生のSQLを書きたいときは $queryRaw を使います。
この場合もテンプレートリテラル(タグ付きテンプレート)を使えば安全です。
// ✅ 安全(テンプレートリテラルを使う)
const result = await prisma.$queryRaw`
SELECT * FROM users WHERE email = ${email}
`
// ❌ 危険($queryRawUnsafe に直接文字列を渡す)
const result = await prisma.$queryRawUnsafe(
`SELECT * FROM users WHERE email = '${email}'`
)対策まとめ
| 状況 | 対応 |
|---|---|
| 通常のCRUD操作 | Prismaの通常操作を使えばOK(自動で安全) |
| 生のSQLを書きたい | $queryRaw + テンプレートリテラルを使う |
$queryRawUnsafe | ユーザー入力を絶対に渡さない |
Prisma を使っている限り、通常の操作では SQLインジェクションは起きません。
安心して開発に集中してください。
ただし、「生のSQL($queryRawUnsafe)を使わなければいけない状況」になったら必ずプレースホルダーを意識してください。
確認しよう
- 脆弱なエンドポイントを作り、SQLインジェクションを体験した
- Prismaの通常操作がなぜ安全かを説明できる
- プレースホルダー(パラメータ化クエリ)とは何かを説明できる
AIに聞いてみよう
「SQLインジェクション以外にもDBに関連するセキュリティリスクにはどんなものがありますか?」