Partilhar via


Introdução à segurança do SignalR (SignalR 1.x)

por Patrick Fletcher, Tom FitzMacken

Aviso

Esta documentação não é para a versão mais recente do SignalR. Dê uma olhada em ASP.NET Core SignalR.

Este artigo descreve os problemas de segurança que você deve considerar ao desenvolver um aplicativo SignalR.

Visão geral

Este documento contém as seguintes seções:

Conceitos de segurança do SignalR

Autenticação e autorização

O SignalR foi projetado para ser integrado à estrutura de autenticação existente de um aplicativo. Ele não fornece recursos para autenticar usuários. Em vez disso, você autentica usuários como normalmente faria em seu aplicativo e, em seguida, trabalha com os resultados da autenticação em seu código SignalR. Por exemplo, você pode autenticar seus usuários com ASP.NET autenticação de formulários e, em seguida, no hub, impor quais usuários ou funções estão autorizados a chamar um método. No hub, você também pode passar informações de autenticação, como nome de usuário ou se um usuário pertence a uma função, para o cliente.

O SignalR fornece o atributo Authorize para especificar quais usuários têm acesso a um hub ou método. Você aplica o atributo Authorize a um hub ou a métodos específicos em um hub. Sem o atributo Authorize, todos os métodos públicos no hub estão disponíveis para um cliente conectado ao hub. Para obter mais informações sobre hubs, consulte Autenticação e autorização para hubs signalr.

O Authorize atributo é usado apenas com hubs. Para impor regras de autorização ao usar um PersistentConnection , você deve substituir o AuthorizeRequest método . Para obter mais informações sobre conexões persistentes, consulte Autenticação e autorização para conexões persistentes do SignalR.

Token de conexão

O SignalR reduz o risco de executar comandos mal-intencionados validando a identidade do remetente. Um token de conexão, contendo a ID de conexão e o nome de usuário para usuários autenticados, é passado entre o cliente e o servidor para cada solicitação. A ID da conexão é um identificador exclusivo gerado aleatoriamente pelo servidor quando uma nova conexão é criada e persiste durante a conexão. O nome de usuário é fornecido pelo mecanismo de autenticação para o aplicativo Web. O token de conexão é protegido com criptografia e uma assinatura digital.

Diagrama Sistema de Token de Conexão, mostrando a relação entre o Cliente, o Servidor, o Sistema de Autenticação e o Token de Conexão.

Para cada solicitação, o servidor valida o conteúdo do token para garantir que a solicitação seja proveniente do usuário especificado. O nome de usuário deve corresponder à ID da conexão. Ao validar a ID de conexão e o nome de usuário, o SignalR impede que um usuário mal-intencionado represente facilmente outro usuário. Se o servidor não puder validar o token de conexão, a solicitação falhará.

Diagrama do sistema de Token de Conexão, mostrando a relação entre o Cliente, o Servidor e o Token Salvo.

Como a ID de conexão faz parte do processo de verificação, você não deve revelar a ID de conexão de um usuário para outros usuários ou armazenar o valor no cliente, como em um cookie.

Reencontrando grupos ao se reconectar

Por padrão, o aplicativo SignalR reatribuirá automaticamente um usuário aos grupos apropriados ao se reconectar de uma interrupção temporária, como quando uma conexão é descartada e restabelecida antes que a conexão chegue ao limite. Ao se reconectar, o cliente passa um token de grupo que inclui a ID de conexão e os grupos atribuídos. O token de grupo é assinado digitalmente e criptografado. O cliente mantém a mesma ID de conexão após uma reconexão; portanto, a ID de conexão passada do cliente reconectado deve corresponder à ID de conexão anterior usada pelo cliente. Essa verificação impede que um usuário mal-intencionado passe solicitações para ingressar em grupos não autorizados ao se reconectar.

No entanto, é importante observar que o token de grupo não expira. Se um usuário pertencia a um grupo no passado, mas foi banido desse grupo, esse usuário pode ser capaz de imitar um token de grupo que inclui o grupo proibido. Se você precisar gerenciar com segurança quais usuários pertencem a quais grupos, será necessário armazenar esses dados no servidor, como em um banco de dados. Em seguida, adicione lógica ao aplicativo que verifica no servidor se um usuário pertence a um grupo. Para obter um exemplo de verificação da associação ao grupo, consulte Trabalhando com grupos.

A reingressão automática de grupos só se aplica quando uma conexão é reconectada após uma interrupção temporária. Se um usuário se desconectar navegando para longe do aplicativo ou o aplicativo for reiniciado, seu aplicativo deverá lidar com como adicionar esse usuário aos grupos corretos. Para obter mais informações, consulte Trabalhando com grupos.

Como o SignalR impede a falsificação de solicitação entre sites

CSRF (Solicitação Entre Sites Forjada) é um ataque em que um site mal-intencionado envia uma solicitação para um site vulnerável em que o usuário está conectado no momento. O SignalR impede o CSRF, tornando extremamente improvável que um site mal-intencionado crie uma solicitação válida para seu aplicativo SignalR.

Descrição do ataque CSRF

Aqui está um exemplo de um ataque CSRF:

  1. Um usuário faz logon no www.example.com, usando a autenticação de formulários.

  2. O servidor autentica o usuário. A resposta do servidor inclui um cookie de autenticação.

  3. Sem fazer logon, o usuário visita um site mal-intencionado. Este site mal-intencionado contém o seguinte formulário 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>
    

    Observe que a ação de formulário é posta no site vulnerável, não no site mal-intencionado. Essa é a parte "entre sites" do CSRF.

  4. O usuário clica no botão Enviar. O navegador inclui o cookie de autenticação com a solicitação.

  5. A solicitação é executada no servidor example.com com o contexto de autenticação do usuário e pode fazer qualquer coisa que um usuário autenticado possa fazer.

Embora este exemplo exija que o usuário clique no botão de formulário, a página mal-intencionada pode executar facilmente um script que envia uma solicitação AJAX para seu aplicativo SignalR. Além disso, o uso de SSL não impede um ataque CSRF, pois o site mal-intencionado pode enviar uma solicitação de "https://".

Normalmente, ataques CSRF são possíveis em sites que usam cookies para autenticação, pois os navegadores enviam todos os cookies relevantes para o site de destino. No entanto, os ataques CSRF não se limitam à exploração de cookies. Por exemplo, a autenticação Básica e Digest também são vulneráveis. Depois que um usuário faz logon com a autenticação Básica ou Digest, o navegador envia automaticamente as credenciais até que a sessão termine.

Mitigações de CSRF obtidas pelo SignalR

O SignalR executa as etapas a seguir para impedir que um site mal-intencionado crie solicitações válidas para seu aplicativo SignalR. Essas etapas são executadas por padrão e não exigem nenhuma ação em seu código.

  • Desabilitar solicitações entre domínios
    Por padrão, as solicitações entre domínios são desabilitadas em um aplicativo SignalR para impedir que os usuários chamem um ponto de extremidade do SignalR de um domínio externo. Qualquer solicitação proveniente de um domínio externo é automaticamente considerada inválida e bloqueada. É recomendável manter esse comportamento padrão; caso contrário, um site mal-intencionado poderia enganar os usuários para enviar comandos para seu site. Se você precisar usar solicitações entre domínios, consulte Como estabelecer uma conexão entre domínios .
  • Passar o token de conexão na cadeia de caracteres de consulta, não no cookie
    O SignalR passa o token de conexão como um valor de cadeia de caracteres de consulta, em vez de como um cookie. Ao não armazenar o token de conexão como um cookie, o token de conexão não é encaminhado inadvertidamente pelo navegador quando um código mal-intencionado é encontrado. Além disso, o token de conexão não é persistente além da conexão atual. Portanto, um usuário mal-intencionado não pode fazer uma solicitação sob as credenciais de autenticação de outro usuário.
  • Verificar o token de conexão
    Conforme descrito na seção Token de conexão , o servidor sabe qual ID de conexão está associada a cada usuário autenticado. O servidor não processa nenhuma solicitação de uma ID de conexão que não corresponda ao nome de usuário. É improvável que um usuário mal-intencionado possa adivinhar uma solicitação válida porque o usuário mal-intencionado teria que saber o nome de usuário e a ID de conexão gerada aleatoriamente atualmente. Essa ID de conexão torna-se inválida assim que a conexão é encerrada. Os usuários anônimos não devem ter acesso a nenhuma informação confidencial.

Recomendações de segurança do SignalR

Protocolo SSL

O protocolo SSL usa criptografia para proteger o transporte de dados entre um cliente e um servidor. Se o aplicativo SignalR transmitir informações confidenciais entre o cliente e o servidor, use SSL para o transporte. Para obter mais informações sobre como configurar o SSL, consulte Como configurar o SSL no IIS 7.

Não usar grupos como um mecanismo de segurança

Os grupos são uma maneira conveniente de coletar usuários relacionados, mas não são um mecanismo seguro para limitar o acesso a informações confidenciais. Isso é especialmente verdadeiro quando os usuários podem reingressar automaticamente em grupos durante uma reconexão. Em vez disso, considere adicionar usuários privilegiados a uma função e limitar o acesso a um método de hub a apenas membros dessa função. Para obter um exemplo de restrição de acesso com base em uma função, consulte Autenticação e autorização para Hubs do SignalR. Para obter um exemplo de verificação do acesso do usuário a grupos ao se reconectar, consulte Trabalhando com grupos.

Manipulando com segurança a entrada de clientes

Todas as entradas de clientes destinadas à difusão para outros clientes devem ser codificadas para garantir que um usuário mal-intencionado não envie scripts para outros usuários. É melhor codificar mensagens nos clientes receptores em vez do servidor, pois seu aplicativo SignalR pode ter muitos tipos diferentes de clientes. Portanto, a codificação HTML funciona para um cliente Web, mas não para outros tipos de clientes. Por exemplo, um método de cliente Web para exibir uma mensagem de chat trataria com segurança o nome de usuário e a mensagem chamando a html() função .

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

Reconciliando uma alteração no status do usuário com uma conexão ativa

Se a autenticação de um usuário status for alterada enquanto houver uma conexão ativa, o usuário receberá um erro informando: "A identidade do usuário não pode ser alterada durante uma conexão do SignalR ativa". Nesse caso, seu aplicativo deve se conectar novamente ao servidor para garantir que a ID de conexão e o nome de usuário sejam coordenados. Por exemplo, se o aplicativo permitir que o usuário faça logoff enquanto houver uma conexão ativa, o nome de usuário da conexão não corresponderá mais ao nome passado para a próxima solicitação. Você desejará interromper a conexão antes que o usuário faça logoff e reiniciá-la.

No entanto, é importante observar que a maioria dos aplicativos não precisará parar e iniciar a conexão manualmente. Se o aplicativo redirecionar os usuários para uma página separada após o logoff, como o comportamento padrão em um aplicativo Web Forms ou aplicativo MVC, ou atualizar a página atual após o logoff, a conexão ativa será desconectada automaticamente e não exigirá nenhuma ação adicional.

O exemplo a seguir mostra como parar e iniciar uma conexão quando o usuário status foi alterado.

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

Ou, o status de autenticação do usuário poderá mudar se o site usar a expiração deslizante com a Autenticação de Formulários e não houver nenhuma atividade para manter o cookie de autenticação válido. Nesse caso, o usuário será desconectado e o nome de usuário não corresponderá mais ao nome de usuário no token de conexão. Você pode corrigir esse problema adicionando algum script que solicita periodicamente um recurso no servidor Web para manter o cookie de autenticação válido. O exemplo a seguir mostra como solicitar um recurso a cada 30 minutos.

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

Arquivos de proxy JavaScript gerados automaticamente

Se você não quiser incluir todos os hubs e métodos no arquivo proxy JavaScript para cada usuário, poderá desabilitar a geração automática do arquivo. Você poderá escolher essa opção se tiver vários hubs e métodos, mas não quiser que todos os usuários estejam cientes de todos os métodos. Desabilite a geração automática definindo EnableJavaScriptProxies comofalse.

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

Para obter mais informações sobre os arquivos de proxy JavaScript, consulte O proxy gerado e o que ele faz por você.

Exceções

Você deve evitar passar objetos de exceção para clientes porque os objetos podem expor informações confidenciais aos clientes. Em vez disso, chame um método no cliente que exibe a mensagem de erro relevante.

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