対話型サーバー側の ASP.NET Core Blazor レンダリングの脅威軽減策に関するガイダンス
Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
この記事では、対話型サーバー側の Blazor 内でセキュリティの脅威を軽減する方法について説明します。
アプリでは、サーバーとクライアントが長期間のリレーションシップを維持する、ステートフルなデータ処理モデルが採用されています。 永続的な状態は回線によって維持されます。これは、長期間続く可能性がある接続にまたがることがあります。
ユーザーが サイトにアクセスすると、サーバーによってサーバーのメモリに回線が作成されます。 ユーザーが UI のボタンを選択したときなどに、この回線によって、レンダリングするコンテンツがブラウザーに指示されたり、イベントへの応答が行われたりします。 これらのアクションを実行するために、回線によって、ユーザーのブラウザーで JavaScript 関数が呼び出され、サーバー上で .NET メソッドが呼び出されます。 この JavaScript ベースの双方向の対話は、JavaScript 相互運用 (JS 相互運用) と呼ばれます。
JS の相互運用はインターネット経由で行われ、クライアントはリモート ブラウザーを使用しているため、各アプリが、ほとんどの Web アプリにとってのセキュリティの問題を共有します。 このトピックでは、サーバーサイド Blazor アプリに対する一般的な脅威について説明し、インターネットに接続されるアプリに注目しながら、脅威の軽減に関するガイダンスを提供します。
企業ネットワーク内やイントラネット内などの制約された環境では、緩和ガイダンスの一部は、次のいずれかになります。
- 制約された環境には適用されません。
- 制約された環境はセキュリティ リスクが低いため、実装する価値がありません。
WebSocket 圧縮が有効な対話型サーバー コンポーネント
圧縮により、接続の TLS 暗号化へのサイドチャネル攻撃 (CRIME や BREACH 攻撃など) に対して、アプリを脆弱性にさらす恐れがあります。 これらの種類の攻撃において、攻撃者は次のことを実行する必要があります。
- クロスサイト フォームを投稿する、または別サイトの iframe 内にサイトを埋め込み、攻撃者が制御するペイロードが含まれた要求を脆弱なサイトに対して発行するように、ブラウザーに強制する。
- ネットワーク経由で、圧縮および暗号化された応答の長さを確認する。
パスやクエリ文字列を応答の中に書き込むなどして、攻撃者からのペイロードを応答に反映させると、そのアプリは脆弱になります。 攻撃者は応答の長さを使用して、接続の暗号化をバイパスし、応答に関する情報を "推測" することができます。
一般に Blazor アプリでは、適切なセキュリティ対策を使用して、WebSocket 接続経由の圧縮を有効にすることができます。
アプリは、攻撃者の影響を受ける可能性のある要求 (パスやクエリ文字列など) からコンテンツを受け取り、それをページの HTML 内に再生成したり、応答の一部にしたりする場合に脆弱になる可能性があります。
Blazor は、次のセキュリティ対策を自動的に適用します。
圧縮が構成されている場合、Blazor はアプリが iframe 内に埋め込まれるのを自動的にブロックします。これにより、サーバーからの初回 (非圧縮) 応答のレンダリングはブロックされ、WebSocket 接続は開始されなくなります。
アプリを iframe 内に埋め込む際の制限は緩和することができます。 ただし、この制限を緩和すると、埋め込みドキュメントがクロスサイト スクリプティングの脆弱性によって侵害された場合に、アプリを攻撃の脆弱性にさらすことになります。これは、攻撃者に攻撃の実行手段を提供してしまうためです。
通常、この種類の攻撃を実行するには、そのアプリが応答内のコンテンツを繰り返し再生成する必要があります。これにより、攻撃者がその応答を推測することができるためです。 Blazor のレンダリング方法を考慮すると、攻撃者が応答を推測することは困難です (1 回レンダリングされた後は、変更された要素に対してのみコンテンツの差分が生成されます)。 ただし、攻撃者にとって不可能というわけではありません。そのため、攻撃者が操作することができる外部情報と共に機密情報をレンダリングしないように注意する必要があります。 この例を次にいくつか示します。
別のユーザーによって追加されたデータベースのデータをレンダリングすると同時に、ページに対して個人を特定することができる情報 (PII) をレンダリングする。
JS 相互運用またはサーバー上のローカル シングルトン サービスを介して別のユーザーから送信されたデータと同時に、PII 情報をページに対してレンダリングする。
一般に、信頼されていないソースからのデータを同じレンダリング バッチの一部としてレンダリングすることができるコンポーネントと共に、機密情報を含むコンポーネントをレンダリングしないことをお勧めします。 信頼されていないソースには、ルート パラメータ、クエリ文字列、JS 相互運用からのデータ、サードパーティのユーザーがコントロールできる他のデータ ソース (データベース、外部サービス) が含まれます。
共有状態
サーバー側 Blazor アプリはサーバー メモリ内に存在し、アプリの複数のセッションが同じプロセス内でホストされます。 アプリ セッションごとに、Blazor によってそれ自体の依存関係挿入コンテナー スコープで回線が開始されるため、スコープ サービスは Blazor セッションごとに一意です。
警告
特別な注意を払っていない限り、同じサーバーの共有状態のアプリがシングルトン サービスを使用することはお勧めできません。これにより、回線をまたいだユーザー状態のリークなど、セキュリティ上の脆弱性が生じる可能性があります。
ステートフル シングルトン サービスがそのために特に設計されている場合、Blazor アプリでそれを使用できます。 たとえば、メモリ キャッシュでは特定のエントリにアクセスするためのキーが必要なため、シングルトン メモリ キャッシュの使用が許容されます。 キャッシュで使われるキャッシュ キーをユーザーが制御できないとすると、キャッシュに格納されている状態が回線間でリークすることはありません。
状態管理に関する一般的なガイダンスについては、「ASP.NET Core Blazor 状態管理」をご覧ください。
Razor コンポーネント内の IHttpContextAccessor
/HttpContext
有効な HttpContext
が使用できないため、対話型レンダリングでは IHttpContextAccessor を避ける必要があります。
IHttpContextAccessor は、サーバー上で静的にレンダリングされるコンポーネントに対して使用することができます。 ただし、可能であれば使用を避けることをお勧めします。
HttpContext は、App
コンポーネント (Components/App.razor
) 内のヘッダーやその他のプロパティの検査や変更などの一般的なタスク用に、"静的にレンダリングされたルート コンポーネント" 内でのみ、カスケード パラメーターとして使用できます。 対話型レンダリングの場合、この値は常に null
です。
[CascadingParameter]
public HttpContext? HttpContext { get; set; }
対話型コンポーネント内で HttpContext が必要なシナリオの場合は、サーバーから永続的なコンポーネントの状態を介してデータを取り込むことをお勧めします。 詳細については、「サーバーサイド ASP.NET Core Blazor のセキュリティに関するその他のシナリオ」を参照してください。
サーバー側 Blazor アプリの Razor コンポーネントでは、IHttpContextAccessor/HttpContext を、直接にも間接的にも使用しないでください。 Blazor アプリは、ASP.NET Core パイプラインのコンテキストの外部で実行されます。 HttpContext は、IHttpContextAccessor 内で使用できるとは限りません。また、HttpContext は、Blazor アプリを開始したコンテキストが保持されることも保証されません。
アプリの初期レンダリング中にルート コンポーネント パラメーターを使って要求の状態を Blazor アプリに渡すことをお勧めします。 または、ルート コンポーネントの初期化ライフサイクル イベントにおいてアプリでスコープ サービスにデータをコピーすることで、アプリ全体で使用することもできます。 詳細については、「サーバーサイド ASP.NET Core Blazor のセキュリティに関するその他のシナリオ」を参照してください。
サーバー側 Blazor のセキュリティでの重要な側面は、特定の回線に接続されているユーザーは Blazor 回線が確立された後のある時点で更新される可能性がありますが、IHttpContextAccessor は "更新されない" ということです。 カスタム サービスでこの状況に対処する方法の詳細については、「サーバーサイド ASP.NET Core Blazor のセキュリティに関するその他のシナリオ」を参照してください。
リソースの枯渇
リソースの枯渇は、クライアントがサーバーとやり取りし、サーバーが過剰にリソースを消費する場合に発生する可能性があります。 過剰なリソースの消費が主に影響するのは次のものです。
サービス拒否 (DoS) 攻撃は、通常、アプリまたはサーバーのリソースを枯渇させようとします。 しかし、リソースの枯渇が必ずしもシステムへの攻撃の結果であるとは限りません。 たとえば、ユーザーの要求が多いことで、有限のリソースが使い果たされる可能性があります。 DoS については、DoS セクションで詳しく説明します。
データベースやファイル ハンドル (ファイルの読み取りと書き込みに使用される) など、Blazor フレームワークの外部のリソースでも、リソースの枯渇が発生する可能性があります。 詳細については、「ASP.NET Core のベスト プラクティス」を参照してください。
CPU
CPU の枯渇は、1 つ以上のクライアントが大量の CPU 処理を実行するようサーバーに強制した場合に発生する可能性があります。
例として、フィボナッチ数を計算するアプリを考えてみましょう。 フィボナッチ数は、フィボナッチ数列から生成されます。この数列の各数は、先行する 2 つの数の合計です。 答えに達するまでに必要な作業量は、数列の長さと初期値のサイズによって異なります。 アプリでクライアントの要求に制限を設けていない場合、CPU 負荷の高い計算によって CPU の時間が占有され、他のタスクのパフォーマンスが低下する可能性があります。 過剰なリソース消費は、可用性に影響を与えるセキュリティ上の懸念事項です。
CPU の枯渇は、すべての公開アプリに関する懸念事項です。 通常の Web アプリでは、セーフガードとして要求と接続がタイムアウトになりますが、Blazor アプリには同じセーフガードが用意されていません。 CPU を集中的に使用する作業を実行する前に、Blazor アプリに適切なチェックと制限を含める必要があります。
メモリ
メモリの枯渇は、1 つ以上のクライアントが大量のメモリを消費するようサーバーに強制した場合に発生する可能性があります。
例として、項目のリストを受け入れて表示するコンポーネントを使用するアプリについて考えてみましょう。 Blazor アプリで許可される項目の数またはクライアントにレンダリングされる項目の数が制限されていない場合、メモリを集中的に使用する処理とレンダリングで、サーバーのメモリが占有され、パフォーマンスが低下する可能性があります。 サーバーがクラッシュしたり、クラッシュしたかのように遅くなったりする場合があります。
サーバー上のメモリの枯渇のシナリオに関連する項目のリストを保持および表示するために、次のシナリオを検討してください。
List<T>
プロパティまたはフィールドの項目は、サーバーのメモリを使用します。 アプリで項目のリストを無制限に拡張することが許可されていると、サーバーでメモリが不足する危険性があります。 メモリが不足すると、現在のセッションが終了 (クラッシュ) し、そのサーバー インスタンス内のすべての同時セッションでメモリ不足の例外が発生します。 このシナリオが発生しないようにするには、同時ユーザーに項目の制限を課すデータ構造をアプリで使用する必要があります。- レンダリングにページング スキームが使用されていない場合、サーバーにより、UI に表示されないオブジェクトに対して追加のメモリが使用されます。 項目の数に制限がないと、メモリの需要によって使用可能なサーバー メモリが枯渇する可能性があります。 このシナリオを回避するには、次のいずれかの方法を使用します。
- レンダリング時にページ分割されたリストを使用します。
- 最初の 100 から 1,000 項目のみを表示し、表示された項目以外の項目を検索するには、検索条件を入力するようユーザーに求めます。
- より高度なレンダリング シナリオでは、"仮想化" をサポートするリストまたはグリッドを実装します。 仮想化を使用すると、リストには、現在ユーザーに表示されている項目のサブセットのみがレンダリングされます。 ユーザーが UI のスクロールバーを操作すると、コンポーネントでは表示に必要な項目だけがレンダリングされます。 現時点で、表示に必要ない項目は、セカンダリ ストレージに保持できます。これが理想的な方法です。 表示されていない項目をメモリに保持することもできますが、これはあまり理想的ではありません。
Note
Blazor には、仮想化のサポートが組み込まれています。 詳しくは、「ASP.NET Core Razor コンポーネントの仮想化」をご覧ください。
Blazor アプリには、WPF、Windows フォーム、Blazor WebAssembly など、ステートフル アプリ用の他の UI フレームワークと同様のプログラミング モデルが用意されています。 主な違いは、いくつかの UI フレームワークでは、アプリによって消費されるメモリがクライアントに属し、その個々のクライアントのみに影響することです。 たとえば、Blazor WebAssembly アプリは、完全にクライアント上で実行され、クライアントのメモリ リソースのみが使用されます。 サーバーサイド Blazor アプリの場合、アプリによって消費されるメモリはサーバーに属しており、サーバー インスタンス上のクライアントとの間で共有されています。
サーバーサイドのメモリの需要は、すべてのサーバーサイド Blazor アプリにおける考慮事項です。 ただし、ほとんどの Web アプリはステートレスであり、要求の処理中に使用されたメモリは、応答が返されると解放されます。 一般的な推奨事項として、クライアント接続を保持する他のサーバー側アプリと同じように、クライアントが解放されているメモリ容量を割り当てられないようにしてください。 サーバーサイド Blazor アプリによって消費されるメモリは、1 つの要求よりも長い時間保持されます。
Note
開発中は、プロファイラーを使用したり、トレースをキャプチャしてクライアントのメモリ要求を評価したりすることができます。 プロファイラーまたはトレースでは、特定のクライアントに割り当てられたメモリはキャプチャされません。 開発時に特定のクライアントのメモリ使用量をキャプチャするには、ダンプをキャプチャし、ユーザーの回線をルートとするすべてのオブジェクトのメモリ要求を調べます。
クライアント接続
接続の枯渇は、1 つ以上のクライアントがサーバーへのコンカレント接続を多数開いていて、他のクライアントが新しい接続を確立できない場合に発生する可能性があります。
Blazor クライアントは、セッションごとに 1 つの接続を確立し、ブラウザー ウィンドウが開いている間は接続を開いたままにします。 接続には永続的な性質があり、サーバーサイド Blazor アプリにはステートフルな性質があることを考えると、接続の枯渇はアプリの可用性に対する大きなリスクだと言えます。
アプリのユーザーあたりの接続数に制限はありません。 アプリで接続制限が必要な場合は、次の方法の 1 つまたは複数を実行します。
- 認証を要求します。これにより、承認されていないユーザーがアプリに接続する能力が自然に制限されます。 このシナリオを有効にするには、ユーザーが新しいユーザーを必要に応じてプロビジョニングできないようにする必要があります。
- ユーザーあたりの接続数を制限します。 接続の制限は、次の方法で実現できます。 正当なユーザーにアプリへのアクセスを許可するようにご注意ください (たとえば、クライアントの IP アドレスに基づいて接続の制限が確立されている場合)。
- アプリケーション レベル
- エンドポイント ルーティングの拡張性。
- アプリに接続し、ユーザーごとにアクティブなセッションを追跡するには、認証を要求する。
- 制限に達したら新しいセッションを拒否する。
- プロキシを使用したアプリへのプロキシ WebSocket 接続 (クライアントからアプリへの接続を多重化する Azure SignalR Service など)。 これにより、1 つのクライアントで確立できるよりも多くの接続容量がアプリに提供されるため、クライアントがサーバーへの接続を使い果たすことを防ぐことができます。
- サーバー レベル
- アプリの前にプロキシ/ゲートウェイを使用します。 たとえば、Azure Application Gateway は、Web アプリケーションに対するトラフィックを管理できる Web トラフィック (OSI レイヤー 7) ロード バランサーです。 詳細については、「Application Gateway での WebSocket のサポートの概要」を参照してください。
- Long Polling は、Azure Front Door の導入を可能にする Blazor アプリでサポートされていますが、推奨されるトランスポート プロトコルは WebSocket です。 2024 年 9 月現在、Azure Front Door は WebSocket をサポートしていませんが、WebSocket のサポートは検討中です。 詳しくは、「Azure Front Door で WebSocket 接続をサポートする」を参照してください。
- アプリケーション レベル
- 認証を要求します。これにより、承認されていないユーザーがアプリに接続する能力が自然に制限されます。 このシナリオを有効にするには、ユーザーが新しいユーザーを必要に応じてプロビジョニングできないようにする必要があります。
- ユーザーあたりの接続数を制限します。 接続の制限は、次の方法で実現できます。 正当なユーザーにアプリへのアクセスを許可するようにご注意ください (たとえば、クライアントの IP アドレスに基づいて接続の制限が確立されている場合)。
- アプリケーション レベル
- エンドポイント ルーティングの拡張性。
- アプリに接続し、ユーザーごとにアクティブなセッションを追跡するには、認証を要求する。
- 制限に達したら新しいセッションを拒否する。
- プロキシを使用したアプリへのプロキシ WebSocket 接続 (クライアントからアプリへの接続を多重化する Azure SignalR Service など)。 これにより、1 つのクライアントで確立できるよりも多くの接続容量がアプリに提供されるため、クライアントがサーバーへの接続を使い果たすことを防ぐことができます。
- サーバー レベル
- アプリの前にプロキシ/ゲートウェイを使用します。
- ロング ポーリングは Blazor アプリ用にサポートされていますが、WebSocket が推奨されるトランスポート プロトコルです。 WebSocket をサポートするプロキシ/ゲートウェイを選択することをお勧めします。
- アプリケーション レベル
サービス拒否 (DoS) 攻撃
サービス拒否 (DoS) 攻撃では、クライアントがサーバーにそのリソースを 1 つ以上使い果たさせることで、アプリを利用できなくします。 Blazor アプリには、既定の制限があり、DoS 攻撃から保護するために CircuitOptions で設定される他の ASP.NET Core や SignalR の制限に依存しています。
- CircuitOptions.DisconnectedCircuitMaxRetained
- CircuitOptions.DisconnectedCircuitRetentionPeriod
- CircuitOptions.JSInteropDefaultCallTimeout
- CircuitOptions.MaxBufferedUnacknowledgedRenderBatches
- HubConnectionContextOptions.MaximumReceiveMessageSize
詳細および構成コーディングの例については、次の記事を参照してください。
ブラウザー (クライアント) との対話
クライアントは、JS 相互運用イベントのディスパッチとレンダリングの完了を通じてサーバーと対話します。 JS 相互運用通信は、JavaScript と .NET の間で双方向に行われます。
- ブラウザー イベントは、非同期方式でクライアントからサーバーにディスパッチされます。
- サーバーは、必要に応じて UI を非同期に再レンダリングして応答します。
.NET から呼び出される JavaScript 関数
.NET メソッドから JavaScript への呼び出しの場合:
- すべての呼び出しには、構成可能なタイムアウトがあり、失敗すると、呼び出し元に OperationCanceledException が返されます。
- 呼び出し (CircuitOptions.JSInteropDefaultCallTimeout) の既定のタイムアウトは 1 分です。 この制限を構成するには、「ASP.NET Core Blazor で .NET メソッドから JavaScript 関数を呼び出す」をご覧ください。
- キャンセル トークンを指定して、呼び出しごとに取り消しを制御できます。 可能な限り既定の呼び出しタイムアウトを使用し、キャンセル トークンが指定されている場合は、クライアントへの呼び出しに時間制限を設けます。
- JavaScript 呼び出しの結果は信頼できません。 ブラウザーで実行されている Blazor アプリ クライアントは、呼び出す JavaScript 関数を検索します。 関数が呼び出され、結果またはエラーのいずれかが生成されます。 悪意のあるクライアントは次のことを試みる可能性があります。
- JavaScript 関数からエラーを返すことによって、アプリで問題を発生させる。
- JavaScript 関数から予期しない結果を返すことによって、サーバーで意図しない動作を誘発させる。
上記のシナリオを保護するために、次の予防措置を講じてください。
- 呼び出し中に発生するおそれのあるエラーを考慮するために、
try-catch
ステートメント内に JS 相互運用呼び出しをラップします。 詳細については、「ASP.NET Core Blazor アプリのエラーを処理する」を参照してください。 - アクションを実行する前に、JS 相互運用の呼び出しから返されたデータ (エラー メッセージを含む) を検証します。
ブラウザーから呼び出される .NET メソッド
JavaScript から .NET メソッドへの呼び出しを信頼しないでください。 .NET メソッドが JavaScript に公開されている場合は、.NET メソッドの呼び出し方法を検討してください。
- JavaScript に公開されているすべての .NET メソッドは、アプリのパブリック エンドポイントと同様に扱います。
- 入力を検証します。
- 値が想定される範囲内であることを確認します。
- ユーザーが要求された操作を実行するアクセス許可を持っていることを確認します。
- .NET メソッドの呼び出しの一部として、過剰な量のリソースを割り当てないでください。 たとえば、チェックを実行し、CPU とメモリの使用量に制限を設けます。
- 静的メソッドとインスタンス メソッドは JavaScript クライアントに公開できることを考慮してください。 設計で適切な制約を使用して状態の共有を行う必要がある場合を除き、セッション間で状態を共有しないでください。
- 依存関係の挿入 (DI) によって最初に作成された DotNetObjectReference オブジェクトを介して公開されるインスタンス メソッドの場合、オブジェクトはスコープ付きオブジェクトとして登録する必要があります。 これは、 アプリが使用するすべての DI サービスに適用されます。
- 静的メソッドの場合は、アプリが明示的にサーバー インスタンス上のすべてのユーザー間で状態を明示的に共有している場合を除き、クライアントにスコープを設定できない状態を確立しないようにしてください。
- ユーザーが指定したデータをパラメーターで JavaScript の呼び出しに渡さないでください。 どうしてもパラメーターでデータを渡す必要がある場合は、クロスサイト スクリプティング (XSS) の脆弱性を取り込むことなく、JavaScript コードでデータの引き渡しを処理できるようにします。 たとえば、要素の
innerHTML
プロパティを設定することによって、ユーザー指定のデータを DOM に書き込まないようにしてください。 コンテンツ セキュリティ ポリシー (CSP) を使用して、eval
およびその他の安全でない JavaScript プリミティブを無効にすることを検討してください。 詳しくは、「ASP.NET Core Blazor のコンテンツ セキュリティ ポリシーを適用する」を参照してください。
- 入力を検証します。
- フレームワークのディスパッチ実装の上に .NET 呼び出しのカスタム ディスパッチを実装しないでください。 ブラウザーに .NET メソッドを公開することは高度なシナリオであるため、一般的な Blazor 開発にはお勧めしません。
イベント
イベントは、アプリへのエントリ ポイントを提供します。 Web アプリでエンドポイントを保護する場合と同じ規則が、Blazor アプリのイベント処理に適用されます。 悪意のあるクライアントが、イベントのペイロードとして送信したい任意のデータを送信する可能性があります。
次に例を示します。
<select>
の変更イベントでは、アプリがクライアントに提示したオプションに含まれていない値が送信されることがあります。<input>
は、クライアント側の検証をバイパスして、テキスト データをサーバーに送信することがあります。
アプリが処理する任意のイベントのデータをアプリで検証する必要があります。 Blazor フレームワークのフォーム コンポーネントでは、基本的な検証が実行されます。 アプリでカスタム フォーム コンポーネントを使用する場合は、必要に応じてカスタム コードを記述してイベント データを検証する必要があります。
イベントは非同期であるため、アプリがレンダーを新たに生成し応答するまでに、サーバーに複数のイベントがディスパッチ されることがあります。 これには、考慮すべきセキュリティへの影響がいくつかあります。 アプリでのクライアント アクションの制限は、イベント ハンドラー内で実行する必要があり、現在レンダリングされているビュー状態に依存してはいけません。
ユーザーがカウンターを最大 3 回インクリメントできるようにするカウンター コンポーネントを考えてみましょう。 カウンターをインクリメントするボタンは、条件付きで count
の値に基づいています。
<p>Count: @count</p>
@if (count < 3)
{
<button @onclick="IncrementCount" value="Increment count" />
}
@code
{
private int count = 0;
private void IncrementCount()
{
count++;
}
}
クライアントは、フレームワークがこのコンポーネントの新しいレンダリングを生成する前に、1 つ以上のインクリメント イベントをディスパッチできます。 その結果、UI によってすぐにボタンが削除されないため、ユーザーは count
を "3 回以上" インクリメントできます。 3 回の count
のインクリメントの制限を実現するための正しい方法を次の例で示します。
<p>Count: @count</p>
@if (count < 3)
{
<button @onclick="IncrementCount" value="Increment count" />
}
@code
{
private int count = 0;
private void IncrementCount()
{
if (count < 3)
{
count++;
}
}
}
ハンドラー内に if (count < 3) { ... }
チェックを追加することによって、count
をインクリメントするかどうかは、現在のアプリの状態に基づいて決定されます。 この決定は、前の例とは異なり、一時的に古くなっている可能性がある UI の状態に基づいていません。
複数のディスパッチから保護する
イベント コールバックによって、長時間実行される操作 (外部サービスまたはデータベースからのデータのフェッチなど) が非同期に呼び出される場合は、セーフガードの使用を検討してください。 セーフガードを使用すると、視覚的なフィードバックにより、操作の進行中にユーザーによる複数操作のエンキューを防ぐことができます。 次のコンポーネント コードでは、DataService.GetDataAsync
がサーバーからデータを取得する間に、isLoading
を true
に設定します。 isLoading
が true
の間は、このボタンは UI では無効になります。
<button disabled="@isLoading" @onclick="UpdateData">Update</button>
@code {
private bool isLoading;
private Data[] data = Array.Empty<Data>();
private async Task UpdateData()
{
if (!isLoading)
{
isLoading = true;
data = await DataService.GetDataAsync(DateTime.Now);
isLoading = false;
}
}
}
前の例で示したセーフガード パターンは、バックグラウンド操作が async
-await
パターンで非同期に実行される場合に機能します。
早期にキャンセルして、破棄後の使用を回避する
コンポーネントが破棄される場合は、「複数のディスパッチから保護する」セクションで説明したセーフガードの使用に加えて、CancellationToken を使用して長時間実行される操作を取り消すことを検討してください。 このアプローチには、コンポーネントで "破棄後の使用" を回避するという追加の利点があります。
@implements IDisposable
...
@code {
private readonly CancellationTokenSource TokenSource =
new CancellationTokenSource();
private async Task UpdateData()
{
...
data = await DataService.GetDataAsync(DateTime.Now, TokenSource.Token);
if (TokenSource.Token.IsCancellationRequested)
{
return;
}
...
}
public void Dispose()
{
TokenSource.Cancel();
}
}
大量のデータを生成するイベントを回避する
oninput
や onscroll
などの一部の DOM イベントでは、大量のデータが生成される可能性があります。 サーバーサイド Blazor のサーバーでは、これらのイベントを使用することは避けてください。
セキュリティに関するその他のガイダンス
サーバーサイド Blazor アプリに、ASP.NET Core アプリを安全に適用するためのガイダンスが、この記事の以降のセクションで説明されています。
ログ記録と機密データ
クライアントとサーバー間の JS 相互運用操作は、ILogger インスタンスと共にサーバーのログに記録されます。 Blazor では、実際のイベントや JS 相互運用の入力や出力などの機密情報のログ記録が回避されます。
サーバーでエラーが発生すると、フレームワークによってクライアントに通知され、セッションが破棄されます。 クライアントは、ブラウザーの開発者ツールで見られる一般的なエラー メッセージを受け取ります。
クライアント側のエラーによって、呼び出し履歴が含まれることはなく、エラーの原因についての詳細も提供されませんが、サーバー ログにはそのような情報が含まれます。 開発目的で、詳細なエラーを有効にすることによって、機密性の高いエラー情報をクライアントが利用できるようにすることができます。
警告
インターネット上でクライアントにエラー情報を公開することは、常に回避すべきセキュリティ リスクです。
HTTPS を使用した転送中の情報の保護
Blazor では、クライアントとサーバー間の通信に SignalR が使用されます。 Blazor では通常、SignalR がネゴシエートするトランスポート (通常は WebSocket) が使用されます。
Blazor では、サーバーとクライアントの間で送信されるデータの整合性と機密性は保証されません。 常に HTTPS を使用します。
クロスサイト スクリプティング (XSS)
クロスサイトス クリプティング (XSS) を使用すると、承認されていないパーティが、ブラウザーのコンテキストで任意のロジックを実行することができます。 侵害されたアプリが、クライアントで任意のコードを実行する可能性があります。 脆弱性を利用して、サーバーに対して多くの悪意のある操作が実行される可能性があります。
- 偽のイベントや無効なイベントをサーバーにディスパッチする。
- 失敗または無効なレンダリング完了をディスパッチする。
- レンダリング完了のディスパッチを回避する。
- JavaScript から相互運用呼び出しを .NET にディスパッチする。
- .NET から JavaScript への相互運用呼び出しの応答を変更する。
- JS 相互運用の結果に .NET をディスパッチすることを回避する。
Blazor フレームワークでは、前述のいくつかの脅威から保護するためのステップが実行されます。
- クライアントがレンダリング バッチを確認していない場合は、新しい UI 更新の生成を停止します。 CircuitOptions.MaxBufferedUnacknowledgedRenderBatches で構成されます。
- クライアントからの応答を受信せずに、1 分後に .NET から JavaScript への呼び出しをタイムアウトにします。 CircuitOptions.JSInteropDefaultCallTimeout で構成されます。
- JS 相互運用中にブラウザーからのすべての入力に対して、次の基本的な検証を実行します。
- .NET 参照は有効であり、.NET メソッドで予期される型であること。
- データが不正な形式ではないこと。
- メソッドの正しい数の引数がペイロードに存在していること。
- メソッドを呼び出す前に、引数または結果が正しく逆シリアル化できること。
- 次に示すディスパッチされたイベントから、ブラウザーからのすべての入力に対して、次の基本的な検証を実行します。
- イベントに有効な型があること。
- イベントのデータを逆シリアル化できること。
- イベントに関連付けられているイベント ハンドラーがあること。
脅威から保護し、適切なアクションを実行するには、フレームワークが実装するセーフガードに加えて、アプリが開発者によってコーディングされている必要があります。
- イベントを処理するときは常にデータを検証します。
- 無効なデータの受信時に適切なアクションを実行します。
- データを無視して戻ります。 これにより、アプリは要求の処理を続行できます。
- アプリは、入力が不正であり、正当なクライアントによって生成されたものではないと判断すると、例外をスローします。 例外をスローすると、回線が破棄され、セッションが終了します。
- ログに含まれるレンダリング バッチの完了によって提供されるエラー メッセージは信頼しないでください。 このエラーは "クライアントによって提供され"、クライアントが侵害されている可能性があるため、通常は信頼できません。
- JavaScript と .NET メソッドの間では、どちらの方向でも JS相互運用呼び出しの入力を信頼しないでください。
- 引数または結果が正しく逆シリアル化された場合でも、引数と結果の内容が有効であることを検証するのはアプリの役割です。
XSS の脆弱性が存在するには、アプリがレンダリングされたページにユーザー入力を組み込む必要があります。 Blazor はコンパイル時のステップを実行し、そこで、.razor
ファイルのマークアップが手続き型 C# ロジックに変換されます。 実行時に、C# ロジックによって、要素、テキスト、および子コンポーネントを記述する "レンダリング ツリー" が構築されます。 これは、JavaScript 命令のシーケンスを通じてブラウザーの DOM に適用されます (または、プリレンダリングの場合は HTML にシリアル化されます)。
- 通常の Razor 構文 (
@someStringValue
など) を使用してレンダリングされたユーザー入力では、XSS 脆弱性は公開されません。これは、Razor 構文が、テキストのみを書き込むことができるコマンドを使用して DOM に追加されるためです。 値に HTML マークアップが含まれている場合でも、値は静的なテキストとして表示されます。 プリレンダリング時に、出力は HTML エンコードされ、コンテンツも静的テキストとして表示されます。 - スクリプト タグは許可されていないため、アプリのコンポーネント レンダリング ツリーに含めることはできません。 コンポーネントのマークアップにスクリプト タグが含まれていると、コンパイル時のエラーが生成されます。
- コンポーネントの作成者は、Razor を使用せずに C# でコンポーネントを作成できます。 コンポーネントの作成者は、出力の生成時に適切な API を使用する必要があります。 たとえば、
builder.AddContent(0, someUserSuppliedString)
を使用します。builder.AddMarkupContent(0, someUserSuppliedString)
は使用 "しないでください"。後者では XSS 脆弱性が生成される可能性があります。
XSS の脆弱性をさらに緩和することを検討してください。 たとえば、制限の厳しいコンテンツ セキュリティ ポリシー (CSP) を実装します。 詳しくは、「ASP.NET Core Blazor のコンテンツ セキュリティ ポリシーを適用する」を参照してください。
詳しくは、「ASP.NET Core でクロスサイト スクリプティング (XSS) を防ぐ」をご覧ください。
クロスオリジン保護
クロスオリジン攻撃では、サーバーに対してアクションを実行する別のオリジンのクライアントが関与します。 悪意のあるアクションは通常、GET 要求またはフォーム POST (クロスサイト リクエスト フォージェリ (CSRF)) ですが、悪意のある WebSocket を開くことも可能です。 Blazor アプリでは、ハブ プロトコルを使用する他の SignalR アプリが提供するのと同じ保証を提供します。
- それを防ぐ追加の手段を講じない限り、アプリはクロスオリジンでアクセスされる可能性があります。 クロスオリジン アクセスを無効にするには、CORS ミドルウェアをパイプラインに追加し、DisableCorsAttribute を Blazor エンドポイント メタデータに追加することでエンドポイントで CORS を無効にするか、クロスオリジン リソース共有用に SignalR を構成することで許可されるオリジンのセットを制限します。 WebSocket の配信元の制限に関するガイダンスについては、ASP.NET Coreでの WebSocket のサポートに関するページを参照してください。
- CORS が有効になっている場合、CORS の構成によっては、アプリを保護するために追加のステップが必要になることがあります。 CORS がグローバルに有効になっている場合、BlazorSignalR ハブに対する CORS を無効にするには、エンドポイント ルート ビルダーで MapBlazorHub を呼び出した後、エンドポイントのメタデータに DisableCorsAttribute メタデータを追加します。
詳細については、「ASP.NET Core でのクロスサイト リクエスト フォージェリ (XSRF/CSRF) 攻撃の防止」を参照してください。
クリック ジャッキング
クリック ジャッキングでは、ユーザーをだまして攻撃を受けているサイトでアクションを実行させるために、サイトを異なるオリジンのサイト内の <iframe>
としてレンダリングします。
<iframe>
内でのレンダリングからアプリを保護するには、コンテンツ セキュリティ ポリシー (CSP) と X-Frame-Options
ヘッダーを使用します。
詳細については、次のリソースを参照してください。
オープン リダイレクト
アプリのセッションが開始されると、そのセッションの開始中に送信される URL の基本的な検証が、サーバーによって実行されます。 フレームワークでは、回線を確立する前に、ベース URL が現在の URL の親であることが確認されます。 フレームワークでは、追加のチェックは実行されません。
クライアントでユーザーがリンクを選択すると、リンクの URL がサーバーに送信され、これにより実行するアクションが決まります。 たとえば、アプリがクライアント側のナビゲーションを実行したり、新しい場所に移動するようにブラウザーに指示したりすることができます。
コンポーネントでも、NavigationManager を使用して、プログラムによってナビゲーション要求をトリガーすることができます。 このようなシナリオでは、アプリがクライアント側のナビゲーションを実行したり、新しい場所に移動するようにブラウザーに指示したりすることができます。
コンポーネントでは、次のことを行う必要があります。
- ナビゲーション呼び出しの引数の一部としてのユーザー入力の使用を避ける。
- 引数を検証して、ターゲットがアプリで許可されていることを保証する。
そうしないと、悪意のあるユーザーが、ブラウザーを攻撃者が制御するサイトに強制的に移動させる可能性があります。 このシナリオでは、攻撃者はアプリをだまして NavigationManager.NavigateTo メソッドの呼び出しの一部としてユーザー入力を使用させます。
このアドバイスは、アプリの一部としてリンクをレンダリングする場合にも当てはまります。
- 可能であれば、相対リンクを使用します。
- 絶対リンクをページに含める前に、リンク先が有効であることを検証します。
詳細については、「ASP.NET Core でオープン リダイレクト攻撃を防止する」を参照してください。
セキュリティ チェックリスト
次のセキュリティの考慮事項のリストは、包括的なものではありません。
- イベントからの引数を検証する。
- JS 相互運用呼び出しからの入力と結果を検証する。
- .NET から JS への相互運用呼び出しに対してユーザー入力を使用しないようにする (または事前に検証する)。
- クライアントが、解放されたメモリ容量を割り当てないようにする。
- コンポーネント内のデータ。
- クライアントに返された DotNetObjectReference オブジェクト。
- 複数のディスパッチから保護します。
- コンポーネントが破棄されるときに、実行時間の長い操作をキャンセルする。
- 大量のデータを生成するイベントを回避する。
- NavigationManager.NavigateTo の呼び出しの一部としてユーザー入力を使用することは避け、避けられない場合は、最初に許可されたオリジンのセットに対して URL のユーザー入力を検証する。
- 承認は、UI の状態に基づいてではなく、コンポーネントの状態からのみ判断する。
- XSS 攻撃から保護するためにコンテンツ セキュリティ ポリシー (CSP) の使用を検討する。 詳しくは、「ASP.NET Core Blazor のコンテンツ セキュリティ ポリシーを適用する」を参照してください。
- クリック ジャッキングから保護するため、CSP と X-Frame-Options の使用を検討する。
- CORS を有効にする場合や Blazor アプリの CORS を明示的に無効にする場合は、CORS 設定が適切であることを確認する。
- Blazor アプリのサーバー側の制限が、許容できないレベルのリスクなしに、許容できるユーザー エクスペリエンスを提供することを保証するためにテストする。
ASP.NET Core