SQLインジェクションの種類を歴史から2026年の最新脅威まで完全解説

SQLインジェクションの種類を歴史から2026年の最新脅威まで完全解説
目次

はじめに


SQLインジェクションは、1990年代の古いWebアプリケーションだけに存在する脆弱性ではありません。

入力フォームからSQL文を直接送り込む古典的な攻撃に加えて、現在では次のような場所が攻撃面になっています。

  • REST APIやJSON

  • CookieやHTTPヘッダー

  • GraphQL Resolver

  • ORMのRaw SQL機能

  • 動的な列名や並び順

  • CSVインポートや夜間バッチ

  • KafkaやRabbitMQを経由する非同期処理

  • 自然言語からSQLを生成するLLMエージェント

SQLインジェクションはMITREでCWE-89として分類されています。2025年のCWE Top 25では第2位でした。OWASP Top 10:2025でも、SQLインジェクションを含むInjectionがA05に位置付けられています。過去の攻撃ではなく、現在も優先的に対策すべき脆弱性です。


結論・要点

SQLインジェクションの本質は、データとして扱うべき外部入力が、SQLの命令や構文として解釈されることです。

根本原因は次の構造にあります。

安全でない外部入力
        │
        ▼
SQL文字列へ直接連結
        │
        ▼
SQL構造と入力値の境界が崩れる
        │
        ▼
DBMSが入力値を命令として解釈する

SQLインジェクションは、一つの基準だけでは正しく分類できません。

分類軸主な種類
結果を観測する経路In-band、Blind、Out-of-band
情報を推測する方法エラー、UNION、真偽差、時間差、DNS・HTTP
実行される時期First-order、Second-order
SQLを変更する方法条件改変、コメント化、UNION、複文、サブクエリ
注入される場所WHERE、ORDER BY、SELECT、INSERT、UPDATE、識別子
データアクセス層Raw SQL、ORM、HQL、JPQL、Query Builder
入力経路URL、JSON、Cookie、ヘッダー、ファイル、メッセージキュー
AI関連Prompt-to-SQL、Text-to-SQL攻撃、モデル汚染、スキーマ推測

これらは排他的ではありません。

一つの脆弱性が、次のような複数の特徴を同時に持つことがあります。

Second-order
  +
Time-based Blind
  +
ORM内部のRaw SQL
  +
非同期バッチ経由

防御の中心はパラメータ化クエリです。

ただし、テーブル名や列名、並び順などは通常のプレースホルダーでは置き換えられません。識別子にはAllowlist、データベースには最小権限、OOB攻撃には外向き通信制御が必要です。OWASPも、Prepared Statementだけでなく、安全なストアドプロシージャ、Allowlist、最小権限を組み合わせる防御を推奨しています。


この記事で分かること

この記事では、次の内容を説明します。

  • SQLインジェクションが発生する根本原因

  • 1998年から2026年までの歴史的変化

  • In-band、Blind、OOBの違い

  • First-orderとSecond-orderの違い

  • UNION型、エラー型、時間差型などの分類

  • ORMやDjangoを使っていても発生する理由

  • GraphQLや非同期処理との関係

  • Prompt-to-SQLと従来型SQLインジェクションの違い

  • ソースコードとログの確認方法

  • Djangoでの安全な実装

  • WAF、IPS、EDR、DB監査の役割

  • 動作確認と再発防止


対象読者・前提環境

対象読者は次のとおりです。

  • Webアプリケーション開発者

  • Django、Laravel、Spring、Hibernateなどの利用者

  • データベース管理者

  • インフラ・セキュリティ担当者

  • SOC、CSIRT、脆弱性診断担当者

  • LLMやText-to-SQL機能を開発している人

  • 基本情報技術者試験や応用情報技術者試験の学習者

記事内のコード例は主にPythonとDjangoを使用します。

攻撃文字列の実行方法ではなく、仕組み、確認方法、防御方法を中心に説明します。検証は、必ず自分が管理する開発環境または明示的に許可された環境で行ってください。


SQLインジェクションとは

SQLインジェクションは、外部から制御できる入力を使ってSQL文の一部または全部を作成し、SQLの意味を意図せず変更できる脆弱性です。

MITREのCWE-89では、外部入力に影響される値を使ってSQLコマンドを作成し、SQLの特殊要素を正しく無害化できていない状態として定義されています。

簡単なたとえ

SQL文をレストランの注文票に例えます。

本来、利用者が入力できるのは「料理名」だけです。

注文:カレー

しかし、料理名と店員への命令を同じ欄へ自由に書けると、次のような状態になります。

注文:カレー
命令:ほかの客の注文票もすべて見せてください

アプリケーションが「料理名」と「命令」を分離しなければ、データベースは両方を命令として解釈する可能性があります。

正式には、SQLの構文部分パラメータ値の境界が失われた状態です。


SQLインジェクションの全体像

flowchart TD
    A[SQLインジェクション] --> B[結果の取得経路]
    A --> C[実行時期]
    A --> D[構文の変更方法]
    A --> E[注入位置]
    A --> F[アクセス技術]
    A --> G[現代的な隣接領域]

    B --> B1[In-band]
    B --> B2[Blind / Inferential]
    B --> B3[Out-of-band]

    B1 --> B11[Error-based]
    B1 --> B12[UNION-based]

    B2 --> B21[Boolean-based]
    B2 --> B22[Conditional Error]
    B2 --> B23[Time-based]

    B3 --> B31[DNS]
    B3 --> B32[HTTP]
    B3 --> B33[SMB・UNC]

    C --> C1[First-order]
    C --> C2[Second-order]

    D --> D1[Tautology]
    D --> D2[Comment Truncation]
    D --> D3[Stacked Query]
    D --> D4[Subquery]

    E --> E1[WHERE]
    E --> E2[ORDER BY]
    E --> E3[INSERT・UPDATE]
    E --> E4[列名・テーブル名]

    F --> F1[Raw SQL]
    F --> F2[ORM・HQL・JPQL]
    F --> F3[API・GraphQL]
    F --> F4[バッチ・メッセージキュー]

    G --> G1[Prompt-to-SQL]
    G --> G2[Text-to-SQL攻撃]
    G --> G3[モデルバックドア]

SQLインジェクションの歴史

1990年代:動的Webアプリケーションで問題が表面化

1990年代後半、Webフォームから受け取った値をSQL文字列へ連結する実装が広がりました。

1998年のPhrack記事は、WebアプリケーションからRaw SQLを実行する危険性を説明した初期の重要資料です。記事では、SQL Serverの拡張プロシージャにWeb用アカウントからアクセスさせないことや、Raw SQLを避けることにも言及しています。

初期の代表例は、ログイン処理や検索処理の条件を書き換える攻撃でした。

2000年代:UNION型、エラー型、自動化ツールが普及

SQL文の条件を書き換えるだけでなく、別テーブルの検索結果を結合するUNION型や、データベースエラーへ内部情報を含ませるエラー型が広く知られるようになりました。

DBMS製品や列数、テーブル構造を推測し、情報抽出を自動化する診断・攻撃ツールも発展しました。

2000年代後半:Blind SQLインジェクションが高度化

本番環境で詳細エラーを表示しない設定が一般化すると、攻撃者は画面の内容、HTTPステータス、応答時間などの差から情報を推測するようになりました。

MITREのCAPEC-7も、エラーを非表示にするだけではSQLインジェクションを防げず、Blind SQL Injectionが成立し得ると説明しています。

2010年代:ORM、API、Second-orderが重要化

ORMの標準APIは通常、値をパラメータ化してSQLを生成します。

しかし、次の処理ではSQLインジェクションが残ります。

  • Raw SQL

  • HQLやJPQLの文字列連結

  • 動的な列名

  • 動的なテーブル名

  • 独自の検索式

  • ORMの低水準API

  • 保存済みデータを後からSQLへ連結する処理

MITREはORMを経由する攻撃をCAPEC-109、Hibernateに関する弱点をCWE-564として整理しています。

2020年代:クラウド、非同期処理、LLM連携へ拡大

現在の入力経路は、フォームだけではありません。

Webフォーム
REST API
JSON
Cookie
HTTPヘッダー
CSV
ログ
Webhook
Kafka
RabbitMQ
外部SaaS
LLMの生成結果
        │
        ▼
アプリケーション・ワーカー
        │
        ▼
ORM・Query Builder・Raw SQL
        │
        ▼
データベース

OWASPも、パラメータ、HTTPヘッダー、URL、Cookie、JSON、SOAP、XMLなど、複数の入力経路を検査する必要があると説明しています。


1.In-band SQLインジェクション

In-bandとは

攻撃用の入力を送る経路と、結果を受け取る経路が同じ方式です。

攻撃入力 ──HTTP──▶ Webアプリ ──SQL──▶ DB
    ▲                                   │
    └──────── HTTPレスポンス ◀─────────┘

代表例は、エラー型とUNION型です。


1-1.エラーベースSQLインジェクション


データベースに意図的なエラーを発生させ、エラーメッセージから情報を取得する方式です。

漏えいする可能性がある情報

  • DBMS製品とバージョン

  • テーブル名

  • 列名

  • データ型

  • 実行されたSQL文

  • ファイルパス

  • スタックトレース

  • 条件によっては検索結果の一部

成立しやすい条件

  • 詳細なDBエラーを利用者へ返している

  • 開発用のデバッグ設定が本番で有効

  • 型変換やXML処理などのエラーに値が含まれる

  • アプリケーションがDBエラーを加工せず返している

注意点

エラーメッセージを非表示にすることは必要です。

ただし、SQL文字列の組み立て方を修正しなければ、Boolean型やTime-based型へ移行される可能性があります。


1-2.UNIONベースSQLインジェクション


本来のSELECT結果へ、別のSELECT結果を結合する方式です。

概念的には次のような構造です。

SELECT product_name, description
FROM products
WHERE category = :category;

脆弱な文字列連結があると、別テーブルの結果が検索結果へ混ぜられる可能性があります。

主な成立条件

  • 元のSQLがSELECT文

  • 注入位置でUNIONが構文上使用可能

  • 結合するSELECT文の列数が一致

  • 対応する列のデータ型に互換性がある

  • 結果の一部が画面やAPIレスポンスへ表示される

主な影響

  • 顧客情報の漏えい

  • パスワードハッシュの漏えい

  • APIキーやトークンの漏えい

  • テナントを越えたデータ参照

  • データベース構造の把握

UNION型は結果を直接表示できるため高速になり得ますが、取得可能な情報はSQLコンテキスト、DBアカウント権限、レスポンスの表示方法によって変わります。


2.Blind SQLインジェクション


Blind SQLインジェクションとは

SQLの実行結果や詳細エラーが画面へ直接表示されない環境で、アプリケーションの挙動差から情報を推測する方式です。

条件を含む入力
      │
      ▼
SQLを実行
      │
  ┌───┴───┐
  │       │
条件が真  条件が偽
  │       │
表示、時間、ステータスなどの差を観測

Blind型は直接表示型より時間がかかる傾向があります。

しかし、エラーを隠しただけでは防止できません。


2-1.Boolean-based Blind SQLインジェクション


条件が真の場合と偽の場合のレスポンス差を観測します。

観測される差

  • 「検索結果あり」の表示

  • HTMLの一部分

  • JSONの項目数

  • Content-Length

  • HTTPステータス

  • リダイレクト先

  • Cookie

  • ETag

  • キャッシュの動作

攻撃者は、一文字ずつ質問するだけでなく、値の大小を比較して探索範囲を半分にする二分探索を利用する場合があります。


2-2.Conditional Error-based Blind SQLインジェクション


条件が真の場合だけエラーを発生させます。

条件が真  → DBエラー → HTTP 500
条件が偽  → 正常処理 → HTTP 200

エラー本文が非表示でも、次の差が残る場合があります。

  • ステータスコード

  • レスポンスサイズ

  • 接続切断

  • ロードバランサのエラーページ

  • リトライの有無

エラーベースとBlind型の両方の性質を持つ方式です。


2-3.Time-based Blind SQLインジェクション


条件が真の場合だけ、データベース処理を遅延させます。

条件が真  → 指定時間だけ応答を遅らせる
条件が偽  → 通常速度で応答する

代表的な遅延機能はDBMSごとに異なります。

DBMS遅延に悪用される可能性がある機能
Microsoft SQL ServerWAITFOR DELAY
PostgreSQLpg_sleep()
MySQL・MariaDBSLEEP()、高負荷演算
Oracle Databaseパッケージやロック、ネットワーク処理など
SQLite大量演算、再帰、重い検索処理など

DBMSごとに構文や利用可能な機能が異なるため、攻撃の成立条件も異なります。

クラウド環境での注意

クラウドでは通常時にも次の遅延が発生します。

  • オートスケール

  • コールドスタート

  • DB接続プール待ち

  • CDNやWAF

  • ネットワークの揺らぎ

  • キャッシュミス

そのため、単発の遅延ではなく、複数回の測定結果から統計的に差を判断される場合があります。


3.Out-of-band SQLインジェクション



3-1.DNS-based OOB


データベースサーバーから、攻撃者が管理するドメインへDNS問い合わせを発生させます。

DNS問い合わせの名前へデータの一部を埋め込める場合、Webレスポンスを経由せず情報が送信される可能性があります。

防御

  • DBサーバーから任意DNSリゾルバへの通信を禁止

  • 指定した社内DNSリゾルバだけを許可

  • 長いサブドメインや高エントロピーなDNS名を監視

  • DBホスト起点の異常な名前解決を検知


3-2.HTTP-based OOB


DBMSのネットワーク機能や拡張機能を利用し、外部HTTPサーバーへ通信させます。

防御

  • DBセグメントからインターネットへのHTTP・HTTPSを原則拒否

  • プロキシ経由に限定

  • 宛先Allowlistを設定

  • DBプロセス起点の通信を監視


3-3.SMB・UNC型


Windows環境では、UNCパスへのアクセスがDNSやSMB通信を発生させる場合があります。

条件によってはNTLM認証の試行が外部へ送られる可能性もあります。

防御

  • DBサーバーから外部へのTCP 445を遮断

  • 不要なファイル参照機能を無効化

  • NTLMの利用範囲を制限

  • DBサービスアカウントを最小権限化


4.First-orderとSecond-order

4-1.First-order SQLインジェクション


入力された値が、同じリクエストや同じ処理の中ですぐSQLへ組み込まれます。

入力
 ↓
脆弱なSQLを生成
 ↓
直ちに実行

検索欄、ログインフォーム、URLパラメータなどで発生する古典的な方式です。


4-2.Second-order SQLインジェクション


入力値が一度データベースやファイルへ保存され、後の処理でSQLへ連結される方式です。

登録API
  │
  ▼
パラメータ化して安全に保存
  │
  ▼
データベース
  │
  │ 数時間・数日後
  ▼
レポート・バッチ・管理機能
  │
  ▼
保存値を文字列連結
  │
  ▼
SQLインジェクション成立

保存時にパラメータ化されていても、取り出した後に不安全なSQLへ連結すれば脆弱になります。

PortSwiggerも、利用者入力が一度保存され、後で別のSQLへ不安全に組み込まれる状態をSecond-order SQL Injectionとして説明しています。

発生しやすい場所

  • CSVインポート

  • 帳票作成

  • 夜間バッチ

  • BIダッシュボード

  • 保存済み検索条件

  • データ移行

  • 監査ログ検索

  • 管理者向け集計

  • ETL処理

  • KafkaやRabbitMQのConsumer

  • 外部SaaSから同期したデータ

発見しにくい理由

入力した直後には異常が発生しません。

脆弱性スキャナーやWAFが、登録処理と後続のバッチ処理を関連付けられないことがあります。


5.SQL構文の変更方法による分類

5-1.Tautology-based


Tautologyは「常に真になる条件」という意味です。

WHERE句を常に真にすることで、検索条件や認証条件を無効化する方式です。

主な目的

  • 認証回避

  • 非公開データの表示

  • テナント条件の解除

  • 論理削除条件の解除

  • 対象行の拡大


5-2.Contradiction-based


条件を必ず偽にします。

直接情報を取得する方式というより、真条件とのレスポンス差を比較して、SQLインジェクションの存在を確認するために使われます。


5-3.コメントによる後続条件の無効化


SQLコメントを利用して、元のSQLの後半を無効化する方式です。

影響を受けやすい条件には次があります。

  • パスワード照合

  • is_active

  • is_deleted

  • tenant_id

  • 公開範囲

  • 所有者ID

  • 権限制御

コメント構文はDBMSによって異なります。


5-4.Stacked Query・Piggy-backed Query


元のSQL文を終了させ、後ろへ別のSQL文を追加する方式です。

本来のSQL
    +
別のSQL文

影響

  • INSERT

  • UPDATE

  • DELETE

  • DDL

  • 権限変更

  • ストアドプロシージャ実行

  • DBMS機能を介した外部通信

複数SQL文を一度に実行できるかどうかは、DBMSだけでなく、データベースドライバや接続オプションにも依存します。


5-5.Subquery-based


条件式やSELECT列の内部へサブクエリを組み込みます。

サブクエリは次の方式と組み合わせられます。

  • Boolean-based

  • Error-based

  • Time-based

  • INSERT

  • UPDATE

  • 存在確認

  • 集計値の推測

UNIONが使えないSQLコンテキストでも、サブクエリが利用できる場合があります。


5-6.Stored Procedure Injection


ストアドプロシージャを呼び出しているだけでは、必ずしも安全ではありません。

アプリケーション
     │
     ▼
ストアドプロシージャ
     │
     ▼
内部で動的SQLを文字列連結
     │
     ▼
SQLインジェクション

安全に構築されたストアドプロシージャはパラメータ化クエリと同等の防御になり得ます。

一方、プロシージャ内部で動的SQLを連結すれば、通常のSQLと同じ脆弱性が発生します。MITREとOWASPも、ストアドプロシージャだけではすべてのSQLインジェクションを防げないと説明しています。


5-7.DBMS機能悪用型


SQLインジェクション成立後、DBMS固有機能へ到達する場合があります。

  • ファイル読み取り

  • ファイル書き込み

  • 外部ネットワーク通信

  • リンクサーバー

  • 拡張機能

  • ジョブ実行

  • ユーザー定義関数

  • OSコマンド実行機能

ただし、OSコマンド実行まで進むには、対応するDBMS機能、権限、OS設定が必要です。

MITREは、SQLインジェクションからコマンド実行へ進む攻撃パターンをCAPEC-108として整理しています。


6.注入される位置による分類

6-1.文字列リテラル

WHERE username = '入力値'

文字列境界を壊せる実装があると、SQL構造を変更される可能性があります。

6-2.数値リテラル

WHERE user_id = 入力値

クォートで囲まれていないため、「シングルクォートを削除するだけ」の対策では防げません。

6-3.WHERE句

検索条件、認証条件、テナント条件などに使われます。

6-4.SELECTリスト

BI、動的レポート、カスタム集計などで発生します。

6-5.ORDER BY

並び替え列やASC・DESCを外部入力から作る場合です。

値用プレースホルダーは、通常、列名やSQLキーワードには利用できません。

6-6.GROUP BY・HAVING

集計レポートや分析画面で発生しやすい場所です。

6-7.LIMIT・OFFSET

ページ番号や表示件数を文字列として連結する実装で発生します。

6-8.INSERT

登録処理でSQL構造を変更されるほか、Second-order攻撃用の値を保存される可能性があります。

6-9.UPDATE

WHERE条件が変更されると、他利用者を含む複数行が更新される可能性があります。

6-10.DELETE

削除条件が変更されると、広範囲のデータが削除される可能性があります。

6-11.識別子

次の名前を動的に作る実装です。

  • テーブル名

  • 列名

  • スキーマ名

  • データベース名

  • 関数名

  • パーティション名

  • 列エイリアス

OWASPは、テーブル名、列名、並び順など、バインド変数を使用できないSQL構造には、コード内の固定値へのマッピングやAllowlistを使用するよう推奨しています。


7.入力経路による分類

SQLインジェクションの入力元は、検索フォームだけではありません。

入力経路
URLクエリ?id=...?sort=...
パスパラメータ/users/{id}/
POSTフォームログイン、検索、更新
JSONREST APIのfilter、sort、fields
CookieトラッキングID、店舗ID、表示条件
HTTPヘッダーUser-Agent、Referer、X-Forwarded-For
ファイルCSV、Excel、XML、JSON
Webhook外部SaaSからのイベント
メッセージキューKafka、RabbitMQ、SQS
ログアクセスログ、監査ログ
GraphQLQueryやMutationの引数
LLM出力自然言語から生成されたSQL

特にCookieやHTTPヘッダーは、利用者が操作できない値と誤認されやすいため注意が必要です。


8.ORM・HQL・JPQLインジェクション

ORMを使っていてもSQLインジェクションは発生する

ORMは、データベース操作をプログラム上のオブジェクトとして扱う仕組みです。

通常のAPIを正しく使用すれば、値はパラメータ化されます。

しかし、次の使い方では危険が残ります。

  • Raw SQL

  • HQLやJPQLの文字列連結

  • 動的な列名

  • 動的なエイリアス

  • 独自Query Builder

  • ORMの低水準API

  • ユーザー入力を展開した**kwargs

  • カスタムSQL関数

  • テーブル名を動的に変更するマルチテナント設計

Djangoの公式ドキュメントも、QuerySetはパラメータ化によってSQLインジェクションから保護される一方、Raw SQLや一部のクエリ式では、信頼できない値をSQL文字列へ補間してはいけないと警告しています。


2025~2026年のDjango事例

ORMを使用していても、フレームワーク本体の不具合や高度なAPIの使い方によってSQLインジェクションが発生する可能性があります。

Djangoでは2025年から2026年にかけて、列エイリアス、_connectororder_by()FilteredRelation、PostGISのRaster Lookupなどに関する複数の潜在的SQLインジェクションが修正されました。

2026年7月28日時点で、Django公式サイトが案内する安定版は6.0.7、LTSは5.2.16です。Django 4.2 LTSは2026年4月7日に延長サポートを終了しています。Django 4.2を使用している環境では、単なるパッチ適用ではなく、サポート中の5.2 LTS以降への移行計画が必要です。


9.GraphQLとSQLインジェクション

GraphQL自体はSQLではありません。

SQLインジェクションが発生するかどうかは、ResolverがGraphQL引数をどのようにデータベース処理へ変換するかで決まります。

GraphQLリクエスト
        │
        ▼
Resolver
        │
        ▼
ORM・Query Builder・Raw SQL
        │
        ▼
データベース

危険な設計

  • sort引数をそのままORDER BYへ使う

  • fields引数からSELECT列を文字列生成する

  • filter文字列をRaw SQLへ変換する

  • 利用者指定のテーブル名や集計関数を使う

  • GraphQL引数をそのままORMの高度な辞書展開へ渡す

正確な理解

GraphQL SQL Injectionという独立した根本原理があるわけではありません。

GraphQLは、SQLへ到達する入力経路の一つです。

バッチリクエストや深いネストは、攻撃回数や負荷、ログの見え方へ影響する可能性がありますが、それだけでSQLインジェクションが成立するわけではありません。


10.WAF・フィルタ回避と難読化

次の項目は独立したSQLインジェクションの種類ではありません。

WAFや単純なブラックリストを回避する補助技術です。

  • 大文字と小文字の変更

  • コメントによるトークン分割

  • URLエンコード

  • 二重エンコード

  • Unicode表現

  • 改行やタブ

  • 16進表現

  • 重複パラメータ

  • JSONの重複キー

  • Content-Typeの不一致

  • DBMS固有の同義関数

WAFとアプリケーションでデコード回数やパラメータ採用ルールが異なると、検査結果に差が生じます。

クライアント
   │
   ▼
CDN・WAFで1回デコード
   │
   ▼
Webサーバーで再デコード
   │
   ▼
アプリケーション

ただし、「少し難読化すればWAFを容易に無効化できる」と一律には言えません。

実際の可否は、WAF製品、ルール、正規化処理、DBMS、アプリケーション実装によって変わります。


11.NoSQL Injectionとの違い

MongoDBなどに対するNoSQL Injectionは、SQLインジェクションとは別の脆弱性です。

共通点は、入力値が検索命令や演算子として解釈されることです。

しかし、対象となる構文は異なります。

種類対象
SQL InjectionSQL文
NoSQL InjectionJSON演算子、検索オブジェクト、NoSQLクエリ
LDAP InjectionLDAP検索フィルタ
XPath InjectionXPath式
Command InjectionOSコマンド
Prompt InjectionLLMへの指示とデータの混同

NoSQL InjectionをSQLインジェクションの一種と表現するのは正確ではありません。

OWASP WSTGでも、SQL InjectionとNoSQL Injectionは別の検査項目として整理されています。


12.最新動向:Prompt-to-SQLとText-to-SQL


従来型SQLインジェクションとの違い

従来型では、プログラムがSQLのひな型を持っています。

固定SQL
  +
外部入力

Text-to-SQLでは、LLMが自然言語からSQL文全体を生成します。

自然言語
   │
   ▼
LLM
   │
   ▼
生成SQL
   │
   ▼
データベース

この違いは重要です。

LLMが危険なSQLを生成しただけでは、必ずしもCWE-89の古典的SQLインジェクションとは限りません。

次の問題が混在します。

  • Prompt Injection

  • 不適切な認可

  • 過剰なツール権限

  • 安全でないSQL生成

  • SQL実行前の検証不足

  • 古典的なSQL文字列連結

  • データベースの過剰権限

英国NCSCも、Prompt InjectionはSQL Injectionと同一ではなく、SQLのような明確なコードとデータの境界をLLM内部で完全に強制できないと説明しています。


12-1.Direct Prompt-to-SQL


利用者が自然言語で、許可されていないテーブルや操作を含むSQLを生成させようとする方式です。

例として、次の要求があります。

  • 全利用者データを取得する

  • 管理テーブルを参照する

  • 更新や削除を実行する

  • テナント条件を外す

  • システムカタログを参照する

プロンプトだけでなく、SQL実行ツールの認可設計が重要です。


12-2.Indirect Prompt-to-SQL


LLMが読み込む外部文書やデータベース内の文章に、隠れた命令が含まれる方式です。

外部文書・メール・DB内文章
           │
           ▼
LLMがRAGで取得
           │
           ▼
文章中の命令へ影響される
           │
           ▼
危険なSQL候補を生成

Second-order型のデータフローとIndirect Prompt Injectionが連鎖する構造です。


12-3.Prompt-to-SQL Injection研究


ICSE 2025では、LangChainやLlamaIndexなどを利用するLLM統合Webアプリケーションを対象に、Prompt-to-SQL Injectionのリスクと防御策を調査した研究が発表されています。複数のLLMと実装例を対象に、自然言語入力が危険なSQL生成へつながる可能性を評価しています。

これは、すべてのText-to-SQL製品で実際の侵害が多発していることを意味しません。

現時点では、重要な研究・設計上の脅威として扱うのが適切です。


12-4.Adversarial Text-to-SQL


AAAI 2025では、Text-to-SQLモデルに対し、入力を調整しながら意図した攻撃的SQLを生成させるAIAという研究フレームワークが発表されました。


12-5.スキーマ推測


Text-to-SQLシステムへ質問を繰り返し、背後のテーブル名、列名、データ型を推測する研究もあります。

NAACL 2025の研究では、事前知識なしにText-to-SQLモデルを調査し、データベーススキーマを再構成する方式が検討されました。

これは古典的SQLインジェクションというより、モデル出力を通じたスキーマ情報漏えいです。


12-6.バックドア型Text-to-SQL


学習データやファインチューニングデータへ毒入りデータを混入し、特定の語句や文字列が入力された場合だけ危険なSQLを生成させる研究があります。

ToxicSQL研究では、少量の汚染データを用いてText-to-SQLモデルへバックドア動作を埋め込む可能性が示されています。これは研究結果であり、一般的な実環境で広範に悪用されていることを示すものではありません。


SQLインジェクションの種類比較

以下は一般的な傾向です。

実際の危険度や速度は、DBMS、権限、ネットワーク、アプリケーション実装で変わります。

種類観測経路一般的な速度検知難易度主な条件
Error-basedHTTPエラー速い低~中詳細エラーが返る
UNION-basedHTTP本文速い低~中列数・型が一致し結果が表示される
Boolean-based表示差・状態差遅い中~高真偽で挙動差が出る
Conditional Errorステータス・エラー差遅い中~高条件付きエラーを起こせる
Time-based応答時間非常に遅い遅延機能が利用可能
DNS OOBDNSログDBからDNS通信可能
HTTP OOB外部HTTPログDBからHTTP通信可能
Second-order後続処理条件依存非常に高い保存値を後で不安全に利用
ORM InjectionORM生成SQL条件依存中~高Raw SQLや動的構造がある
Identifier Injection列名・テーブル名条件依存中~高識別子を外部入力から生成
Prompt-to-SQLLLM生成SQL条件依存LLMにSQL実行権限がある

確認方法

1.Raw SQLと危険なAPIを検索する

目的

Djangoプロジェクト内のRaw SQL、低水準API、動的SQL候補を探します。

実行場所

manage.pyが存在するDjangoプロジェクトのルートディレクトリです。

コマンド

rg -n --glob '*.py' \
'cursor\.execute|cursor\.executemany|\.raw\(|RawSQL\(|\.extra\(|extra_context|as_sql\(|order_by\(|annotate\(|aggregate\(' \
.

正常例

検索結果がゼロ、または安全なパラメータ化が確認できる状態です。

cursor.execute(
    "SELECT id, email FROM users WHERE email = %s",
    [email],
)

要確認例

cursor.execute(
    f"SELECT id, email FROM users WHERE email = '{email}'"
)
cursor.execute(
    "SELECT id FROM users ORDER BY " + sort_column
)

結果の判断方法

次を手作業で確認します。

  • SQL文字列へf-stringを使用していないか

  • +で入力値を連結していないか

  • .format()%でSQLを組み立てていないか

  • 列名やテーブル名が外部入力になっていないか

  • paramsを使用しているか

  • プレースホルダーをクォートで囲んでいないか

  • 保存済みデータを後続処理で連結していないか

Django公式ドキュメントも、Raw SQLではparamsを使用し、プレースホルダー自体をクォートで囲まないよう警告しています。


2.Djangoのバージョンを確認する

目的

サポート切れや、SQLインジェクション修正前のDjangoを使用していないか確認します。

実行場所

Djangoをインストールしている仮想環境またはコンテナ内です。

コマンド

python -m django --version

Docker環境の例です。

docker exec <Djangoコンテナ名> python -m django --version

正常例

5.2.16

または、現在サポート中の系列の最新パッチです。

異常・要対応例

4.2.16

Django 4.2系列は2026年4月7日にサポートを終了しています。

判断方法

  • 5.2 LTSまたは6.0の最新パッチか確認する

  • 4.2以前なら移行計画を作る

  • メジャー・マイナー番号だけでなくパッチ番号も確認する

  • 本番と開発環境の両方を確認する


3.ログで確認する

Web・WAFログ

次の兆候を確認します。

  • 同じURLへの大量リクエスト

  • 条件部分だけが少しずつ変化する入力

  • ステータス200と500の反復

  • 一定時間遅れるリクエスト

  • 異常に長いCookie

  • 不自然なエンコード

  • 同じパラメータの重複

  • JSONの型や構造の不一致

DB監査ログ

次を確認します。

  • システムカタログへの不審なアクセス

  • 通常使わない関数

  • 大量の部分文字列処理

  • 不自然な条件式の反復

  • 遅延関数

  • DDL

  • 権限変更

  • 外部接続機能

  • 異常な大量SELECT

DNS・ネットワークログ

次を確認します。

  • DBサーバー起点の外部DNS問い合わせ

  • 高エントロピーなサブドメイン

  • DBセグメントからのHTTP・HTTPS

  • 外部へのSMB接続

  • DBホストから未知の宛先への通信


対処方法

1.値はパラメータ化する

危険な実装

query = f"""
SELECT id, email
FROM users
WHERE email = '{email}'
"""

cursor.execute(query)

安全な実装

query = """
SELECT id, email
FROM users
WHERE email = %s
"""

cursor.execute(query, [email])

SQL構造とパラメータ値を別々に渡します。

Prepared Statementの安全性を「必ずDBサーバーが先にSQLをコンパイルするから」とだけ説明するのは正確ではありません。ドライバによってはクライアント側で処理する場合もあります。

重要なのは、SQL構造と値をデータベースドライバのパラメータ機構で分離することです。OWASPはパラメータ化クエリを第一の防御策として推奨しています。


2.識別子はAllowlistへ変換する

危険な実装

sort = request.GET.get("sort", "created_at")
users = User.objects.raw(
    f"SELECT * FROM users ORDER BY {sort}"
)

安全な実装

from django.core.exceptions import SuspiciousOperation

SORT_MAP = {
    "newest": "-created_at",
    "oldest": "created_at",
    "name": "name",
}

requested_sort = request.GET.get("sort", "newest")
sort_field = SORT_MAP.get(requested_sort)

if sort_field is None:
    raise SuspiciousOperation("許可されていない並び順です")

users = User.objects.order_by(sort_field)

利用者入力をSQLの列名として直接使用しません。

アプリケーション内部で定義した固定値へ変換します。


3.ORMの標準APIを優先する

user = User.objects.filter(email=email).first()

通常のQuerySetはパラメータ化されます。

ただし、次の機能は重点的にレビューします。

  • raw()

  • RawSQL

  • extra()

  • 独自のFunc

  • extra_context

  • カスタムLookup

  • ユーザー入力から生成したorder_by()

  • ユーザー入力を展開する**kwargs

  • 独自SQLを実行するManager


4.DBアカウントを最小権限にする

参照専用機能には、書き込み権限を与えません。

公開検索機能
  └─ SELECT専用ユーザー

一般アプリ機能
  └─ 必要テーブルのみSELECT・INSERT・UPDATE

管理バッチ
  └─ 対象スキーマのみ

DDL・マイグレーション
  └─ 通常アプリとは別アカウント

通常のWebアプリ用アカウントへ、次の権限を安易に与えないようにします。

  • CREATE

  • ALTER

  • DROP

  • SUPERUSER

  • 拡張機能作成

  • ファイル操作

  • OSコマンド実行

  • 他スキーマへの無制限アクセス


5.DBからの外向き通信を遮断する

DBサーバー
  ├─ DNS:承認済みリゾルバのみ
  ├─ HTTP:原則拒否
  ├─ HTTPS:原則拒否
  ├─ SMB:拒否
  ├─ SMTP:拒否
  └─ 管理・バックアップ先:必要な宛先のみ

OOB SQLインジェクションだけでなく、侵害後の外部通信やデータ持ち出しも抑制できます。


6.詳細エラーを外部へ返さない

利用者には一般的なエラーを返します。

{
  "error": "処理に失敗しました"
}

詳細情報は内部ログへ記録します。

request_id
user_id
endpoint
例外クラス
DBエラーコード
スタックトレース

ただし、エラーを非表示にするだけでは根本対策になりません。


7.WAF・IPS・EDRを補助対策として使う

WAF

  • 既知のSQL構文パターン

  • 異常なエンコード

  • パラメータ重複

  • 不自然なJSON

  • 大量の類似リクエスト

IPS・NDR

  • Boolean探索の反復

  • 一定時間の遅延を伴う通信

  • DBサーバー起点のDNS通信

  • 外部SMB接続

  • 通常と異なるDB通信量

EDR

  • DBプロセスがシェルを起動

  • DBサービスアカウントによる異常な子プロセス

  • 不審なファイル作成

  • PowerShellやコマンドシェルの起動

  • DBホストからの外部通信

WAF、IPS、EDRは被害軽減や早期発見には役立ちます。

しかし、Second-order、内部バッチ、Raw SQL、LLM生成SQLを含め、すべてのSQLインジェクションを単独で防ぐことはできません。


LLM・Text-to-SQLの対策

推奨構成

flowchart LR
    A[利用者の自然言語] --> B[LLM]
    B --> C[SQL候補]
    C --> D[SQL Parser・AST]
    D --> E{ポリシー検査}
    E -->|拒否| F[安全なエラー]
    E -->|許可| G[読み取り専用DB]
    G --> H[行数・列の制限]
    H --> I[利用者へ結果返却]

必須制御

  • SELECT以外を拒否

  • 複数ステートメントを拒否

  • 許可テーブルを限定

  • 許可列を限定

  • システムカタログを拒否

  • 必須のテナント条件を付与

  • 最大行数を制限

  • Statement Timeoutを設定

  • 読み取り専用トランザクション

  • 読み取り専用レプリカ

  • Row-Level Security

  • クエリコスト上限

  • SQL監査ログ

  • 高リスク操作は人間が承認

LangChainの公式ドキュメントも、SQLエージェントのサンプルはそのまま本番利用する安全な実装ではなく、狭いDB権限とアプリケーション固有の検証を追加するよう明記しています。


動作確認

1.パラメータがデータとして扱われるか

検証環境で、アポストロフィを含む正常な氏名などを登録します。

O'Reilly

正常

  • SQLエラーにならない

  • 正しい1件のデータとして保存される

  • SQL構造は変化しない

  • ログ上では同じクエリ形状になる

異常

  • SQL構文エラー

  • 検索結果が不自然に増える

  • 別利用者のデータが表示される

  • 500エラーになる


2.Allowlist外の並び順を拒否できるか

sort=unknown

正常

  • HTTP 400

  • 安全な既定値へフォールバック

  • DBへ不正な識別子を送らない

異常

  • 文字列がそのままORDER BYへ入る

  • DBエラーが返る

  • SQL全文が画面へ表示される


3.Second-orderを確認する

  1. テスト用データを登録する

  2. 管理画面を開く

  3. CSV出力を実行する

  4. 夜間バッチを動かす

  5. 集計レポートを生成する

登録時だけでなく、保存値が使われるすべての後続処理を確認します。


4.外向き通信を確認する

DBサーバーから、不要な外部通信が遮断されていることを確認します。

正常

  • DNSは指定リゾルバのみ

  • HTTP・HTTPSは原則拒否

  • SMBは拒否

  • 許可されていない宛先への接続はファイアウォールで遮断

異常

  • DBホストから任意のインターネット宛先へ接続できる

  • 外部DNSサーバーを直接指定できる

  • TCP 445が外部へ到達する


再発防止

コーディング規約

1. SQL文字列と外部入力を連結しない
2. 値は必ずパラメータとして渡す
3. テーブル名・列名は固定マッピングから選ぶ
4. Raw SQLにはセキュリティレビューを必須とする
5. DB内の保存値も信頼済みとみなさない
6. ORMへ任意の辞書をそのまま展開しない
7. LLM生成SQLを直接実行しない
8. アプリ用DBユーザーへDDL権限を与えない
9. DBからの外向き通信を制限する
10. サポート中の最新パッチを適用する

CI・DevSecOps

  • SASTでSQL文字列連結を検出

  • Raw SQL使用箇所を一覧化

  • DASTを検証環境で実行

  • IASTで実行時の入力経路を追跡

  • 依存関係スキャン

  • Djangoなどのセキュリティリリース監視

  • DB監査ログのテスト

  • Second-order用の結合テスト

  • LLM SQLポリシーの自動テスト

設計レビュー

次のデータフローを作成します。

入力元
 ↓
バリデーション
 ↓
保存先
 ↓
再利用する処理
 ↓
SQL生成部分
 ↓
DB権限
 ↓
結果の出力先

入力時点だけでなく、保存後にどこで使われるかを確認することが重要です。


注意点・よくある誤解

シングルクォートを削除すれば安全

安全ではありません。

数値コンテキスト、識別子、ORDER BY、エンコード差、DBMS固有構文などがあります。

入力値をエスケープすれば安全

自前エスケープは、文字コード、接続設定、DBMS、ドライバの違いで破綻する可能性があります。

OWASPは、すべての入力をエスケープする方式を強く非推奨としています。

ORMを使えば安全

通常APIは安全性を高めます。

Raw SQL、動的識別子、高度なクエリ式、フレームワーク本体の脆弱性は別途確認が必要です。

エラーを非表示にすれば安全

Blind SQLインジェクションへ移行する可能性があります。

WAFがあるから安全

WAFは補助対策です。

コード修正、DB権限、ネットワーク制御の代わりにはなりません。

管理画面は社内だけなので安全

社内入力、CSV、ログ、連携データも侵害される可能性があります。

管理画面は高権限DBアカウントを利用している場合があり、被害が大きくなることがあります。

Prompt InjectionはSQL Injectionと同じ

別の脆弱性です。

ただし、LLMがSQL実行ツールを持つ場合、Prompt Injectionが危険なSQL生成と実行へ連鎖する可能性があります。


まとめ

SQLインジェクションの根本原因は、1990年代から変わっていません。

データとSQL命令の境界が崩れる

一方、SQLへ到達する経路は大きく変化しました。

1990年代
フォーム・認証画面

2000年代
UNION・Error・Blind・OOB

2010年代
ORM・API・Second-order

2020年代
GraphQL・クラウド・非同期処理

2025年以降
Prompt-to-SQL・Text-to-SQL・SQLエージェント

対策では、次の順序が重要です。

  1. SQL構造を固定する

  2. 値をパラメータ化する

  3. 識別子をAllowlistへ変換する

  4. ORMのRaw機能を監査する

  5. DBアカウントを最小権限にする

  6. DBからの外向き通信を制限する

  7. 詳細エラーを外部へ返さない

  8. WAF、IPS、EDR、DB監査を組み合わせる

  9. 保存済みデータも信頼しない

  10. LLM生成SQLを直接実行しない

  11. フレームワークを最新パッチへ更新する

SQLインジェクションは「特殊文字を禁止すれば終わる問題」ではありません。

入力からデータベースまでのデータフロー、後続バッチ、DB権限、ネットワーク、ORM、生成AIを含めた多層防御が必要です。


FAQ

Q1.SQLインジェクションは現在も多いですか?

脆弱性の発生頻度はフレームワークや開発手法によって変わりますが、2025年CWE Top 25ではCWE-89が第2位です。現在も重要度の高い弱点です。

Q2.Django ORMだけを使えば防げますか?

通常のQuerySetはパラメータ化されます。

ただし、Raw SQL、動的列名、独自SQL関数、辞書展開、サポート切れバージョンなどは別途確認が必要です。

Q3.Prepared Statementで100%安全ですか?

値をSQL構造から分離する主対策として非常に強力です。

ただし、テーブル名や列名などの動的識別子、過剰権限、フレームワーク脆弱性、LLMの認可不足までは解決しません。

Q4.入力チェックだけでは防げませんか?

入力チェックは補助対策です。

正当な文章にはアポストロフィなどの特殊文字が含まれるため、危険文字をすべて禁止する設計は現実的ではありません。

Q5.Second-orderはWAFで防げますか?

困難な場合があります。

入力時と発火時が別処理であり、発火が内部バッチの場合はWAFを通らないためです。

Q6.OOB対策で最も重要なものは何ですか?

DBサーバーからの外向き通信制御です。

DNS、HTTP、HTTPS、SMBを必要な宛先だけに限定します。

Q7.LLMへ読み取り専用DBを与えれば十分ですか?

被害は減らせますが、十分ではありません。

機密情報の過剰取得、テナント越境、スキーマ漏えい、高負荷クエリは依然として起こり得ます。

Q8.WAFとIPSは不要ですか?

不要ではありません。

既知攻撃の遮断、反復試行の検知、OOB通信の発見に役立ちます。ただし根本対策ではありません。


参考情報


読んだ内容を10問練習と実技で確認

記事で理解した用語を、StudyQuestの演習とクラウド実技ラボで定着させます。

10問練習 実技ラボ

コメント(0件)

まだコメントはありません。最初のコメントを投稿してください!

コメントを投稿