Wprowadzenie do zabezpieczeń usługi SignalR (SignalR 1.x)

Autor: Patrick Fletcher, Tom FitzMacken

Ostrzeżenie

Ta dokumentacja nie jest przeznaczona dla najnowszej wersji usługi SignalR. Przyjrzyj się ASP.NET Core SignalR.

W tym artykule opisano problemy z zabezpieczeniami, które należy wziąć pod uwagę podczas tworzenia aplikacji usługi SignalR.

Omówienie

Ten dokument zawiera następujące sekcje:

Pojęcia dotyczące zabezpieczeń usługi SignalR

Uwierzytelnianie i autoryzacja

Usługa SignalR została zaprojektowana tak, aby została zintegrowana z istniejącą strukturą uwierzytelniania dla aplikacji. Nie udostępnia żadnych funkcji uwierzytelniania użytkowników. Zamiast tego uwierzytelniasz użytkowników tak, jak zwykle w aplikacji, a następnie pracujesz z wynikami uwierzytelniania w kodzie SignalR. Możesz na przykład uwierzytelnić użytkowników za pomocą uwierzytelniania ASP.NET formularzy, a następnie w centrum wymusić, którzy użytkownicy lub role są autoryzowani do wywoływania metody. W centrum możesz również przekazać informacje uwierzytelniania, takie jak nazwa użytkownika lub czy użytkownik należy do roli, do klienta.

Usługa SignalR udostępnia atrybut Autoryzuj , aby określić, którzy użytkownicy mają dostęp do centrum lub metody. Atrybut Autoryzuj stosuje się do centrum lub określonych metod w centrum. Bez atrybutu Autoryzuj wszystkie publiczne metody w centrum są dostępne dla klienta połączonego z koncentratorem. Aby uzyskać więcej informacji na temat centrów, zobacz Authentication and Authorization for SignalR Hubs (Uwierzytelnianie i autoryzacja dla usług SignalR Hubs).

Atrybut Authorize jest używany tylko z koncentratorami. Aby wymusić reguły autoryzacji w przypadku używania PersistentConnection metody , należy zastąpić metodę AuthorizeRequest . Aby uzyskać więcej informacji na temat połączeń trwałych, zobacz Uwierzytelnianie i autoryzacja dla połączeń trwałych usługi SignalR.

Token połączenia

Usługa SignalR ogranicza ryzyko wykonywania złośliwych poleceń, sprawdzając tożsamość nadawcy. Token połączenia, zawierający identyfikator połączenia i nazwę użytkownika uwierzytelnionych użytkowników, jest przekazywany między klientem a serwerem dla każdego żądania. Identyfikator połączenia jest unikatowym identyfikatorem generowanym losowo przez serwer podczas tworzenia nowego połączenia i jest utrwalany przez czas trwania połączenia. Nazwa użytkownika jest dostarczana przez mechanizm uwierzytelniania dla aplikacji internetowej. Token połączenia jest chroniony za pomocą szyfrowania i podpisu cyfrowego.

Diagram systemu tokenów połączenia, przedstawiający relację między klientem, serwerem, systemem uwierzytelniania i tokenem połączenia.

Dla każdego żądania serwer weryfikuje zawartość tokenu, aby upewnić się, że żądanie pochodzi od określonego użytkownika. Nazwa użytkownika musi odpowiadać identyfikatorowi połączenia. Sprawdzając zarówno identyfikator połączenia, jak i nazwę użytkownika, usługa SignalR uniemożliwia złośliwemu użytkownikowi łatwe personifikację innego użytkownika. Jeśli serwer nie może zweryfikować tokenu połączenia, żądanie zakończy się niepowodzeniem.

Diagram systemu tokenów połączenia przedstawiający relację między klientem, serwerem i zapisanym tokenem.

Ponieważ identyfikator połączenia jest częścią procesu weryfikacji, nie należy ujawniać identyfikatora połączenia jednego użytkownika innym użytkownikom lub przechowywać wartość na kliencie, na przykład w pliku cookie.

Ponowne dołączanie grup podczas ponownego nawiązywania połączenia

Domyślnie aplikacja SignalR automatycznie przypisze użytkownika do odpowiednich grup podczas ponownego nawiązywania połączenia z powodu tymczasowego zakłóceń, na przykład po usunięciu połączenia i ponownym nawiązaniu połączenia przed przekroczeniem limitu czasu połączenia. Podczas ponownego nawiązywania połączenia klient przekazuje token grupy, który zawiera identyfikator połączenia i przypisane grupy. Token grupy jest podpisany cyfrowo i zaszyfrowany. Klient zachowuje ten sam identyfikator połączenia po ponownym połączeniu; w związku z tym identyfikator połączenia przekazany z ponownie połączonego klienta musi być zgodny z poprzednim identyfikatorem połączenia używanym przez klienta. Ta weryfikacja uniemożliwia złośliwemu użytkownikowi przekazywanie żądań dołączenia do nieautoryzowanych grup podczas ponownego nawiązywania połączenia.

Należy jednak pamiętać, że token grupy nie wygasa. Jeśli użytkownik należał do grupy w przeszłości, ale został zakazany z tej grupy, ten użytkownik może być w stanie naśladować token grupy, który zawiera zabronione grupy. Jeśli musisz bezpiecznie zarządzać użytkownikami należącymi do tych grup, musisz przechowywać te dane na serwerze, na przykład w bazie danych. Następnie dodaj logikę do aplikacji, która weryfikuje na serwerze, czy użytkownik należy do grupy. Aby zapoznać się z przykładem weryfikowania członkostwa w grupie, zobacz Praca z grupami.

Automatyczne ponowne dołączanie grup ma zastosowanie tylko wtedy, gdy połączenie zostanie ponownie nawiązane po tymczasowym zakłóceniu. Jeśli użytkownik rozłącza się, przechodząc z dala od aplikacji lub ponownie uruchamianą aplikację, aplikacja musi obsługiwać sposób dodawania tego użytkownika do odpowiednich grup. Aby uzyskać więcej informacji, zobacz Praca z grupami.

Jak usługa SignalR uniemożliwia fałszerzowaniu żądań między lokacjami

Fałszerzować żądania między witrynami (CSRF) jest atakiem, w którym złośliwa witryna wysyła żądanie do podatnej na zagrożenia lokacji, w której użytkownik jest obecnie zalogowany. Usługa SignalR uniemożliwia csrF, co sprawia, że jest bardzo mało prawdopodobne, aby złośliwa witryna utworzyła prawidłowe żądanie dla aplikacji SignalR.

Opis ataku CSRF

Oto przykład ataku CSRF:

  1. Użytkownik loguje się do www.example.comusługi przy użyciu uwierzytelniania formularzy.

  2. Serwer uwierzytelnia użytkownika. Odpowiedź z serwera zawiera plik cookie uwierzytelniania.

  3. Bez wylogowywania użytkownik odwiedza złośliwą witrynę internetową. Ta złośliwa witryna zawiera następujący formularz 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>
    

    Zwróć uwagę, że akcja formularza publikuje w witrynie podatnej na zagrożenia, a nie złośliwą witrynę. Jest to część CSRF "cross-site".

  4. Użytkownik kliknie przycisk Prześlij. Przeglądarka zawiera plik cookie uwierzytelniania z żądaniem.

  5. Żądanie jest uruchamiane na serwerze example.com z kontekstem uwierzytelniania użytkownika i może wykonywać wszystkie czynności, które może wykonać uwierzytelniony użytkownik.

Mimo że ten przykład wymaga kliknięcia przycisku formularza przez użytkownika, złośliwa strona może równie łatwo uruchomić skrypt, który wysyła żądanie AJAX do aplikacji SignalR. Ponadto użycie protokołu SSL nie zapobiega atakowi CSRF, ponieważ złośliwa witryna może wysłać żądanie "https://".

Zazwyczaj ataki CSRF są możliwe w przypadku witryn internetowych, które używają plików cookie do uwierzytelniania, ponieważ przeglądarki wysyłają wszystkie odpowiednie pliki cookie do docelowej witryny sieci Web. Jednak ataki CSRF nie są ograniczone do wykorzystywania plików cookie. Na przykład uwierzytelnianie podstawowe i szyfrowane jest również podatne na zagrożenia. Po zalogowaniu się użytkownika za pomocą uwierzytelniania podstawowego lub szyfrowego przeglądarka automatycznie wysyła poświadczenia do momentu zakończenia sesji.

Środki zaradcze CSRF podjęte przez usługę SignalR

Usługa SignalR wykonuje następujące kroki, aby zapobiec tworzeniu prawidłowych żądań do aplikacji SignalR przez złośliwą witrynę. Te kroki są wykonywane domyślnie i nie wymagają żadnej akcji w kodzie.

  • Wyłączanie żądań między domenami
    Domyślnie żądania między domenami są wyłączone w aplikacji SignalR, aby uniemożliwić użytkownikom wywoływanie punktu końcowego usługi SignalR z domeny zewnętrznej. Każde żądanie pochodzące z domeny zewnętrznej jest automatycznie uznawane za nieprawidłowe i jest blokowane. Zaleca się zachowanie tego domyślnego zachowania; w przeciwnym razie złośliwa witryna może skłonić użytkowników do wysyłania poleceń do witryny. Jeśli chcesz użyć żądań między domenami, zobacz Jak ustanowić połączenie między domenami .
  • Przekazywanie tokenu połączenia w parametrach zapytania, a nie pliku cookie
    Usługa SignalR przekazuje token połączenia jako wartość parametrów zapytania, a nie jako plik cookie. Nie przechowując tokenu połączenia jako pliku cookie, token połączenia nie jest przypadkowo przekazywany przez przeglądarkę po napotkaniu złośliwego kodu. Ponadto token połączenia nie jest utrwalany poza bieżącym połączeniem. W związku z tym złośliwy użytkownik nie może wysłać żądania w ramach poświadczeń uwierzytelniania innego użytkownika.
  • Weryfikowanie tokenu połączenia
    Zgodnie z opisem w sekcji Token połączenia serwer wie, który identyfikator połączenia jest skojarzony z każdym uwierzytelnionymi użytkownikami. Serwer nie przetwarza żadnych żądań z identyfikatora połączenia, który nie jest zgodny z nazwą użytkownika. Jest mało prawdopodobne, aby złośliwy użytkownik mógł odgadnąć prawidłowe żądanie, ponieważ złośliwy użytkownik musiałby znać nazwę użytkownika i bieżący losowo wygenerowany identyfikator połączenia. Ten identyfikator połączenia staje się nieprawidłowy zaraz po zakończeniu połączenia. Użytkownicy anonimowi nie powinni mieć dostępu do żadnych poufnych informacji.

Zalecenia dotyczące zabezpieczeń usługi SignalR

Protokół Secure Socket Layer (SSL)

Protokół SSL używa szyfrowania w celu zabezpieczenia transportu danych między klientem a serwerem. Jeśli aplikacja SignalR przesyła poufne informacje między klientem a serwerem, użyj protokołu SSL do transportu. Aby uzyskać więcej informacji na temat konfigurowania protokołu SSL, zobacz How to set up SSL on IIS 7 (Jak skonfigurować protokół SSL w usługach IIS 7).

Nie używaj grup jako mechanizmu zabezpieczeń

Grupy to wygodny sposób zbierania powiązanych użytkowników, ale nie są bezpiecznym mechanizmem ograniczania dostępu do poufnych informacji. Jest to szczególnie istotne, gdy użytkownicy mogą automatycznie ponownie dołączać grupy podczas ponownego nawiązywania połączenia. Zamiast tego rozważ dodanie uprzywilejowanych użytkowników do roli i ograniczenie dostępu do metody centrum tylko do członków tej roli. Aby zapoznać się z przykładem ograniczania dostępu na podstawie roli, zobacz Uwierzytelnianie i autoryzacja dla usługi SignalR Hubs. Aby zapoznać się z przykładem sprawdzania dostępu użytkowników do grup podczas ponownego nawiązywania połączenia, zobacz Praca z grupami.

Bezpieczne obsługa danych wejściowych od klientów

Wszystkie dane wejściowe od klientów przeznaczonych do emisji do innych klientów muszą być zakodowane w celu zapewnienia, że złośliwy użytkownik nie wysyła skryptu do innych użytkowników. Najlepiej kodować komunikaty na odbierających klientach, a nie na serwerze, ponieważ aplikacja SignalR może mieć wiele różnych typów klientów. W związku z tym kodowanie HTML działa dla klienta internetowego, ale nie w przypadku innych typów klientów. Na przykład metoda klienta sieci Web umożliwiająca wyświetlenie komunikatu czatu bezpiecznie obsłuży nazwę użytkownika i komunikat przez wywołanie html() funkcji .

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

Uzgadnianie zmiany stanu użytkownika z aktywnym połączeniem

Jeśli stan uwierzytelniania użytkownika zmieni się, gdy istnieje aktywne połączenie, zostanie wyświetlony błąd informujący o błędzie "Tożsamość użytkownika nie może ulec zmianie podczas aktywnego połączenia usługi SignalR". W takim przypadku aplikacja powinna ponownie nawiązać połączenie z serwerem, aby upewnić się, że identyfikator połączenia i nazwa użytkownika są skoordynowane. Jeśli na przykład aplikacja umożliwia użytkownikowi wylogowanie się, gdy istnieje aktywne połączenie, nazwa użytkownika połączenia nie będzie już zgodna z nazwą przekazaną dla następnego żądania. Należy zatrzymać połączenie, zanim użytkownik wyloguje się, a następnie ponownie go uruchomić.

Należy jednak pamiętać, że większość aplikacji nie będzie musiała ręcznie zatrzymywać i uruchamiać połączenia. Jeśli aplikacja przekierowuje użytkowników do oddzielnej strony po wylogowaniu, na przykład domyślne zachowanie w aplikacji Web Forms lub aplikacji MVC albo odświeża bieżącą stronę po wylogowaniu, aktywne połączenie zostanie automatycznie rozłączone i nie wymaga żadnej dodatkowej akcji.

W poniższym przykładzie pokazano, jak zatrzymać i uruchomić połączenie po zmianie stanu użytkownika.

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

Lub stan uwierzytelniania użytkownika może ulec zmianie, jeśli witryna używa przesuwanego wygaśnięcia z uwierzytelnianiem formularzy i nie ma żadnych działań w celu zachowania poprawności pliku cookie uwierzytelniania. W takim przypadku użytkownik zostanie wylogowany, a nazwa użytkownika nie będzie już zgodna z nazwą użytkownika w tokenie połączenia. Ten problem można rozwiązać, dodając skrypt, który okresowo żąda zasobu na serwerze internetowym, aby zachować prawidłowy plik cookie uwierzytelniania. W poniższym przykładzie pokazano, jak zażądać zasobu co 30 minut.

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

Automatycznie generowane pliki serwera proxy JavaScript

Jeśli nie chcesz uwzględniać wszystkich centrów i metod w pliku serwera proxy języka JavaScript dla każdego użytkownika, możesz wyłączyć automatyczne generowanie pliku. Możesz wybrać tę opcję, jeśli masz wiele centrów i metod, ale nie chcesz, aby każdy użytkownik wiedział o wszystkich metodach. Automatyczne generowanie można wyłączyć, ustawiając wartość EnableJavaScriptProxies na false.

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableJavaScriptProxies = false;
RouteTable.Routes.MapHubs("/signalr", hubConfiguration);

Aby uzyskać więcej informacji na temat plików serwera proxy języka JavaScript, zobacz Wygenerowany serwer proxy i co robi za Ciebie.

Wyjątki

Należy unikać przekazywania obiektów wyjątków do klientów, ponieważ obiekty mogą uwidaczniać poufne informacje klientom. Zamiast tego wywołaj metodę na kliencie, która wyświetla odpowiedni komunikat o błędzie.

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