インジェクションの防止

Injection Prevention Cheat Sheet

原典
Injection Prevention Cheat Sheet(OWASP Cheat Sheet Series)
原文
GitHub 上の Markdown
底本
bac04fb5(2026-07-29 時点)
ライセンス
CC BY-SA 4.0(原典と同一。訳文も同ライセンスで再配布できます)

はじめに

この記事は、アプリケーションにおけるインジェクションの欠陥という分類の全体を防ぐための、明確で単純な、実行可能な指針を示すことに主眼を置いている。 インジェクション攻撃、とくに SQL インジェクションは、残念ながら非常に多い。

アプリケーションに手を入れられるかどうかは、インジェクションの欠陥の保護と防止において非常に重要な要素である。 企業内のすべてのアプリケーションのうち自社で開発されたものは少数派であり、多くは外部の供給元から来ている。 オープンソースのアプリケーションは少なくとも問題を修正する機会を与えるが、クローズドソースのアプリケーションではインジェクションの欠陥に別の方法で対処する必要がある。

インジェクションの欠陥は、アプリケーションが信頼できないデータをインタプリタへ送るときに生じる。 インジェクションの欠陥は非常に広く存在し、とくにレガシーなコードにおいて、SQL のクエリ、LDAP のクエリ、XPath のクエリ、OS のコマンド、プログラムの引数などにしばしば見つかる。 インジェクションの欠陥はコードを調べれば見つけやすいが、テストによって見つけるのはより難しい。 スキャナやファザーは攻撃者がそれを見つける助けになる。

手を入れられるかどうかに応じて、修正のために採る行動は変わる。 ソースコード自体で問題を修正すること、あるいはアプリケーションの一部を再設計することが常に最善である。 しかしソースコードが手に入らない場合、あるいはレガシーなソフトウェアの修正が単に経済的に見合わない場合は、仮想パッチのみが意味を持つ。

アプリケーションの種類

企業内では通常、三つの分類のアプリケーションが見られる。 インジェクションの欠陥を防止または修正するために必要な行動を見極めるには、この三分類が必要になる。

A1。新しいアプリケーション

設計の段階、または開発の初期段階にある新しい Web アプリケーション。

A2。稼働中のオープンソースのアプリケーション

すでに稼働しており、容易に手を入れられるアプリケーション。 Model-View-Controller(MVC)型のアプリケーションは、アーキテクチャに手を入れやすいものの一例である。

A3。稼働中のクローズドソースのアプリケーション

変更できない、あるいは困難を伴ってしか変更できない、稼働中のアプリケーション。

インジェクションの形態

インジェクションには、SQL のクエリ、LDAP のクエリ、XPath のクエリ、OS のコマンドなど、異なる技術を標的とする複数の形態がある。

クエリ言語

最も有名なインジェクションの形態は SQL インジェクションであり、攻撃者は既存のデータベースクエリを書き換えられる。 さらに詳しくは SQL インジェクションの防止を参照する。

しかし LDAP、SOAP、XPath、REST に基づくクエリもインジェクション攻撃を受けやすく、データの取得や対策の回避を許しうる。

SQL インジェクション

SQL インジェクション攻撃は、データ入力を通じて、あるいはクライアント(ブラウザ)から Web アプリケーションへ送られる形で、部分的または完全な SQL クエリを挿入(注入)するものである。

SQL インジェクション攻撃が成功すると、データベースから機微なデータを読み取り、データベースのデータを変更(挿入、更新、削除)し、データベースの管理操作(DBMS の停止など)を実行し、DBMS のファイルシステム上のファイルの内容を取得したりファイルシステムへ書き込んだりでき、場合によっては OS へコマンドを発行できる。 SQL インジェクション攻撃はインジェクション攻撃の一種であり、あらかじめ定められた SQL コマンドの実行に影響を与えるために、データ面の入力へ SQL のコマンドを注入する。

SQL インジェクション攻撃は次の三つの分類に分けられる。

  • インバンド:SQL コードを注入するのと同じ経路でデータを抽出する。これは最も直接的な攻撃であり、取得したデータがアプリケーションの Web ページに直接表示される。
  • アウトオブバンド:別の経路でデータを取得する(クエリの結果を含むメールが生成され、試験者へ送られるなど)。
  • 推論型(ブラインド):実際のデータの転送は起こらないが、特定のリクエストを送ってデータベースサーバーの振る舞いを観測することで、試験者は情報を再構成できる。
この問題の検査方法
コードレビューの際

データベースへのクエリがプリペアドステートメント経由で行われていない箇所を確認する。

動的な文が作られている場合は、文の一部として使われる前にデータがサニタイズされているかを確認する。

監査担当者は SQL Server のストアドプロシージャ内で sp_executeexecuteexec が使われていないかを常に確認するべきである。 他のベンダーの同等の機能についても、同様の監査指針が必要になる。

自動化された悪用

以下に挙げる状況と手法のほとんどは、いくつかの道具を使って自動的に実行できる。 SQLMap を用いた自動監査の記事に、その実施方法の情報がある。

同様に、静的コード解析のデータフローの規則によって、サニタイズされていない利用者由来の入力が SQL クエリを変えられるかを検出できる。

ストアドプロシージャへのインジェクション

ストアドプロシージャ内で動的 SQL を使う場合、コード注入のリスクを消すために、アプリケーションは利用者の入力を適切にサニタイズしなければならない。 サニタイズしなければ、利用者は悪意ある SQL を入力し、それがストアドプロシージャ内で実行されうる。

時間遅延を用いた悪用の手法

時間遅延を用いた悪用の手法は、操作の結果について何もわからないブラインド SQL インジェクションの状況で非常に有用である。 この手法は注入したクエリを送り、条件が真である場合にサーバーの応答にかかる時間を観測するものである。 遅延があれば、試験者は条件付きクエリの結果が真であると推定できる。 この悪用の手法は DBMS ごとに異なりうる(DBMS 固有の節を確認する)。

http://www.example.com/product.php?id=10 AND IF(version() like '5%', sleep(10), 'false'))--

この例で試験者は、MySQL のバージョンが 5.x かどうかを確認しており、サーバーの応答を 10 秒遅らせている。 試験者は遅延時間を増やして応答を観測できる。 応答を待つ必要もない。 非常に大きな値(100 など)を設定し、数秒後にリクエストを打ち切ることもある。

アウトオブバンドを用いた悪用の手法

この手法も、操作の結果について何もわからないブラインド SQL インジェクションの状況で非常に有用である。 この手法は、DBMS の機能を使って外部への接続を行い、注入したクエリの結果を試験者のサーバーへのリクエストの一部として届けるものである。 エラーに基づく手法と同じく、DBMS ごとに固有の機能がある。 DBMS 固有の節を確認する。

対処
防御策 1。プリペアドステートメント(パラメータ化クエリ)

プリペアドステートメントは、攻撃者が SQL コマンドを挿入したとしても、クエリの意図を変えられないことを保証する。 以下の安全な例では、攻撃者が userID として tom' or '1'='1 を入力しても、パラメータ化クエリは脆弱にならず、tom' or '1'='1 という文字列全体に文字通り一致するユーザー名を探す。

防御策 2。ストアドプロシージャ

プリペアドステートメントとストアドプロシージャの違いは、ストアドプロシージャでは SQL コードがデータベース自体に定義され保存されており、それをアプリケーションから呼び出す点にある。

この二つの手法は SQL インジェクションの防止において同等に有効なので、組織にとって最も理にかなう方法を選べばよい。 ストアドプロシージャは常に SQL インジェクションから安全とはいえない。 しかし決まったプログラミング構成を安全に実装すれば、パラメータ化クエリと同じ効果を持つ。 ほとんどのストアドプロシージャ言語ではそれが標準である。

注記:「安全に実装する」とは、そのストアドプロシージャが安全でない動的 SQL の生成を含まないことを意味する。

防御策 3。許可リスト方式による入力検証

SQL クエリの各部分のうち、テーブル名やカラム名、ソート順の指定(ASC や DESC)のように、バインド変数を使えない箇所がある。 そうした状況では、入力検証またはクエリの再設計が最も適切な防御になる。 テーブル名やカラム名については、その値は利用者のパラメータではなくコード側から与えるのが理想である。

しかしテーブル名やカラム名を切り替えるために利用者のパラメータ値を使う場合は、そのパラメータ値を正当な(想定された)テーブル名やカラム名へ対応づけ、検証されていない利用者入力がクエリに入り込まないようにするべきである。 これは設計が悪いことの兆候であり、時間が許すなら全面的な書き直しを検討するべきである。

防御策 4。利用者由来の入力すべてをエスケープする

この手法は、上記のいずれも実現できない場合の最後の手段としてのみ使うべきである。 この方法は他の防御と比べて脆く、あらゆる状況ですべての SQL インジェクションを防げるとは保証できないので、入力検証のほうがおそらくよい選択である。

この手法は、利用者の入力をクエリに入れる前にエスケープするものである。 通常は、入力検証の実装が費用面で見合わないレガシーなコードに後から手を入れる場合にのみ推奨される。

コード例。Java
安全な Java プリペアドステートメントの例

次のコード例は、Java におけるパラメータ化クエリの実装である PreparedStatement を使って、同じデータベースクエリを実行している。

// This should REALLY be validated too
String custname = request.getParameter("customerName");
// Perform input validation to detect attacks
String query = "SELECT account_balance FROM user_data WHERE user_name = ?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, custname);
ResultSet results = pstmt.executeQuery();

ここでは Java の例を示したが、Cold Fusion や Classic ASP を含む実質的に他のすべての言語がパラメータ化クエリのインタフェースを備えている。

安全な Java ストアドプロシージャの例

次のコード例は、Java におけるストアドプロシージャのインタフェース実装である CallableStatement を使って、同じデータベースクエリを実行している。 sp_getAccountBalance ストアドプロシージャはデータベース側にあらかじめ定義し、上で定義したクエリと同じ機能を実装しておく必要がある。

// This should REALLY be validated
String custname = request.getParameter("customerName");
try {
 CallableStatement cs = connection.prepareCall("{call sp_getAccountBalance(?)}");
 cs.setString(1, custname);
 ResultSet results = cs.executeQuery();
 // Result set handling...
} catch (SQLException se) {
 // Logging and error handling...
}

LDAP インジェクション

LDAP インジェクションは、利用者の入力に基づいて LDAP の文を組み立てる Web アプリケーションを悪用する攻撃である。 アプリケーションが利用者の入力を適切にサニタイズしないと、SQL インジェクションと似た手法で LDAP の文を書き換えられる。 LDAP インジェクション攻撃は、認可されていないクエリへの権限の付与や、LDAP のツリー内のコンテンツの改変を招きうる。 LDAP インジェクション攻撃についてさらに詳しくは LDAP injection を参照する。

LDAP インジェクション攻撃が多いのは、次の二つの要因による。

  1. より安全なパラメータ化された LDAP クエリのインタフェースが存在しないこと
  2. システムへの利用者の認証に LDAP が広く使われていること
この問題の検査方法
コードレビューの際

LDAP へのクエリが特殊文字をエスケープしているかを確認する。こちらを参照する。

自動化された悪用

OWASP ZAP のような道具のスキャナのモジュールには、LDAP インジェクションの問題を検出するものがある。

対処
適切な LDAP のエンコード関数ですべての変数をエスケープする

LDAP が名前を保持する主な方法は DN(distinguished name)に基づく。 これは一意な識別子のようなものと考えられる。 DN はユーザー名のように、リソースへのアクセスに使われることがある。

DN は次のような形をとる。

cn=Richard Feynman, ou=Physics Department, dc=Caltech, dc=edu

あるいは次のようになる。

uid=inewton, ou=Mathematics Department, dc=Cambridge, dc=com

DN において特殊文字とみなされる文字がある。 網羅的な一覧は \ # + < > , ; " = と、先頭または末尾の空白である。

各 DN はちょうど一つのエントリを指す。 エントリは RDBMS の行のようなものと考えられる。 各エントリには一つ以上の属性があり、これは RDBMS のカラムに相当する。 特定の属性を持つ利用者を LDAP で検索したい場合は、検索フィルタを使える。 検索フィルタでは、標準の論理演算を用いて任意の制約に一致する利用者の一覧を得られる。 検索フィルタはポーランド記法(前置記法)で書く。

例を示す。

(&(ou=Physics)(| (manager=cn=Freeman Dyson,ou=Physics,dc=Caltech,dc=edu)
(manager=cn=Albert Einstein,ou=Physics,dc=Princeton,dc=edu) ))

アプリケーションのコードで LDAP のクエリを組み立てるときは、クエリに加える信頼できないデータをすべてエスケープしなければならない。 LDAP のエスケープには二つの形式がある。 LDAP 検索のためのエンコードと、LDAP DN のためのエンコードである。 適切なエスケープは、検索フィルタ向けに入力をサニタイズしているのか、それとも何らかのリソースへアクセスするためのユーザー名相当の資格情報として DN を使っているのかによって決まる。

コード例。Java
LDAP のエスケープを行う安全な Java の例
public String escapeDN (String name) {
 //From RFC 2253 and the / character for JNDI
 final char[] META_CHARS = {'+', '"', '<', '>', ';', '/'};
 String escapedStr = new String(name);
 //Backslash is both a Java and an LDAP escape character,
 //so escape it first
 escapedStr = escapedStr.replaceAll("\\\\\\\\","\\\\\\\\");
 //Positional characters - see RFC 2253
 escapedStr = escapedStr.replaceAll("\^#","\\\\\\\\#");
 escapedStr = escapedStr.replaceAll("\^ | $","\\\\\\\\ ");
 for (int i=0 ; i < META_CHARS.length ; i++) {
        escapedStr = escapedStr.replaceAll("\\\\" +
                     META_CHARS[i],"\\\\\\\\" + META_CHARS[i]);
 }
 return escapedStr;
}

バックスラッシュは Java の文字列リテラルにおけるエスケープ文字であり、正規表現のエスケープ文字でもあることに注意する。

public String escapeSearchFilter (String filter) {
 //From RFC 2254
 String escapedStr = new String(filter);
 escapedStr = escapedStr.replaceAll("\\\\\\\\","\\\\\\\\5c");
 escapedStr = escapedStr.replaceAll("\\\\\*","\\\\\\\\2a");
 escapedStr = escapedStr.replaceAll("\\\\(","\\\\\\\\28");
 escapedStr = escapedStr.replaceAll("\\\\)","\\\\\\\\29");
 escapedStr = escapedStr.replaceAll("\\\\" +
               Character.toString('\\u0000'), "\\\\\\\\00");
 return escapedStr;
}

XPath インジェクション

(原典では未執筆)

スクリプト言語

Web アプリケーションで使われるスクリプト言語はいずれも、実行時にコードを受け取って実行する eval 相当の呼び出しを持つ。 検証もエスケープもされていない利用者の入力を用いてコードが組み立てられると、コードインジェクションが起こりうる。 これにより攻撃者はアプリケーションのロジックを覆し、最終的にローカルのアクセスを得られる。

スクリプト言語が使われる場面では常に、「上位の」スクリプト言語の実際の実装が C のような「下位の」言語で行われている。 そのスクリプト言語のデータ処理のコードに欠陥があると、ヌルバイトインジェクションの攻撃経路が使われてメモリの他の領域へのアクセスが得られ、攻撃が成功する結果になりうる。

OS のコマンド

OS コマンドインジェクションは、Web のインタフェースを通じて Web サーバー上で OS のコマンドを実行する手法である。 利用者は OS のコマンドを実行するために、Web のインタフェースを通じてそのコマンドを与える。

適切にサニタイズされていない Web のインタフェースは、いずれもこの悪用の対象になる。 OS のコマンドを実行できれば、利用者は悪意あるプログラムをアップロードでき、パスワードを入手することさえできる。 OS コマンドインジェクションは、アプリケーションの設計と開発において安全性を重視すれば防げる。

この問題の検査方法

コードレビューの際

コマンドを実行するメソッドが呼ばれているかを確認し、検証されていない利用者の入力がそのコマンドのデータとして使われていないかを確認する。

それとは別に、URL のクエリパラメータの末尾にセミコロンを付け、その後に OS のコマンドを続けると、そのコマンドが実行される。 %3B は URL エンコードされたセミコロンである。 これは ; がコマンドの区切りとして解釈されるためである。

例:http://sensitive/something.php?dir=%3Bcat%20/etc/passwd

アプリケーションが /etc/passwd ファイルの内容を返せば、攻撃が成功したことがわかる。 多くの Web アプリケーションのスキャナはコマンドインジェクションの変種を注入して応答を検査するので、この攻撃の検査に使える。

同様に、静的コード解析の道具は信頼できない利用者の入力が Web アプリケーションへ流れる経路を確認し、そのデータが利用者の入力をコマンドとして実行する危険なメソッドへ入っていないかを確認する。

対処

利用者が与えた値を含むシステムのコマンドの呼び出しが避けられないとみなされる場合、攻撃を防ぐためにソフトウェア内で次の二つの防御の層を使うべきである。

  1. パラメータ化。利用できる場合は、データとコマンドの分離を自動的に強制する構造化された仕組みを使う。この仕組みは、適切な引用符付けやエンコードを与える助けになる。
  2. 入力検証。コマンドの値と、それに対応する引数の両方を検証するべきである。実際のコマンドとその引数では、検証の度合いが異なる。
    • 使われるコマンドについては、許可されたコマンドの一覧に対して検証しなければならない。
    • これらのコマンドに使われる引数については、次の方法で検証するべきである。
      • 肯定形(許可リスト方式)の入力検証。許可される引数を明示的に定義する。
      • 許可リスト方式の正規表現。許可される良い文字の一覧と、文字列の最大長を明示的に定義する。& | ; $ > < \ !` のようなメタ文字と空白が正規表現に含まれない状態にする。たとえば次の正規表現は小文字と数字のみを許可し、メタ文字を含まない。長さも 3 文字から 10 文字に制限されている。

^[a-z0-9]{3,10}$

コード例。Java

誤った使い方
ProcessBuilder b = new ProcessBuilder("C:\DoStuff.exe -arg1 -arg2");

この例ではコマンドと引数が一つの文字列として渡されているので、その式を操作して悪意ある文字列を注入することが容易になる。

正しい使い方

作業ディレクトリを変えてプロセスを開始する例を示す。 コマンドと各引数は別々に渡される。 これにより各項の検証が容易になり、悪意ある文字列を挿入されるリスクが下がる。

ProcessBuilder pb = new ProcessBuilder("TrustedCmd", "TrustedArg1", "TrustedArg2");
Map<String, String> env = pb.environment();
pb.directory(new File("TrustedDir"));
Process p = pb.start();

ネットワークプロトコル

Web アプリケーションはしばしば(SMTP、IMAP、FTP のような)ネットワークのデーモンと通信し、利用者の入力が通信の流れの一部になる。 ここでは、確立されたセッションを濫用するためにコマンドの列を注入できる場合がある。

インジェクション防止の規則

規則 1。適切な入力検証を行う

適切な入力検証を行う。 適切な正規化を伴う肯定形(許可リスト方式)の入力検証も推奨されるが、多くのアプリケーションが入力に特殊文字を必要とするため、それは完全な防御ではない

規則 2。安全な API を使う

望ましい選択は、インタプリタの使用を完全に避けるか、パラメータ化されたインタフェースを提供する安全な API を使うことである。 ストアドプロシージャのように、パラメータ化されていても内部でインジェクションを持ち込みうる API には注意する。

規則 3。利用者のデータを文脈に応じてエスケープする

パラメータ化された API が使えない場合は、そのインタプリタに固有のエスケープ構文を用いて特殊文字を注意深くエスケープするべきである。

インジェクションに関する他のチートシート