Consideraciones de seguridad en ASP.NET Core SignalR
Por Andrew Andrew Andrewton-Nurse
En este artículo se proporciona información sobre cómo proteger SignalR .
Uso compartido de recursos entre orígenes
El uso compartido de recursos entre orígenes (CORS) se puede usar para permitir conexiones entre SignalR orígenes en el explorador. Si el código JavaScript se hospeda en un dominio diferente de la aplicación, el SignalR middleware de CORS debe estar habilitado para permitir que JavaScript se conecte a la SignalR aplicación. Permitir solicitudes entre orígenes solo desde dominios de confianza o de control. Por ejemplo:
- El sitio se hospeda en
http://www.example.com - La SignalR aplicación se hospeda en
http://signalr.example.com
CORS debe configurarse en la SignalR aplicación para permitir solo el origen www.example.com .
Para obtener más información sobre cómo configurar CORS, vea Habilitar solicitudes entre orígenes (CORS). SignalRrequiere las siguientes directivas de CORS:
- Permita los orígenes esperados específicos. Permitir cualquier origen es posible, pero no es seguro ni recomendado.
- Se deben
GETpermitir los métodos HTTPPOSTy . - Se deben permitir credenciales para que cookie las sesiones temporales basadas en funcionen correctamente. Deben habilitarse incluso cuando no se usa la autenticación.
Sin embargo, en la versión 5.0 hemos proporcionado una opción en el cliente de TypeScript para no usar credenciales. La opción de no usar credenciales solo se debe usar cuando se sabe al 100 % que las credenciales como s no son necesarias en la aplicación ( las usa Azure App Service cuando se usan varios servidores para sesiones Cookie cookie permanentes).
Por ejemplo, la siguiente directiva de CORS permite que un cliente de explorador hospedado en acceda a SignalR https://example.com la aplicación SignalR hospedada en https://signalr.example.com :
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ... other middleware ...
// Make sure the CORS middleware is ahead of SignalR.
app.UseCors(builder =>
{
builder.WithOrigins("https://example.com")
.AllowAnyHeader()
.WithMethods("GET", "POST")
.AllowCredentials();
});
// ... other middleware ...
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ChatHub>("/chathub");
});
// ... other middleware ...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ... other middleware ...
// Make sure the CORS middleware is ahead of SignalR.
app.UseCors(builder =>
{
builder.WithOrigins("https://example.com")
.AllowAnyHeader()
.WithMethods("GET", "POST")
.AllowCredentials();
});
// ... other middleware ...
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chathub");
});
// ... other middleware ...
}
Restricción de origen de WebSocket
Las protecciones proporcionadas por CORS no se aplican a WebSockets. Para ver la restricción de origen en WebSockets, lea Restricción de origen de WebSockets.
Las protecciones proporcionadas por CORS no se aplican a WebSockets. Los exploradores no hacen lo siguiente:
- Efectúan solicitudes preparatorias CORS.
- Respetan las restricciones especificadas en los encabezados
Access-Controlal efectuar solicitudes de WebSocket.
En cambio, sí que envían el encabezado Origin al emitir solicitudes de WebSocket. Las aplicaciones deben configurarse para validar estos encabezados a fin de garantizar que solo se permitan los WebSockets procedentes de los orígenes esperados.
En ASP.NET Core 2.1 y versiones posteriores, la validación de encabezados se puede lograr mediante un middleware personalizado colocado antes de UseSignalR y el middleware de autenticación en Configure :
// In Startup, add a static field listing the allowed Origin values:
private static readonly HashSet<string> _allowedOrigins = new HashSet<string>()
{
// Add allowed origins here. For example:
"https://www.mysite.com",
"https://mysite.com",
};
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ... other middleware ...
// Validate Origin header on WebSocket requests to prevent unexpected cross-site
// WebSocket requests.
app.Use((context, next) =>
{
// Check for a WebSocket request.
if (string.Equals(context.Request.Headers["Upgrade"], "websocket"))
{
var origin = context.Request.Headers["Origin"];
// If there is an origin header, and the origin header doesn't match
// an allowed value:
if (!string.IsNullOrEmpty(origin) && !_allowedOrigins.Contains(origin))
{
// The origin is not allowed, reject the request
context.Response.StatusCode = (int) HttpStatusCode.Forbidden;
return Task.CompletedTask;
}
}
// The request is a valid Origin or not a WebSocket request, so continue.
return next();
});
// ... other middleware ...
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chathub");
});
// ... other middleware ...
}
Nota
El encabezado Origin está controlado por el cliente y, al igual que el encabezado Referer, se puede falsificar. Estos encabezados no deben usarse como mecanismo de autenticación.
ConnectionId
Exponer puede dar lugar a suplantación malintencionada si la versión del servidor o el ConnectionId SignalR cliente ASP.NET Core 2.2 o anterior. Si la SignalR versión del servidor y del cliente ASP.NET Core 3.0 o posterior, el en lugar de debe mantenerse en ConnectionToken ConnectionId secreto. no ConnectionToken se expone a propósito en ninguna API. Puede ser difícil asegurarse de que los clientes más antiguos no se conectan al servidor, por lo que incluso si la versión del servidor es SignalR SignalR ASP.NET Core 3.0 o posterior, no se debe ConnectionId exponer.
Registro de tokens de acceso
Al usar WebSockets o Server-Sent Eventos, el cliente del explorador envía el token de acceso en la cadena de consulta. La recepción del token de acceso a través de la cadena de consulta suele ser segura, ya que se usa el encabezado Authorization estándar. Use siempre HTTPS para garantizar una conexión segura de un extremo a otro entre el cliente y el servidor. Muchos servidores web registra la dirección URL de cada solicitud, incluida la cadena de consulta. El registro de las direcciones URL puede registrar el token de acceso. ASP.NET Core registra la dirección URL de cada solicitud de forma predeterminada, que incluirá la cadena de consulta. Por ejemplo:
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:5000/chathub?access_token=1234
Si le preocupa registrar estos datos con los registros del servidor, puede deshabilitar este registro por completo configurando el registrador en el nivel o superior (estos mensajes se escriben en Microsoft.AspNetCore.Hosting Warning el nivel Info ). Para obtener más información, vea Aplicar reglas de filtro de registro en el código. Si todavía desea registrar cierta información de solicitud, puede escribir un middleware para registrar los datos que necesita y filtrar el valor de la cadena de consulta access_token (si está presente).
Excepciones
Los mensajes de excepción generalmente se consideran datos confidenciales que no se deben revelar a un cliente. De forma predeterminada, no envía los detalles de una excepción producida por un método SignalR de concentrador al cliente. En su lugar, el cliente recibe un mensaje genérico en el que se indica que se ha producido un error. La entrega de mensajes de excepción al cliente se puede invalidar (por ejemplo, en desarrollo o prueba) con EnableDetailedErrors. Los mensajes de excepción no deben exponerse al cliente en aplicaciones de producción.
Administración de búfer
SignalR usa búferes por conexión para administrar los mensajes entrantes y salientes. De forma predeterminada, SignalR limita estos búferes a 32 KB. El mensaje más grande que un cliente o servidor puede enviar es de 32 KB. La memoria máxima consumida por una conexión para los mensajes es de 32 KB. Si los mensajes siempre son menores de 32 KB, puede reducir el límite, que:
- Impide que un cliente pueda enviar un mensaje mayor.
- El servidor nunca tendrá que asignar búferes grandes para aceptar mensajes.
Si los mensajes son mayores de 32 KB, puede aumentar el límite. Aumentar este límite significa:
- El cliente puede hacer que el servidor asigne búferes de memoria grandes.
- La asignación de servidores de búferes grandes puede reducir el número de conexiones simultáneas.
Hay límites para los mensajes entrantes y salientes, ambos se pueden configurar en el objeto HttpConnectionDispatcherOptions configurado en MapHub :
ApplicationMaxBufferSizerepresenta el número máximo de bytes del cliente que almacena en búfer el servidor. Si el cliente intenta enviar un mensaje mayor que este límite, es posible que se cierre la conexión.TransportMaxBufferSizerepresenta el número máximo de bytes que el servidor puede enviar. Si el servidor intenta enviar un mensaje (incluidos los valores devueltos de los métodos centrales) mayor que este límite, se producirá una excepción.
Establecer el límite en 0 deshabilita el límite. Al quitar el límite, un cliente puede enviar un mensaje de cualquier tamaño. Los clientes malintencionados que envían mensajes grandes pueden hacer que se asigne un exceso de memoria. El uso excesivo de memoria puede reducir significativamente el número de conexiones simultáneas.