ASP.NET では行わないことと、その代わりに行うこと

このトピックでは、ユーザーが web プロジェクト内で行ういくつかの一般的な間違い ASP.NET 説明します。 これらの一般的な間違いを回避するために行う必要がある推奨事項が提供されます。 これは、ノルウェー開発者会議でのダミアン・エドワーズによるプレゼンテーションに基づいています。

免責情報

このトピックは、アプリケーションのセキュリティと効率を確保するための完全なガイドではありません。 このトピックに記載されていないセキュリティとパフォーマンスのベスト プラクティスに従う必要があります。 これは、.NET のクラスとプロセスに関連する一般的な間違いを回避する方法のみを示しています。

概要

このトピックは、次のセクションで構成されています。

標準コンプライアンス

コントロール アダプター

推奨事項: アダプティブ レンダリングにコントロール アダプターの使用を停止し、代わりに CSS メディア クエリと標準に準拠した HTML を使用します。

さまざまなデバイスと環境用にカスタマイズされたプレゼンテーション コードをレンダリングするために、.NET 2.0 でコントロール アダプターが導入されました。 これで、このアダプティブ レンダリングは CSS と HTML で実現できます。 コントロール アダプターの使用を停止し、既存のアダプターを CSS と HTML に変換する必要があります。

詳細については、「メディア クエリ」および「方法: ASP.NET Web Forms/MVC アプリケーションにモバイル ページを追加する」を参照してください。

コントロールのスタイル プロパティ

推奨事項: コントロール マークアップでのスタイル値の設定を停止し、代わりに CSS スタイルシートで書式設定値を設定します。

Web サーバー コントロールには、インライン スタイルのプロパティを設定するために使用できる多数のプロパティが含まれています。 たとえば、ForeColor プロパティはコントロールのテキストの色を設定します。 CSS スタイルシートを使用すると、この同じ効果をより効率的に実現できます。 スタイルシートを使用すると、スタイル値を一元化し、アプリケーション全体でこれらの値を設定しないようにすることができます。

次の例は、テキストを赤に設定する CSS クラスを示しています。

.CautionRow {
    color: red;
}

次の例では、CSS クラスを動的に適用する方法を示します。

protected void CustomersGridView_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.Cells[2].Text == "Unconfirmed")
    {
        e.Row.CssClass = "CautionRow";
    }
}

ページとコントロールのコールバック

推奨事項: ページとコントロールのコールバックの使用を停止し、代わりに AJAX、UpdatePanel、MVC アクション メソッド、Web API、SignalR のいずれかを使用します。

以前のバージョンの ASP.NET では、Page および Control コールバック メソッドを使用すると、ページ全体を更新せずに Web ページの一部を更新できます。 AJAXUpdatePanelMVCWeb APISignalR を使用して部分ページ更新を実行できるようになりました。 コールバック メソッドの使用は、フレンドリ URL とルーティングに関する問題を引き起こす可能性があるため、使用を停止する必要があります。 既定では、コントロールはコールバック メソッドを有効にしませんが、コントロールでこの機能を有効にした場合は無効にする必要があります。

ブラウザー機能の検出

推奨事項: 静的ブラウザー機能の検出の使用を停止し、代わりに動的機能検出を使用します。

以前のバージョンの ASP.NET では、各ブラウザーでサポートされている機能が XML ファイルに格納されていました。 静的ルックアップを使用して機能のサポートを検出することは、最適な方法ではありません。 Modernizr などの機能検出フレームワークを使用して、ブラウザーでサポートされている機能を動的に検出できるようになりました。 機能検出では、メソッドまたはプロパティを使用し、ブラウザーが目的の結果を生成したかどうかを確認することでサポートが決定されます。 既定では、Modernizr は Web アプリケーション テンプレートに含まれています。

セキュリティ

要求の検証

推奨事項: ユーザー入力を検証し、ユーザーからの出力をエンコードします。

要求の検証は、各要求を検査し、認識された脅威が見つかった場合に要求を停止する ASP.NET の機能です。 クロスサイト スクリプティング攻撃からアプリケーションをセキュリティで保護するための要求の検証に依存しないでください。 代わりに、ユーザーからの入力をすべて検証し、出力をエンコードします。 場合によっては、正規表現を使用して入力を検証できますが、より複雑な場合は、値が許可された値と一致するかどうかを判断する .NET クラスを使用してユーザー入力を検証する必要があります。

次の例では、Uri クラスの静的メソッドを使用して、ユーザーによって提供される Uri が有効かどうかを判断する方法を示します。

var isValidUri = Uri.IsWellFormedUriString(passedUri, UriKind.Absolute);

ただし、Uri を十分に検証するには、 または httpsを指定httpしていることを確認するチェックも必要です。 次の例では、インスタンス メソッドを使用して Uri が有効であることを確認します。

var uriToVerify = new Uri(passedUri);
var isValidUri = uriToVerify.IsWellFormedOriginalString();
var isValidScheme = uriToVerify.Scheme == "http" || uriToVerify.Scheme == "https";

ユーザー入力を HTML としてレンダリングするか、SQL クエリにユーザー入力を含める前に、悪意のあるコードが含まれないように値をエンコードします。

次に示すように、%: %> 構文を使用してマークアップ内の<値を HTML エンコードできます。

<span><%: userInput %></span>

または、Razor 構文では、次に示すように、@で HTML エンコードできます。

<span>@userInput</span>

次の例では、コード ビハインドで値を HTML エンコードする方法を示します。

var encodedInput = Server.HtmlEncode(userInput);

SQL コマンドの値を安全にエンコードするには、 SqlParameter などのコマンド パラメーターを使用します。

Cookieless フォームの認証とセッション

推奨事項: Cookie が必要です。

クエリ文字列に認証情報を渡すことは安全ではありません。 そのため、アプリケーションに認証が含まれている場合は Cookie が必要です。 Cookie に機密情報が格納されている場合は、Cookie に SSL を要求することを検討してください。

次の例は、フォーム認証で SSL 経由で送信される Cookie が必要であることをWeb.config ファイルで指定する方法を示しています。

<authentication mode="Forms">
  <forms loginUrl="member_login.aspx"
    cookieless="UseCookies"
    requireSSL="true"
    path="/MyApplication" />
</authentication>

EnableViewStateMac

推奨事項: false に設定しないでください。

既定では、EnableViewStateMac は true に設定されています。 アプリケーションでビュー ステートが使用されていない場合でも、EnableViewStateMac を false に設定しないでください。 この値を false に設定すると、アプリケーションはクロスサイト スクリプティングに対して脆弱になります。

ASP.NET 4.5.2 以降、ランタイムは EnableViewStateMac=true を強制します。 false に設定した場合でも、ランタイムはこの値を無視し、値を true に設定して続行します。 詳細については、「 ASP.NET 4.5.2」および「EnableViewStateMac」を参照してください。

次の例は、EnableViewStateMac を true に設定する方法を示しています。 この値は既定で true であるため、実際には true に設定する必要はありません。 ただし、アプリケーション内の任意のページで false に設定した場合は、すぐにこの値を修正する必要があります。

<%@ Page language="C#" EnableViewStateMac="true" %>

中程度の信頼

推奨事項: セキュリティ境界として中信頼 (またはその他の信頼レベル) に依存しないでください。

部分信頼ではアプリケーションが適切に保護されないため、使用しないでください。 代わりに、完全信頼を使用し、信頼されていないアプリケーションを別のアプリケーション プールに分離します。 また、各アプリケーション プールを一意の ID で実行します。 詳細については、「 部分信頼でアプリケーションの分離が保証されない ASP.NET」を参照してください。

<appSettings>

推奨事項: appSettings 要素の <セキュリティ設定を> 無効にしないでください。

appSettings 要素には、セキュリティ更新プログラムに必要な多くの値が含まれています。 これらの値を変更または無効にしないでください。 更新プログラムをデプロイするときにこれらの値を無効にする必要がある場合は、デプロイの完了後すぐに再度有効にします。

詳細については、「 ASP.NET appSettings 要素」を参照してください。

UrlPathEncode

推奨事項: 代わりに UrlEncode を 使用してください。

UrlPathEncode メソッドは、非常に具体的なブラウザー互換性の問題を解決するために、.NET Frameworkに追加されました。 URL を適切にエンコードせず、クロスサイト スクリプトからアプリケーションを保護しません。 アプリケーションで使用しないでください。 代わりに、 UrlEncode を使用します。

次の例は、ハイパーリンク コントロールのクエリ文字列パラメーターとしてエンコードされた URL を渡す方法を示しています。

string destinationURL = "http://www.contoso.com/default.aspx?user=test";
NextPage.NavigateUrl = "~/Finish?url=" + Server.UrlEncode(destinationURL);

信頼性とパフォーマンス

PreSendRequestHeaders と PreSendRequestContent

推奨事項: これらのイベントはマネージド モジュールでは使用しないでください。 代わりに、必要なタスクを実行するネイティブ IIS モジュールを作成します。 「 Native-Code HTTP モジュールの作成」を参照してください。

ネイティブ IIS モジュールでは、 PreSendRequestHeaders イベントと PreSendRequestContent イベントを使用できます。

警告

を実装IHttpModuleするマネージド モジュールでは、 と PreSendRequestContent を使用PreSendRequestHeadersしないでください。 これらのプロパティを設定すると、非同期要求で問題が発生する可能性があります。 アプリケーション要求ルーティング (ARR) と Websocket の組み合わせにより、アクセス違反の例外が発生し、w3wp がクラッシュする可能性があります。 たとえば、iiscore!iiscore.dll の W3_CONTEXT_BASE::GetIsLastNotification+68 によって、アクセス違反の例外が発生しました (0xC0000005)。

Web フォームを使用した非同期ページ イベント

推奨事項: Web Formsでは、Page ライフサイクル イベントの非同期 void メソッドを記述しないようにし、代わりに非同期コードに Page.RegisterAsyncTask を使用します。

ページ イベントを async および void でマークすると、非同期コードがいつ終了したかを判断できません。 代わりに、Page.RegisterAsyncTask を使用して、完了を追跡できる方法で非同期コードを実行します。

次の例は、非同期コードを含むボタン クリック ハンドラーを示しています。 この例には、非同期的な文字列値の読み取りが含まれます。これは、推奨される方法ではなく、非同期タスクの簡略化された例としてのみ提供されます。

protected void StartAsync_Click(object sender, EventArgs e)
{
    Page.RegisterAsyncTask(new PageAsyncTask(async() =>
    {
        string stringToRead = "Long text value";

        using (StringReader reader = new StringReader(stringToRead))
        {
            string readText = await reader.ReadToEndAsync();
            Result.Text = readText;
        }
    }));
}

非同期タスクを使用している場合は、Web.config ファイルで Http ランタイム ターゲット フレームワークを 4.5 (またはそれ以降) に設定します。 ターゲット フレームワークを 4.5 に設定すると、.NET 4.5 で追加された新しい同期コンテキストが有効になります。 この値は、Visual Studio の新しいプロジェクトでは既定で設定されますが、既存のプロジェクトを使用している場合は設定されません。

<system.web>
    <httpRuntime TargetFramework="4.5" />
</system.web>

火と忘れ作業

推奨事項: ASP.NET 内で要求を処理する場合は、fire-and-forget 作業 (ThreadPool.QueueUserWorkItem メソッドの呼び出しやデリゲートを繰り返し呼び出すタイマーの作成など) を起動しないようにします。

アプリケーションに、ASP.NET 内で実行される fire-and-forget 作業がある場合は、アプリケーションが同期を取り消す可能性があります。アプリ ドメインはいつでも破棄できます。つまり、進行中のプロセスがアプリケーションの現在の状態と一致しなくなる可能性があります。

この種類の作業は、ASP.NET の外部に移動する必要があります。 Azure で Web ジョブ、Windows サービス、または Worker ロールを使用して継続的な作業を実行し、別のプロセスからそのコードを実行できます。

ASP.NET 内でこの作業を実行する必要がある場合は、 WebBackgrounder という Nuget パッケージを追加してコードを実行できます。

要求エンティティの本文

推奨事項: ハンドラーの実行イベントの前に Request.Form または Request.InputStream を読み取らないようにします。

Request.Form または Request.InputStream から最も早く読み取る必要があるのが、ハンドラーの実行イベント中です。 MVC では Controller がハンドラーであり、execute イベントはアクション メソッドの実行時です。 Web Formsでは、Page がハンドラーであり、Execute イベントは Page.Init イベントが発生したときに発生します。 execute イベントより前に要求エンティティ本文を読み取った場合は、要求の処理に干渉します。

execute イベントの前に要求エンティティ本文を読み取る必要がある場合は、 Request.GetBufferlessInputStream または Request.GetBufferedInputStream を使用します。 GetBufferlessInputStream を使用すると、要求から生ストリームを取得し、要求全体を処理する責任を負います。 GetBufferlessInputStream を呼び出した後、Request.Form と Request.InputStream は、ASP.NET によって設定されていないため使用できません。 GetBufferedInputStream を使用すると、要求からストリームのコピーが取得されます。 request.Form と Request.InputStream は、ASP.NET が他のコピーを設定するため、要求の後半で引き続き使用できます。

Response.Redirect と Response.End

推奨事項: Response.Redirect(String) を呼び出した後のスレッドの処理方法の違いに注意してください。

Response.Redirect(String) メソッドは Response.End メソッドを呼び出します。 同期プロセスでは、Request.Redirect を呼び出すと、現在のスレッドが直ちに中止されます。 ただし、非同期プロセスでは、Response.Redirect を呼び出しても現在のスレッドは中止されないため、要求に対してコードの実行が続行されます。 非同期プロセスでは、 メソッドから Task を返してコードの実行を停止する必要があります。

MVC プロジェクトでは、Response.Redirect を呼び出さないでください。 代わりに、RedirectResult を返します。

EnableViewState と ViewStateMode

推奨事項: EnableViewState ではなく ViewStateMode を使用して、ビューステートを使用するコントロールを細かく制御します。

Page ディレクティブで EnableViewState を false に設定すると、ページ内のすべてのコントロールに対してビューステートが無効になり、有効にすることはできません。 ページ内の特定のコントロールに対してのみビューステートを有効にする場合は、Page に対して ViewStateMode を Disabled に設定します。

<%@ Page ViewStateMode="Disabled" . . . %>

次に、実際にビューステートが必要なコントロールでのみ、ViewStateMode を Enabled に設定します。

<asp:GridView ViewStateMode="Enabled" runat="server">

必要なコントロールに対してのみビュー ステートを有効にすると、Web ページのビュー ステートのサイズを縮小できます。

SqlMembershipProvider

推奨事項: ユニバーサル プロバイダーを使用します。

現在のプロジェクト テンプレートでは、SqlMembershipProvider は、NuGet パッケージとして使用できる ASP.NET ユニバーサル プロバイダーに置き換えられました。 以前のバージョンのテンプレートでビルドされたプロジェクトで SqlMembershipProvider を使用している場合は、ユニバーサル プロバイダーに切り替える必要があります。 ユニバーサル プロバイダーは、Entity Framework でサポートされているすべてのデータベースと連携します。

詳細については、「ASP.NET ユニバーサル プロバイダーの概要」を参照してください。

実行時間の長い要求 (>110 秒)

推奨事項: 接続されているクライアントには WebSocket または SignalR を使用し、非同期 I/O 操作を使用します。

実行時間の長い要求により、予期しない結果が発生し、Web アプリケーションのパフォーマンスが低下する可能性があります。 要求の既定のタイムアウト設定は 110 秒です。 実行時間の長い要求でセッション状態を使用している場合、ASP.NET は 110 秒後に Session オブジェクトのロックを解放します。 ただし、ロックが解除されると、アプリケーションが Session オブジェクトに対する操作の途中にあり、操作が正常に完了しない可能性があります。 最初の要求の実行中にユーザーからの 2 番目の要求がブロックされた場合、2 番目の要求は不整合な状態で Session オブジェクトにアクセスする可能性があります。

アプリケーションにブロック (同期) I/O 操作が含まれている場合、アプリケーションは応答しません。

パフォーマンスを向上させるには、.NET Frameworkの非同期 I/O 操作を使用します。 また、クライアントをサーバーに接続するには、WebSocket または SignalR を使用します。 これらの機能は、実行時間の長い要求を効率的に処理するように設計されています。