Compartir a través de


Introducción a la seguridad de SignalR (SignalR 1.x)

por Patrick Fletcher, Tom FitzMacken

Advertencia

Esta documentación no es para la última versión de SignalR. Eche un vistazo a ASP.NET Core SignalR.

En este artículo se describen los problemas de seguridad que debe tener en cuenta al desarrollar una aplicación SignalR.

Información general

Este documento contiene las siguientes secciones:

Conceptos de seguridad de SignalR

Autenticación y autorización

SignalR está diseñado para integrarse en la estructura de autenticación existente para una aplicación. No proporciona ninguna característica para autenticar a los usuarios. En su lugar, usted autentica a los usuarios como lo haría normalmente en su aplicación, y después trabaja con los resultados de la autenticación en su código de SignalR. Por ejemplo, puede autenticar a sus usuarios con la autenticación de formularios ASP.NET y, después, en su hub, imponer qué usuarios o roles están autorizados a llamar a un método. En su hub, también puede pasar información de autenticación, como el nombre de usuario o si un usuario pertenece a un rol, al cliente.

SignalR proporciona el atributo Authorize para especificar qué usuarios tienen acceso a un hub o a un método. Puede aplicar el atributo Authorize a un hub o a determinados métodos de un hub. Sin el atributo Authorize, todos los métodos públicos del hub están disponibles para un cliente que esté conectado al mismo. Para más información sobre los hubs, consulte Autenticación y autorización para SignalR Hubs.

El atributo Authorize se usa solo con hubs. Para aplicar reglas de autorización al usar un PersistentConnection debe invalidar el método AuthorizeRequest. Para más información sobre las conexiones persistentes, consulte Autenticación y autorización para las conexiones persistentes de SignalR.

Token de conexión

SignalR mitiga el riesgo de ejecutar comandos maliciosos validando la identidad del remitente. Un token de conexión, que contiene el identificador de conexión y el nombre de usuario para los usuarios autenticados, se pasa entre el cliente y el servidor para cada solicitud. El identificador de conexión es un identificador único generado aleatoriamente por el servidor cuando se crea una nueva conexión y se conserva durante la duración de la conexión. El nombre de usuario lo proporciona el mecanismo de autenticación de la aplicación web. El token de conexión está protegido con cifrado y una firma digital.

Diagram Connection Token system, showing the relationship between the Client, Server, Authentication System, and Connection Token.

En cada solicitud, el servidor valida el contenido del token para asegurarse de que la solicitud procede del usuario especificado. El nombre de usuario debe corresponder al id. de conexión. Al validar tanto el id. de conexión como el nombre de usuario, SignalR impide que un usuario malintencionado se haga pasar fácilmente por otro usuario. Si el servidor no puede validar el token de conexión, la solicitud falla.

Diagram of the Connection Token system, showing the relationship between the Client, Server, and Saved Token.

Dado que el id. de conexión forma parte del proceso de verificación, no debe revelar el id. de conexión de un usuario a otros usuarios ni almacenar el valor en el cliente, como en una cookie.

Volver a unir grupos al volver a conectarse

De manera predeterminada, la aplicación SignalR reasignará automáticamente a un usuario a los grupos adecuados cuando se vuelva a conectar tras una interrupción temporal, como cuando se pierde una conexión y se vuelve a establecer antes de que se agote el tiempo de conexión. Al volver a conectarse, el cliente pasa un token de grupo que incluye el id. de conexión y los grupos asignados. El token de grupo está firmado digitalmente y cifrado. El cliente conserva el mismo id. de conexión después de una reconexión; por lo tanto, el id. de conexión que se pase desde el cliente reconectado debe coincidir con el id. de conexión anterior usado por el cliente. Esta verificación impide que un usuario malintencionado pase solicitudes para unirse a grupos no autorizados al volver a conectarse.

Sin embargo, es importante tener en cuenta que el token de grupo no expira. Si un usuario perteneció a un grupo en el pasado, pero se le prohibió la entrada a ese grupo, ese usuario puede ser capaz de imitar un token de grupo que incluya al grupo prohibido. Si necesita administrar de forma segura qué usuarios pertenecen a qué grupos, deberá almacenar esos datos en el servidor, por ejemplo en una base de datos. Después, agregue a su aplicación una lógica que verifique en el servidor si un usuario pertenece a un grupo. Para ver un ejemplo de verificación de la pertenencia a un grupo, consulte Trabajo con grupos.

La reincorporación automática a los grupos solo se aplica cuando se reconecta una conexión tras una interrupción temporal. Si un usuario se desconecta saliendo de la aplicación o la aplicación se reinicia, su aplicación debe controlar cómo agregar ese usuario a los grupos correctos. Para más información, consulte Trabajar con grupos.

Cómo evita SignalR la falsificación de solicitud entre sitios

La falsificación de solicitud entre sitios (CSRF) es un ataque en el que un sitio malicioso envía una solicitud a un sitio vulnerable en el que el usuario ha iniciado sesión. SignalR previene CSRF haciendo extremadamente improbable que un sitio malicioso cree una solicitud válida para su aplicación de SignalR.

Descripción del ataque CSRF

Este es un ejemplo de ataque CSRF:

  1. Un usuario inicia sesión en www.example.com, usando la autenticación de formularios.

  2. El servidor autentica al usuario. La respuesta del servidor incluye una cookie de autenticación.

  3. Sin cerrar la sesión, el usuario visita un sitio web malicioso. Este sitio malicioso contiene el siguiente formulario 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 la acción del formulario publica en el sitio vulnerable, no en el sitio malicioso. Esta es la parte "entre sitios" de CSRF.

  4. El usuario hace clic en el botón Enviar. El explorador incluye la cookie de autenticación con la solicitud.

  5. La solicitud se ejecuta en el servidor de example.com con el contexto de autenticación del usuario, y puede hacer cualquier cosa que un usuario autenticado esté autorizado a hacer.

Aunque este ejemplo requiere que el usuario haga clic en el botón del formulario, la página maliciosa podría con la misma facilidad ejecutar un script que envíe una solicitud AJAX a su aplicación SignalR. Además, usar SSL no evita un ataque CSRF, porque el sitio malicioso puede enviar una solicitud "https://".

Normalmente, los ataques CSRF son posibles contra sitios web que usan cookies para la autenticación, porque los exploradores envían todas las cookies relevantes al sitio web de destino. Sin embargo, los ataques CSRF no se limitan a aprovechar las vulnerabilidades de las cookies. Por ejemplo, la autenticación básica e implícita también son vulnerables. Después de que un usuario inicie sesión con autenticación básica o autenticación implícita, el explorador envía automáticamente las credenciales hasta que finaliza la sesión.

Mitigación de CSRF adoptada por SignalR

SignalR toma las siguientes medidas para evitar que un sitio malicioso cree solicitudes válidas a su aplicación de SignalR. Estos pasos se realizan de manera predeterminada y no requieren ninguna acción en su código.

  • Deshabilitar solicitudes entre dominios
    De manera predeterminada, las solicitudes entre dominios están deshabilitadas en una aplicación SignalR para evitar que los usuarios llamen a un punto de conexión de SignalR desde un dominio externo. Cualquier solicitud que provenga de un dominio externo se considera automáticamente inválida y se bloquea. Se recomienda mantener este comportamiento predeterminado; de lo contrario, un sitio malicioso podría engañar a los usuarios para que envíen comandos a su sitio. Si necesita usar solicitudes entre dominios, consulte Cómo establecer una conexión entre dominios.
  • Pasar el token de conexión en la cadena de consulta, no en la cookie
    SignalR pasa el token de conexión como un valor de cadena de consulta, en lugar de como una cookie. Al no almacenar el token de conexión como una cookie, el explorador no lo reenvía inadvertidamente cuando se encuentra un código malintencionado. Además, el token de conexión no se conserva más allá de la conexión actual. Por lo tanto, un usuario malintencionado no puede realizar una solicitud con las credenciales de autenticación de otro usuario.
  • Verificar el token de conexión
    Como se describe en la sección Token de conexión, el servidor sabe qué id. de conexión está asociado a cada usuario autenticado. El servidor no procesa ninguna solicitud procedente de un id. de conexión que no coincida con el nombre de usuario. Es poco probable que un usuario malintencionado pueda adivinar una solicitud válida porque tendría que conocer el nombre de usuario y el id. de conexión actual generado aleatoriamente. Ese id. de conexión deja de ser válido en cuanto finaliza la conexión. Los usuarios anónimos no deberían tener acceso a ninguna información confidencial.

Recomendaciones de seguridad de SignalR

Protocolo de Capas de sockets seguros (SSL)

El protocolo SSL usa el cifrado para proteger el transporte de datos entre un cliente y un servidor. Si su aplicación de SignalR transmite información confidencial entre el cliente y el servidor, use SSL para el transporte. Para más información sobre cómo configurar SSL, consulte Cómo configurar SSL en IIS 7.

No use grupos como mecanismo de seguridad

Los grupos son una forma cómoda de agrupar a los usuarios relacionados, pero no son un mecanismo seguro para limitar el acceso a la información confidencial. Esto es especialmente cierto cuando los usuarios pueden volver a unirse automáticamente a los grupos durante una reconexión. En su lugar, considere la posibilidad de añadir usuarios con privilegios a un rol y limitar el acceso a un método de hub solo a los miembros de ese rol. Para ver un ejemplo de restricción de acceso basada en un rol, consulte Autenticación y autorización para SignalR Hubs. Para ver un ejemplo de comprobación del acceso del usuario a los grupos al volver a conectarse, consulte Trabajar con grupos.

Control seguro de las entradas de los clientes

Todas las entradas de los clientes destinadas a ser difundidas a otros clientes deben codificarse para asegurar que un usuario malintencionado no envíe scripts a otros usuarios. Es mejor codificar los mensajes en los clientes receptores que en el servidor, porque su aplicación de SignalR puede tener muchos tipos diferentes de clientes. Por lo tanto, la codificación HTML funciona para un cliente web, pero no para otros tipos de clientes. Por ejemplo, un método de cliente web para mostrar un mensaje de chat controlaría con seguridad el nombre del usuario y el mensaje llamando a la función 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>');
};

Reconciliación de un cambio en el estado del usuario con una conexión activa

Si el estado de autenticación de un usuario cambia mientras existe una conexión activa, el usuario recibirá un error que dice: "La identidad del usuario no puede cambiar durante una conexión de SignalR activa." En ese caso, su aplicación debería volver a conectarse al servidor para asegurarse de que el id. de conexión y el nombre de usuario están coordinados. Por ejemplo, si su aplicación permite al usuario desconectarse mientras existe una conexión activa, el nombre de usuario de la conexión ya no coincidirá con el nombre que se pase para la siguiente solicitud. Querrá detener la conexión antes de que el usuario cierre la sesión y después reiniciarla.

Sin embargo, es importante tener en cuenta que la mayoría de las aplicaciones no necesitarán detener e iniciar manualmente la conexión. Si su aplicación redirige a los usuarios a una página distinta después de cerrar la sesión, como el comportamiento predeterminado en una aplicación de Web Forms o una aplicación de MVC, o actualiza la página actual después de cerrar la sesión, la conexión activa se desconecta automáticamente y no requiere ninguna acción adicional.

El siguiente ejemplo muestra cómo detener e iniciar una conexión cuando ha cambiado el estado del usuario.

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

O bien, el estado de autenticación del usuario puede cambiar si su sitio usa la expiración variable con la autenticación de formularios, y no hay actividad para mantener válida la cookie de autenticación. En ese caso, se cerrará la sesión del usuario y su nombre dejará de coincidir con el nombre de usuario que figura en el token de conexión. Puede solucionar este problema añadiendo algún script que solicite periódicamente un recurso en el servidor web para mantener válida la cookie de autenticación. El siguiente ejemplo muestra cómo solicitar un recurso cada 30 minutos.

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

Archivos proxy de JavaScript generados automáticamente

Si no quiere incluir todos los hubs y métodos en el archivo proxy de JavaScript para cada usuario, puede deshabilitar la generación automática del archivo. Puede elegir esta opción si tiene varios hubs y métodos, pero no quiere que todos los usuarios conozcan todos los métodos. Para deshabilitar la generación automática, establezca EnableJavaScriptProxies en false.

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

Para más información sobre los archivos proxy de JavaScript, consulte El proxy generado y lo que hace por usted.

Excepciones

Debe evitar pasar objetos de excepción a los clientes porque los objetos pueden exponer información confidencial a los clientes. En su lugar, llame a un método en el cliente que muestre el mensaje de error correspondiente.

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