次の方法で共有


ASP.NET 1.0 へのクロスサイト スクリプティング保護の追加

 

Scott Hanselman
チーフ アーキテクト
コリリアン株式会社

2003 年 11 月

概要: ASP.NET 1.1 では、サイトをクロスサイト スクリプティングから保護するために ValidateRequest 属性が追加されました。 ただし、Web サイトがまだ 1.0 ASP.NET 実行されている場合は、どうすればよいですか? Scott Hanselman は、ASP.NET 1.0 Web サイトに同様の機能を追加する方法を示しています。 (12ページ印刷)

内容

問題を
IL ガイの C#-Eye
HttpModule
プログラマの意図
インストールと構成
結果
まとめ

問題

Microsoft ASP.NET と Microsoft® .NET Framework 1.0 にサイトを®展開したお客様がいます。 それは大規模なサイトであり、彼らは大規模な顧客であり、大規模な顧客として、彼らは移動する傾向があります, よく, 遅い. ASP.NET/Framework 1.1 が出たとき、大規模なデプロイの途中でした。チームは、ゴールラインに近い ASP.NET/Framework 1.1にすべてを動かすのは危険すぎると感じました。 そこで、年内に ASP.NET/Framework 1.1 に移行することにしました。 ただし、多くのビジネスラインをまたぎ、人々のお金を扱う複雑な電子銀行 Web サイトを構築するため、セキュリティはジョブ #1 (ゼロベースの場合は job #0) です。 クライアントには、クロスサイト スクリプティング ("XSS" と呼ばれることが多い) 攻撃に積極的に対処するという要件があります。

XSS は、特に不吉な種類のハッキングです。l33t hx0r (エリート ハッカー) または "スクリプト キディ" は、個人情報を取得したり、Web フォームに JavaScript を入力したり、URL のパラメーターにスクリプトをエンコードしたりして、サイトをだまして実行しようとします。 簡単な例として、1 つのテキスト ボックスと 1 つのボタンを含む Web フォームがあります。 ユーザーがテキスト ボックスに自分の名前を入力し、フォームを送信します。 次に、文字列連結、String.Format、Response.Write、またはサーバー側ラベルを使用して、"Hello firstname" ** を出力します。

ms972967.scriptingprotection_fig01(en-us,MSDN.10).gifms972967.scriptingprotection_fig01

図 1. テキストを入力します。十分に安全に見える

ページはユーザーの入力を受け取り、直接「逆流」するので、私は誓いの言葉を入力した場合、私は別の種類の挨拶を得るだろう! しかし、ユーザーが自分の名前を入力する代わりに、"<script alert('bad stuff happens')" のようなスクリプト>フラグメントを入力するとどうなりますか。</script>."分離コードは次のようになります。

if (this.IsPostBack) Response.Write("Hello " + this.TextBox1.Text);

テキスト ボックスの内容が応答ストリームに直接書き込まれ、JavaScript がユーザーのブラウザーで評価されることがわかります。 これは簡単な例ですが、悪意のある JavaScript にユーザーの Cookie コレクションにアクセスしたり、フォームの投稿を別のサイトにリダイレクトしたりするコードが含まれているとします。

ms972967.scriptingprotection_fig02(en-us,MSDN.10).gifms972967.scriptingprotection_fig02

図 2. テキストが必要な JavaScript の入力

ms972967.scriptingprotection_fig03(en-us,MSDN.10).gifms972967.scriptingprotection_fig03

図 3: JavaScript が応答時に実行される

わかりやすくするために、フォーム フィールドやその他のシックに JavaScript を入力するユーザーに対処するために、Web 層やビジネス ロジックに余分な複雑さを組み込む必要はありません。 実際のページが実行される前に、HTTP ワーカー要求チェーンの早い段階で、おそらくフィルターとして、何らかの中心的な方法で XSS を処理したいと考えます。 ASP.NET 1.1 には、これを行うための新しい @Page ディレクティブが含まれています。 入力検証は既定で有効になり、 ディレクティブの ValidateRequest 属性を使用して @Page 制御できます。

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" 
    ValidateRequest="true" AutoEventWireup="false" 
    Inherits="Junk.WebForm1" %>

ASP.NET 1.1 要求の検証では、Cookie コレクション、QueryString、および Forms 投稿内の悪意のあるスクリプト コードがキャッチされます。 すべての入力データが、危険な可能性のある値の一覧に対してチェックされます。 この種の検証によってユーザーの機能が何らかの形で損なわれることが心配な場合は、ユーザーがフォーム フィールドに JavaScript を入力している場合、ユーザーが目的のユーザーではないことを保証します。 ValdidateRequest=true は、ユーザーのエクスペリエンスを妨げることはありません。 一部の入力データで悪意のあるスクリプトが検出されると、 HttpRequestValidationException がスローされます。 Global.asax でこのエラーを確実にキャッチし、必要に応じて既定のエラー ページを独自の個人的な脅威に置き換えることができます。

ASP.NET 1.1 にこの強力なフィルターが無料で含まれていることは素晴らしいことですが、私とクライアントの保留中の ASP.NET 1.0 サイトの立ち上げには役立ちません。 クライアントのアップグレードを待っている間に、ASP.NET 1.0 でクロスサイト スクリプティングから保護するにはどうすればよいですか? いくつかの正規表現の記述や、 Application_BeginRequestでの HTTP ヘッダーの検索など、いくつかのアイデアを思い浮かべますが、どのアイデアも良いと感じていません。 また、クロスサイト スクリプティング攻撃を防ぐためのコンポーネントを作る会社 ではなく 、電子金融会社で働いていることも思い出しました。 私が車輪を再発明しようとする必要はありません。

その後、私はソリューションが私の顔の前に座っているのに気づきました。ASP.NET 1.1はすでにこの問題を解決していましたが、私はちょうど問題を後方に解決する必要がありました。 そこで、既存の 1.1 を ASP.NET 1.0 に移植することにしました

IL ガイの C#-Eye

ASP.NET 1.1 内で何が起こっていたかを調べるには、.NET Framework SDK に含まれる .NET 逆アセンブラーILDASM.EXEよりも少し高いレベルのツールが必要でした。 私はより賢い人だったら、おそらくILDASMだけでSystem.Webを離れることができましたが、ILを読むことは簡単ではなく、私はスケジュールを持っていました。 私はルッツ・ローダーのリフレクターでそのツールを見つけました。 リフレクターは、基底クラス ライブラリ (BCL) が提供するすべての名前空間とクラスの優れたツリー ビューを提供するオブジェクト ブラウザーです。

ms972967.scriptingprotection_fig04(en-us,MSDN.10).gifms972967.scriptingprotection_fig04

図 4: リフレクターの CrossSiteScriptingValidation クラスを確認する

ms972967.scriptingprotection_fig05(en-us,MSDN.10).gifms972967.scriptingprotection_fig05

図 5: CrossSiteScriptingValidation クラスのソース コードのエクスポート

ただし、リフレクターが本当に輝いているのは、.NET アセンブリを逆コンパイルし、結果を IL としてではなく、同等の C# または Microsoft® Visual Basic® .NET コードとして表示する機能にあります。 もちろん、ローカル変数名など、プロセスでは明らかな忠実性が失われますが、それは生命 (およびコード) です。

そのため、 CrossSiteScriptingValidation という内部クラスが見つかるまで、System.Web で実行しました。 有望に聞こえました。 ここでは、 IsDangerousString や IsDangerousScriptString など、難しい質問に答 えますCrossSiteScriptingValidation のすべてのメソッドはブール値を返します。ほとんどの場合、危険と見なされます。 しかし、どの文字列を評価し、誰がこのユーティリティ クラスを呼び出していますか? すべての要求を検証しようとしているので、答えは HttpRequest にあるようです。

HttpRequest には、 Form 変数、 CookieQueryString のコレクションが含まれています。 NameValueCollection 型のこれらのオブジェクト (Cookie は実際には HttpCookieCollection であり、いくつかの簡単な追加のものがあります)、URL が の場合、https://localhost/junk/test.aspx?id=3QueryString コレクションには値 3 の名前 ID のエントリが含まれます。 HttpRequest にはこのコレクションのパブリック get プロパティがあるため、 Request.QueryString をコーディングすると、そのプロパティにアクセスします。 これがすべて起こる場所です。 コレクションに最初の名前でアクセスすると、 ValidateNameValueCollection を使用して危険な文字列がチェックされます。 HttpRequestValidationException がスローされない場合は、現在有効な QueryString が返され、コレクションを再度チェックするオーバーヘッドを回避するためにフラグが設定されます。

if (this._flags[1] != null)
{
    this._flags[1] = 0;
    this.ValidateNameValueCollection(this._queryString,
               "Request.QueryString");
}
return this._queryString;

このような検証コードはすべて、ASP.NET 1.1 の HttpRequest コレクションを通じて行われます。 もちろん、ASP.NET 1.0 で実行されるソリューションが必要であり、 FormsQueryStringCookie コレクションの動作をオーバーライドできないため、コレクションを検証するには、呼び出し履歴内で別の機会を見つける必要があります。

HttpModule

HttpModule が最適な選択肢のようでした。 IHttpModule を実装する単純なカスタム パブリック クラス。 IHttpModule インターフェイスは、Init()Dispose() の 2 つのメソッドのみで構成されます。 Init() は、 httpApplication を唯一のパラメーターとして ASP.NET することによって 1 回呼び出され、任意のイベント ハンドラーをアプリケーションにフックする機会です。 パフォーマンス上の理由から、クロスサイト スクリプティング検証コードが 1 回だけ実行され、ページと関連するビジネス ロジックとは別に実行されるようにしたいと考えました。

HttpApplication には、次の順序で発生する次のイベントがあります。

  1. BeginRequest
  2. AuthenticateRequest
  3. AuthorizeRequest
  4. ResolveRequestCache
  5. [この時点でハンドラー (要求 URL に対応するページ) が作成されます。
  6. AcquireRequestState
  7. PreRequestHandlerExecute
  8. [ハンドラーが実行されます。 ここでは、 ページ]
  9. PostRequestHandlerExecute
  10. ReleaseRequestState
  11. [応答フィルターがある場合は、出力をフィルター処理します。
  12. UpdateRequestCache
  13. Endrequest

検証コントロールを実行する時間は、ページ自体の直前の PreRequestHandlerExecute イベント ハンドラーの間にあるように見えます。 潜在的に危険なものを見つけて例外をスローした場合、ページは実行されません。 これは期待した動作です。

そこで、IHttpModule を実装する ValidateInput というクラスを作成し、Init() でPreRequestHandlerExecuteEventHandler をフックしてカスタム関数 ValidateRequest を呼び出しました。 これは ValidateRequest 内にあり、ここでは、ASP.NET 1.1 から引き継ぐ関数を呼び出します。

また、クイック バージョンチェックを追加して、ASP.NET 1.1 でこのモジュールを誰も使用しないようにします。 1.1 にアップグレードするときに、誰かがこのモジュールを削除することを忘れさせるのは嫌です。

public class ValidateInput : IHttpModule
{
HttpContext context;
HttpApplication application;
public ValidateInput(){}
public void Init(HttpApplication app)
{
    Version v = System.Environment.Version;
    if (v.Major != 1 && v.Minor != 0)
        throw new NotSupportedException(@"The ValidateInput HttpModule is 
           not supported on this version of ASP.NET. 
           Remove it from your Web.config file!");
    app.PreRequestHandlerExecute += new EventHandler(this.ValidateRequest) ;
}

PreRequestHandlerExecute をクラスの ValidateRequest メソッドにフックしました。 FormsQueryStringおよび Cookies コレクションにフックできないため、検証済みの要求のみが Page ハンドラーに渡されるようにするには、ここですべての要求検証を行う必要があります。

public void ValidateRequest(Object src, EventArgs e)
{
   //Store away what may be useful during this Request...
   application = (HttpApplication)src;
   context = application.Context;
   this.ValidateNameValueCollection(context.Request.Form, "Request.Form"); 
   this.ValidateNameValueCollection(context.Request.QueryString, 
          "Request.QueryString"); 
   this.ValidateCookieCollection(context.Request.Cookies);
}

ValidateRequest で、ValidateNameValueCollection と ValidateCookieCollection の独自の実装を呼び出しました。 各コレクションは、事前に解析された CookieQueryString など、Form POST データを表す既に解析されたコレクションをスピンします。

要求からの悪意のある可能性のあるデータがまだ Page ハンドラーまたはブラウザーに到達していないので、この HTTP ヘッダー データの解析と NameValueCollections への整理は安全であることを知しておくことが重要です。 さらに、PreRequestHandlerExecute ではなく BeginRequest アプリケーション イベントを選択した場合は、生の HTTP 要求を自分で解析する必要がありました。 だから、私は両方の世界の最高を得る、面倒な解析が私のために行われ(そしてすでに十分にテストされたコードにある)、ページはまだ実行されていないため、例外をスローして要求の実行を停止する時間が与えられます。

次に、IsDangerousExpressionString、IsDangerousOnStringIsDangerousScriptStringIsDangerousStringIsAtoZ など、他のすべてのヘルパー関数をリフレクターから新しいクラスにプルしました。 リフレクターが示す逆コンパイルされた C# コードは、実際にはアセンブリに含まれる IL の新しい C# 表現であることを言及する価値があります。 ローカル変数名が変更され、かつてループだったものは、一連の goto ステートメントと if ステートメントになる可能性があります。 IL 表現からコードのライターを判断しないでください。 コンパイラは最終的な IL を生成するときに自由を取る必要があり、さらに重要なのはプログラマの意図の概念です。 これについては、後で少しお話しします。

ms972967.scriptingprotection_fig06(en-us,MSDN.10).gifms972967.scriptingprotection_fig06

図 6: IsAtoZ メソッドを見る

ここで、HttpRequestValidiationException という名前の ApplicationException から派生したカスタム例外クラスが必要になります。 これは偶然にも、ASP.NET 1.1 で使用されているのと同じ名前ですが、別の名前空間にあります。 この例外は、危険な可能性のあるスクリプトが HttpRequest に表示された場合にスローされます。 例外ページを表示するか、例外をログに記録することを選択した場合は、ユーザーが選択します。 スクリプト攻撃の可能性が重大なイベントであると感じる場合があり、この例外の処理方法が異なる場合があります。 どちらの方法でも、必ず例外処理戦略を立てるようにしてください。

プログラマの意図

プログラマーの意図について少しメンションしたいと思いました。 ここで本当に逆コンパイルされているのは、プログラマの意図です。 元のライターが記述したように、実際には C# ソース コードを見ていません。 IL に逆コンパイルし、その同じ IL の C# 表現に変換すると、状況は変わります。 たとえば、 IsDangerousOnString のコードのビットは、リフレクターでは次のようになります。

goto L_0045;
L_0040:
index = (index + 1);
L_0045:
if (index >= len)
{
goto L_005E;
}
if (CrossSiteScriptingValidation.IsAtoZ(s[index]))
{
goto L_0040;
}

これは平均的なプログラマにとっては読みにくいですが、プログラマの意図を正しく伝えています。 しかし、その意図は何でしたか? コードを "フォールド" してバックアップできるのは、ここまでだけです。 これは、知っているすべての人に対して並んでいた String.IndexOf の呼び出しだった可能性があります。 ただし、次のように書き換えることができます (または他の 50 の方法)。

//Programmer intent: look for non-alphas...
while (index < len)
{ 
   if (!CrossSiteScriptingValidation.IsAtoZ(s[index]))
   break;
   index++;
}

"有害と見なされる Gotos" は、コンパイラではなく、YOU にのみ適用されることに注意してください。 また、このコードは "for" ループまたはその他のループコンストラクトとして表現されている可能性もあり、意図はまだ正しく表現されていることに注意してください。

インストールと構成

ValidateInputASPNET10 を Web サーバーにインストールするには、web.configで構成された httpModules の一覧に追加するだけで済みます。この場合、アセンブリValidateInputASPNET10.dll、サイトの \bin フォルダーと、保護するボックス上の他のサイトに存在する必要があります。

<configuration>
    <system.web>
        <httpModules>
            <add name="ValidateInput" 
               type="Corillian.Web.ValidateInput,ValidateInputASPNET10" />
        </httpModules>
    </system.web>
</configuration>

結果

HttpModule をweb.configに追加すると、HttpModule は独自のアセンブリと Microsoft® Visual Studio® .NET プロジェクトであるため、再コンパイルせずに同じ ASP.NET アプリケーションを起動できます。 起動時に、ASP.NET は新しい ValidateInputASPNET10 HttpModuleで Init() を呼び出し、PreRequestHandlerExecute イベントにチェーンします。 以前のように フォーム (または QueryString または Cookies コレクション) に JavaScript を入力しようとすると、 HttpRequestValidationException を宣言するこのエラー メッセージが表示されます。 JavaScript の一部は表示されますが、一部のみであることに注意してください。エラー メッセージに対して、自分で保護しようとしているのと同じ JavaScript を出力して実行することは望んでいません。

ms972967.scriptingprotection_fig07(en-us,MSDN.10).gifms972967.scriptingprotection_fig07

図 7: スクリプト入力から Web サイトを保護する

メモ 逆コンパイルは、主にデバッグと個人的な教育に使用する必要があります。 知的財産ルールに注意し、難読化されていないアセンブリは C++ アプリケーションよりも逆コンパイルが簡単であるため、コードをスワイプするデカルト ブランシュは提供されません。 コードと知的繁栄が心配な場合は、Visual Studio .NET 2003 に付属する Dotfuscator Community Edition をご確認ください。

まとめ

クロスサイト スクリプティングは、ASP.NET Web サイトを作成するときに心配する必要がある多くの種類のハッキングの 1 つです。 ハッカーは、この手法を使用してサーバー上でコードを実行し、データの損失や顧客情報の盗難につながる可能性があります。 防御型プログラミングでは、これらの攻撃から身を守る必要があります。 この記事で行った入力への検証の追加は、Web サイトを保護するための最初の手順です。

著者について

Scott Hanselman は、電子金融イネーブラーであるコリリアン社のチーフ アーキテクトです。 彼は、C、C++、Visual Basic、COM、および現在 Visual Basic .NET および C# でソフトウェアを開発してきた 10 年以上の経験を持っています。 Scott は、過去 3 年間、オレゴン州ポートランドの MSDN 地域ディレクターに任命され、Developer Days と Visual Studio .NET の発表で、ポートランドとシアトルの両方でコンテンツを開発し、講演したことを誇りに思っています。 Scott は、4 つの都市で Microsoft® Windows Server™ 2003 および Visual Studio .NET 2003 のリリースでも講演しました。 彼は Microsoft テクノロジについて国際的に話し、Wrox Press から 2 冊の書籍を共同執筆しています。 2001 年、Scott は Microsoft、Compaq、Intel との 15 都市の国内ツアーで、Microsoft のテクノロジを取り入れ、優れた設計プラクティスを紹介しました。 今年 Scott は、4 つの PacWest 都市の Windows Server 2003 打ち上げイベント、米国とマレーシアの TechEd、およびオーランドの ASPLive で講演しました。 .NET、プログラミング、Web サービスの Zen に関する彼の考えは、 にあります http://www.computerzen.com

© Microsoft Corporation. All rights reserved.