Freigeben über


Einführung in die Sicherheit von SignalR-Anwendungen

von Patrick Fletcher, Tom FitzMacken

Warnung

Diese Dokumentation ist nicht für die neueste Version von SignalR vorgesehen. Sehen Sie sich ASP.NET Core SignalR an.

In diesem Artikel werden die Sicherheitsprobleme beschrieben, die Sie beim Entwickeln einer SignalR-Anwendung berücksichtigen müssen.

In diesem Thema verwendete Softwareversionen

Frühere Versionen dieses Themas

Informationen zu früheren Versionen von SignalR finden Sie unter Ältere Versionen von SignalR.

Fragen und Kommentare

Bitte hinterlassen Sie Feedback darüber, wie Ihnen dieses Tutorial gefallen hat und was wir in den Kommentaren unten auf der Seite verbessern könnten. Wenn Sie Fragen haben, die sich nicht direkt auf das Tutorial beziehen, können Sie diese im ASP.NET SignalR-Forum oder im StackOverflow.com posten.

Überblick

Dieses Dokument enthält folgende Abschnitte:

SignalR-Sicherheitskonzepte

Authentifizierung und Autorisierung

SignalR bietet keine Features für die Authentifizierung von Benutzern. Stattdessen integrieren Sie die SignalR-Features in die vorhandene Authentifizierungsstruktur für eine Anwendung. Sie authentifizieren Benutzer wie gewohnt in Ihrer Anwendung und arbeiten mit den Ergebnissen der Authentifizierung in Ihrem SignalR-Code. Beispielsweise können Sie Ihre Benutzer mit ASP.NET Formularauthentifizierung authentifizieren und dann in Ihrem Hub erzwingen, welche Benutzer oder Rollen zum Aufrufen einer Methode autorisiert sind. In Ihrem Hub können Sie auch Authentifizierungsinformationen wie benutzername oder ob ein Benutzer einer Rolle angehört, an den Client übergeben.

SignalR stellt das Authorize-Attribut bereit, um anzugeben, welche Benutzer Zugriff auf einen Hub oder eine Methode haben. Sie wenden das Authorize-Attribut entweder auf einen Hub oder auf bestimmte Methoden in einem Hub an. Ohne das Authorize-Attribut sind alle öffentlichen Methoden auf dem Hub für einen Client verfügbar, der mit dem Hub verbunden ist. Weitere Informationen zu Hubs finden Sie unter Authentifizierung und Autorisierung für SignalR Hubs.

Sie wenden das Authorize Attribut auf Hubs an, aber nicht auf persistente Verbindungen. Um Autorisierungsregeln bei Verwendung eines PersistentConnection zu erzwingen, müssen Sie die AuthorizeRequest -Methode überschreiben. Weitere Informationen zu persistenten Verbindungen finden Sie unter Authentifizierung und Autorisierung für persistente SignalR-Verbindungen.

Verbindungstoken

SignalR verringert das Risiko, böswillige Befehle auszuführen, indem die Identität des Absenders überprüft wird. Für jede Anforderung übergeben der Client und der Server ein Verbindungstoken, das die Verbindungs-ID und den Benutzernamen für authentifizierte Benutzer enthält. Die Verbindungs-ID identifiziert jeden verbundenen Client eindeutig. Der Server generiert nach dem Zufallsprinzip die Verbindungs-ID, wenn eine neue Verbindung erstellt wird, und speichert diese ID für die Dauer der Verbindung. Der Authentifizierungsmechanismus für die Webanwendung stellt den Benutzernamen bereit. SignalR verwendet Verschlüsselung und eine digitale Signatur, um das Verbindungstoken zu schützen.

Diagramm, das einen Pfeil von Client Neue Verbindungsanforderung an Server empfangene Verbindungsanforderung an Serverantwort an Empfangene Clientantwort zeigt. Das Authentifizierungssystem generiert ein Verbindungstoken in den Feldern Antwort und Empfangene Antwort.

Für jede Anforderung überprüft der Server den Inhalt des Tokens, um sicherzustellen, dass die Anforderung vom angegebenen Benutzer stammt. Der Benutzername muss der Verbindungs-ID entsprechen. Durch die Überprüfung sowohl der Verbindungs-ID als auch des Benutzernamens verhindert SignalR, dass ein böswilliger Benutzer leicht die Identität eines anderen Benutzers annehmen kann. Wenn der Server das Verbindungstoken nicht überprüfen kann, schlägt die Anforderung fehl.

Diagramm, das einen Pfeil von client request to Server Received Request to Saved Token zeigt. Verbindungstoken und Meldung befinden sich sowohl im Feld Client als auch im Feld Server.

Da die Verbindungs-ID Teil des Überprüfungsprozesses ist, sollten Sie die Verbindungs-ID eines Benutzers anderen Benutzern nicht preisgeben oder den Wert auf dem Client speichern, z. B. in einem Cookie.

Verbindungstoken im Vergleich zu anderen Tokentypen

Verbindungstoken werden gelegentlich von Sicherheitstools gekennzeichnet, da es sich um Sitzungstoken oder Authentifizierungstoken handelt, was ein Risiko darstellt, wenn sie verfügbar gemacht werden.

Das Verbindungstoken von SignalR ist kein Authentifizierungstoken. Es wird verwendet, um zu bestätigen, dass der Benutzer, der diese Anforderung stellt, derselbe ist, der die Verbindung erstellt hat. Das Verbindungstoken ist erforderlich, da ASP.NET SignalR das Verschieben von Verbindungen zwischen Servern ermöglicht. Das Token ordnet die Verbindung einem bestimmten Benutzer zu, bestätigt aber nicht die Identität des Benutzers, der die Anforderung stellt. Damit eine SignalR-Anforderung ordnungsgemäß authentifiziert wird, muss sie über ein anderes Token verfügen, das die Identität des Benutzers bestätigt, z. B. ein Cookie oder Bearertoken. Das Verbindungstoken selbst gibt jedoch nicht an, dass die Anforderung von diesem Benutzer gestellt wurde, nur dass die im Token enthaltene Verbindungs-ID diesem Benutzer zugeordnet ist.

Da das Verbindungstoken keinen eigenen Authentifizierungsanspruch bereitstellt, wird es nicht als Sitzungs- oder Authentifizierungstoken betrachtet. Die Verwendung des Verbindungstokens eines bestimmten Benutzers und die Wiedergabe in einer Anforderung, die als ein anderer Benutzer (oder eine nicht authentifizierte Anforderung) authentifiziert wurde, schlägt fehl, da die Benutzeridentität der Anforderung und die im Token gespeicherte Identität nicht übereinstimmen.

Erneutes Beitreten von Gruppen beim erneuten Herstellen der Verbindung

Standardmäßig weist die SignalR-Anwendung einen Benutzer automatisch den entsprechenden Gruppen zu, wenn die Verbindung nach einer vorübergehenden Unterbrechung wiederhergestellt wird, z. B. wenn eine Verbindung unterbrochen und erneut hergestellt wird, bevor das Verbindungstimeout auftritt. Beim erneuten Herstellen der Verbindung übergibt der Client ein Gruppentoken, das die Verbindungs-ID und die zugewiesenen Gruppen enthält. Das Gruppentoken wird digital signiert und verschlüsselt. Der Client behält die gleiche Verbindungs-ID nach einer erneuten Verbindung bei. Daher muss die verbindungs-ID, die vom erneut verbundenen Client übergeben wurde, mit der vorherigen Verbindungs-ID übereinstimmen, die vom Client verwendet wurde. Diese Überprüfung verhindert, dass ein böswilliger Benutzer Anforderungen zum Beitreten nicht autorisierter Gruppen übergibt, wenn die Verbindung wiederhergestellt wird.

Beachten Sie jedoch, dass das Gruppentoken nicht abläuft. Wenn ein Benutzer in der Vergangenheit zu einer Gruppe gehört, aber für diese Gruppe gesperrt wurde, kann dieser Benutzer möglicherweise ein Gruppentoken nachahmen, das die unzulässige Gruppe enthält. Wenn Sie sicher verwalten müssen, welche Benutzer zu welchen Gruppen gehören, müssen Sie diese Daten auf dem Server speichern, z. B. in einer Datenbank. Fügen Sie dann Ihrer Anwendung Logik hinzu, die auf dem Server überprüft, ob ein Benutzer zu einer Gruppe gehört. Ein Beispiel für die Überprüfung der Gruppenmitgliedschaft finden Sie unter Arbeiten mit Gruppen.

Die automatische Wiedereingliederung von Gruppen gilt nur, wenn eine Verbindung nach einer vorübergehenden Unterbrechung wiederhergestellt wird. Wenn ein Benutzer die Verbindung trennt, indem er von der Anwendung navigiert, oder die Anwendung neu gestartet wird, muss ihre Anwendung behandeln, wie dieser Benutzer den richtigen Gruppen hinzugefügt werden kann. Weitere Informationen finden Sie unter Arbeiten mit Gruppen.

So verhindert SignalR die standortübergreifende Anforderungsfälschung

Cross-Site Request Forgery (CSRF) ist ein Angriff, bei dem eine böswillige Website eine Anforderung an einen anfälligen Standort sendet, auf dem der Benutzer derzeit angemeldet ist. SignalR verhindert CSRF, da es extrem unwahrscheinlich ist, dass eine böswillige Website eine gültige Anforderung für Ihre SignalR-Anwendung erstellt.

Beschreibung des CSRF-Angriffs

Hier sehen Sie ein Beispiel für einen CSRF-Angriff:

  1. Ein Benutzer meldet sich mit der Formularauthentifizierung bei www.example.com an.

  2. Der Server authentifiziert den Benutzer. Die Antwort vom Server enthält ein Authentifizierungscookies.

  3. Ohne Sich abmelden, besucht der Benutzer eine böswillige Website. Diese böswillige Website enthält das folgende HTML-Formular:

    <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>
    

    Beachten Sie, dass die Formularaktion Beiträge auf der anfälligen Website und nicht auf der böswilligen Website sendet. Dies ist der "websiteübergreifende" Teil von CSRF.

  4. Der Benutzer klickt auf die Schaltfläche Senden. Der Browser enthält das Authentifizierungscookies mit der Anforderung.

  5. Die Anforderung wird auf dem example.com Server mit dem Authentifizierungskontext des Benutzers ausgeführt und kann alles tun, was ein authentifizierter Benutzer tun darf.

Obwohl in diesem Beispiel der Benutzer auf die Formularschaltfläche klicken muss, kann die böswillige Seite genauso einfach ein Skript ausführen, das eine AJAX-Anforderung an Ihre SignalR-Anwendung sendet. Darüber hinaus verhindert die Verwendung von SSL keinen CSRF-Angriff, da die böswillige Website eine "https://"-Anforderung senden kann.

In der Regel sind CSRF-Angriffe auf Websites möglich, die Cookies für die Authentifizierung verwenden, da Browser alle relevanten Cookies an die Zielwebsite senden. CSRF-Angriffe sind jedoch nicht auf die Nutzung von Cookies beschränkt. Beispielsweise sind auch die Standard- und Digestauthentifizierung anfällig. Nachdem sich ein Benutzer mit der Standard- oder Digestauthentifizierung angemeldet hat, sendet der Browser die Anmeldeinformationen automatisch, bis die Sitzung endet.

CSRF-Entschärfungen von SignalR

SignalR führt die folgenden Schritte aus, um zu verhindern, dass eine böswillige Website gültige Anforderungen an Ihre Anwendung erstellt. SignalR führt diese Schritte standardmäßig aus. Sie müssen keine Aktionen im Code ausführen.

  • Deaktivieren domänenübergreifender Anforderungen SignalR deaktiviert domänenübergreifende Anforderungen, um zu verhindern, dass Benutzer einen SignalR-Endpunkt aus einer externen Domäne aufrufen. SignalR betrachtet jede Anforderung von einer externen Domäne als ungültig und blockiert die Anforderung. Es wird empfohlen, dieses Standardverhalten beizubehalten. Andernfalls könnte eine böswillige Website Benutzer dazu verleiten, Befehle an Ihre Website zu senden. Wenn Sie domänenübergreifende Anforderungen verwenden müssen, finden Sie weitere Informationen unter Einrichten einer domänenübergreifenden Verbindung .
  • Übergeben des Verbindungstokens in der Abfragezeichenfolge, nicht in einem Cookie SignalR übergibt das Verbindungstoken als Abfragezeichenfolgenwert und nicht als Cookie. Das Speichern des Verbindungstokens in einem Cookie ist unsicher, da der Browser das Verbindungstoken versehentlich weiterleiten kann, wenn schädlicher Code gefunden wird. Außerdem verhindert das Übergeben des Verbindungstokens in der Abfragezeichenfolge, dass das Verbindungstoken über die aktuelle Verbindung hinaus beibehalten wird. Daher kann ein böswilliger Benutzer keine Anforderung unter den Authentifizierungsanmeldeinformationen eines anderen Benutzers stellen.
  • Überprüfen des Verbindungstokens Wie im Abschnitt Verbindungstoken beschrieben, weiß der Server, welche Verbindungs-ID jedem authentifizierten Benutzer zugeordnet ist. Der Server verarbeitet keine Anforderung von einer Verbindungs-ID, die nicht mit dem Benutzernamen übereinstimmt. Es ist unwahrscheinlich, dass ein böswilliger Benutzer eine gültige Anforderung erraten könnte, da der böswillige Benutzer den Benutzernamen und die aktuelle zufällig generierte Verbindungs-ID kennen müsste. Diese Verbindungs-ID wird ungültig, sobald die Verbindung beendet wird. Anonyme Benutzer sollten keinen Zugriff auf vertrauliche Informationen haben.

SignalR-Sicherheitsempfehlungen

SSL-Protokoll (Secure Socket Layers)

Das SSL-Protokoll verwendet verschlüsselung, um den Transport von Daten zwischen client und server zu schützen. Wenn Ihre SignalR-Anwendung vertrauliche Informationen zwischen Client und Server überträgt, verwenden Sie SSL für den Transport. Weitere Informationen zum Einrichten von SSL finden Sie unter Einrichten von SSL in IIS 7.

Verwenden Sie keine Gruppen als Sicherheitsmechanismus

Gruppen sind eine bequeme Möglichkeit zum Sammeln verwandter Benutzer, aber sie sind kein sicherer Mechanismus zum Einschränken des Zugriffs auf vertrauliche Informationen. Dies gilt insbesondere, wenn Benutzer während einer erneuten Verbindung automatisch Gruppen beitreten können. Erwägen Sie stattdessen, einer Rolle privilegierte Benutzer hinzuzufügen und den Zugriff auf eine Hubmethode nur auf Mitglieder dieser Rolle zu beschränken. Ein Beispiel für die Einschränkung des Zugriffs basierend auf einer Rolle finden Sie unter Authentifizierung und Autorisierung für SignalR Hubs. Ein Beispiel für die Überprüfung des Benutzerzugriffs auf Gruppen beim erneuten Herstellen der Verbindung finden Sie unter Arbeiten mit Gruppen.

Sichere Verarbeitung von Eingaben von Clients

Um sicherzustellen, dass ein böswilliger Benutzer kein Skript an andere Benutzer sendet, müssen Sie alle Eingaben von Clients codieren, die für die Übertragung an andere Clients vorgesehen sind. Sie sollten Nachrichten auf den empfangenden Clients und nicht auf dem Server codieren, da Ihre SignalR-Anwendung viele verschiedene Typen von Clients aufweisen kann. Daher funktioniert die HTML-Codierung für einen Webclient, aber nicht für andere Clienttypen. Beispielsweise würde eine Webclientmethode zum Anzeigen einer Chatnachricht den Benutzernamen und die Nachricht sicher verarbeiten, indem die html() Funktion aufgerufen wird.

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>');
};

Abgleich einer Änderung in der Benutzer-status mit einer aktiven Verbindung

Wenn sich die Authentifizierung eines Benutzers status ändert, während eine aktive Verbindung besteht, erhält der Benutzer eine Fehlermeldung, die besagt: "Die Benutzeridentität kann sich während einer aktiven SignalR-Verbindung nicht ändern." In diesem Fall sollte Ihre Anwendung erneut eine Verbindung mit dem Server herstellen, um sicherzustellen, dass die Verbindungs-ID und der Benutzername koordiniert sind. Wenn Ihre Anwendung beispielsweise dem Benutzer ermöglicht, sich abzumelden, während eine aktive Verbindung besteht, stimmt der Benutzername für die Verbindung nicht mehr mit dem Namen überein, der für die nächste Anforderung übergeben wird. Sie sollten die Verbindung beenden, bevor sich der Benutzer abmeldet, und sie dann neu starten.

Beachten Sie jedoch, dass die meisten Anwendungen die Verbindung nicht manuell beenden und starten müssen. Wenn Ihre Anwendung Benutzer nach dem Abmelden auf eine separate Seite umleitet, z. B. das Standardverhalten in einer Web Forms- oder MVC-Anwendung, oder aktualisiert die aktuelle Seite nach dem Abmelden, wird die aktive Verbindung automatisch getrennt und erfordert keine zusätzliche Aktion.

Das folgende Beispiel zeigt, wie Sie eine Verbindung beenden und starten, wenn sich der Benutzer status geändert hat.

<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>

Oder die Authentifizierungs-status des Benutzers kann sich ändern, wenn Ihre Website den gleitenden Ablauf mit der Formularauthentifizierung verwendet und es keine Aktivität gibt, um das Authentifizierungscookie gültig zu halten. In diesem Fall wird der Benutzer abgemeldet, und der Benutzername stimmt nicht mehr mit dem Benutzernamen im Verbindungstoken überein. Sie können dieses Problem beheben, indem Sie ein Skript hinzufügen, das regelmäßig eine Ressource auf dem Webserver anfordert, damit das Authentifizierungscooky gültig bleibt. Das folgende Beispiel zeigt, wie sie alle 30 Minuten eine Ressource anfordern.

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

Automatisch generierte JavaScript-Proxydateien

Wenn Sie nicht alle Hubs und Methoden für jeden Benutzer in die JavaScript-Proxydatei einschließen möchten, können Sie die automatische Generierung der Datei deaktivieren. Sie können diese Option wählen, wenn Sie über mehrere Hubs und Methoden verfügen, aber nicht möchten, dass jeder Benutzer alle Methoden kennt. Sie deaktivieren die automatische Generierung, indem Sie EnableJavaScriptProxies auf false festlegen.

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

Weitere Informationen zu den JavaScript-Proxydateien finden Sie unter Der generierte Proxy und was er für Sie tut.

Ausnahmen

Sie sollten das Übergeben von Ausnahmeobjekten an Clients vermeiden, da die Objekte vertrauliche Informationen für die Clients verfügbar machen können. Rufen Sie stattdessen eine Methode auf dem Client auf, die die entsprechende Fehlermeldung anzeigt.

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.");
    }
}