この文書は2016年以降更新されていません

SQL インジェクション対策に関するチートシート

OWASP 作成
ジャンプ先: 移動検索
Cheatsheets-header.jpg

最終改訂日 (yy/mm/dd):2015/11/05

はじめに

この資料は、アプリケーションに SQL インジェクションの脆弱性が入り込まないようにするための、簡単でわかりやすい実用的なガイダンスを提供することに重点を置いています。残念なことに、SQL インジェクション攻撃は、次に示す 2 つの要因により非常に一般的なものになっています。

  1. SQL インジェクション脆弱性の高い発生率、および
  2. 標的の魅力 (つまり、データベースには、通常、アプリケーションにとって興味深い / 不可欠なデータがすべて格納されているということ)。

これほど多くの SQL インジェクション攻撃が成功しているのは情けないことです。というのも、コーディングで SQL インジェクション脆弱性を回避するのは、非常に簡単だからです。

SQL インジェクションの脆弱性は、ソフトウェア開発者が、ユーザーによる入力を含む動的データベースクエリを作成するときに入り込みます。SQL インジェクションの脆弱性を回避するのは簡単です。開発者は、次のどちらか (または両方) を実行すればよいのです。 a) 動的クエリの記述をやめる、 b) ユーザーによる悪意のある SQL を含む入力が、実行されるクエリ構文に影響しないようにする。

この資料では、これら 2 つの問題を避けることで、SQL インジェクションの脆弱性を防ぐ簡単な技法について説明します。ここで説明する技法は、ほぼすべての種類のプログラミング言語とデータベースに使用できます。別の種類のデータベース (XML データベースなど) にも同様の問題 (XPath インジェクションや XQuery インジェクション) が存在しますが、そのような問題を防ぐ場合にも、ここで説明する技法が使用できます。

一次的な対策:

  • 対策 #1: プリペアドステートメント (パラメーター化されたクエリ) を使用する。
  • 対策 #2: ストアドプロシージャを使用する。
  • 対策 #3: ユーザーによるすべての入力をエスケープ処理する。

追加の対策:

  • 最小権限の原則
  • ホワイトリスト型の入力検証


安全でない例

SQL インジェクションの脆弱性は典型的には、次に示すようなものです。

次の例 (Java) は安全ではなく、攻撃者はデータベースで実行されるクエリにコードを注入できる可能性があります。検証されていない "customerName" パラメーターがそのままクエリに追加されており、攻撃者は任意の SQL コードを注入できるようになります。残念なことに、データベースへのアクセスには、この方法がよく使用されています。

 String query = "SELECT account_balance FROM user_data WHERE user_name = "
   + request.getParameter("customerName");
 
 try {
 	Statement statement = connection.createStatement( … );
 	ResultSet results = statement.executeQuery( query );
 }

一次的な対策

対策 #1: プリペアドステートメント (パラメーター化されたクエリとの併用)

プリペアドステートメントと変数バインド (つまり、パラメーター化されたクエリ) の使用は、すべての開発者が最初に学ぶべきデータベースクエリの記述方法です。これは、記述が簡単で、動的クエリよりも理解しやすいものです。パラメーター化されたクエリを使用する場合、開発者は、最初にすべての SQL コードを定義しておき、その後、クエリ実行時に各パラメーターに値を渡します。このコーディングスタイルを採用すると、ユーザーによる入力の内容にかかわらず、コードとデータをデータベースで区別できるようになります。

攻撃者が SQL コマンドを挿入したとしても、プリペアドステートメントにより、クエリの意図を攻撃者が変更することは不可能です。下記の安全な例では、攻撃者が userID に tom' または '1'='1 を入力しても、パラメーター化されたクエリは脆弱にならず、tom' または '1'='1 の文字列全体に文字どおりに一致するユーザー名が検索されます。

言語固有の推奨事項:

  • Java EE – PreparedStatement() とバインド変数を併用します。
  • .NET – SqlCommand() や OleDbCommand() などのパラメーター化されたクエリとバインド変数を併用します。
  • PHP – PDO と厳密に型指定されたパラメーター化されたクエリ (bindParam() を使用) を併用します。
  • Hibernate – createQuery() とバインド変数 (Hibernate では名前付きパラメーターと呼ぶ) を併用します。
  • SQLite – sqlite3_prepare() を使用して、ステートメントオブジェクトを作成します。

まれな状況で、プリペアドステートメントがパフォーマンスの低下を招くことがあります。この状況に直面した場合は、a) すべてのデータに強力な検証を行うか、b) ユーザーによる入力のすべてをエスケープします (プリペアドステートメントを使用するのではなく、後述するように、データベース固有のエスケープ処理ルーチンを使用します)。

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

次のコード例では、Java のパラメーター化されたクエリの実装である PreparedStatement を使用して、同じデータベースクエリを実行します。

 String custname = request.getParameter("customerName"); // これも実際に検証される必要がある
 // 入力検証を実行して攻撃を検出する
 String query = "SELECT account_balance FROM user_data WHERE user_name = ? ";
 
 PreparedStatement pstmt = connection.prepareStatement( query );
 pstmt.setString( 1, custname); 
 ResultSet results = pstmt.executeQuery( );
安全な C# .NET プリペアドステートメントの例

.NET の場合は、もっと簡単になります。クエリの作成と実行の部分は変更しません。ただ単に、ここに示すように Parameters.Add() を使用して、クエリにパラメーターを渡すだけでよいのです。

 String query = 
 	 "SELECT account_balance FROM user_data WHERE user_name = ?";
 try {
 	OleDbCommand command = new OleDbCommand(query, connection);
 	command.Parameters.Add(new OleDbParameter("customerName", CustomerName Name.Text));
 	OleDbDataReader reader = command.ExecuteReader();
 	// …
 } catch (OleDbException se) {
 	// エラー処理
 } 

Java と .NET の例を示しましたが、Cold Fusion や Classic ASP なども含めて、ほぼすべての言語でパラメーター化されたクエリのインターフェイスがサポートされています。Hibernate Query Language (HQL) のような SQL 抽象化レイヤーにも、同様の種類のインジェクション問題があります (この問題を HQL インジェクションと呼びます)。パラメーター化されたクエリは HQL でもサポートされているため、この問題を回避できます。

Hibernate Query Language (HQL) プリペアドステートメント (名前付きパラメーター) の例
 最初に安全でない HQL ステートメントを示します
 
 Query unsafeHQLQuery = session.createQuery("from Inventory where productID='"+userSuppliedParameter+"'");
 
 同じクエリに名前付きパラメーターを使用した安全なバージョンを次に示します
 
 Query safeHQLQuery = session.createQuery("from Inventory where productID=:productid");
 safeHQLQuery.setParameter("productid", userSuppliedParameter);

その他の言語 (Ruby、PHP、Cold Fusion、Perl など) のパラメーター化されたクエリの例については、「クエリのパラメーター化に関するチートシート」または http://bobby-tables.com/ を参照してください。

開発者は、プリペアドステートメントのアプローチを好む傾向があります。これは、すべての SQL コードがアプリケーション内に収まるためです。これにより、アプリケーションは相対的にデータベースに依存しなくなります。

対策 #2: ストアドプロシージャ

ストアドプロシージャは、安全な実装* により、プリペアドステートメントを使用した場合と同じ効果が得られます (ほとんどのストアドプロシージャ言語では、標準で安全な実装になります)。そのために開発者に要求されることは、パラメーターを受け取る SQL ステートメントを作成することだけです。この SQL ステートメントは、開発者が基準から大きく外れたことを行わない限り自動的にパラメーター化されます。プリペアドステートメントとストアドプロシージャでは、ストアドプロシージャの SQL コードがデータベースで定義 / 保存され、アプリケーションから呼び出されるという点が異なります。どちらの技法も SQL インジェクションの対策においては同じ効果があるため、組織にとって最適になるほうのアプローチを選択する必要があります。

*注: ”安全な実装“ とは、ストアドプロシージャに安全でない動的 SQL の生成が含まれていないという意味です。開発者は通常、ストアドプロシージャの内部で動的 SQL を生成することはありません。生成は可能ですが、避ける必要があります。それでも避けられない場合は、ユーザーがストアドプロシージャに入力しても、動的に生成されるクエリに SQL コードが注入されないように、入力検証を使用するか、適切なエスケープ処理を行う必要があります。これについては後述します。監査担当者は、SQL Server のストアドプロシージャ内で使用される sp_execute、execute または exec を逃さず見つけ出す必要があります。その他のベンダーの同様の機能にも、同様の監査のガイドラインが必要になります。

また、ストアドプロシージャがリスクの増加につながる状況もあります。たとえば、MS SQL Server には、3 つの主な既定の役割 db_datareader、db_datawriter および db_owner があります。ストアドプロシージャの使用開始前に、DBA は Web サービスのユーザーに db_datareader 権限または db_datawriter 権限を要件に応じて付与します。ただし、ストアドプロシージャには実行権限が必要になります。これは、既定では利用できない役割です。ユーザー管理が一元化され、これら 3 つの役割に限定されている一部のセットアップでは、ストアドプロシージャを動作させるために、すべての Web アプリケーションを db_owner 権限で実行するようになります。サーバーが侵害されると、それまで読み取りアクセスのみが許可されていた攻撃者は、当然ながらデータベースに対する権限をすべて手に入れることになります。このトピックについての詳細は、次を参照してください。http://www.sqldbatips.com/showarticle.asp?ID=8

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

次のコード例では、Java のストアドプロシージャインターフェイスの実装である CallableStatement を使用して、同じデータベースクエリを実行します。"sp_getAccountBalance" ストアドプロシージャは、データベース内で事前定義されている必要があり、上記で定義したクエリと同じ機能を実装している必要があります。

 String custname = request.getParameter("customerName"); // これは実際に検証される必要がある
 try {
 	CallableStatement cs = connection.prepareCall("{call sp_getAccountBalance(?)}");
 	cs.setString(1, custname);
 	ResultSet results = cs.executeQuery();		
 	// … 結果セットの処理
 } catch (SQLException se) {			
 	// … ロギングおよびエラー処理
 }
安全な VB .NET ストアドプロシージャの例

次のコード例では、.NET のストアドプロシージャインターフェイスの実装である SqlCommand を使用して、同じデータベースクエリを実行します。"sp_getAccountBalance" ストアドプロシージャは、データベース内で事前定義されている必要があり、上記で定義したクエリと同じ機能を実装している必要があります。

 Try
 	Dim command As SqlCommand = new SqlCommand("sp_getAccountBalance", connection)
 	command.CommandType = CommandType.StoredProcedure
 	command.Parameters.Add(new SqlParameter("@CustomerName", CustomerName.Text))
 	Dim reader As SqlDataReader = command.ExecuteReader()
 	‘ …
 Catch se As SqlException 
 	‘ エラー処理
 End Try

対策 #3: ユーザーによるすべての入力をエスケープ処理する

この 3 番目の技法では、ユーザー入力をエスケープしてからクエリに取り入れます。ただし、この方法はパラメーター化されたクエリと比べて脆弱であり、すべての状況であらゆる SQL インジェクションを防止できるという保証はできません。この技法は、レガシーコードをコストをかけずに改造する場合にのみ慎重に使用するようにしてください。ゼロから作成するアプリケーションや、低いリスク許容度を要求するアプリケーションは、パラメーター化されたクエリを使用して作成するか、書き直す必要があります。

この技法は、次のように動作します。どの DBMS でも、ある種のクエリに固有の文字エスケープ処理方法を 1 つ以上サポートしています。使用中のデータベースに適したエスケープ処理方法でユーザーによる入力をすべてエスケープすると、DBMS は、ユーザーによる入力と開発者が記述した SQL コードを混同しなくなり、あらゆる SQL インジェクション脆弱性の可能性を回避することになります。

データベースのエンコーダー専用の Javadoc を見つけるには、左側の 'Codec' クラスをクリックします。多数の’Codec’ クラスの実装があります。データベース固有の 2 つの ‘Codec’ クラス は、OracleCodec と MySQLCodec です。

[Interface Codec] ページの上部にある [All Known Implementing Classes:] で、それらの名前をクリックします。

現時点では、ESAPI には次のデータベースエンコーダーがあります。

  • Oracle
  • MySQL (ANSI モードとネイティブモードの両方がサポートされています)

次のデータベースエンコーダー

  • SQL Server
  • PostgreSQL

は、近日中に入手できます。目的のデータベースエンコーダーが見つからない場合は、お知らせください。

データベース固有のエスケープ処理の詳細

独自のエスケープ処理ルーチンを作成しようとしている場合は、ESAPI エンコーダーが開発されている、次の各データベースのエスケープ処理の詳細を参考にしてください。

Oracle のエスケープ処理

この情報は、次の場所にある Oracle のエスケープ文字情報に基づいています。http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F

動的クエリのエスケープ処理

ESAPI データベースコーデックは、とても簡単に使用できます。Oracle の用例は、次のようになります。

 ESAPI.encoder().encodeForSQL( new OracleCodec(), queryparam );

次に示すように、Oracle に向けられてコード内で生成される既存の動的クエリがあったとします。

 String query = "SELECT user_id FROM user_data WHERE user_name = '" + req.getParameter("userID") 
 + "' and user_password = '" + req.getParameter("pwd") +"'";
 try {
     Statement statement = connection.createStatement( … );
     ResultSet results = statement.executeQuery( query );
 }

次に示すように、最初の行を書き直します。

Codec ORACLE_CODEC = new OracleCodec();
 String query = "SELECT user_id FROM user_data WHERE user_name = '" + 
   ESAPI.encoder().encodeForSQL( ORACLE_CODEC, req.getParameter("userID")) + "' and user_password = '"
   + ESAPI.encoder().encodeForSQL( ORACLE_CODEC, req.getParameter("pwd")) +"'";

これで、このコードは何が入力されようと、SQL インジェクションに対して安全になります。

最大限コードを読みやすくするために、独自の OracleEncoder を作成することもできます。

 Encoder oe = new OracleEncoder();
 String query = "SELECT user_id FROM user_data WHERE user_name = '" 
   + oe.encode( req.getParameter("userID")) + "' and user_password = '" 
   + oe.encode( req.getParameter("pwd")) +"'";

この種類のソリューションで開発者に必要なことは、ESAPI.encoder().encodeForOracle( ) (または任意の名前) に渡されるユーザー指定の各パラメーターをラップすることだけです。

文字の置換をオフにする

SET DEFINE OFF または SET SCAN OFF を使用して、自動的な文字置換をオフにします。この文字置換がオンになっていると、& 文字は SQLPlus 変数のプレフィックスのように扱われることになり、攻撃者による機密データの取得に利用される可能性があります。

詳細については、http://download.oracle.com/docs/cd/B19306_01/server.102/b14357/ch12040.htm#i2698854 および http://stackoverflow.com/questions/152837/how-to-insert-a-string-which-contains-an を参照してください。

LIKE 句のワイルドカード文字のエスケープ処理

LIKE キーワードにより、テキストの検索が可能になります。Oracle では、アンダースコア '_' 文字は 1 文字のみを検索し、パーセント '%' 文字はゼロ個以上の任意の文字を検索します。これらの文字は、LIKE 句の条件内ではエスケープする必要があります。次に例を示します。

SELECT name FROM emp 
WHERE id LIKE '%/_%' ESCAPE '/';
SELECT name FROM emp 
WHERE id LIKE '%\%%' ESCAPE '\';
Oracle 10g のエスケープ処理

Oracle 10g 以降では、文字列を囲むように { と } 配置して、その文字列全体をエスケープする方法も使用できます。ただし、文字列の中にすでに } 文字が含まれていないことに注意する必要があります。このような文字を検索して、それが見つかった場合には、}} に置き換える必要があります。さもないと、その文字の位置で早まってエスケープ処理が終了してしまい、脆弱性を招き入れる可能性があります。

MySQL のエスケープ処理

MySQL は、2 つのエスケープ処理モードをサポートしています。

  1. ANSI_QUOTES SQL モードと、これをオフにした
  2. MySQL モードというモードです。

ANSI SQL モード: 単にすべての ' (1 つのシングルクォート) 文字を '' (2 つのシングルクォート) にエンコードします。

MySQL モードは、以下のエンコードが行われます。

 NUL (0x00) --> \0  [これはゼロです。英文字の O ではありません]
 BS  (0x08) --> \b
 TAB (0x09) --> \t
 LF  (0x0a) --> \n
 CR  (0x0d) --> \r
 SUB (0x1a) --> \Z
 "   (0x22) --> \"
 %   (0x25) --> \%
 '   (0x27) --> \'
 \   (0x5c) --> \\
 _   (0x5f) --> \_ 
 ASCII コードが 256 未満のその他の英数文字以外の文字  --> \c
 この 'c' は元の英数文字以外の文字です。

この情報は、次の場所にある MySQL のエスケープ文字情報に基づいています。http://mirror.yandex.ru/mirrors/ftp.mysql.com/doc/refman/5.0/en/string-syntax.html

SQL Server のエスケープ処理

SQL Server のエスケープ処理ルーチンはまだ実装していませんが、次の資料には SQL Server で SQL インジェクション攻撃を防止する方法について説明した記事への適切なリンクが含まれています。


DB2 のエスケープ処理

この情報は、次の場所にある DB2 WebQuery 特殊文字の情報に基づいています。https://www-304.ibm.com/support/docview.wss?uid=nas14488c61e3223e8a78625744f00782983、同様に一部の情報は次の場所にある Oracle の JDBC DB2 ドライバーの情報に基づいています。 http://docs.oracle.com/cd/E12840_01/wls/docs103/jdbc_drivers/sqlescape.html

いくつかの DB2 Universal ドライバー間の相違点に関する情報は、次の場所にあります。http://publib.boulder.ibm.com/infocenter/db2luw/v8/index.jsp?topic=/com.ibm.db2.udb.doc/ad/rjvjcsqc.htm

すべての入力の 16 進数エンコード処理

多少特殊なケースのエスケープ処理として、ユーザーから受け取った文字列をすべて 16 進数エンコードする処理があります (これは、文字ごとのエスケープ処理と見なせます)。Web アプリケーションでは、ユーザーの入力を 16 進数エンコードしてから、SQL ステートメントに含めるようにする必要があります。SQL ステートメントは、この事実を考慮に入れて、それに応じたデータの比較を行う必要があります。たとえば、セッション ID と一致するレコードを検索する必要があり、ユーザーがセッション ID として文字列 abc123 を送信した場合、SELECT ステートメントは次のようになります。

   SELECT ... FROM session
   WHERE hex_encode (sessionID) = '606162313233'

(hex_encode は、使用しているデータベースの特定の機能に置き換える必要があります)。文字列 606162313233 は、ユーザーから受け取った文字列の 16 進数エンコード化バージョンです (つまり、ユーザーデータの ASCII / UTF-8 コードを 16 進数値にして並べたものです)。

攻撃者がシングルクォートと注入しようとしている SQL コードを含む文字列を送信した場合でも、作成された SQL ステートメントは次のようになるだけです。

   WHERE hex_encode ( ...) = '2720 ...'

27 はシングルクォートの ASCII コード (16 進数表記) であり、文字列内の他の文字と同様に単に 16 進数エンコードされたものです。結果の SQL には、数字と a から f までの文字のみが含まれ、SQL インジェクションを可能にするような特殊文字は一切含まれなくなります。

追加の対策

3 つある一次的な対策のうち 1 つを採用することに加え、ここに示す追加の対策すべてを採用して、多層防御を実現するようにお勧めします。追加の対策は、次のとおりです。

  • 最小権限の原則
  • ホワイトリスト型の入力検証

最小権限の原則

SQL インジェクション攻撃が成功したときの潜在的な被害を最小化するには、各データベースアカウントに割り当てる権限を最小限にする必要があります。DBA や管理者タイプのアクセス権は、アプリケーションのアカウントに割り当ててはいけません。そうすれば何でもすぐに "動作する" ので便利なことは確かですが、とても危険です。アプリケーションアカウントにとって不要なアクセス権を削除しようとするのではなく、そもそも必要なのはどのアクセス権かを一から考えて決定していきます。読み取りアクセスのみが必要なアカウントには、アクセスする必要のあるテーブルへの読み取りアクセスのみを付与していることを確認します。テーブルの一部のみへのアクセスを必要とする場合は、そのデータ部分にアクセスを限定するビューを作成して、アカウントのアクセスを元のテーブルではなくビューに割り当てることを検討します。特別な場合を除いて、データベースアカウントに作成や削除のアクセス権は付与しません。

アプリケーションアカウントに独自クエリの直接実行を許可せず、すべてにおいてストアドプロシージャを使用するポリシーを採用している場合は、アカウントのアクセス権を制限して必要なストアドプロシージャのみを実行できるようにします。データベースのテーブルへの直接的なアクセス権を付与してはいけません。

SQL インジェクションは、データベースのデータに限られた脅威ではありません。攻撃者は、パラメーターに指定されている正当な値を不正な値に変更するだけで、アプリケーション自体がこの不正な値にアクセスするのを待てばよいのです。したがって、アプリケーションに付与する権限を最小限に抑えることで、不正アクセスが試行される可能性が減ります。これは、攻撃者が攻略手段の一環として SQL インジェクションを使用しようとしていない場合でも同じです。

それと同時に、DBMS を実行するオペレーティングシステムアカウントの権限も最小化する必要があります。root または system として DBMS を実行してはいけません。ほとんどの DBMS は、初期状態では非常に強力な system アカウントで実行します。たとえば、MySQL は既定で Windows の system として実行します。DBMS の OS アカウントは、より適切に権限が制限されたものに変更してください。

複数の DB ユーザー

Web アプリケーションの設計者は、Web アプリケーションでデータベース接続する際、同一の所有者 / 管理者アカウントの使用を避けるだけでは不十分です。異なる Web アプリケーションには、異なる DB ユーザーを使用できます。一般に、データベースへのアクセスを必要とする個別の Web アプリケーションには、それぞれに指定されたデータベースユーザーアカウントがあり、Web アプリケーションは、このアカウントを使用して DB に接続します。そうすることでアプリケーション設計者は、アクセス制御の適切な詳細度を設定し、権限を可能な限り小さくできます。各 DB ユーザーは、必要とするアクセスのみを選択して、必要に応じて書き込みアクセスを選択します。

たとえば、ログインページでは、テーブルに含まれるユーザー名フィールドとパスワードフィールドへの読み取りアクセスが必要になりますが、書き込みアクセス (挿入、更新、または削除) はどれも必要ありません。その一方、サインアップページでは、そのテーブルへの挿入権限が確実に必要になります。このアクセス制限は、Web アプリケーションが異なる DB ユーザーを使用してデータベースに接続している場合にのみ実施できます。

ビュー

SQL のビューは、特定テーブルのフィールドまたはテーブルの結合に読み取りアクセスを制限することで、アクセスの詳細度をさらに緻密にできます。これには、追加のメリットもあります。たとえば、salted ハッシュ化したパスワードの代わりに、ユーザーのパスワードを保存するように要求されているシステムがあるとします (特殊な法的要件などにより)。設計者は、ビューを使用することで、この制限を補強できます。テーブルへのアクセス (所有者 / 管理者を除く、すべての DB ユーザーからのアクセス) をすべて取り消して、パスワードフィールドのハッシュを出力するビューを作成して、フィールド自体は出力しないようにします。SQL インジェクション攻撃が DB 情報の盗み出しに成功したとしても、パスワードのハッシュ (鍵付きのハッシュにすることもできます) を盗み出すにとどまります。なぜなら、テーブル自体にアクセスできる Web アプリケーションの DB ユーザーは存在しないからです。

ホワイトリスト型の入力検証

入力検証を使用すると、不正な入力は SQL クエリに渡される前に検出できます。詳細については、「入力認証に関するチートシート」を参照してください。次の点に注意が必要です。検証済みのデータであっても、文字列作成によって SQL クエリに挿入すると、安全でなくなることがあります。

関連資料

SQL インジェクション攻撃に関するチートシート

次に示す資料では、各種プラットフォーム上のさまざまな SQL インジェクション脆弱性 (この資料で回避方法を説明したもの) を攻略する方法について説明しています。


SQL インジェクション脆弱性の説明

SQL インジェクション脆弱性を回避する方法

SQL インジェクション脆弱性についてコードをレビューする方法

SQL インジェクション脆弱性についてテストする方法



Authors and Primary Editors

Dave Wichers - dave.wichers[at]owasp.org
Jim Manico - jim[at]owasp.org
Matt Seil - mseil[at]acm.org


Other Cheatsheets

Developer Cheat Sheets (Builder)

Assessment Cheat Sheets (Breaker)

Mobile Cheat Sheets

OpSec Cheat Sheets (Defender)

Draft Cheat Sheets