SQL インジェクションの防止
SQL Injection Prevention Cheat Sheet
- 原典
- SQL Injection Prevention Cheat Sheet(OWASP Cheat Sheet Series)
- 原文
- GitHub 上の Markdown
- 底本
bac04fb5(2026-07-29 時点)- ライセンス
- CC BY-SA 4.0(原典と同一。訳文も同ライセンスで再配布できます)
はじめに
このチートシートは、アプリケーションにおける SQL インジェクションの欠陥を防ぐための手引きである。 SQL インジェクションとは何かを定義し、その欠陥がどこで生じるのかを説明し、攻撃に対する四つの防御策を示す。 SQL インジェクション攻撃が多いのは、次の理由による。
- SQL インジェクションの脆弱性は非常にありふれている。
- アプリケーションのデータベースは、機微なデータや重要なデータを保持しているのが通例であり、攻撃者にとって頻繁な標的となる。
SQL インジェクション攻撃とは何か
文字列連結と利用者由来の入力を使って動的にデータベースクエリを組み立てているアプリケーションは、SQL インジェクションの標的になる。 この欠陥を避けるために、開発者は次の二点を実行する必要がある。
- 文字列連結による動的クエリの記述をやめる。
- 悪意ある SQL の入力が、実行されるクエリに混入しないようにする。
SQL インジェクションの脆弱性を防ぐ手法は単純であり、実質的にどのプログラミング言語でも、どの種類のデータベースでも使える。 XML データベースも同種の問題(XPath インジェクションや XQuery インジェクションなど)を抱えうるが、これらの手法はその保護にも使える。
典型的な SQL インジェクション脆弱性の構造
Java によくある SQL インジェクションの欠陥を以下に示す。
検証されていない 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:プリペアドステートメント(パラメータ化クエリ)を使う
- 選択肢 2:適切に構成されたストアドプロシージャを使う
- 選択肢 3:許可リスト方式による入力検証
- 選択肢 4:強く非推奨。利用者由来の入力すべてをエスケープする
防御策 1:プリペアドステートメント(パラメータ化クエリ)
データベースクエリの書き方を教えるときは、変数バインディングを伴うプリペアドステートメント(パラメータ化クエリ)を使うように伝えるべきである。 プリペアドステートメントは書くのが簡単で、動的クエリよりも理解しやすい。 パラメータ化クエリは、SQL コードをすべて先に定義し、各パラメータを後からクエリに渡すことを開発者に強制する。
この書き方でクエリを組み立てていれば、どのような利用者入力が与えられたとしても、データベースは常にコードとデータを区別する。 さらにプリペアドステートメントは、攻撃者が SQL コマンドを挿入したとしても、クエリの意図を変えられないことを保証する。
安全な Java プリペアドステートメントの例
以下の安全な Java の例では、攻撃者が userID として tom' or '1'='1 を入力しても、パラメータ化クエリは tom' or '1'='1 という文字列全体に文字通り一致するユーザー名を探す。
したがって、データベースは悪意ある SQL コードの注入から保護される。
次のコード例は、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( );
安全な 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) {
// error handling
}
ここでは Java と .NET の例を示したが、実質的に他のすべての言語(Cold Fusion や Classic ASP を含む)がパラメータ化クエリのインタフェースを備えている。 Hibernate Query Language(HQL)のような SQL の抽象化層も、同種の注入問題(HQL インジェクションと呼ばれる)を持つ一方で、パラメータ化クエリに対応している。
Hibernate Query Language(HQL)のプリペアドステートメント(名前付きパラメータ)の例
// This is an unsafe HQL statement
Query unsafeHQLQuery = session.createQuery("from Inventory where productID='"+userSuppliedParameter+"'");
// Here is a safe version of the same query using named parameters
Query safeHQLQuery = session.createQuery("from Inventory where productID=:productid");
safeHQLQuery.setParameter("productid", userSuppliedParameter);
安全なプリペアドステートメントのその他の例
Ruby、PHP、Cold Fusion、Perl、Rust などにおけるプリペアドクエリやパラメータ化の例が必要であれば、クエリのパラメータ化、または bobby-tables.com を参照するとよい。
一般に開発者はプリペアドステートメントを好む。 SQL コードがすべてアプリケーション内に留まるため、アプリケーションが特定のデータベースに比較的依存しなくなるからである。
防御策 2:ストアドプロシージャ
ストアドプロシージャは常に SQL インジェクションから安全とはいえないが、開発者は決まったプログラミング構成を用いることができる。 ストアドプロシージャが安全に実装されている限り(ほとんどのストアドプロシージャ言語ではそれが標準である)、この方法はパラメータ化クエリと同じ効果を持つ。
ストアドプロシージャの安全な使い方
ストアドプロシージャが必要な場合、最も安全な使い方は、パラメータが自動的にパラメータ化される形で SQL 文を構築することである。 開発者が標準から大きく外れたことをしない限り、それは自動的に実現される。 プリペアドステートメントとストアドプロシージャの違いは、ストアドプロシージャでは SQL コードがデータベース自体に定義され保存されており、それをアプリケーションから呼び出す点にある。 プリペアドステートメントと安全なストアドプロシージャは SQL インジェクションの防止において同等に有効なので、組織にとって最も理にかなう方法を選べばよい。
ストアドプロシージャがリスクを高める場合
システムが攻撃されたとき、ストアドプロシージャがかえってリスクを高めることがある。
たとえば MS SQL Server には、db_datareader、db_datawriter、db_owner という三つの主要な既定ロールがある。
ストアドプロシージャが使われるようになる前は、DBA は要件に応じて Web サービスのユーザーに db_datareader または db_datawriter の権限を与えていた。
しかしストアドプロシージャには実行権限が必要であり、これは既定では用意されていないロールである。
ユーザー管理が中央集約されているものの、その三つのロールに限られている構成では、ストアドプロシージャを動かすために Web アプリケーションを db_owner として実行せざるを得なくなる。
当然これは、サーバーが侵害されたときに攻撃者がデータベースへの完全な権限を得ることを意味する。
それまでは読み取り権限しか得られなかったかもしれないところが、そうなる。
安全な 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
}
安全な 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
'error handling
End Try
防御策 3:許可リスト方式による入力検証
テーブル名、カラム名、ソート順の指定(ASC や DESC)のように、バインド変数を使えない部分が SQL クエリにある場合は、入力検証またはクエリの再設計が最も適切な防御になる。 テーブル名やカラム名が必要なときは、その値は利用者のパラメータではなくコード側から与えるのが理想である。
安全なテーブル名検証の例
警告:テーブル名やカラム名を利用者のパラメータ値で指定しているのは設計が悪いことの兆候であり、時間が許すなら全面的な書き直しを検討すべきである。 それが不可能な場合、開発者はパラメータ値を正当な(想定された)テーブル名やカラム名へ対応づけ、検証されていない利用者入力がクエリに入り込まないようにする。
以下の例では、tableName はこのクエリにおいて正当な(想定された)テーブル名の値の一つとして識別されているため、SQL クエリに直接連結してよい。
汎用のテーブル検証関数は、テーブル名が想定されていないクエリで使われるとデータの喪失につながりうることに注意する。
String tableName;
switch(PARAM):
case "Value1": tableName = "fooTable";
break;
case "Value2": tableName = "barTable";
break;
...
default : throw new InputValidationException("unexpected value provided"
+ " for table name");
動的 SQL 生成の最も安全な使い方(非推奨)
ストアドプロシージャが「安全に実装されている」とは、安全でない動的 SQL の生成を含まないことを意味する。 開発者がストアドプロシージャの内部で動的 SQL を生成することは通常ない。 可能ではあるが、避けるべきである。
避けられない場合、そのストアドプロシージャは本稿で述べる入力検証または適切なエスケープを用いて、ストアドプロシージャに渡される利用者入力のいずれも、動的に生成されるクエリへ SQL コードを注入するのに使えないようにしなければならない。
監査担当者は SQL Server のストアドプロシージャ内で sp_execute、execute、exec が使われていないかを常に確認するべきである。
他のベンダーの同等の機能についても、同様の監査指針が必要になる。
より安全な動的クエリ生成の例(非推奨)
ソート順のような単純なものであれば、利用者入力をいったん真偽値に変換し、その真偽値でクエリに連結する安全な値を選ぶのが最善である。 これは動的クエリの生成でよく現れる要件である。
たとえば次のようになる。
public String someMethod(boolean sortOrder) {
String SQLquery = "some SQL ... order by Salary " + (sortOrder ? "ASC" : "DESC");
...
利用者入力をクエリに連結する前、あるいはクエリに連結する値の選択に用いる前に、日付、数値、真偽値、列挙型などの文字列以外の型へ変換できるなら、そうすることで安全性が確保される。
入力検証は、本稿で先に述べたバインド変数を使う場合も含め、あらゆる場合において二次的な防御として推奨される。 強固な入力検証の実装手法については入力値の検証でさらに詳しく述べている。
防御策 4:強く非推奨。利用者由来の入力すべてをエスケープする
この方法では、開発者は利用者入力をクエリに入れる前にすべてエスケープする。 実装はデータベース製品に強く依存する。 この手法は他の防御策と比べて壊れやすく、あらゆる状況ですべての SQL インジェクションを防げるとは保証できない。
アプリケーションをゼロから作る場合、あるいはリスク許容度が低い場合は、パラメータ化クエリ、ストアドプロシージャ、またはクエリを組み立ててくれる何らかの ORM(Object Relational Mapper)を用いて構築、あるいは書き直すべきである。
追加の防御策
四つの主要な防御策のいずれかを採用したうえで、多層防御のために以下の追加策もすべて採用することを推奨する。
- 最小権限
- 許可リスト方式による入力検証
最小権限
SQL インジェクション攻撃が成功したときの被害を最小化するには、環境内のすべてのデータベースアカウントに与える権限を最小化する。 どのアクセス権を取り上げる必要があるかを考えるのではなく、アプリケーションのアカウントがどのアクセス権を必要とするのかをゼロから決めていく。
読み取りだけが必要なアカウントには、必要なテーブルへの読み取り権限のみを与える。 アプリケーションのアカウントに DBA や管理者相当の権限を与えてはならない。 そうすれば何でも「動く」ので楽であることは理解できるが、それは極めて危険である。
アプリケーションと OS の権限の最小化
データベースのデータに対する脅威は SQL インジェクションだけではない。 攻撃者は、提示された正当な値のいずれかを、自分には認可されていないがアプリケーション自体はアクセスを認可されている値に書き換えるだけでよい。 したがってアプリケーションに与える権限を最小化しておけば、攻撃者が SQL インジェクションを使わない場合でも、そうした不正なアクセス試行の成功する見込みが下がる。
あわせて、DBMS が動作する OS アカウントの権限も最小化するべきである。 DBMS を root や system で動かしてはならない。 ほとんどの DBMS は、初期状態では非常に強力なシステムアカウントで動作する。 たとえば MySQL は Windows 上では既定で system として動作する。 DBMS の OS アカウントは、権限を制限した適切なものに変更する。
開発時における最小権限の詳細
あるアカウントがテーブルの一部にしかアクセスする必要がないなら、その範囲にアクセスを限定したビューを作り、元のテーブルではなくそのビューへのアクセス権をアカウントに与えることを検討する。 データベースアカウントに作成権限や削除権限を与えるのは、あったとしてもごく稀にとどめる。
すべてストアドプロシージャ経由とし、アプリケーションのアカウントが自前のクエリを直接実行することを許さない方針をとるなら、それらのアカウントには必要なストアドプロシージャの実行のみを許可する。 データベースのテーブルに対する権限を直接与えてはならない。
複数データベースにおける管理権限の最小化
Web アプリケーションの設計者は、データベースへの接続に同一の所有者アカウントや管理者アカウントを使うことを避けるべきである。 Web アプリケーションごとに異なるデータベースユーザーを使う。
一般に、データベースへのアクセスを必要とする Web アプリケーションはそれぞれ、接続に用いる専用のデータベースユーザーアカウントを持つべきである。 そうすればアクセス制御の粒度を細かくでき、権限を可能なかぎり絞れる。 各データベースユーザーは、必要なものだけに対する参照権限と、必要に応じた書き込み権限を持つ。
たとえばログインページは、あるテーブルのユーザー名とパスワードのフィールドに対する読み取り権限を必要とするが、いかなる形の書き込み権限(挿入、更新、削除)も必要としない。 一方でサインアップページは、そのテーブルへの挿入権限を確かに必要とする。 この区別は、これらの Web アプリケーションが異なるデータベースユーザーで接続している場合にのみ強制できる。
SQL ビューによる最小権限の強化
SQL のビューを使えば、テーブルの特定のフィールドやテーブルの結合結果に読み取りを限定することで、アクセスの粒度をさらに細かくできる。 これには副次的な利点もある。
たとえば、(何らかの法的要件などにより)システムがソルト付きハッシュではなく利用者のパスワードそのものを保存することを要求されている場合、設計者はビューによってこの制約を補える。 所有者や管理者を除くすべてのデータベースユーザーからそのテーブルへのアクセス権を剥奪し、パスワードのフィールドそのものではなくそのハッシュを出力するビューを作る。
どの Web アプリケーションのデータベースユーザーもそのテーブル自体にアクセスできないので、データベースの情報を盗むことに成功した SQL インジェクション攻撃であっても、盗めるのはパスワードのハッシュ(鍵付きハッシュにもできる)に限られる。
許可リスト方式による入力検証
入力検証は、他に手段がない場合(バインド変数が使えない場合など)の主要な防御であるだけでなく、SQL クエリに渡される前に不正な入力を検出する二次的な防御にもなる。 詳しくは入力値の検証を参照する。 ただしここでは慎重に進める必要がある。 検証を通ったデータであっても、文字列の組み立てによって SQL クエリに埋め込んで安全とは限らない。
関連資料
SQL インジェクション攻撃のチートシート
以下の記事は、さまざまなプラットフォームにおける各種の SQL インジェクション脆弱性を、どのように悪用するのかを説明している(本稿はそれを避ける助けとして作られた)。
- SQL Injection Cheat Sheet
- SQLi による WAF の回避 SQL Injection Bypassing WAF
SQL インジェクション脆弱性の解説
- SQL Injection 脆弱性に関する OWASP の記事
- Blind SQL Injection 脆弱性に関する OWASP の記事
SQL インジェクション脆弱性を避ける方法
- SQL インジェクション脆弱性を避ける方法に関する OWASP Developers Guide の記事
- プリペアドステートメントとストアドプロシージャの両方について、言語別のパラメータ化クエリの例を多数示した OWASP のチートシート
- Bobby Tables のサイト(XKCD の Web コミックに由来する)には、パラメータ化されたプリペアドステートメントとストアドプロシージャの例が多数の言語で載っている
SQL インジェクション脆弱性のコードレビュー
SQL インジェクション脆弱性のテスト