How to secure Blazor Server against cross-origin attacks?

Rob Nelder 96 Reputation points
2021-06-01T10:56:44.03+00:00

I would like to ensure that a Blazor Server app is secure against cross-origin attacks, in particular against the SignalR hub that Blazor Server is using. The app in question has no need to enable any cross-origin connections as far as I am aware.

Microsoft have a Blazor Server Threat Mitigation document, that states the need for Cross-origin protection (see here: threat-mitigation). This document suggests that "opening a malicious WebSocket is also possible" and that:

  • Blazor Server apps can be accessed cross-origin unless additional
    measures are taken to prevent it. To disable cross-origin access,
    either disable CORS in the endpoint by adding the CORS middleware to
    the pipeline and adding the DisableCorsAttribute to the Blazor
    endpoint metadata or limit the set of allowed origins by configuring
    SignalR for cross-origin resource sharing.
    • If CORS is enabled, extra
      steps might be required to protect the app depending on the CORS
      configuration. If CORS is globally enabled, CORS can be disabled for
      the Blazor Server hub by adding the DisableCorsAttribute metadata to
      the endpoint metadata after calling MapBlazorHub on the endpoint
      route builder.

If I understand the above correctly, I need to "disable CORS in the endpoint by adding the CORS middleware to the pipeline and adding the DisableCorsAttribute to the Blazor endpoint metadata", I think I have managed this by adding .WithMetadata(new DisableCorsAttribute()) to the MapBlazorHub endpoint builder in UseEndpoints().

The above article also references another on SignalR security (see "Security considerations in ASP.NET Core SignalR" here: security) which says that "The protections provided by CORS don't apply to WebSockets. For origin restriction on WebSockets, read websockets".

The article on WebSockets origin restriction describes how to configure cross origin protection for WebSockets, however this is not explained in the context of configuring the WebSockets used by Blazor Server and I am yet to figure out how to do this.

How do I ensure that my Blazor Server application is secure against cross origin requests to the SignalR hub via its WebSocket(s) or any other relevant connection mechanism(s)?

Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,374 questions
ASP.NET
ASP.NET
A set of technologies in the .NET Framework for building web applications and XML web services.
3,243 questions
{count} votes

Accepted answer
  1. Rob Nelder 96 Reputation points
    2021-06-07T09:20:02.837+00:00

    I think I have arrived at a solution. I'm not entirely happy with it, but it seems to do the job. This solution is in three parts:

    1. Apply the DisableCorsAttribute to the Blazor endpoint as recommended in the docs.
    2. Block cross-origin requests to the Blazor WebSockets endpoint.
    3. Apply authorization to the Blazor endpoint (strictly speaking this has nothing to do with cross origin requests, but I thought it worth a mention here).

    1. DisableCorsAttribute

    In Startup.ConfigureServices() add services.AddCors(); - I don't think I need to add a policy here, as I don't actually want to enable cors for any origins other than my own site.

    In Startup.Configure() add app.UseCors(); somewhere after app.UseRouting(); but before app.UseEndpoints();

    Use WithMetadata() to add DisableCorsAttribute to the Blazor endpoint metadata:

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapBlazorHub().WithMetadata(new DisableCorsAttribute());
        endpoints.MapFallbackToPage("/_Host");
    });
    

    #2 Block cross-origin requests to the Blazor WebSockets endpoint CORS policies aren't applied to websockets (hence all the trouble). This solution seems a bit hacky, but it seems to work, I'm open to suggestions for improvements or better solutions. I have this at the top of my Startup.Configure() method so that cross-origin requests to the Blazor WebSockets endpoint will effectively just be ignored rather than have error codes returned to potential attackers.

    app.Use(async (context, next) =>
    {
        if (context.Request.Path.Value == "/_blazor")
        {
            if (context.Request.Headers.TryGetValue("Origin", out var origin)
                && origin != $"https://{ context.Request.Host.Value }")
            {
                return; // Refuse to handle request
            }
        }
        await next();
    });
    

    #3 Authorization I'm already using OIDC cookie based authentication in this application, so it seemed worth applying authorization to the Blazor endpoint so that only authenticated users can access it. It's worth noting that this in its self does not provide protection against cross-origin attacks that might somehow get an authenticated user to run code that connects to our Blazor endpoint, as the OIDC cookies are SameSite=None out of necessity. It seems like protection worth applying as an addition though.

    This is achieved by adding .RequireAuthorization() to MapBlazorHub().

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapBlazorHub()
            .RequireAuthorization()
            .WithMetadata(new DisableCorsAttribute());
        endpoints.MapFallbackToPage("/_Host");
    });
    

2 additional answers

Sort by: Most helpful
  1. AgaveJoe 26,181 Reputation points
    2021-06-03T12:45:40.043+00:00

    We seem to be talking past each other and going round in circles here. This could be due to my lack of understanding of the topic.

    I agree. From my perspective the question has been answered. If you have not done so, create your own test and play around. Also, learn vulnerability attach vectors. Blazor Server as the same type of vulnerabilities as any web application.


  2. AgaveJoe 26,181 Reputation points
    2021-06-02T20:00:39.773+00:00

    It provides code to configure WebSockets to do the bit in bold. However, I have found no documentation on how to tell Blazor Server to apply this configuration to the WebSockets used by its SignalR hub.

    I have no idea what problem you are trying to fix. Can provide code that illustrates the issue?

    I created a simple test solution with two Razor Page applications; server (Port 44304) and client (Port 44347). The server project has the SignalR Hub. Both projects have the SignalR client. The official SignalR chat tutorial is used for source code.

    The first modification is enable CORS on the server application.

    public class Startup  
        {  
            public Startup(IConfiguration configuration)  
            {  
                Configuration = configuration;  
            }  
      
            public IConfiguration Configuration { get; }  
            readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";  
      
            // This method gets called by the runtime. Use this method to add services to the container.  
            public void ConfigureServices(IServiceCollection services)  
            {  
                services.AddCors(options =>  
                {  
                    options.AddPolicy(name: MyAllowSpecificOrigins,  
                                      builder =>  
                                      {  
                                          builder.WithOrigins("https://localhost:44347")  
                                          .AllowAnyHeader()  
                                          .AllowAnyMethod()  
                                          .AllowCredentials(); ;  
                                      });  
                });  
      
                services.AddRazorPages();  
                services.AddSignalR();  
            }  
      
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  
            {  
                if (env.IsDevelopment())  
                {  
                    app.UseDeveloperExceptionPage();  
                }  
                else  
                {  
                    app.UseExceptionHandler("/Error");  
                    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.  
                    app.UseHsts();  
                }  
      
                app.UseHttpsRedirection();  
                app.UseStaticFiles();  
      
                app.UseRouting();  
      
                app.UseCors(MyAllowSpecificOrigins);  
      
                app.UseAuthorization();  
      
                app.UseEndpoints(endpoints =>  
                {  
                    endpoints.MapRazorPages();  
                    endpoints.MapHub<ChatHub>("/chatHub");  
                });  
            }  
        }  
    

    Next modify the chat.js file in the client application to point to the server's SignalR Hub

    "use strict";  
      
    var connection = new signalR.HubConnectionBuilder().withUrl("https://localhost:44304/chatHub").build();  
    

    When I remove Port 44347 from the server configuration, the 44347 client can not longer connect to the SignalR Hub which is expected and explained in the documentation.

    I added a Blazor Server app to the solution and the results are the same. The client can make a connection if CORS is enabled for the client's domain. There's a JSON error due to not following the Blazor server protocol. How are you testing the Blazor app?