結論から
SQLインジェクションは、検索窓や入力欄に「命令」を混ぜて送ると、データベースがそれを命令として実行してしまう穴です。20年以上前からある古典的な穴ですが、2026年8月にも BI ツール Metabase のクラウド版がこの穴で侵入され、複数の企業が被害を公表しました。AIが書くコードにも、同じ形はよく出てきます。
この記事で伝えたいのは、この一点だけです。入力を「文の一部」として貼り付けるコードが穴。入力は「値」として渡す(プレースホルダ)。 直すのは書き方1つで、AIに一言で頼めます。
- 何が起きるか:検索欄に
' OR 1=1 --と打つと全件が出る。'; DROP TABLE users; --なら表が消える。ログイン欄なら、パスワードなしで入れる - なぜ:
"SELECT * FROM users WHERE name = '" + 入力 + "'"のように、入力を文字列でつなぐと、入力の中の'が文の区切りになる - 直す:入力を文につながず、
?や$1の穴(プレースホルダ)に「値」として渡す。Supabase の通常の呼び出し(.from().select())や ORM は最初からそうなっている。危ないのはrawrpcsql\…\`` で自分で文を組んでいる所 - 確かめる:自分の検索欄に
'(シングルクォート1つ)を入れる。エラー画面が出たら要注意
動画版(約2分半)は本文の上に埋め込んであります。
① 何が起きるのか:検索欄に「命令」を書く
例として、AIで作った予約アプリに「顧客名で検索」の機能があるとします。裏側でデータベースに送られる文(SQL)はこうです。
SELECT * FROM customers WHERE name = '佐藤'
検索欄に 佐藤 と打てば '佐藤' の部分が入れ替わるだけです。では、検索欄にこう打ったら。
' OR 1=1 --
文はこうなります。
SELECT * FROM customers WHERE name = '' OR 1=1 --'
「名前が空、または 1=1(常に真)」なので、全顧客が返ってきます。-- 以降はコメント扱いで無視されます。検索欄に打ったのは「値」のつもりでも、データベースは「命令の続き」として読んだ。これが SQLインジェクションです。
同じ要領で、'; DROP TABLE customers; -- なら表そのものが消え、ログイン欄の ' OR '1'='1 ならパスワードを知らなくても入れることがあります。
② なぜ起きるのか:入力を「文」につないでいる
原因はコードの書き方1つです。
穴のある書き方(入力を文字列でつなぐ)
"SELECT * FROM customers WHERE name = '" + name + "'"
穴のない書き方(プレースホルダに値として渡す)
"SELECT * FROM customers WHERE name = $1", [name]
後者では、name に何が入っていても「1つの値」として扱われ、' も OR も文の一部にはなりません。IPA の「安全なウェブサイトの作り方」も、対策の根本としてバインド機構(プレースホルダ)を挙げています。エスケープ(' を無害化する処理)は次善で、抜けが出ます。
AIはどちらを書くか。 ほとんどの場合、後者です。Supabase の supabase.from('customers').select().eq('name', name) や Prisma・Drizzle のような ORM は、最初からプレースホルダ方式です。危ないのは、AIが「複雑な検索」「集計」「全文検索」を頼まれて、自分で SQL 文を組み立てるときです。supabase.rpc() に渡す関数の中、sql\SELECT … ${name}\` のようなテンプレート、.raw() executeRaw` と付く呼び出し。ここで文字列連結が出ます。「動く」ので、AIも気づきません。
これは古典的な穴ですが、無くなっていません。2026年8月6日、BI ツール Metabase のクラウド版が、パスワードリセットのAPIに隠れていた SQLインジェクション(CVE-2026-72898)で侵入されました。攻撃者が送った値が、そのまま SQL に埋め込まれる形でした。Framework、Tally、n8n、Anaconda など複数の企業が、これを起点とした被害を公表しています。プロのチームが作った製品でも起きる、ということです。
③ どう直すのか:AIに「プレースホルダで」と頼む
以下をそのまま AI ツールに貼ってください。
「このプロジェクトで SQL 文を組み立てている箇所をすべて列挙してください(raw、rpc に渡す関数、sql テンプレート、executeRaw、文字列連結で SELECT/INSERT/UPDATE/DELETE を作っている所)。ユーザーの入力が文字列として連結されている箇所は、すべてプレースホルダ(パラメータバインド)に書き換えてください。テーブル名や並び順など、プレースホルダにできない部分は、許可リストで固定してください。変更前後の一覧も出してください。」
ポイントは2つ。「すべて列挙」と言う(AIは頼まれた1か所しか見ない)。「テーブル名・並び順は許可リスト」と言う(ORDER BY ${sort} のような、プレースホルダにできない場所が残りやすい)。
Supabase を使っている場合は、加えて RLS を確認してください(→ RLS の記事)。SQLインジェクションで「全件を取る命令」が通っても、RLS が「自分の行だけ」に絞っていれば、被害は本人の分で止まります。RLS は最後の砦です。
④ 直ったかを確かめる:シングルクォートを1つ
- 検索欄・ログイン欄・URLの `?q=` に `'`(シングルクォート1つ)を入れる。データベースのエラー画面(
syntax error、unterminated quoted string)が出たら ✗。「見つかりません」「0件」なら ◯の可能性が高い - `' OR 1=1 --` を入れる。全件出たら ✗
- エラー画面に SQL 文や内部情報が出ないか。出るなら、直し方のヒントを攻撃者に渡している状態(→ 20項目の18番)
- 分からなければ、AIに「検索欄に
' OR 1=1 --を入れるテストを書いて、全件が返らないことを確認して」
自分で直せないとき:エンジニアに送る一文
「検索機能に ' を入れるとデータベースのエラーが出ます。SQL を文字列連結で組み立てている箇所を全部洗い出して、プレースホルダに直してください。Supabase の RLS も合わせて確認をお願いします。」
よくある質問
Supabase を使っています。SQL を書いた覚えがありません。
.from().select() だけなら、この穴はまず開きません。AIに「このプロジェクトに raw SQL や rpc 関数はある?」と聞いて、無ければ次のチェックへ。あれば③のプロンプトを貼ってください。
入力欄で `'` を禁止すれば済みますか。
済みません。「O'Brien」さんが登録できなくなりますし、' 以外にも文を壊す方法があります。入力を制限するのではなく、受け取り方(プレースホルダ)を直します。
NoSQL(Firebase・MongoDB)なら無関係ですか。
SQL ではなくても、入力を「命令」として解釈する穴(NoSQL インジェクション)はあります。考え方は同じで、入力を命令の構造に混ぜない、です。
この穴が塞がれば、データベースは安全ですか。
「命令を混ぜられない」だけです。「誰が読めるか」(RLS・本人確認)は別の話で、そちらのほうが AI 製アプリでは頻度が高い穴です(→ URLの数字・/admin・RLS)。


