How To: ASP.NET で SQL インジェクションから保護する方法
J.D. Meier, Alex Mackman, Blaine Wastell, Prashant Bansode, Andy Wigley
Microsoft Corporation
May 2005
日本語版最終更新日 2006 年 1 月 19 日
適用対象
- ASP.NET version 1.1
- ASP.NET version 2.0
概要
このガイドでは、SQL インジェクションから ASP.NET アプリケーションを保護するいくつかの方法を示します。 その技法には、データ アクセスでの入力の制約と、タイプが異なっても支障のない SQL パラメータの使用、およびデータベースでの制限付きの許可を与えられた最小特権付きアカウントの使用などがあります。 SQL インジェクションが起きる可能性があるのは、アプリケーションが入力を使用してダイナミック SQL ステートメントを構成するときや、またはストアド プロシージャを使用してデータベースに接続するときです。 SSL や IPSec などを使用する従来型のセキュリティ対策では、SQL インジェクション攻撃からアプリケーションを保護することはできません。 SQL インジェクション攻撃が成功してしまうと、不純な意図をもったユーザーがアプリケーションのデータベース内でコマンドを実行できるようになります。目次
このガイドの目的はじめに
ステップの概要
ステップ 1 - 入力の制約
ステップ 2 - ストアド プロシージャでのパラメータの使用
ステップ 3 - ダイナミック SQL でのパラメータの使用
その他の考慮事項
このガイドの目的
- SQL インジェクション攻撃がどのように作動するかを学習する。
- 入力を制約して、SQL インジェクションを防止する。
- タイプが異なっても支障のない SQL コマンド パラメータを使用して、SQL インジェクションを防止する。
- リスクをさらに削減するためのその他の対抗手段を学習する。
はじめに
SQL インジェクション攻撃が成功すると、不純な意図をもったユーザーは、アプリケーション ログインに対して認可された特権を使用して、アプリケーションのデータベースでコマンドを実行できるようになります。 アプリケーションが、特権を超えるアカウントを使用してデータベースに接続している場合は、問題はさらに深刻になります。 たとえば、データベースを除去する特権をアプリケーションのログインがもっている場合、十分な安全防護策がないと、攻撃者は除去操作を実行できるようになります。データ アクセス コードが SQL インジェクション攻撃の被害にあう原因となる一般的な弱点には、次のものがあります。
- 入力検証の不徹底。
- タイプが異なっても支障のないパラメータを使用しない SQL ステートメントのダイナミック構成。
- 特権を超えるデータベース ログインの使用。
SQL インジェクションの例
ユーザーが、nnn-nn-nnnn というフォームの社会保障番号を前提とする SSN テキスト ボックスに次のような文字列を入力したら、どうなるかを考察してみます。' ; DROP DATABASE pubs --アプリケーションは、この入力を使用して、ダイナミック SQL ステートメントまたはストアド プロシージャを実行します。すると、それに似た SQL ステートメントが内部的に実行されます。
// ダイナミック SQL を使用する場合。 SqlDataAdapter myCommand = new SqlDataAdapter( "SELECT au_lname, au_fname FROM authors WHERE au_id = '" + SSN.Text + "'", myConnection); // ストアド プロシージャを使用する場合。 SqlDataAdapter myCommand = new SqlDataAdapter( "LoginStoredProcedure '" + SSN.Text + "'", myConnection);開発者の思惑では、コードは、実行されると、ユーザーの入力を挿入して、次のような SQL ステートメントを生成します。
SELECT au_lname, au_fname FROM authors WHERE au_id = '172-32-9999'ただしこのコードは、ユーザーの有害な入力を挿入し、以下のクエリを生成してしまいます。
SELECT au_lname, au_fname FROM authors WHERE au_id = ''; DROP DATABASE pubs --'この場合、有害入力を開始する ' (単一引用符) 文字によって、SQL ステートメント内の現在のリテラル文字列が終了してしまいます。 これが現在のステートメントをクローズするのは、その後の解析済みのトークンが、現在のステートメントの継続としてはつじつまは合わないけれども、新規のステートメントの開始としてはつじつまが合う場合だけです。 その結果、有害入力の開始の単一の引用符は、次のようなステートメントを生じます。
SELECT au_lname, au_fname FROM authors WHERE au_id = ''; (セミコロン) 文字は、これが現在のステートメントの終了地点であることを SQL に知らせますが、その後に、次のような有害な SQL コードが続きます。
; DROP DATABASE pubs
注意 複数の SQL ステートメントを区切るのに、セミコロンは必ずしも必要ありません。 必要かどうかは、ベンダまたはインプリメンテーションによって異なりますが、Microsoft SQL server では不要です。 たとえば、以下は SQL Server では 2 つの別々のステートメントとして解析されます。
SELECT * FROM MyTable DELETE FROM MyTable
最後に、-- (二重ダッシュ) 文字シーケンスは、以後のテキストを無視するように SQL に指示する SQL コメントです。 この場合、SQL は、閉じる ' (単一引用符) 文字を無視します。これは、他の場合であれば SQL パーサー エラーを生じる文字です。
--'
ガイドライン
SQL インジェクション攻撃に対抗するには、以下を行う必要があります。- 入力データを制約して清浄化します。 タイプ、長さ、フォーマット、および範囲を検証して、既知の正常なデータかどうかをチェックします。
- タイプが異なっても支障のない SQL パラメータをデータ アクセスで使用します。 そのようなパラメータは、ストアド プロシージャまたは動的に作成される SQL コマンド文字列で使用することができます。 SqlParameterCollection などのパラメータ コレクションでは、タイプ チェックと長さの検証を行うことができます。 パラメータ コレクションを使用すると、入力はリテラル値として扱われ、SQL Server で実行可能コードとして扱われなくなります。 パラメータ コレクションを使用する別の利点として、タイプと長さのチェックを実施することができます。 値が範囲外であると、例外が起動されます。 これは、防御の増強を示す好例です。
- データベース内で制限された許可をもつアカウントを使用します。 理想的には、データベース内の選択したストアド プロシージャに対する実行許可だけを認可し、テーブルへの直接アクセス権は与えないでください。
- データベース エラー情報を公開しないようにします。 データベース エラーが起きた場合、詳細なエラー メッセージをユーザーに開示しないように努めます。
注意 SSL (Secure Sockets Layer) や IPSec (IP Security) などを使用する従来型のセキュリティ対策では、 SQL インジェクション攻撃からアプリケーションを保護することはできません。
ステップの概要
アプリケーションを SQL インジェクションから保護するには、次のようなステップを行います。- ステップ 1 - 入力の制約。
- ステップ 2 - ストアド プロシージャでのパラメータの使用。
- ステップ 3 - ダイナミック SQL でのパラメータの使用。
ステップ 1 - 入力の制約
ASP.NET アプリケーションへのすべての入力で、タイプ、長さ、フォーマット、および範囲を検証する必要があります。 データ アクセス クエリ中で使用する入力を制約すれば、SQL インジェクションからアプリケーションを保護することができます。ASP.NET Web ページでの入力の制約
まず、ASP.NET Web ページのサーバー側コード内の入力の制約から開始します。 クライアント側検証は、簡単にう回できるので、頼りきらないでください。 サーバーへのラウンド トリップを削減し、ユーザー エクスペリエンスを改善する場合のみ、クライアント側検証を使用します。サーバー コントロールの使用時には、RegularExpressionValidator や RangeValidator コントロールなどの ASP.NET バリデータ コントロールを使用して、入力を制約します。 通常の HTML 入力コントロールの使用時には、サーバー側コード内で Regex クラスを使用して、入力を制約します。
上記のコード サンプルにおいて、SSN 値が ASP.NET の TextBox コントロールによって捕捉される場合にその入力を制約するには、以下に示されているとおり、RegularExpressionValidator コントロールを使用します。
<%@ language="C#" %>
<form id="form1" runat="server">
<asp:TextBox ID="SSN" runat="server"/>
<asp:RegularExpressionValidator ID="regexpSSN" runat="server"
ErrorMessage="Incorrect SSN Number"
ControlToValidate="SSN"
ValidationExpression="^\d{3}-\d{2}-\d{4}$" />
</form>
HTML コントロール、クエリ文字列パラメータ、または cookie などの別のソースから SSN 入力を取り込む場合にその入力を制約するには、System.Text.RegularExpressions 名前空間から Regex クラスを使用します。
以下の例では、cookie から入力を取得すると想定しています。using System.Text.RegularExpressions;
if (Regex.IsMatch(Request.Cookies["SSN"], "^\d{3}-\d{2}-\d{4}$"))
{
// データベースにアクセスします。
}
else
{
// 有害な入力を処理します。
}
ASP.NET Web ページ内で入力を制約する方法の詳細は、How To: ASP.NET でインジェクション攻撃から保護する方法 を参照してください。データ アクセス コード内の入力の制約
場合によっては、おそらく ASP.NET のページ レベルの検証に加えて、データ アクセス コードでの検証を行えるようにする必要があります。 データ アクセス コードでの検証を行えるようにする必要がある一般的な状況には、以下の 2 通りあります。- 信頼されていないクライアント。 信頼されないソースからのデータの取り込みが許可されている場合や、データがどの程度徹底的に検証および制約されたかが判然としない場合、データ アクセス ルーチンへの入力を制約する検証ロジックを追加します。
- ライブラリ コード。 複数のアプリケーションで使用するように設計されたライブラリとしてデータ アクセス コードがパッケージ化されている場合、データ アクセス コードは独自の検証を行う必要があります。クライアント アプリケーションに関して、安全であるという想定を行えないからです。
以下の例は、SQL ステートメント内でパラメータを使用する前に、正規表現を使用して、データ アクセス ルーチンが入力パラメータをどのように検証できるかを示しています。
using System;
using System.Text.RegularExpressions;
public void CreateNewUserAccount(string name, string password)
{
// 小文字または大文字、アポストロフィ、ピリオド、または空白
// のみが名前で使用されているかどうかをチェックします。また、
// 1 ~ 40 文字の長さであるかどうかもチェックします。
if ( !Regex.IsMatch(userIDTxt.Text, @"^[a-zA-Z'./s]{1,40}$"))
throw new FormatException("Invalid name format");
// パスワードは、少なくとも 1 つの数字、1 つの小文字、
// 1 つの大文字から成り、8 ~ 10 文字の長さであるか
// どうかをチェックします。
if ( !Regex.IsMatch(passwordTxt.Text,
@"^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$" ))
throw new FormatException("Invalid password format");
// データ アクセス ロジックを実行します (タイプが異なっても支障のないパラメータを使用して)
...
}
ステップ 2 - ストアド プロシージャでのパラメータの使用
ストアド プロシージャを使用しても、SQL インジェクションから免れるとはかぎりません。 重要なのは、ストアド プロシージャでパラメータを使用することです。 パラメータを使用しないと、この資料の 『はじめに』 の項で説明したとおり、フィルタなしの入力の使用時には、ストアド プロシージャは SQL インジェクションに対して無防備になる可能性があります。以下のコードは、ストアド プロシージャを呼び出すときの SqlParameterCollection の使用方法を示しています。
using System.Data;
using System.Data.SqlClient;
using (SqlConnection connection = new SqlConnection(connectionString))
{
DataSet userDataset = new DataSet();
SqlDataAdapter myCommand = new SqlDataAdapter(
"LoginStoredProcedure", connection);
myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11);
myCommand.SelectCommand.Parameters["@au_id"].Value = SSN.Text;
myCommand.Fill(userDataset);
}
上記の場合、@au_id パラメータは、実行可能コードとしてではなく、リテラル値として扱われます。
また、このパラメータは、タイプと長さもチェックされます。
上記のコード サンプルでは、入力値は 11 文字を超えることはできません。
パラメータで定義されたタイプまたは長さをデータが順守していないと、SqlParameter クラスから例外がスローされます。パラメータ化したストアド プロシージャをアプリケーションが使用する方法の確認
パラメータを指定してストアド プロシージャを使用しても、SQL インジェクションを防止できるとはかぎらないので、このタイプのストアド プロシージャをアプリケーションで使用する方法を見直す必要があります。 たとえば、次のようなパラメータ化ストアド プロシージャには、セキュリティ上のいくつかの弱点があります。CREATE PROCEDURE dbo.RunQuery @var ntext AS exec sp_executesql @var GO上記のコード サンプルのもののようなストアド プロシージャを使用するアプリケーションには、次のような 2 つの弱点があります。
- ストアド プロシージャは、ステートメントに引き渡されたものをすべて実行します。
@var 変数を以下のものに設定することを検討してください。
DROP TABLE ORDERS;
この場合、ORDERS テーブルが除去されます。
- ストアド プロシージャは、dbo 特権で稼働します。
- ストアド プロシージャ用に選択した名前 (RunQuery) は不適切です。 攻撃者がデータベースを綿密に調べれば、ストアド プロシージャの名前が判明してしまいます。 RunQuery といった名前を付けると、そのストアド プロシージャは、指示されたクエリを実行するに違いないと見当をつけられます。
ステップ 3 - ダイナミック SQL でのパラメータの使用
ストアド プロシージャを使用できない場合でも、ダイナミック SQL ステートメントの作成時に、やはりパラメータを使用する必要があります。 以下のコードは、ダイナミック SQL で SqlParametersCollection を使用する方法を示しています。using System.Data;
using System.Data.SqlClient;
using (SqlConnection connection = new SqlConnection(connectionString))
{
DataSet userDataset = new DataSet();
SqlDataAdapter myDataAdapter = new SqlDataAdapter(
"SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id",
connection);
myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11);
myCommand.SelectCommand.Parameters["@au_id"].Value = SSN.Text;
myDataAdapter.Fill(userDataset);
}
パラメータ バッチの使用
よくある思い違いがあります。すなわち、複数の SQL ステートメントを連結して、ステートメント バッチを 1 回のラウンド トリップでサーバーに送信する場合、パラメータを使用できないという誤解です。 ただし、パラメータ名を反復しないかぎり、この技法を使用することはできます。 これを行うのは簡単です。以下に示されているとおり、SQL テキストの連結時に必ず固有のパラメータ名を使用すればよいのです。using System.Data;
using System.Data.SqlClient;
. . .
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlDataAdapter dataAdapter = new SqlDataAdapter(
"SELECT CustomerID INTO #Temp1 FROM Customers " +
"WHERE CustomerID > @custIDParm; SELECT CompanyName FROM Customers " +
"WHERE Country = @countryParm and CustomerID IN " +
"(SELECT CustomerID FROM #Temp1);",
connection);
SqlParameter custIDParm = dataAdapter.SelectCommand.Parameters.Add(
"@custIDParm", SqlDbType.NChar, 5);
custIDParm.Value = customerID.Text;
SqlParameter countryParm = dataAdapter.SelectCommand.Parameters.Add(
"@countryParm", SqlDbType.NVarChar, 15);
countryParm.Value = country.Text;
connection.Open();
DataSet dataSet = new DataSet();
dataAdapter.Fill(dataSet);
}
. . .
その他の考慮事項
SQL インジェクションを防止するための対抗手段を開発する際に考慮するその他の事項には、次のようなものがあります。- 最小特権をもったデータベース アカウントを使用します。
- エラー情報を公開しないようにします。
最小特権をもったデータベース アカウントの使用
アプリケーションからは、最小特権付きのアカウントを使用してデータベースに接続する必要があります。 Windows 認証を使用して接続する場合、オペレーティング システムの観点から見て最低限の特権が Windows アカウントに付帯していて、Windows リソースへのアクセスのための制限付きの特権と制限付きの権限が与えられなければなりません。 それ以外に、Windows 認証または SQL 認証のどちらを使用する場合でも、それに対応する SQL Server ログインは、データベースでの許可を介して制限を受ける必要があります。Microsoft Windows Server 2003 上で稼動していて、同一ドメイン内の別のサーバー上のデータベースにアクセスする ASP.NET アプリケーションの例を考察してみてください。 既定では、ASP.NET アプリケーションは、ネットワーク サービス アカウントのもとで稼動するアプリケーション プール内で稼動します。 そのアカウントは、最小特権付きのアカウントです。
ネットワーク サービス アカウントを使用した SQL Server へのアクセス
- Web サーバーのネットワーク サービス アカウント用の SQL Server ログインを作成します。 ネットワーク サービス アカウントは、DOMAIN\WEBSERVERNAME$ という ID でデータベース サーバーに提示されるネットワーク資格情報をもっています。 たとえば、ドメインが XYZ という名前で、Web サーバーが 123 という名前であれば、XYZ\123$ 用のデータベース ログインを作成します。
- データベース ユーザーを作成し、そのユーザーをデータベース ロールに追加して、該当するデータベースへの新規のログイン アクセス権を認可します。
- そのデータベース ロールから該当するストアド プロシージャを呼び出したり、データベース内の該当するテーブルにアクセスしたりできるようにするための許可を確立します。
アプリケーションが使用する必要のあるストアド プロシージャへのアクセス権だけを認可し、アプリケーションの最低限の要件をベースにして、それに見合ったテーブルへのアクセス権だけを認可します。
たとえば、データベースの検索だけを実行し、データの更新はまったく行わない ASP.NET アプリケーションに対しては、テーブルへの読み取りアクセス権しか認可する必要はありません。 それによって、攻撃者が SQL インジェクション攻撃に成功しても、その攻撃者が与える可能性のある被害は限られます。
エラー情報の公開の差し控え
構造化例外処理を使用して、エラーをキャッチし、元のクライアントにさかのぼって伝搬されないようにします。 ローカル側では詳細なエラー情報を記録しますが、クライアントに戻すエラー詳細は限定します。ユーザーがデータベースに接続中にエラーが起きた場合、必ず、そのエラーの特性について限られた情報だけをそのユーザーに知らせてください。 データベース アクセスやデータベース エラーの関連情報を開示すると、データベースのセキュリティを損なうために悪用するのにふさわしい情報が、不純な意図をもったユーザーの手中に収まってしまいます。 攻撃者は、詳細なエラー メッセージ内の情報を悪用して、有害コードと一緒に注入するつもりの SQL クエリを分解します。 詳細なエラー メッセージには、接続文字列、SQL Server 名、またはデータベースの命名規則などの、貴重な情報が開示されていることがあります。
ご意見、ご提案
このガイドに関するご意見、ご提案等がございましたら、Wiki またはメールでお寄せください。- Wiki: Security Guidance Feedback ページ: http://channel9.msdn.com/wiki/default.aspx/Channel9.SecurityGuidanceFeedback (英語)
- メール: secguide@microsoft.com (英語) までメールをお寄せください。
特に、以下に関するご意見、ご提案をお待ちしております。
- 推奨事項に関する技術的な問題
- 実用性および利便性に関する問題
テクニカル サポート
本ガイドに記載されているマイクロソフトの製品およびテクノロジに関するテクニカル サポートは、Microsoft Support Services で対応いたします。製品サポートの詳細につきましては、Microsoft Support のサイト http://support.microsoft.com/ を参照してください。コミュニティおよびニュースグループ
次のフォーラムおよびニュースグループより、コミュニティ サポートが提供されています。- MSDN ニュースグループ: http://www.microsoft.com/japan/msdn/newsgroups/
- ASP.NET Forums: http://forums.asp.net/ (英語)
上手なご利用方法としては、実際にお使いのテクノロジあるいは実際の問題に対応したニュースグループを参照していただくとよいでしょう。たとえば、ASP.NET のセキュリティ機能に関する問題が発生している場合は、ASP.NET Security フォーラムのご利用をお奨めします。
ご協力いただいた方々
- ご協力いただいた社外の方々: Andy Eunson; Chris Ullman, Content Master; David Raphael, Rudolph Araujo, Foundstone Professional Services; Manoranjan M. Paul
- MCS (Microsoft Consulting Services) および PSS 関係者: Wade Mascia, Tom Christian, Adam Semel, Nobuyuki Akama, Microsoft Corporation
- MPD (Microsoft Product Group) 関係者: Stefan Schackow, Vikas Malhotra, Microsoft Corporation
- MSDN 関係者: Kent Sharkey, Microsoft Corporation
- Microsoft IT 関係者: Eric Rachner, Shawn Veney (ACE Team), Microsoft Corporation
- テスト チーム: Larry Brader, Microsoft Corporation; Nadupalli Venkata Surya Sateesh, Sivanthapatham Shanmugasundaram, Sameer Tarey, Infosys Technologies Ltd
- 編集チーム: Nelly Delgado, Microsoft Corporation; Sharon Smith, Tina Burden McGrayne, Linda Werner & Associates Inc
- リリース管理: Sanjeev Garg, Microsoft Corporation
表示: