Giter Club home page Giter Club logo

netescapades.aspnetcore.securityheaders's Introduction

NetEscapades.AspNetCore.SecurityHeaders

Build status

NuGet MyGet CI

A small package to allow adding security headers to ASP.NET Core websites

Installing

Install using the NetEscapades.AspNetCore.SecurityHeaders NuGet package from the Visual Studio Package Manager Console:

PM> Install-Package NetEscapades.AspNetCore.SecurityHeaders

Or using the dotnet CLI

dotnet add package NetEscapades.AspNetCore.SecurityHeaders

Usage

When you install the package, it should be added to your .csproj. Alternatively, you can add it directly by adding:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="NetEscapades.AspNetCore.SecurityHeaders" Version="0.16.1" />
  </ItemGroup>
  
</Project>

Simply add the middleware to your ASP.NET Core application by configuring it as part of your normal Startup pipeline. Note that the order of middleware matters, so to apply the headers to all requests it should be configured first in your pipeline.

To use the default security headers for your application, add the middleware using:

public void Configure(IApplicationBuilder app)
{
    app.UseSecurityHeaders();

    // other middleware e.g. static files, MVC etc  
}

This adds the following headers to all responses that pass through the middleware:

  • X-Content-Type-Options: nosniff
  • Strict-Transport-Security: max-age=31536000; includeSubDomains - only applied to HTTPS responses
  • X-Frame-Options: Deny - only applied to "document" responses
  • X-XSS-Protection: 1; mode=block - only applied to "document" responses
  • Referrer-Policy: strict-origin-when-cross-origin - only applied to "document" responses
  • Content-Security-Policy: object-src 'none'; form-action 'self'; frame-ancestors 'none' - only applied to "document" responses

"Document" responses are defined as responses that return one of the following content-types:

  • text/html
  • text/javascript
  • application/javascript

Customising the security headers added to responses

To customise the headers returned, you should create an instance of a HeaderPolicyCollection and add the required policies to it. There are helper methods for adding a number of security-focused header values to the collection, or you can alternatively add any header by using the CustomHeader type. For example, the following would set a number of security headers, and a custom header X-My-Test-Header.

public void Configure(IApplicationBuilder app)
{
    var policyCollection = new HeaderPolicyCollection()
        .AddFrameOptionsDeny()
        .AddXssProtectionBlock()
        .AddContentTypeOptionsNoSniff()
        .AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 60 * 60 * 24 * 365) // maxage = one year in seconds
        .AddReferrerPolicyStrictOriginWhenCrossOrigin()
        .RemoveServerHeader()
        .AddContentSecurityPolicy(builder =>
        {
            builder.AddObjectSrc().None();
            builder.AddFormAction().Self();
            builder.AddFrameAncestors().None();
        })
        .AddCrossOriginOpenerPolicy(builder =>
        {
            builder.SameOrigin();
        })
        .AddCrossOriginEmbedderPolicy(builder =>
        {
            builder.RequireCorp();
        })
        .AddCrossOriginResourcePolicy(builder =>
        {
            builder.SameOrigin();
        })
        .AddCustomHeader("X-My-Test-Header", "Header value");

    app.UseSecurityHeaders(policyCollection);

    // other middleware e.g. static files, MVC etc  
}

The security headers above are also encapsulated in another extension method, so you could rewrite it more tersely using

public void Configure(IApplicationBuilder app)
{
    var policyCollection = new HeaderPolicyCollection()
        .AddDefaultSecurityHeaders()
        .AddCustomHeader("X-My-Test-Header", "Header value");

    app.UseSecurityHeaders(policyCollection);

    // other middleware e.g. static files, MVC etc  
}

If you want to use the default security headers, but change one specific header, you can simply add another header to the default collection. For example, the following uses the default headers, but changes the max-age on the Strict-Transport-Security header:

public void Configure(IApplicationBuilder app)
{
    var policyCollection = new HeaderPolicyCollection()
        .AddDefaultSecurityHeaders()
        .AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 63072000);

    app.UseSecurityHeaders(policyCollection);

    // other middleware e.g. static files, MVC etc  
}

There is also a convenience overload for UseSecurityHeaders that takes an Action<HeaderPolicyCollection>, instead of requiring you to instantiate a HeaderPolicyCollection yourself:

public void Configure(IApplicationBuilder app)
{
    app.UseSecurityHeaders(policies =>
        policies
            .AddDefaultSecurityHeaders()
            .AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 63072000)
    );

    // other middleware e.g. static files, MVC etc  
}

RemoveServerHeader

One point to be aware of is that the RemoveServerHeader method will rarely (ever?) be sufficient to remove the Server header from your output. If any subsequent middleware in your application pipeline add the header, then this will be able to remove it. However Kestrel will generally add the Server header too late in the pipeline to be able to modify it.

Luckily, Kestrel exposes it's own mechanism to allow you to prevent it being added:

var host = new WebHostBuilder()
    .UseKestrel(options => options.AddServerHeader = false)
    //...

In Program.cs, when constructing your app's WebHostBuilder, configure the KestrelServerOptions to prevent the Server tag being added.

AddContentSecurityPolicy

The Content-Security-Policy (CSP) header is a very powerful header that can protect your website from a wide range of attacks. However, it's also totally possible to create a CSP header that completely breaks your app.

The CSP has a dizzying array of options, only some of which are implemented in this project. Consequently, I highly recommend reading this post by Scott Helme, in which he discusses the impact of each "directive". I also highly recommend using the "report only" version of the header when you start. This won't break your site, but will report instances that it would be broken, by providing reports to a service such as report-uri.com.

Set the header to report-only by using the AddContentSecurityPolicyReportOnly() extension. For example:

public void Configure(IApplicationBuilder app)
{
    var policyCollection = new HeaderPolicyCollection()
        .AddContentSecurityPolicyReportOnly(builder => // report-only
        {
            // configure policies
        });
}

or by by passing true to the AddContentSecurityPolicy command

public void Configure(IApplicationBuilder app)
{
    var policyCollection = new HeaderPolicyCollection()
        .AddContentSecurityPolicy(builder =>
        {
            // configure policies
        },
        asReportOnly: true); // report-only
}

You configure your CSP policy when you configure your HeaderPolicyCollection in Startup.Configure. For example:

public void Configure(IApplicationBuilder app)
{
    var policyCollection = new HeaderPolicyCollection()
        .AddContentSecurityPolicy(builder =>
        {
            builder.AddUpgradeInsecureRequests(); // upgrade-insecure-requests
            builder.AddBlockAllMixedContent(); // block-all-mixed-content

            builder.AddReportUri() // report-uri: https://report-uri.com
                .To("https://report-uri.com");

            builder.AddDefaultSrc() // default-src 'self' http://testUrl.com
                .Self()
                .From("http://testUrl.com");

            builder.AddConnectSrc() // connect-src 'self' http://testUrl.com
                .Self()
                .From("http://testUrl.com");

            builder.AddFontSrc() // font-src 'self'
                .Self();

            builder.AddObjectSrc() // object-src 'none'
                .None();

            builder.AddFormAction() // form-action 'self'
                .Self();

            builder.AddImgSrc() // img-src https:
                .OverHttps();

            builder.AddScriptSrc() // script-src 'self' 'unsafe-inline' 'unsafe-eval' 'report-sample'
                .Self()
                .UnsafeInline()
                .UnsafeEval()
                .ReportSample();

            builder.AddStyleSrc() // style-src 'self' 'strict-dynamic'
                .Self()
                .StrictDynamic();

            builder.AddMediaSrc() // media-src https:
                .OverHttps();

            builder.AddFrameAncestors() // frame-ancestors 'none'
                .None();

            builder.AddBaseUri() // base-ri 'self'
                .Self();

            builder.AddFrameSource() // frame-src http://testUrl.com
                .From("http://testUrl.com");

            // You can also add arbitrary extra directives: plugin-types application/x-shockwave-flash"
            builder.AddCustomDirective("plugin-types", "application/x-shockwave-flash");

        })
        .AddCustomHeader("X-My-Test-Header", "Header value");

    app.UseSecurityHeaders(policyCollection);

    // other middleware e.g. static files, MVC etc  
}

AddPermissionsPolicy

The permissions-policy is a header that allows a site to control which features and APIs can be used in the browser. It is similar to CSP but controls features instead of security behaviour.

With Permissions-Policy, you opt-in to a set of "policies" for the browser to enforce on specific features used throughout a website. These policies restrict what APIs the site can access or modify the browser's default behaviour for certain features.

By adding Permissions-Policy to headers to your website, you can ensure that sensitive APIs like geolocation or the camera cannot be used, even if your site is otherwise compromised, for example by malicious third-party attacks.

For more information about the permissions, I recommend the following resources:

You configure your CSP policy when you configure your HeaderPolicyCollection in Startup.Configure. For example:

public void Configure(IApplicationBuilder app)
{
    var policyCollection = new HeaderPolicyCollection()
        .AddPermissionsPolicy(builder =>
        {
            builder.AddAccelerometer() // accelerometer 'self' http://testUrl.com
                .Self()
                .For("http://testUrl.com");

            builder.AddAmbientLightSensor() // ambient-light-sensor 'self' http://testUrl.com
                .Self()
                .For("http://testUrl.com");

            builder.AddAutoplay() // autoplay 'self'
                .Self();

            builder.AddCamera() // camera 'none'
                .None();

            builder.AddEncryptedMedia() // encrypted-media 'self'
                .Self();

            builder.AddFullscreen() // fullscreen *:
                .All();

            builder.AddGeolocation() // geolocation 'none'
                .None();

            builder.AddGyroscope() // gyroscope 'none'
                .None();

            builder.AddMagnetometer() // magnetometer 'none'
                .None();

            builder.AddMicrophone() // microphone 'none'
                .None();

            builder.AddMidi() // midi 'none'
                .None();

            builder.AddPayment() // payment 'none'
                .None();

            builder.AddPictureInPicture() // picture-in-picture 'none'
                .None();

            builder.AddSpeaker() // speaker 'none'
                .None();

            builder.AddSyncXHR() // sync-xhr 'none'
                .None();

            builder.AddUsb() // usb 'none'
                .None();

            builder.AddVR() // vr 'none'
                .None();

            // You can also add arbitrary extra directives: plugin-types application/x-shockwave-flash"
            builder.AddCustomFeature("plugin-types", "application/x-shockwave-flash");
            // If a new feature policy is added that follows the standard conventions, you can use this overload
            // iframe 'self' http://testUrl.com
            builder.AddCustomFeature("iframe") // 
                .Self()
                .For("http://testUrl.com");
        });

    app.UseSecurityHeaders(policyCollection);

    // other middleware e.g. static files, MVC etc  
}

Using Nonces and generated-hashes with Content-Security-Policy

The use of a secure Content-Security-Policy can sometimes be problematic when you need to include inline-scripts, styles, or other objects that haven't been whitelisted. You can achieve this in two ways - using a "nonce" (or "number-used-once"), or specifying the hash of the content to include.

To help with this you can install the NetEscapades.AspNetCore.SecurityHeaders.TagHelpers package, which provides helpers for generating a nonce per request, which is attached to the HTML element, and included in the CSP header. A similar method helper exists for <style> and <script> tags, which will take a SHA256 hash of the contents of the HTML element and add it to the CSP whitelist.

To use a nonce or an auto-generated hash with your ASP.NET Core application, use the following steps.

1. Install the NetEscapades.AspNetCore.SecurityHeaders.TagHelpers NuGet package, e.g.

dotnet package add Install-Package NetEscapades.AspNetCore.SecurityHeaders.TagHelpers

This adds the package to your .csproj file

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="NetEscapades.AspNetCore.SecurityHeaders" Version="0.16.1" />
    <PackageReference Include="NetEscapades.AspNetCore.SecurityHeaders.TagHelpers" Version="0.16.1" />
  </ItemGroup>
  
</Project>

2. Configure your CSP to use nonces and/or hashes

Configure your security headers in the usual way. Use the WithNonce() extension method when configuring ContentSecurityPolicy directives to allow whitelisting with a nonce. Use the WithHashTagHelper() extension methods on script-src and style-src directives to allow automatic generation of whitelisted inline-scripts

public void Configure(IApplicationBuilder app)
{
    var policyCollection = new HeaderPolicyCollection()
        .AddContentSecurityPolicy(builder =>
        {
            builder.AddUpgradeInsecureRequests(); 
            builder.AddDefaultSrc() // default-src 'self' http://testUrl.com
                .Self()
                .From("http://testUrl.com");

            builder.AddScriptSrc() // script-src 'self' 'unsafe-inline' 'nonce-<base64-value>'
                .Self()
                .UnsafeInline()
                .WithNonce(); // Allow elements marked with a nonce attribute

            builder.AddStyleSrc() // style-src 'self' 'strict-dynamic' 'sha256-<base64-value>'
                .Self()
                .StrictDynamic()
                .WithHashTagHelper(); // Allow whitelsited elements based on their SHA256 hash value
        })
        .AddCustomHeader("X-My-Test-Header", "Header value");

    app.UseSecurityHeaders(policyCollection);

    // other middleware e.g. static files, MVC etc  
}

3. Add a using directive for the TagHelpers

Add the following to the _ViewImports.cshtml file in your application. This makes the tag-helper available in your Razor views.

@addTagHelper *, NetEscapades.AspNetCore.SecurityHeaders.TagHelpers

4. Whitelist elements using the TagHelpers

Add the NonceTagHelper to an element by adding the asp-add-nonce attribute.

<script asp-add-nonce>
    var body = document.getElementsByTagName('body')[0];
    var div = document.createElement('div');
    div.innerText = "I was added using the NonceHelper";
    body.appendChild(div);
</script>

This will use a unique value per-request and attach the required attribute at runtime, to generate markup similar to the following:

<script nonce="ryPzmoZScSR2xOwV0qTU9mFdFwGPN&#x2B;gy3S2E1/VK1vg=">
    var body = document.getElementsByTagName('body')[0];
    var blink = document.createElement('div');
    blink.innerText = "And I was added using the NonceHelper";
    body.appendChild(blink);
</script>

Note that some browsers will hide the value of the nonce attribute when viewed from DevTools. View the page source to see the raw nonce value

While the CSP policy would look something like the following:

Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-ryPzmoZScSR2xOwV0qTU9mFdFwGPN&#x2B;gy3S2E1/VK1vg='; style-src 'self' 'strict-dynamic'; default-src 'self' http://testUrl.com

To use a whitelisted hash instead, use the HashTagHelper, by adding the asp-add-content-to-csp attribute to <script> or <style> tags. You can optionally add the csp-hash-type attribute to choose between SHA256, SHA384, and SHA512:

<script asp-add-content-to-csp>
    var msg = document.getElementById('message');
    msg.innerText = "I'm allowed";
</script>

<style asp-add-content-to-csp csp-hash-type="SHA384">
#message {
    color: @color;
}  
</style>

At runtime, these attributes are removed, but the hash values of the contents are added to the Content-Security-Policy header.

Using the generated nonce without a TagHelpers

If you aren't using Razor, or don't want to use the TagHelpers library, you can access the Nonce for a request using an extension method on HttpContext:

var nonce = HttpContext.GetNonce();

Note that you must have enabled nonce generation by using the WithNonce() method. HttpContext.GetNonce() will return an string.Empty if nonce generation has not been added to the middleware.

Additional Resources

Note, Building on Travis is currently disabled, due to issues with the mono framework. For details, see

netescapades.aspnetcore.securityheaders's People

Contributors

aligholipour avatar andrewlock avatar damienbod avatar garbageous avatar huan086 avatar jeremylindsayni avatar johnnyreilly avatar jotatoledosai avatar luhis avatar prajaybasu avatar raceprouk avatar supertr0n avatar thomasbjallas avatar trevorpilley avatar vascofernandes avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

netescapades.aspnetcore.securityheaders's Issues

Devtools warning "The 'X-Frame-Options' header should not be used"

Hi Andrew, Thanks for all the blog posts. And your book.

I found my way to this library because of Edge Dev Tools Issues.

And it solved the one security issue it was warning me about. But created another with the following message and link:

"The 'X-Frame-Options' header should not be used. A similar effect, with more consistent support and stronger checks, can be achieved with the 'Content-Security-Policy' header and 'frame-ancestors' directive."

https://webhint.io/docs/user-guide/hints/hint-no-disallowed-headers/?source=devtools

[Discuss] Code duplication

It seems like there are some opportunities for simplification. Nearly every header class has a static method that returns an instance of said header class. Then there is an extension method that shares nearly the same xml documentation and a similar name which calls the static method. Wouldn't it be less maintenance to remove the static header method? For example, a current method inside XssProtectionHeader:

        /// <summary>
        /// Enables the XSS Protections
        /// </summary>
        /// <returns></returns>
        public static XssProtectionHeader Enabled()
        {
            return new XssProtectionHeader("1");
        }

The related method inside XssProtectionHeaderExtensions:

        /// <summary>
        /// Add X-XSS-Protection 1 to all requests.
        /// Enables the XSS Protections
        /// </summary>
        /// <param name="policies">The collection of policies</param>
        public static HeaderPolicyCollection AddXssProtectionEnabled(this HeaderPolicyCollection policies)
        {
            return policies.ApplyPolicy(XssProtectionHeader.Enabled());
        }

Since the extension method is the only place where XssProtectionHeader.Enabled() is used, why not remove the Enabled() method and implement directly inside the extension like so?

        /// <summary>
        /// Add X-XSS-Protection 1 to all requests.
        /// Enables the XSS Protections
        /// </summary>
        /// <param name="policies">The collection of policies</param>
        public static HeaderPolicyCollection AddXssProtectionEnabled(this HeaderPolicyCollection policies)
        {
            return policies.ApplyPolicy(new XssProtectionHeader("1"));
        }

That way you wouldn't need to repeat the documentation in multiple places, and you would have fewer methods to test. What do you think?

AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 31_536_000) not working as expected

I've been tweaking the security headers in my APIs this morning as what I had there was a couple years old and the grade on securityheaders.com had went down from an A to a C.

Just getting then newest version of the library and swapping out all the individual header tweaks with the extension method for the defaults got it back up to A.

        var policyCollection = new HeaderPolicyCollection()
            .AddDefaultSecurityHeaders();

The only complaint from securityheaders.com was "strict-transport-security" with the message:

HTTP Strict Transport Security is an excellent feature to support on your site and strengthens your implementation of TLS by getting the User Agent to enforce the use of HTTPS. Recommended value "strict-transport-security: max-age=31536000; includeSubDomains".

So I changed my code to:

        var policyCollection = new HeaderPolicyCollection()
            .AddDefaultSecurityHeaders()
            .AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 31_536_000);

However, this had no impact at all on securityheaders.com. It gave me the exact same message.

The API can only be reached via an API manager (I'm using Ocelot for this) and I'm wondering if this is interfering. Ocelot interfered with two other (very minor) attributes (server and X-Powered-By) but so far everything else from the API headers seems ok?

Any thoughts as to why this might not be working as I expected?

Should we add ReportSample() fluent extension for style-src

Hi - I've been using this project to improve my site content security policy. After running through some of the web-based tools to score my policy, I found one that recommended using 'report-sample' with script-src and style-src.

I noticed that this package has a .ReportSample() for AddScriptSrc(), but not for AddStyleSrc().

@andrewlock - would you be happy if I added a .ReportSample() fluent extension for AddStyleSrc() directive? I've coded the changes in my fork already and can submit changes with a pull request if you're happy with the idea.

Thanks!
Jeremy

Transient vs Singleton

Wouldn't it be better to register singleton service instead of transient, since it is executed on every request?

Inconsistent documentation grammar.

Some public classes lack xml documentation. Some xml documentation comments contain sentences that end in periods. Others contain sentences that end with no period. From a programming perspective, this ticket is fairly inconsequential. From a library adoption perspective, this ticket is important. Having consistent and complete documentation helps give library consumers confidence that the library author pays great attention to detail, and thus leads to a belief that said author will catch more bugs in the library before releasing.

Create sensible defaults for simple registration

It would be really cool if all you had to do was to add this to the Startup.cs:

app.UseCustomHeadersMiddleware();

It would then use a set of good default policies that would work for most websites. The reason for this suggestion is:

  1. Makes my Configure method less cluttered
  2. I don't have to learn about security in order to secure my app
  3. Using the default ensures secure headers. Customize to make less secure
  4. Default doesn't add unneeded headers. For instance, X-Frame-Options and X-XSS-Protection should only be added to text/html responses and not to CSS and image files.

Dynamic paths for report-uri

We have in-house infrastructure code that is being used across multiple services. We have started integrating this library when setting up our CSP header. Our idea is that each application exposes a report url where we can send data to our logging system.

Currently we have multiple applications hosted on the same IIS site using different IIS applications. IIS transmits the the application path using the HttpContext.Request.BasePath. In the future we are moving over to Kubernetes which may use a different setup.

In order to create code that can adapt to the running environment, I need to discover at runtime what context the application is running in. I tried subclassing CspDirectiveBuilderBase but that contains internal abstract methods.

Is there any way to easily support this?
The next option to explore is to patch the raw header value before it's sent back; not something I would like to do.

Any chance that Nonce generation might be included for custom headers or CSP?

I've been more recently requiring the use of nonce based entries when using the asp helper tags for example;

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.6/semantic.min.css"
asp-fallback-href="~/lib/semantic/dist/semantic.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"/>

Where the fallback tests create inline meta as such;

<script>!function(a,b,c,d){var e,f=document,g=f.getElementsByTagName("SCRIPT"),h=g[g.length-1].previousElementSibling,i=f.defaultView&&f.defaultView.getComputedStyle?f.defaultView.getComputedStyle(h):h.currentStyle;if(i&&i[a]!==b)for(e=0;e<c.length;e++)f.write('<link href="'+c[e]+'" '+d+"/>")}("position","absolute",["\/lib\/semantic\/dist\/semantic.min.css"], "rel=\u0022stylesheet\u0022 nws-csp-add-nonce=\u0022true\u0022 ");</script>

Any chance this could be added?

Also, thanks for adding the "AddReferrerPolicyStrictOriginWhenCrossOrigin" recently ๐Ÿ‘

Boz.

The source list for Content Security Policy directive 'script-src' contains an invalid source: ''strict-dynamic''. It will be ignored.

Hello,

In our project we use your library, we've implemented the newest version and add some new stuff (We've added following methods - grandnode/grandnode@529594a ) , we noticed following problem: "The source list for Content Security Policy directive 'script-src' contains an invalid source: ''strict-dynamic''. It will be ignored."

Is it something on our side or it should be fixed directly in the library? Thanks for any help!

asp-fallback-test & nonce attribute

Having checked the issue #19, which is kind of related to this, I wonder if there is a solution since a couple of years passed already.

The problem:

Having this:

<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
    asp-fallback-src="~/js/bootstrap.min.js"
    asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
    crossorigin="anonymous" asp-add-nonce="true"></script>

Will generate this:

<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" 
        integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" 
        crossorigin="anonymous" nonce="x&#x2B;2R9PRWupQLWDUYp1vp3uGsAMPpOIouI2TfKcZdVxQ="></script>
<script>(window.jQuery && window.jQuery.fn && window.jQuery.fn.modal||document.write("\u003Cscript src=\u0022/js/bootstrap.min.js\u0022 crossorigin=\u0022anonymous\u0022\u003E\u003C/script\u003E"));</script>

Which will result into this:
image

Obviously the solution could be to ignore asp-fallback-* attributes and implement that by yourself, but I was wondering if there is a way for asp-fallback-test script that is generated to have nonce attribute when AddScriptSrc().WithNonce() is present.

Thank you!

Failed to set referrer policy.

When setting the default security headers, I get the following error in my browser (Chrome 58 beta):

Failed to set referrer policy: The value 'strict-origin-when-cross-origin' is not one of 'no-referrer', 'no-referrer-when-downgrade', 'origin', 'origin-when-cross-origin', or 'unsafe-url'. The referrer policy has been left unchanged.

Apparently strict-origin-when-cross-origin is not sent along in the preflight headers. Is this something we can pick-up ourselves, or should this package solve this?

No inbuilt support for some CSP options

Including but not limited to:

unsafe-eval on scripts
=> ScriptSourceDirectiveBuilder.AllowUnsafeEval()
unsafe-inline on scripts and styles
=> ScriptSourceDirectiveBuilder.AllowUnsafeInline() | StyleSourceDirectiveBuilder.AllowUnsafeInline()
strict-dynamic on scripts
=> ScriptSourceDirectiveBuilder.UseStrictDynamic()
block-all-mixed-content tag
=> CspBuilder.AddBlockAllMixedContent()
base-uri source set
=> CspBuilder.AddBaseUriSrc()

I've added suggested fluent signatures for them.

one method only

I installed with Nuget but found out there's only one method AddDefaultSecurityHeaders available
you might need to open up other methods which is in the tutorial readme :)

How do I add multiple entries to the same directive?

I have an array of urls in my configuration which I need to add to CSP, so I've tried the following code:

List<string> scripts = configuration.GetSection("Scripts").Get<List<string>>();
List<string> styles = configuration.GetSection("Styles").Get<List<string>>();
List<string> images = configuration.GetSection("Images").Get<List<string>>();
List<string> connect = configuration.GetSection("Connect").Get<List<string>>();
List<string> frameAncestors = configuration.GetSection("FrameAncestors").Get<List<string>>();
scripts.ForEach(s => csp.AddScriptSrc().From(s));
styles.ForEach(s => csp.AddStyleSrc().From(s));
images.ForEach(s => csp.AddImgSrc().From(s));
connect.ForEach(s => csp.AddConnectSrc().From(s));
frameAncestors.ForEach(s => csp.AddFrameAncestors().From(s));

However, at runtime, the directive sources only have one entry in them which is the last one in the array.

I can't find a way of adding multiple entries to the same directive. How can I go about doing this?

[Feature Request] Add Additional IApplicationBuilder Extension

It would be really nice if library consumers didn't have to instantiate a HeaderPolicyCollection to customize the middleware. Would you be willing to add an overload such as the following (for us lazy folks)?

public static IApplicationBuilder UseSecurityHeaders(
    this IApplicationBuilder app,
    Action<HeaderPolicyCollection> configure)

CrossOriginResourcePolicyHeader should not be HtmlOnly

We swapped our solution from custom header notation to use the inbuilt functions provided by the package:
image

When releasing into our test environment with our CDN we found our site broke due to the CORP policy not being applied to our resources: image

I suggest that the issue is relating to the use of a HtmlOnly header for CORP. Which when you consider it's expected to be present on the resources makes little sense: https://github.com/andrewlock/NetEscapades.AspNetCore.SecurityHeaders/blob/45da7a4bf59b1680652e50e70d61f7e23483d2bf/src/NetEscapades.AspNetCore.SecurityHeaders/Headers/CrossOriginResourcePolicyHeader.cs

I imagine it's a simple change to use the standard HeaderPolicyBase instead.

I'm going to revert that header only and push out into test to see if all our issues are resolved, in case we experience any issues with the other two headers.

Allow configuration through ConfigureOptions

In the average application it's easy for the Startup file to get untidy and require a lot of scrolling to find what you need. For this reason we consistently use ConfigureOptions to move as much out of our Startup file as possible
image

With this package, when you have an application that has some specific security headers including a more complex CSP, you can end up adding a good chunk of code into the Startup file.

This is a request to see if it's possible to permit the configuration of security headers though options configuration.

I'd certainly retain the current inline configuration as an option but perhaps if someone uses app.UseSecurityHeaders(); it'd be great to attempt to resolve the configuration from the options e.g:

        public static IApplicationBuilder UseSecurityHeaders(
            this IApplicationBuilder app)
        {
            var headerPolicy = app
                .ApplicationServices
                .GetService<IOptions<SecurityHeaderOptions>>()
                ?.HeaderPolicy;
                
            // Use `headerPolicy`
        }

I've not looked at the code for HeaderPolicyCollection so I don't know how feasible this is.


On a separate note, I always like to take a moment to say thank you for producing this package. It seems like the best option around right now and I hope you continue to develop it. Thanks again for your efforts so far!

`AddCustomHeader` throws misleading exception

var collection = new HeaderPolicyCollection();
collection.AddCustomHeader(null, "asdf");

The snippet above throws an ArgumentNullException with the following message: "Value cannot be null.
". This is misleading because it is actually the header parameter that is null, not the value parameter.

Remove obsoleted classes

Since you are willing to bypass marking something as obsolete for a package version less than v1, why not remove the existing classes that are marked as obsolete?

Authenticode signing

Hi Andrew,

This is a great library, very useful to add a little bit of fire and forget hardening to our microservices. There's just one issue and that's our CI machines don't like it due to it not being signed and our locked down environments. If you could sign the dlls then I could ask that the public key is added to our whitelisted producers?

I can provide some example signing code if that helps, but you'd need to produce the certificate of course!

Thanks,
Ben

Adding `strict-dynamic` to a Script CSP breaks nonce tag helpers

Okay so this might be user error, but I have a test page with the following scripts:

<script nonce="@Context.GetNonce()">
            console.log("JS running with an injected nonce");
</script> 

<script asp-add-nonce>
            console.log("JS running with a tag helper nonce");
</script>

<script asp-add-content-to-csp csp-hash-type="SHA384">
            console.log("JS running with the hashtags");
</script>

Just as tests for whether the CSP is allowing scripts with nonces to run.

If my CSP is set as follows:

.AddContentSecurityPolicy(builder =>
                {
                    builder.AddUpgradeInsecureRequests();

                    builder.AddObjectSrc().None();

                    builder.AddScriptSrc()
                        .Self()
                        .UnsafeInline()
                        .StrictDynamic()
                        .WithHashTagHelper();
                    
                    builder.AddBaseUri().None();

                    builder.AddReportUri().To("/csp/report");

                });

Nothing loads.

If I remove the StrictDynamic() part, things start working again and those three scripts run.

Am I doing something stupid, or are the taghelpers not running for strict dynamic?

Add COOP/CORP/COEP headers - feature request

Hi Andrew

There are a few relatively new security headers (COOP/CORP/COEP) that would be really nice to have in the library - more information here:
https://scotthelme.co.uk/coop-and-coep/
https://web.dev/coop-coep/
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy

I'm added these using the 'AddCustomHeader' extension right now.

I've a couple of potential implementation design suggestions below (using the Cross-Origin-Opener-Policy header for the example):

First (my preference):

var policyCollection = new HeaderPolicyCollection()
		.AddCrossOriginOpenerPolicy(builder =>
                {
                    builder
			.ReportTo("default")
                        .UnsafeNone();

			// you could alternatively use one of the fluent extensions below in your policy collection instead of unsafe-none
			//.SameOrigin()
			//.SameOriginAllowPopups();
                });

		// ...
		// or use the implementation below for reporting only
                .AddCrossOriginOpenerPolicyReportOnly(builder =>
                {
                    builder
			.ReportTo("default")
                        .UnsafeNone();

			// you could alternatively use one of the fluent extensions below in your policy collection instead of unsafe-none
			//.SameOrigin()
			//.SameOriginAllowPopups();
                });

Pros: Consistency with existing features like CSP, also very extensible if the standard changes
Cons: More complex implementation for end users in the Startup.cs file

Second:

var policyCollection = new HeaderPolicyCollection() 
	.AddCrossOriginOpenerPolicyUnsafeNone(); 

	// also need to write code for the extensions below
	.AddCrossOriginOpenerPolicyUnsafeNone(reportTo:"default")
	.AddCrossOriginOpenerPolicyUnsafeNoneReportOnly(reportTo:"default")
	.AddCrossOriginOpenerPolicySameOrigin()
	.AddCrossOriginOpenerPolicySameOrigin(reportTo:"default")
	.AddCrossOriginOpenerPolicySameOriginReportOnly(reportTo:"default")
	.AddCrossOriginOpenerPolicySameOriginAllowPopups()
	.AddCrossOriginOpenerPolicySameOriginAllowPopups(reportTo:"default")
	.AddCrossOriginOpenerPolicySameOriginAllowPopupsReportOnly(reportTo:"default")

Pros: Simple implementation in the Startup.cs file
Cons: Risk of an unwieldy amount of code in the source library, expecially if the standard changes.

Or the implementation could be something else I haven't considered :)

What do you think? I've started writing code for a pull request but wanted to ask your views before doing too much.

Jeremy

Possible to change CSP after it has been built?

Is there a way to change the CSP after it has been built with AddContentSecurityPolicy? As far as I can see the only option is overwrite it by calling the method again but I am utilising this in a common library of my own and I would like the consumer of my common library to be able to change the policy from the default that is set.

Add permission policy extension to allow FLoC opt out

Hi Andrew,

I've read Chrome is experimenting with a new ad selection feature called FLoC, more info here: https://web.dev/floc/

And more information/opinions at the links below:
https://scotthelme.co.uk/what-the-floc/
https://www.eff.org/deeplinks/2021/03/googles-floc-terrible-idea
https://seirdy.one/2021/04/16/permissions-policy-floc-misinfo.html
https://floc.glitch.me/

To make sure pages are opted-out of the calculation, we can add this response header:
Permissions-Policy: interest-cohort=()

It would be useful to update the SecurityHeaders library so that a site can declare that it does not want to be included in the user's list of sites for cohort calculation.

My suggestion for a new extension is below, which hopefully is consistent with the other directive extensions:

var policyCollection = new HeaderPolicyCollection()
                                      .AddPermissionsPolicy(
                                          options =>
                                          {
                                              options.AddFederatedLearningOfCohortsCalculation().None();
                                          }
                                      );

Note: It's possible that since FLoC is only in trial at the moment, the response header specifics might change later.

I've already made changes in a feature branch in my fork - pull request coming soon for review. And as always, thank you for maintaining this fantastic library!

Missing CSP3 directives?

CSP level 3 add new directives, and some of them appear to be missing:

  • prefetch-src
  • script-src-elem
  • script-src-attr
  • style-src-elem
  • style-src-attr
  • sandbox
  • navigate-to

Using AddCustomDirective isn't a proper workaround, because some of these directives need the Nonce, and I'm not sure how to access it while creating the CSP.

.RemoveServerHeader() With Kestrel

Kestrel seems to add the server header later in the pipeline than when the middleware can interact with them.

Fortunately there is a workaround, in your WebHostBuilder add the following options when you setup kestrel:

.UseKestrel(o => o.AddServerHeader = false)

Not really a bug but someone else may run into the same problem.

Shaun

Question nonce on 3rd party nuget injected scripts?

Hi there,

Firstly a great library. This is more a question rather than an issue.

If I am using a 3rd party nuget package that injects an inline script tag
<script>alert('3rd party tool');</script>

Would I have to manually add nonce=XXXX using HttpContext.GetNonce(); through some sort of rewrite middleware?

Thanks for your time and effort

Allow use of named security header policies, similar to the CORS policy middleware

As it is now, the only way to set which security headers are to be used is at the middleware level. This lacks flexibility, as path-dependent policies become hard to set up.

The foundations for this seems to be already layed of in #57 , but it went stale as ATM the lack of interest in this feature does not justify the added complexity that it would become for the maintainer(s) once its merged.

This issue is meant to track the interest in this functionality, so please add your upvote/reaction and feel free to discuss about your own use cases where this feature would help you.

Can't get asp-add-nonce to display a nonce

Hi,

I've been using your library for a while now to populate CSP headers, Permission Policy headers and a couple of others and they have been very successful, I moved to this solution rather than embedding headers via nginx as my dev environment then behaves largely like my production environment.

Following a fairly recent post by Scott Helme I thought I'd look into nonces once again, I've looked in the past as a way to banish unsafe-inline but never manged to get things working.

So, I've added both the SecurityHeaders NuGet package (was already present) and the extra SecurityHeaders.TagHelpers NuGet package.

I already had the middleware running in Program.cs (I'm using ASP.NET 6 and the new WebApplication.CreateBuilder layout, so no Startup.cs).

app.UseSecurityHeaders(SecurityHeaders.GetHeaderPolicyCollection(app.Environment.IsDevelopment()));

Which calls the following code, which works fine, I just added the two marked .WithNonce() entries:

namespace HRW;

public class SecurityHeaders
{
	public static HeaderPolicyCollection GetHeaderPolicyCollection(bool isDev)
	{
		var policy = new HeaderPolicyCollection()
			.AddContentTypeOptionsNoSniff()
			.AddFrameOptionsSameOrigin()
			.AddXssProtectionBlock()
			.AddReferrerPolicyStrictOriginWhenCrossOrigin()

			.AddContentSecurityPolicy(builder =>
			{
				builder.AddUpgradeInsecureRequests();
				builder.AddBlockAllMixedContent();

				builder.AddDefaultSrc()
					.None();

				builder.AddStyleSrc()
					.Self()
					.UnsafeInline()
					.WithNonce()  // Recently added this line
					.From("fonts.googleapis.com");

				builder.AddScriptSrc()
					.Self()
					.UnsafeInline()
					.WithNonce()  // Recently added this line
					.From("kit.fontawesome.com")
					.From("maps.googleapis.com");

				builder.AddImgSrc()
					.Self()
					.Data()
					.From("maps.gstatic.com")
					.From("maps.googleapis.com")
					.From("cbks0.googleapis.com")
					.From("geo0.ggpht.com")
					.From("geo1.ggpht.com")
					.From("geo2.ggpht.com")
					.From("geo3.ggpht.com")
					.From("lh3.ggpht.com")
					.From("khms0.googleapis.com")
					.From("khms1.googleapis.com");

				builder.AddFontSrc()
					.Self()
					.From("fonts.gstatic.com");

				builder.AddConnectSrc()
					.Self()
					.From("ka-f.fontawesome.com")
					.From("maps.googleapis.com");

				builder.AddManifestSrc()
					.Self();

				builder.AddFrameAncestors()
					.Self();

				builder.AddFormAction()
					.Self();

				builder.AddBaseUri()
					.Self();

				builder.AddReportUri()
					.To("https://roseitsolutions.dev/CSP");
			})

			.AddPermissionsPolicy(builder =>
			 {
				 builder.AddAccelerometer().Self();
				 builder.AddAutoplay().None();
				 builder.AddCamera().None();
				 builder.AddEncryptedMedia().None();
				 builder.AddFullscreen().None();
				 builder.AddGeolocation().Self();
				 builder.AddGyroscope().Self();
				 builder.AddMagnetometer().Self();
				 builder.AddMicrophone().None();
				 builder.AddMidi().None();
				 builder.AddPayment().None();
				 builder.AddPictureInPicture().None();
				 builder.AddSyncXHR().None();
				 builder.AddUsb().None();
			 });

		if (!isDev)
		{
			policy.AddStrictTransportSecurityMaxAgeIncludeSubDomainsAndPreload(maxAgeInSeconds: 63072000);
		}

		return policy;
	}
}

When I run the website I can see the nonce header getting generated in DevTools:

content-security-policy: style-src 'self' 'unsafe-inline' fonts.googleapis.com 'nonce-LMxrnSne0Hb5I/UIyrIcKs15XHVy46zV4puSOLU3TWE='; script-src 'self' 'unsafe-inline' kit.fontawesome.com maps.googleapis.com 'nonce-LMxrnSne0Hb5I/UIyrIcKs15XHVy46zV4puSOLU3TWE='; upgrade-insecure-requests; block-all-mixed-content; default-src 'none'; img-src 'self' data: maps.gstatic.com maps.googleapis.com cbks0.googleapis.com geo0.ggpht.com geo1.ggpht.com geo2.ggpht.com geo3.ggpht.com lh3.ggpht.com khms0.googleapis.com khms1.googleapis.com; font-src 'self' fonts.gstatic.com; connect-src 'self' ka-f.fontawesome.com maps.googleapis.com; manifest-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; report-uri https://roseitsolutions.dev/CSP;

I can also use var nonce = HttpContext.GetNonce(); and output the result to the page but wherever I use asp-add-nonce nothing happens and I get a CSP error saying inline script execution has failed.

image

I'm not sure what I'm missing so any help would be greatly appreciated.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.