July 2010
Volume 25 Number 07
セキュリティに関するブリーフィング - ビューステートのセキュリティ
Bryan Sullivan | July 2010
Web アプリケーションでユーザー状態を効果的に管理するには、パフォーマンス、スケーラビリティ、保守性、およびセキュリティのバランスを巧みに取らなければならない場合があります。特に、クライアントに保存されているユーザー状態を管理する場合は、セキュリティを考慮する必要があることは明らかです。私の同僚は、状態データをクライアントに渡すことは、コーンに乗ったアイスクリームを 5 歳児に渡すようなものだと言っていました。アイスクリームは戻ってくるかもしれませんが、渡したときと同じ状態で戻ってくることはまず期待できません。
今月のコラムでは、ASP.NET アプリケーションのクライアント側での状態管理に関するセキュリティの問題を検討します。具体的には、ビュー ステートのセキュリティについて説明します (注: 今月のコラムは、読者が ASP.NET のビュー ステートの概念を理解していることを前提としています。ASP.NET のビュー ステートの概念をご存じでない方は、Scott Mitchell による「ASP.NET のビュー ステートの理解」を参照してください)。
アプリケーションのビュー ステートに保存されるデータで、保護に値するデータがあると思われない方は考え直しててください。気付かないうちに、機密情報がビュー ステートに保存されている可能性があります。ビュー ステートからの機密情報の漏えい対策には抜かりがなかったとしても、攻撃者によってビュー ステートが改ざんされ、管理者にとってもユーザーにとってもより大きな問題が引き起こされる可能性があります。さいわい、ASP.NET には、このような攻撃に対する保護機能が組み込まれています。では、こうした保護機能の正しい使い方を見て行きましょう。
脅威その 1: 情報漏えい
マイクロソフトの開発チームは STRIDE モデルを使用して脅威を分類しています。STRIDE は、次の脅威を表す略語です。
- なりすまし (Spoofing)
- 改ざん (Tampering)
- 否認 (Repudiation)
- 情報漏えい (Information Disclosure)
- サービス拒否 (Denial of Service)
- 特権の昇格 (Elevation of Privilege)
ビュー ステートのセキュリティの観点では、STRIDE のうち主に問題になる 2 つの脅威は情報漏えいと改ざんです (ただし、改ざん攻撃が成功した場合、特権の昇格につながる可能性があります。これについては、後ほど詳述します)。この 2 つの脅威のうち、情報漏えいの方が説明しやすいので、まず情報漏えいについて説明します。
ビュー ステートについて最も残念ながら解消されないでいる誤解の 1 つは、ビュー ステートは暗号化されるかどのような方法でもユーザーからは読み取れないと考えられていることです。なにしろ、ビュー ステートの文字列は確かに解読できないように見えますから。
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwULLTE2MTY2ODcyMjkPFgIeCHBhc3N3b3JkBQlzd29yZGZpc2hkZA==" />
しかし、この文字列は Base64 でエンコードされているに過ぎず、何かしら暗号として強度の高いアルゴリズムを使用して暗号化されているわけではありません。System.Web.UI.LosFormatter の LOS (Limited Object Serialization) フォーマッター クラスを使用することで、ビュー ステートの文字列は簡単にデコードおよびシリアル化解除できます。
LosFormatter formatter = new LosFormatter();
object viewstateObj = formatter.Deserialize("/wEPDwULLTE2MTY2ODcyMjkPFgIeCHBhc3N3b3JkBQlzd29yZGZpc2hkZA==");
デバッガーを覗いてみると (図 1 参照)、シリアル化解除されたビュー ステート オブジェクトは、実は、値が "password" で、対応する文字列値が "swordfish" である、System.Web.UI.IndexedString で終わる一連の System.Web.UI.Pair オブジェクトであることがわかります。
図 1 デバッガーによって解読されたビュー ステートの機密データ
ビュー ステート オブジェクトをシリアル化解除するコードを自分で作成する手間を省きたければ、Fritz Onion 氏の ViewState Decoder ツール (alt.pluralsight.com/tools.aspx、英語) など、インターネットから無料でダウンロードできる優秀なビュー ステートのデコーダーがいくつかあります。
ビュー ステートを暗号化する
『The Security Development Lifecycle: SDL: A Process for Developing Demonstrably More Secure Software』(Microsoft Press、2006 年) の中で、Michael Howard と Steve Lipner は STRIDE の脅威対策に使用できるテクノロジについて説明しています。図 2 は、脅威の種類と、各脅威に関係する緩和対策を示しています。
図 2 STRIDE の脅威への緩和対策
脅威の種類 | 緩和対策 |
なりすまし | 認証 |
改ざん | 完全性 |
否認 | 否認防止サービス |
情報漏えい | 機密性 |
サービス拒否 | 可用性 |
特権の昇格 | 承認 |
ここでは、ビュー ステートに保存されているデータの情報漏えいの脅威に取り組むため、機密性の緩和対策を講じる必要があります。この場合、最も有効な機密性緩和対策テクノロジは暗号化です。
ASP.NET 2.0 には、ViewStateEncryptionMode プロパティという、ビュー ステートの暗号化を有効にする機能が組み込まれています。ViewStateEncryptionMode プロパティは、ページ ディレクティブまたは web.config ファイルから有効にします。
<%@ Page ViewStateEncryptionMode="Always" %>
または
<configuration>
<system.web>
<pages viewStateEncryptionMode="Always">
ViewStateEncryptionMode に設定できる値には、Always (ビュー ステートが常に暗号化される)、Never (ビュー ステートが暗号化されない)、および Auto (ページのコントロールの 1 つが明示的に暗号化を要求している場合にのみ、ビュー ステートが暗号化される) の 3 種類があります。Always と Never は名前が示すとおりですが、Auto についてはもう少し説明が必要です。
サーバー コントロールによってページのビュー ステートに機密情報が保持される場合、そのコントロールから Page.RegisterRequiresViewStateEncryption メソッドを呼び出すことで、ページにビュー ステートの暗号化を要求できます (この場合、暗号化を要求したコントロールに対応するビュー ステートだけでなく、ビュー ステート全体が暗号化されることに注意してください)。
public class MyServerControl : WebControl
{
protected override void OnInit(EventArgs e)
{
Page.RegisterRequiresViewStateEncryption();
base.OnInit(e);
}
...
}
ただし、注意することがあります。メソッドの名前が EnableViewStateEncryption などではなく RegisterRequiresViewStateEncryption なのは、ページによって要求が無視される可能性があるためです。ページの ViewStateEncryptionMode が Auto (または Always) に設定されている場合は、コントロールの要求が承認されて、ビュー ステートが暗号化されます。ViewStateEncryptionMode が Never に設定されている場合は、コントロールの要求は無視され、ビュー ステートは保護されません。
これは、コントロールの開発者であれば、認識しておく必要がある点です。機密情報であり得る情報がビュー ステートに渡されないようにすることを検討してください (これはあらゆる場合の推奨動作です)。この動作を利用できない極端なケースでは、コントロールの SaveViewState メソッドと LoadViewState メソッドをオーバーライドして、これらのメソッドからビュー ステートを手動で暗号化および復号化します。
サーバー ファームでの注意点
単一サーバー環境では、ViewStateEncryptionMode を有効にするだけで十分ですが、サーバー ファーム環境では、他にも必要な作業があります。ASP.NET がビュー ステートの暗号化に使用するアルゴリズムなど、対称暗号化のアルゴリズムにはキーが必要です。web.config ファイルでキーを明示的に指定することも、ASP.NET によって自動的にキーを生成することもできます。単一サーバー環境では、ASP.NET によってキーを自動的に生成してもかまいませんが、やはりこの方法もサーバー ファームでは使用できません。各サーバーが一意のキーを生成しますが、その結果、復号化キーが一致しないため、複数のサーバー間で負荷分散される要求は失敗します。
アプリケーションの web.config ファイルの machineKey 要素に、使用する暗号化アルゴリズムとキーの両方を明示的に設定できます。
<configuration>
<system.web>
<machineKey decryption="AES" decryptionKey="143a...">
暗号化アルゴリズムには、AES (既定値)、DES、または 3DES を使用できます。これらのうち、DES は Microsoft SDL 暗号化標準によって禁止されています。また、3DES は、使用しないことが強く推奨されています。私は、最大限のセキュリティが得られるように、常に AES を使用することをお勧めします。
アルゴリズムを選択したら、キーを作成する必要があります。ただし、このシステムのセキュリティの強度は、このキーの強度にかかっていることを忘れないでください。自分のペットの名前や大切な人の誕生日など、容易に推測できる値は使用しないでください。暗号としての強度がある乱数を使用する必要があります。以下は、.NET RNGCryptoServiceProvider クラスを使用して、machineKey 要素に設定できる形式 (16 進数文字のみ) で乱数を作成するコード スニペットです。
RNGCryptoServiceProvider csp = new RNGCryptoServiceProvider();
byte[] data = new byte[24];
csp.GetBytes(data);
string value = String.Join("", BitConverter.ToString(data).Split('-'));
最低でも、キーには 16 バイトの乱数値を生成してください。これは、SDL 暗号化標準によって認められている最小値です。AES キーに使用できる最大長は、Microsoft .NET Framework 3.5 では 24 バイト (48 桁の 16 進数文字)、.NET Framework 4 では 32 バイト (64 桁の 16 進数文字) です。一方、.NET Framework のバージョンにかかわらず、DES でサポートされる最大長は 8 バイトのみで、3DES では 24 バイトです。やはり、DES と 3DES は使用せず、AES を使用することをお勧めします。
脅威その 2: 改ざん
もう一つの脅威は、改ざんです。ビュー ステート内の情報の読み取りを防ぐ場合と同じく、暗号化による保護によって、情報の改ざんも防止できると思われるかもしれませんが、それは誤りです。暗号化では、改ざんに対する保護は得られません。データが暗号化されていても、攻撃者は暗号化されているデータの内容を変更できます。
図 2 をもう一度見てください。改ざんの脅威緩和対策としては、データの完全性テクノロジを使用する必要があります。この場合の最適な方法はやはり暗号化の一種で、これも ASP.NET に組み込まれていますが、対称アルゴリズムを使用してデータを暗号化するのではなく、ハッシュ アルゴリズムを使用して、データのメッセージ認証コード (MAC) を作成します。
MAC を適用する ASP.NET 機能は EnableViewStateMac という名前で、ViewStateEncryptionMode と同様に、ページ ディレクティブまたはアプリケーションの web.config ファイルを使用して適用できます。
<%@ Page EnableViewStateMac="true" %>
または
<configuration>
<system.web>
<pages enableViewStateMac="true">
EnableViewStateMac が実際に内部でどのような処理をしているかを理解するため、まず、ビュー ステートの MAC が有効でない場合に、ビュー ステートがページにどのように書き込まれるかを大まかに説明します。
- ページのビュー ステートおよび関係するすべてのコントロールが、1 つのステート グラフ オブジェクトにまとめられます。
- ステート グラフが、バイナリ形式にシリアル化されます。
- シリアル化されたバイト配列が、Base-64 文字列にエンコードされます。
- Base-64 文字列が、ページの __VIEWSTATE フォーム値に書き込まれます。
ビュー ステートの MAC が有効な場合、前の手順の 2. と 3. の間にさらに 3 つの処理があります。
- ページのビュー ステートおよび関係するすべてのコントロールが、1 つのステート グラフ オブジェクトにまとめられます。
- ステート グラフが、バイナリ形式にシリアル化されます。
a. シリアル化されたバイト配列の最後に、秘密キーの値が追加されます。
b. この新しいシリアル化されたバイト配列の暗号化ハッシュが計算されます。
c. 計算されたハッシュがシリアル化されたバイト配列の最後に追加されます。 - シリアル化されたバイト配列が、Base-64 文字列にエンコードされます。
- Base-64 文字列が、ページの __VIEWSTATE フォーム値に書き込まれます。
このページがサーバーにポスト バックされる場合は常に、ページのコードによって、受信ステート グラフ データ (__VIEWSTATE 値からシリアル化解除されたデータ) が取得され、同じ秘密キー値を追加して、ハッシュ値を再計算することで、受信 __VIEWSTATE が検証されます。新たに計算されたハッシュ値が、受信 __VIEWSTATE の最後にあるハッシュ値と一致すれば、ビュー ステートは有効であると見なされ、処理が実行されます (図 3 参照)。一致しなければ、ビュー ステートは改ざんされていると見なされ、例外がスローされます。
図 3 メッセージ認証コード (MAC) の適用
このシステムのセキュリティは、秘密キーの値の秘匿性にあります。この値は常に、メモリまたは構成ファイルを使用してサーバー側に保存され (これについては後述します)、ページに書き込まれることはありません。キーを知らない限り、攻撃者が有効なビュー ステート ハッシュを計算する手段はありません。
理論的には、十分な処理能力があれば、攻撃者はキーをリバース エンジニアリングできます。計算されたハッシュ値と、対応するテキスト値がわかれば、利用できるハッシュ アルゴリズムの数は多過ぎるということはありません。キー値の全候補を処理し、既知のテキスト値と現在のキーのハッシュを再計算して、既知のハッシュと比較するだけでよいのです。値が一致したら、正しいキーが見つかったことになり、攻撃者はシステムを意のままに攻撃できます。この方法の唯一の難点は、値の候補数の多さです。既定のキーのサイズは 512 ビットであるため、2 の 512 乗とおりの可能性があり、これでは候補数が多すぎて、ブルート フォース攻撃はまず不可能です。
MAC が無効なビュー ステートを悪用する
EnableViewStateMac の既定値は true であるため、これを false に設定しないでおくだけで、アプリケーションは保護されます。残念ながら、EnableViewStateMac のパフォーマンスの影響について誤解を招くドキュメントがあり、一部の Web サイトでは、アプリケーションのパフォーマンスを向上するために、ビュー ステート MAC を無効にするよう勧めています。PagesSection.EnableViewStateMacProperty についての MSDN オンラインのドキュメントでさえ、この間違いを犯していて、「パフォーマンスが重要な考慮事項である場合には、EnableViewStateMac を true に設定しないでください」としています。このアドバイスには従わないでください (このコラムが皆さんの目に触れるまでに、より正しいセキュリティの考慮事項に即して、このドキュメントが変更されていることを願っています)。
ビュー ステート MAC が無効にされているページは、__VIEWSTATE パラメーターを対象としたクロスサイト スクリプト攻撃に対して脆弱である可能性があります。この攻撃の最初の概念実証は Trustwave の David Byrne 氏によって開発され、2010 年 2 月に開催された Black Hat DC カンファレンスにおいて Byrne 氏と同氏の同僚である Rohini Sulatycki 氏によって実演されました。この攻撃を実行するには、ページの form 要素の innerHtml プロパティに、実行したい悪意のあるスクリプト コードを固定値として設定したビュー ステート グラフを作成します。XML 形式では、このビュー ステート グラフは図 4 のようになります。
図 4 ビュー ステート MAC 攻撃の XML コード
<viewstate>
<Pair>
<Pair>
<String>...</String>
<Pair>
<ArrayList>
<Int32>0</Int32>
<Pair>
<ArrayList>
<Int32>1</Int32>
<Pair>
<ArrayList>
<IndexedString>innerhtml</IndexedString>
<String>...malicious script goes here...</String>
</ArrayList>
</Pair>
</ArrayList>
</Pair>
</ArrayList>
</Pair>
</Pair>
</Pair>
</viewstate>
次に、悪意のあるビュー ステートを Base-64 でエンコードし、この文字列を脆弱なページに対する __VIEWSTATE クエリ文字列パラメーターとして追加します。たとえば、www.contoso.com というサイトの home.aspx というページでは、ビュー ステート MAC が無効であることがわかっている場合、攻撃用 URI は https://www.contoso.com/home.aspx?\_\_VIEWSTATE=/w143a... となります。
あとは、被害者となる見込みのあるユーザーがこのリンクをたどるようにしかけるだけです。ユーザーがリンクをたどると、ページのコードによって、受信する __VIEWSTATE クエリ文字列パラメーターからビュー ステートのシリアル化が解除され、悪意のあるスクリプトがフォームの innerHtml として書き込まれます。被害者がページにアクセスすると、攻撃者のスクリプトが、被害者の資格情報を使用して被害者のブラウザで直ちに実行されます。
この攻撃は、通常のすべてのクロスサイト スクリプト (XSS) 保護機能を完全に回避できるため、特に危険です。Internet Explorer 8 の XSS フィルターは、この攻撃をブロックしません。ASP.NET の ValidateRequest 機能は一般的な XSS 攻撃のいくつかをブロックしますが、受信ビュー ステートのシリアル化解除や分析は行わないため、この機能もこの場合は役に立ちません。Microsoft Anti-Cross Site Scripting (Anti-XSS) Library (現在は Microsoft Web Protection Library に含まれています) は、ValidateRequest よりも XSS 対策として有効です。ただし、Anti-XSS Library の入力データの除去機能も、出力データのエンコード機能も、この攻撃からの保護はできません。実効力のある唯一の対策は、ビュー ステート MAC を常にすべてのページに適用することです。
サーバー ファームでのその他の注意点
ViewStateEncryptionMode と同様に EnableViewStateMac についても、アプリケーションをサーバー ファーム環境に配置する場合に特別な注意点があります。ビュー ステート ハッシュに使用されるシークレット値は、ファーム内のすべてのコンピューターで同じである必要があります。異なる場合、ビュー ステートの検証は失敗します。
ビュー ステートの暗号化キーとアルゴリズムを指定したときと同じ場所、つまり web.config ファイルの machineKey 要素に、使用する検証キーと HMAC アルゴリズムの両方を指定できます。
<configuration>
<system.web>
<machineKey validation="AES" validationKey="143a...">
アプリケーションが .NET Framework 3.5 以前のバージョンを基盤としている場合は、SHA1 (既定値)、AES、MD5、3DES を MAC アルゴリズムに使用できます。.NET Framework 4 を実行している場合は、SHA-2 ファミリの MAC (HMACSHA256、HMACSHA384、または HMACSHA512) も使用できます。これらの選択肢のうち、MD5 は Microsoft SDL 暗号化標準によって禁止されています。また、3DES は、使用しないことが強く推奨されています。SHA1 も使用しないことが推奨されていますが、.NET Framework 3.5 以前のアプリケーションでは SHA1 が最適です。.NET Framework 4 アプリケーションでは、必ず検証アルゴリズムに HMACSHA512 または HMACSHA256 を構成してください。
MAC アルゴリズムを選択したら、手動で検証キーを指定する必要もあります。忘れずに暗号としての強度がある乱数を使用してください。その場合、必要に応じて、前出のキー生成コードを参照してください。HMACSHA384 または HMACSHA512 には少なくとも 128 バイトの検証キー を、その他のアルゴリズムには少なくとも 64 バイトのキーを使用します。
脆弱なビュー ステートは隠ぺい不能
脆弱なファイルのアクセス許可やデータベース コマンドは、サーバー側コードの深い部分に隠ぺいできますが、脆弱なビュー ステートは、検索するだけで容易に発見できます。攻撃者がページのビュー ステートが保護されているかどうかを確認するためにページをテストする場合、攻撃者自身がそのページに対して要求を行い、__VIEWSTATE フォーム値から Base-64 でエンコードされたビュー ステート値を取得できます。LosFormatter クラスによりその値のシリアル化解除が成功すると、その値は暗号化されていません。多少面倒ですが、ビュー ステートの MAC が適用されているかどうかを判断できます。
MAC は常にビュー ステート値の最後に適用され、ハッシュのサイズはハッシュ アルゴリズムによって決まっているため、MAC が存在するかどうかは非常に簡単に判断できます。HMACSHA512 が使用されている場合、MAC は 64 バイトであり、HMACSHA384 が使用されている場合は 48 バイト、それ以外のアルゴリズムが使用されている場合は 32 バイトです。Base-64 でデコードされたビュー ステート値の最後の 32、48、または 64 バイトを抜き出して、これらを LosFormatter を使用してシリアル化解除し、前と同じオブジェクトにできれば、ビュー ステートの MAC は適用されています。ビュー ステートのバイト配列から抜き出したどの値もシリアル化解除されない場合は、ビュー ステートの MAC は適用されておらず、ページは脆弱です。
Casaba Security は、このテストを自動化できる Watcher と呼ばれる開発者向けの無料ツールを開発しています。Watcher は、Eric Lawrence 氏の Fiddler Web デバッグ プロキシ ツールのプラグインで、このプロキシを通過する HTTP トラフィックを受動的に分析することで機能します。Watcher は、__VIEWSTATE に MAC が設定されていない .aspx ページなど、通過したリソースで脆弱である可能性があるものに印を付けます。テスト プロセスの一環で Fiddler も Watcher も使用していない場合は、試用されることを強くお勧めします。
まとめ
ビュー ステートのセキュリティは、特に、新しいビュー ステートの改ざん攻撃が最近実演されていることを考えると、軽く考えられるものではありません。ASP.NET に組み込まれているセキュリティ メカニズムの ViewStateEncryptionMode および EnableViewStateMac をご活用ください。
Bryan Sullivan は、マイクロソフトのセキュリティ開発ライフサイクル チームのセキュリティ プログラム マネージャであり、Web アプリケーションのセキュリティ問題を専門に扱っています。『Ajax セキュリティ』(毎日コミュニケーションズ、2008 年) の著者でもあります。
この記事のレビューに協力してくれた技術スタッフの Michael Howard に心より感謝いたします。