Browse Source

Enhance security headers middleware to improve header management and add tests for ignored headers

pull/23196/head
maliming 7 months ago
parent
commit
814f44d533
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 31
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs
  2. 8
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController.cs
  3. 14
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController_Tests.cs

31
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs

@ -25,24 +25,13 @@ public class AbpSecurityHeadersMiddleware : AbpMiddlewareBase, ITransientDepende
{
var endpoint = context.GetEndpoint();
if (endpoint?.Metadata.GetMetadata<IgnoreAbpSecurityHeaderAttribute>() != null)
if (endpoint?.Metadata.GetMetadata<IgnoreAbpSecurityHeaderAttribute>() != null ||
await AlwaysIgnoreContentTypes(context) ||
Options.Value.IgnoredScriptNoncePaths.Any(x => context.Request.Path.StartsWithSegments(x.EnsureStartsWith('/'), StringComparison.OrdinalIgnoreCase)))
{
await next.Invoke(context);
return;
}
var requestAcceptTypeHtml = context.Request.Headers["Accept"].Any(x =>
x!.Contains("text/html") || x.Contains("*/*") || x.Contains("application/xhtml+xml"));
if (!requestAcceptTypeHtml
|| !Options.Value.UseContentSecurityPolicyHeader
|| await AlwaysIgnoreContentTypes(context)
|| endpoint == null
|| Options.Value.IgnoredScriptNoncePaths.Any(x => context.Request.Path.StartsWithSegments(x.EnsureStartsWith('/'), StringComparison.OrdinalIgnoreCase)))
{
AddOtherHeaders(context);
await next.Invoke(context);
return;
}
/*X-Content-Type-Options header tells the browser to not try and “guess” what a mimetype of a resource might be, and to just take what mimetype the server has returned as fact.*/
AddHeader(context, "X-Content-Type-Options", "nosniff");
@ -53,6 +42,20 @@ public class AbpSecurityHeadersMiddleware : AbpMiddlewareBase, ITransientDepende
/*The X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a <frame>, <iframe> or <object>. SAMEORIGIN makes it being displayed in a frame on the same origin as the page itself. The spec leaves it up to browser vendors to decide whether this option applies to the top level, the parent, or the whole chain*/
AddHeader(context, "X-Frame-Options", "SAMEORIGIN");
var requestAcceptTypeHtml = context.Request.Headers["Accept"].Any(x =>
x!.Contains("text/html", StringComparison.OrdinalIgnoreCase) ||
x.Contains("*/*", StringComparison.OrdinalIgnoreCase) ||
x.Contains("application/xhtml+xml", StringComparison.OrdinalIgnoreCase));
if (!requestAcceptTypeHtml
|| !Options.Value.UseContentSecurityPolicyHeader
|| endpoint == null)
{
AddOtherHeaders(context);
await next.Invoke(context);
return;
}
if (Options.Value.UseContentSecurityPolicyScriptNonce)
{
var randomValue = Guid.NewGuid().ToString("N");

8
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController.cs

@ -2,10 +2,18 @@
namespace Volo.Abp.AspNetCore.Mvc.Security.Headers;
[Route("SecurityHeadersTest")]
public class SecurityHeadersTestController : AbpController
{
[HttpGet("Get")]
public ActionResult Get()
{
return Content("OK");
}
[HttpGet("ignored")]
public ActionResult Get_Ignored()
{
return Content("OK");
}
}

14
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController_Tests.cs

@ -15,6 +15,8 @@ public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase
{
options.UseContentSecurityPolicyHeader = true;
options.Headers["Referrer-Policy"] = "no-referrer";
options.IgnoredScriptNoncePaths.Add("/SecurityHeadersTest/ignored");
});
base.ConfigureServices(services);
@ -27,7 +29,6 @@ public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase
responseMessage.Headers.ShouldContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff");
responseMessage.Headers.ShouldContain(x => x.Key == "X-XSS-Protection" & x.Value.First().ToString() == "1; mode=block");
responseMessage.Headers.ShouldContain(x => x.Key == "X-Frame-Options" & x.Value.First().ToString() == "SAMEORIGIN");
responseMessage.Headers.ShouldContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff");
}
[Fact]
@ -37,4 +38,15 @@ public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase
responseMessage.Headers.ShouldNotBeEmpty();
responseMessage.Headers.ShouldContain(x => x.Key == "Referrer-Policy" && x.Value.First().ToString() == "no-referrer");
}
[Fact]
public async Task SecurityHeaders_Should_Be_Ignored()
{
var responseMessage = await GetResponseAsync("/SecurityHeadersTest/ignored");
responseMessage.Headers.ShouldNotBeEmpty();
responseMessage.Headers.ShouldNotContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff");
responseMessage.Headers.ShouldNotContain(x => x.Key == "X-XSS-Protection" & x.Value.First().ToString() == "1; mode=block");
responseMessage.Headers.ShouldNotContain(x => x.Key == "X-Frame-Options" & x.Value.First().ToString() == "SAMEORIGIN");
responseMessage.Headers.ShouldNotContain(x => x.Key == "Referrer-Policy" && x.Value.First().ToString() == "no-referrer");
}
}

Loading…
Cancel
Save