Share via


SignalR セキュリティ入門

作成者: Patrick FletcherTom FitzMacken

警告

このドキュメントは、SignalR の最新バージョン用ではありません。 SignalR の ASP.NET Coreを見てみましょう。

この記事では、SignalR アプリケーションを開発するときに考慮する必要があるセキュリティの問題について説明します。

このトピックで使用するソフトウェアのバージョン

このトピックの以前のバージョン

SignalR の以前のバージョンの詳細については、「 SignalR の古いバージョン」を参照してください。

質問とコメント

このチュートリアルを気に入った方法と、ページの下部にあるコメントで改善できる内容に関するフィードバックをお寄せください。 チュートリアルに直接関連していない質問がある場合は、 ASP.NET SignalR フォーラム または StackOverflow.com に投稿できます。

概要

このドキュメントは、次のトピックに分かれています。

SignalR セキュリティの概念

認証と権限承認

SignalR には、ユーザーを認証するための機能はありません。 代わりに、SignalR 機能をアプリケーションの既存の認証構造に統合します。 アプリケーションで通常と同じようにユーザーを認証し、SignalR コードで認証の結果を操作します。 たとえば、ASP.NET フォーム認証を使用してユーザーを認証し、ハブで、メソッドの呼び出しを許可されているユーザーまたはロールを適用できます。 ハブでは、ユーザー名やユーザーがロールに属しているかどうかなどの認証情報をクライアントに渡すこともできます。

SignalR には、ハブまたはメソッドにアクセスできるユーザーを指定する Authorize 属性が用意されています。 ハブまたはハブ内の特定のメソッドに Authorize 属性を適用します。 Authorize 属性を使用しない場合、ハブ上のすべてのパブリック メソッドは、ハブに接続されているクライアントで使用できます。 ハブの詳細については、「 SignalR Hubs の認証と承認」を参照してください。

この属性は Authorize ハブに適用しますが、永続的な接続には適用しません。 を使用するときに承認規則を PersistentConnection 適用するには、 メソッドをオーバーライドする AuthorizeRequest 必要があります。 永続的な接続の詳細については、「 SignalR 永続的接続の認証と承認」を参照してください。

接続トークン

SignalR は、送信者の ID を検証することで、悪意のあるコマンドを実行するリスクを軽減します。 要求ごとに、クライアントとサーバーは、認証されたユーザーの接続 ID とユーザー名を含む接続トークンを渡します。 接続 ID は、接続されている各クライアントを一意に識別します。 サーバーは、新しい接続の作成時に接続 ID をランダムに生成し、その ID を接続の間保持します。 Web アプリケーションの認証メカニズムによって、ユーザー名が提供されます。 SignalR は、暗号化とデジタル署名を使用して接続トークンを保護します。

[Client New Connection Request to Server Received Connection Request to Server Response to Client Received Response]\(クライアントの新しい接続要求からサーバーへの接続要求を受信したサーバーへの応答\) の矢印を示す図。認証システムは、[応答] ボックスと [受信した応答] ボックスに接続トークンを生成します。

要求ごとに、サーバーはトークンの内容を検証して、要求が指定されたユーザーから送信されていることを確認します。 ユーザー名は、接続 ID に対応している必要があります。SignalR は、接続 ID とユーザー名の両方を検証することで、悪意のあるユーザーが別のユーザーになりすますのを簡単に防ぎます。 サーバーが接続トークンを検証できない場合、要求は失敗します。

クライアント要求からサーバーへの矢印を示す図は、保存されたトークンへの要求を受信しました。[接続トークン] と [メッセージ] は、[クライアント] ボックスと [サーバー] ボックスの両方にあります。

接続 ID は検証プロセスの一部であるため、1 人のユーザーの接続 ID を他のユーザーに表示したり、Cookie などの値をクライアントに格納したりしないでください。

接続トークンと他のトークンの種類

接続トークンは、セッション トークンまたは認証トークンのように見えるので、セキュリティ ツールによってフラグが設定されることがあります。これにより、公開されるとリスクが生じます。

SignalR の接続トークンは認証トークンではありません。 この要求を行うユーザーが接続を作成したのと同じであることを確認するために使用されます。 ASP.NET SignalR では接続をサーバー間で移動できるため、接続トークンが必要です。 トークンは、接続を特定のユーザーに関連付けますが、要求を行うユーザーの ID をアサートしません。 SignalR 要求を適切に認証するには、Cookie やベアラー トークンなど、ユーザーの ID をアサートする他のトークンが必要です。 ただし、接続トークン自体は、要求がそのユーザーによって行われたという要求を行いません。トークン内に含まれる接続 ID がそのユーザーに関連付けられているというだけです。

接続トークンは独自の認証要求を提供しないため、"セッション" トークンや "認証" トークンとは見なされません。 要求のユーザー ID とトークンに格納されている ID が一致しないため、特定のユーザーの接続トークンを取得し、別のユーザー (または認証されていない要求) として認証された要求で再生すると失敗します。

再接続時にグループに再参加する

既定では、SignalR アプリケーションは、接続が切断され、接続がタイムアウトする前に再確立されたときなど、一時的な中断から再接続するときに、適切なグループにユーザーを自動的に再割り当てします。再接続時に、クライアントは接続 ID と割り当てられたグループを含むグループ トークンを渡します。 グループ トークンはデジタル署名され、暗号化されます。 クライアントは、再接続後も同じ接続 ID を保持します。そのため、再接続されたクライアントから渡される接続 ID は、クライアントによって使用される以前の接続 ID と一致している必要があります。 この検証により、悪意のあるユーザーが再接続時に承認されていないグループに参加する要求を渡すことができなくなります。

ただし、グループ トークンの有効期限が切れないことに注意することが重要です。 ユーザーが過去にグループに属していたが、そのグループから禁止されていた場合、そのユーザーは禁止されたグループを含むグループ トークンを模倣できる可能性があります。 どのユーザーがどのグループに属するかを安全に管理する必要がある場合は、データベースなどのサーバーにそのデータを格納する必要があります。 次に、ユーザーがグループに属しているかどうかをサーバー上で検証するロジックをアプリケーションに追加します。 グループ メンバーシップを確認する例については、「グループの 操作」を参照してください。

グループの自動再参加は、一時的な中断の後に接続が再接続された場合にのみ適用されます。 ユーザーがアプリケーションから離れて切断した場合、またはアプリケーションが再起動した場合、アプリケーションは、そのユーザーを正しいグループに追加する方法を処理する必要があります。 詳細については、「 グループの操作」を参照してください。

SignalR がクロスサイト リクエスト フォージェリを防止する方法

クロスサイト リクエスト フォージェリ (CSRF) は、悪意のあるサイトが、ユーザーが現在ログインしている脆弱なサイトに要求を送信する攻撃です。 SignalR は、悪意のあるサイトが SignalR アプリケーションに対して有効な要求を作成する可能性が極めて低く、CSRF を防止します。

CSRF 攻撃の説明

CSRF 攻撃の例を次に示します。

  1. ユーザーは、フォーム認証を使用して www.example.com にログインします。

  2. サーバーはユーザーを認証します。 サーバーからの応答には、認証 Cookie が含まれています。

  3. ログアウトせずに、ユーザーは悪意のある Web サイトにアクセスします。 この悪意のあるサイトには、次の HTML 形式が含まれています。

    <h1>You Are a Winner!</h1>
    <form action="http://example.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
        <input type="submit" value="Click Me"/>
    </form>
    

    フォーム アクションは、悪意のあるサイトではなく、脆弱なサイトに投稿されます。 これが CSRF の "クロスサイト" 部分です。

  4. ユーザーが [送信] ボタンをクリックします。 ブラウザーには、要求を含む認証 Cookie が含まれています。

  5. 要求は、ユーザーの認証コンテキストを使用して example.com サーバー上で実行され、認証されたユーザーが実行できるあらゆる操作を実行できます。

この例では、ユーザーがフォーム ボタンをクリックする必要がありますが、悪意のあるページでは、SignalR アプリケーションに AJAX 要求を送信するスクリプトを簡単に実行できます。 さらに、SSL を使用しても、悪意のあるサイトから "https://" 要求が送信される可能性があるため、CSRF 攻撃を防ぐことはありません。

通常、認証に Cookie を使用する Web サイトに対して CSRF 攻撃が発生する可能性があります。これは、ブラウザーが関連するすべての Cookie を宛先 Web サイトに送信するためです。 ただし、CSRF 攻撃は Cookie の悪用に限定されません。 たとえば、基本認証やダイジェスト認証も脆弱です。 ユーザーが基本認証またはダイジェスト認証でログインすると、ブラウザーはセッションが終了するまで資格情報を自動的に送信します。

SignalR によって取得された CSRF 軽減策

SignalR は、悪意のあるサイトがアプリケーションに対して有効な要求を作成するのを防ぐために、次の手順を実行します。 SignalR は既定でこれらの手順を実行します。コードでアクションを実行する必要はありません。

  • クロス ドメイン要求を無効にする SignalR はクロス ドメイン要求を無効にして、ユーザーが外部ドメインから SignalR エンドポイントを呼び出さないようにします。 SignalR は、外部ドメインからの要求が無効であると見なし、要求をブロックします。 この既定の動作は維持することをお勧めします。そうしないと、悪意のあるサイトがユーザーをだましてサイトにコマンドを送信する可能性があります。 クロスドメイン要求を使用する必要がある場合は、「クロス ドメイン接続を確立する方法 」を参照してください。
  • Cookie ではなくクエリ文字列に接続トークンを渡す SignalR は、Cookie ではなくクエリ文字列値として接続トークンを渡します。 悪意のあるコードが検出されると、ブラウザーが誤って接続トークンを転送する可能性があるため、Cookie に接続トークンを格納することは安全ではありません。 また、クエリ文字列に接続トークンを渡すと、接続トークンが現在の接続を超えて保持されなくなります。 したがって、悪意のあるユーザーは、別のユーザーの認証資格情報で要求を行うことはできません。
  • 接続トークンを確認する接続トークン 」セクションで説明されているように、サーバーは、認証された各ユーザーに関連付けられている接続 ID を認識します。 サーバーは、ユーザー名と一致しない接続 ID からの要求を処理しません。 悪意のあるユーザーがユーザー名と現在ランダムに生成された接続 ID を知る必要があるため、悪意のあるユーザーが有効な要求を推測する可能性はほとんどありません。接続が終了するとすぐに、その接続 ID が無効になります。 匿名ユーザーは機密情報にアクセスできません。

SignalR のセキュリティに関する推奨事項

Secure Socket Layers (SSL) プロトコル

SSL プロトコルでは、暗号化を使用して、クライアントとサーバー間のデータ転送をセキュリティで保護します。 SignalR アプリケーションがクライアントとサーバーの間で機密情報を送信する場合は、トランスポートに SSL を使用します。 SSL の設定の詳細については、「 IIS 7 で SSL を設定する方法」を参照してください。

セキュリティ メカニズムとしてグループを使用しない

グループは、関連するユーザーを収集する便利な方法ですが、機密情報へのアクセスを制限するための安全なメカニズムではありません。 これは、ユーザーが再接続中にグループに自動的に再参加できる場合に特に当てはまります。 代わりに、特権ユーザーをロールに追加し、ハブ メソッドへのアクセスをそのロールのメンバーのみに制限することを検討してください。 ロールに基づいてアクセスを制限する例については、「 SignalR Hubs の認証と承認」を参照してください。 再接続時にグループへのユーザー アクセスを確認する例については、「 グループの操作」を参照してください。

クライアントからの入力を安全に処理する

悪意のあるユーザーが他のユーザーにスクリプトを送信しないようにするには、他のクライアントへのブロードキャストを目的としたクライアントからの入力をすべてエンコードする必要があります。 SignalR アプリケーションにはさまざまな種類のクライアントが含まれている可能性があるため、サーバーではなく、受信側のクライアントでメッセージをエンコードする必要があります。 したがって、HTML エンコードは Web クライアントでは機能しますが、他の種類のクライアントでは機能しません。 たとえば、チャット メッセージを表示する Web クライアント メソッドは、 関数を呼び出すことによってユーザー名とメッセージを安全に html() 処理します。

chat.client.addMessageToPage = function (name, message) {
    // Html encode display name and message. 
    var encodedName = $('<div />').text(name).html();
    var encodedMsg = $('<div />').text(message).html();
    // Add the message to the page. 
    $('#discussion').append('<li><strong>' + encodedName
        + '</strong>:  ' + encodedMsg + '</li>');
};

アクティブな接続を使用したユーザー状態の変更の調整

アクティブな接続が存在する間にユーザーの認証状態が変更された場合、ユーザーは"アクティブな SignalR 接続中にユーザー ID を変更できません" というエラーを受け取ります。その場合、アプリケーションはサーバーに再接続して、接続 ID とユーザー名が調整されていることを確認する必要があります。 たとえば、アクティブな接続が存在する間にアプリケーションでユーザーがログアウトすることを許可している場合、接続のユーザー名は、次の要求に渡される名前と一致しなくなります。 ユーザーがログアウトする前に接続を停止し、再起動します。

ただし、ほとんどのアプリケーションでは、接続を手動で停止して開始する必要はありません。 Web Forms アプリケーションや MVC アプリケーションの既定の動作など、ログアウト後にアプリケーションが別のページにユーザーをリダイレクトした場合、またはログアウト後に現在のページを更新した場合、アクティブな接続は自動的に切断され、追加のアクションは必要ありません。

次の例は、ユーザーの状態が変更されたときに接続を停止して開始する方法を示しています。

<script type="text/javascript">
    $(function () {
        var chat = $.connection.sampleHub;
        $.connection.hub.start().done(function () {
            $('#logoutbutton').click(function () {
                chat.connection.stop();
                $.ajax({
                    url: "Services/SampleWebService.svc/LogOut",
                    type: "POST"
                }).done(function () {
                    chat.connection.start();
                });
            });
        });
    });
</script>

または、サイトでフォーム認証でスライディング有効期限が使用され、認証 Cookie を有効にしておくアクティビティがない場合は、ユーザーの認証状態が変更される可能性があります。 その場合、ユーザーはログアウトされ、ユーザー名は接続トークンのユーザー名と一致しなくなります。 この問題を解決するには、認証 Cookie を有効に保つために Web サーバー上のリソースを定期的に要求するスクリプトを追加します。 次の例は、30 分ごとにリソースを要求する方法を示しています。

$(function () {
    setInterval(function() {
        $.ajax({
            url: "Ping.aspx",
            cache: false
        });
    }, 1800000);
});

自動生成された JavaScript プロキシ ファイル

各ユーザーの JavaScript プロキシ ファイルにすべてのハブとメソッドを含める必要がない場合は、ファイルの自動生成を無効にすることができます。 複数のハブとメソッドがあるが、すべてのユーザーがすべてのメソッドを認識しないようにする場合は、このオプションを選択できます。 自動生成を無効にするには 、EnableJavaScriptProxies を false に設定 します

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableJavaScriptProxies = false;
app.MapSignalR(hubConfiguration);

JavaScript プロキシ ファイルの詳細については、「 生成されたプロキシとその機能」を参照してください。

例外

オブジェクトが機密情報をクライアントに公開する可能性があるため、例外オブジェクトをクライアントに渡さないようにする必要があります。 代わりに、関連するエラー メッセージを表示するメソッドをクライアントで呼び出します。

public Task SampleMethod()
{
    try
    { 
        // code that can throw an exception
    }
    catch(Exception e)
    {
        // add code to log exception and take remedial steps

        return Clients.Caller.DisplayError("Sorry, the request could not be processed.");
    }
}