Aspnetcore: Add support for content security policy

Created on 29 Aug 2017  路  11Comments  路  Source: dotnet/aspnetcore

This is a placeholder issue.

Similar to the support that we have for CORS in APIs, we should have support for Content Security Policy to make sites safer by default.
Support for CSP would be policy based, similar to the one we offer for CORS.

Usage from middleware

```C#
ConfigureServices(IServiceCollection services)
{
...
services.AddCsp();
...
}

```C#
Configure(IApplicationBuilder app)
{
    ...
    app.UseCsp();
    ...
}

Usage from MVC

```C#
ConfigureServices(IServiceCollection services)
{
...
services.AddMvc(); // Add MVC will call AddCsp similar to what we do for CORS today.
...
}

```C#
[EnableCsp]
public IActionResult Index()
{
    return View();
}

We will provide a default policy that limits content to your domain, defines best practices for HTTPS and will be set to report-only. This behavior can be switched per endpoint so that you can progressively enforce the policy one endpoint at a time.

References

https://en.wikipedia.org/wiki/Content_Security_Policy

https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

https://www.w3.org/TR/CSP2/

http://caniuse.com/#search=content%20security%20policy

Design affected-medium area-middleware enhancement severity-minor

Most helpful comment

I created a policy based Content Security Policy library to use on my own sites while waiting for this support in the core libraries. Since content security policy support is now planned for 3.0, it might help you with the design discussion. I also followed your standards on the off chance you could use any of the code for this issue.

Code

Microsoft.AspNetCore.Csp
Microsoft.AspNetCore.Mvc.Csp

Samples

CspSample
CspSample.Mvc

public void ConfigureServices(IServiceCollection services)
{
    services.AddCsp(options =>
    {
        options.AddPolicy("Policy1", policy => policy
            .AddDefaultSrc(src =>
            {
                src.AllowSchema(CspDirectiveSchemas.Http);
                src.AllowSchema(CspDirectiveSchemas.Https);
                src.AllowSelf();
                src.AllowEval();
                src.AllowHash("sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng=");
            })
            .AddScriptSrc(src => src.AddNonce())
            .AddStyleSrc(src => src.AddNonce())
            .ReportUri("/csp-reports")
            .ReportTo("csp-reports")
            .AddManifestSrc(src =>
            {
                src.AllowHost("http://*.example.com");
            })
            .ReportOnly()
        );

        options.AddPolicy("Policy2", policy =>
            policy.AddDefaultSrc(src => src.AllowNone().AddNonce())
        );

        options.AddPolicy("BetaUsers", policy =>
            policy.AddDefaultSrc(src => src.AllowHost("beta-testers.example.org"))
        );
    });

    services.AddMvc()
        .AddCspReportMediaType();

    services.AddMvcCore().AddCsp();

    services.AddScoped<IConfigureOptions<CspOptions>, TrialUserSrc>();
}

public class TrialUserSrc :  IConfigureOptions<CspOptions>
{
    public void Configure (CspOptions options)
    {
        if (context.User.HasClaim(c => c.Type == ClaimTypes.TrialUser))
        {
            var currentPolicy = options.GetPolicy("Policy1");
        currentPolicy.Append(policy => 
                policy.AddDefaultSrc(src => src.AllowHost("trial.company.com"))
            );
        }
    }
}

Configuration with MVC Attributes

  • AppendCsp and OverrideCsp are optional attributes that modify the main policy on a per action basis.
[EnableCsp("Policy1", "Policy2")]
[AppendCsp("BetaUsers", Targets = "Policy1, Policy2")]
[OverrideCsp("BetaUsers")]
public IActionResult EnablePolicy1And2()
{
    ...
}

[DisableCsp]
public IActionResult Disabled()
{
    ...
}

Default Content-Security-Policy

This is the default policy I'm using:

    public class CspOptions
    {
        // Default Content-Security-Policy:
        //     default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:;
        //     object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'
        private static readonly ContentSecurityPolicy DefaultContentSecurityPolicy

Tag Helpers

CspMetaTagHelper - Adds the content security policies as meta tags to the page.

<meta http-equiv="Content-Security-Policy" />

CspNonceTagHelper - Adds a nonce to script or style tags and automatically ensures a nonce is returned in the HTTP header.

CspGenerateHashTagHelper - Automatically generates hashes for inline scripts and styles and ensures the hashes are in the HTTP header.

<style asp-add-nonce="true" asp-generate-hash="true">
    p {
        color: #0000ff;
    }
</style>
<script asp-add-nonce="true" asp-generate-hash="true" asp-generate-hash-algorithms="HashAlgorithms.SHA256 | HashAlgorithms.SHA512">
    console.log("I'm a script that will be hashed dynamically.");
</script>

CspPluginTypeTagHelper - Automatically adds the used plugin types to the content security policy.

<embed asp-type="application/testplugintype2" />

CspFallBackTagHelper - Generates CSP hashes for the inline scripts that are generated when using asp-fallback-href or asp-fallback-src.

CspSubresourceIntegrityTagHelper - Automatically generates SRI hashes for the remote scripts using a local fallback script.

<link href="https://ajax.aspnetcdn.com/ajax/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet"
      asp-fallback-href="~/lib/bootstrap/css/bootstrap.min.css"
      asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"
      asp-subresource-integrity="true"
      asp-subresource-integrity-algorithms="HashAlgorithms.SHA256 | HashAlgorithms.SHA384 | HashAlgorithms.SHA512" />

CSP Report Support

  • CspReportRequest model

  • Added 'application/csp-report' media type support.

/// <summary>
/// Adds the 'application/csp-report' media type to the JSON input formatter so that csp reports can be received.
/// </summary>
/// <param name="builder">The <see cref="IServiceCollection" /> to add services to.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IMvcBuilder AddCspReportMediaType(this IMvcBuilder builder)

cc @sebastienros - When this feature lands in 3.0 I'll implement CSP for Orchard Core :tada:

All 11 comments

We'll need a design for this guys.

cc @shirhatti

I created a policy based Content Security Policy library to use on my own sites while waiting for this support in the core libraries. Since content security policy support is now planned for 3.0, it might help you with the design discussion. I also followed your standards on the off chance you could use any of the code for this issue.

Code

Microsoft.AspNetCore.Csp
Microsoft.AspNetCore.Mvc.Csp

Samples

CspSample
CspSample.Mvc

public void ConfigureServices(IServiceCollection services)
{
    services.AddCsp(options =>
    {
        options.AddPolicy("Policy1", policy => policy
            .AddDefaultSrc(src =>
            {
                src.AllowSchema(CspDirectiveSchemas.Http);
                src.AllowSchema(CspDirectiveSchemas.Https);
                src.AllowSelf();
                src.AllowEval();
                src.AllowHash("sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng=");
            })
            .AddScriptSrc(src => src.AddNonce())
            .AddStyleSrc(src => src.AddNonce())
            .ReportUri("/csp-reports")
            .ReportTo("csp-reports")
            .AddManifestSrc(src =>
            {
                src.AllowHost("http://*.example.com");
            })
            .ReportOnly()
        );

        options.AddPolicy("Policy2", policy =>
            policy.AddDefaultSrc(src => src.AllowNone().AddNonce())
        );

        options.AddPolicy("BetaUsers", policy =>
            policy.AddDefaultSrc(src => src.AllowHost("beta-testers.example.org"))
        );
    });

    services.AddMvc()
        .AddCspReportMediaType();

    services.AddMvcCore().AddCsp();

    services.AddScoped<IConfigureOptions<CspOptions>, TrialUserSrc>();
}

public class TrialUserSrc :  IConfigureOptions<CspOptions>
{
    public void Configure (CspOptions options)
    {
        if (context.User.HasClaim(c => c.Type == ClaimTypes.TrialUser))
        {
            var currentPolicy = options.GetPolicy("Policy1");
        currentPolicy.Append(policy => 
                policy.AddDefaultSrc(src => src.AllowHost("trial.company.com"))
            );
        }
    }
}

Configuration with MVC Attributes

  • AppendCsp and OverrideCsp are optional attributes that modify the main policy on a per action basis.
[EnableCsp("Policy1", "Policy2")]
[AppendCsp("BetaUsers", Targets = "Policy1, Policy2")]
[OverrideCsp("BetaUsers")]
public IActionResult EnablePolicy1And2()
{
    ...
}

[DisableCsp]
public IActionResult Disabled()
{
    ...
}

Default Content-Security-Policy

This is the default policy I'm using:

    public class CspOptions
    {
        // Default Content-Security-Policy:
        //     default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:;
        //     object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'
        private static readonly ContentSecurityPolicy DefaultContentSecurityPolicy

Tag Helpers

CspMetaTagHelper - Adds the content security policies as meta tags to the page.

<meta http-equiv="Content-Security-Policy" />

CspNonceTagHelper - Adds a nonce to script or style tags and automatically ensures a nonce is returned in the HTTP header.

CspGenerateHashTagHelper - Automatically generates hashes for inline scripts and styles and ensures the hashes are in the HTTP header.

<style asp-add-nonce="true" asp-generate-hash="true">
    p {
        color: #0000ff;
    }
</style>
<script asp-add-nonce="true" asp-generate-hash="true" asp-generate-hash-algorithms="HashAlgorithms.SHA256 | HashAlgorithms.SHA512">
    console.log("I'm a script that will be hashed dynamically.");
</script>

CspPluginTypeTagHelper - Automatically adds the used plugin types to the content security policy.

<embed asp-type="application/testplugintype2" />

CspFallBackTagHelper - Generates CSP hashes for the inline scripts that are generated when using asp-fallback-href or asp-fallback-src.

CspSubresourceIntegrityTagHelper - Automatically generates SRI hashes for the remote scripts using a local fallback script.

<link href="https://ajax.aspnetcdn.com/ajax/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet"
      asp-fallback-href="~/lib/bootstrap/css/bootstrap.min.css"
      asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"
      asp-subresource-integrity="true"
      asp-subresource-integrity-algorithms="HashAlgorithms.SHA256 | HashAlgorithms.SHA384 | HashAlgorithms.SHA512" />

CSP Report Support

  • CspReportRequest model

  • Added 'application/csp-report' media type support.

/// <summary>
/// Adds the 'application/csp-report' media type to the JSON input formatter so that csp reports can be received.
/// </summary>
/// <param name="builder">The <see cref="IServiceCollection" /> to add services to.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IMvcBuilder AddCspReportMediaType(this IMvcBuilder builder)

cc @sebastienros - When this feature lands in 3.0 I'll implement CSP for Orchard Core :tada:

@jrestall Your approach is pretty solid. It's pretty much what I had in mind. I have some ideas on how to better integrate this with Razor, but the foundation is solid.

The way I see this, there will be a core library (similar to what we have for CORS) and then some MVC specific glue/enhancements/opinions.

In particular, I think it might be better to specify the policy as a Razor directive as its very resource specific. In many actions you don't have the ability to know what's going to be rendered. By doing it as a razor directive it can be something that you put on a _viewStart, _viewImports (not really sure which one of them yet) or simply at the start of your page or view.

That way the policy lives close to the resources, regarding hashes and things like that I would have to think more about this. One thing that I'm concern is the size of the policy on the header growing to a significant size, so I'm more inclined to use/recommend a different approach where you hoist the inline-scripts into js files for most cases.

I think that you are on point with the need to append to an initial policy and to completely override it in some cases.

Having a default policy is also great, but the default for the system should be to simply report so that you can start changing the code base to enforce the policy.

@mkArtakMSFT @javiercn Is this still happening in 3.0?

Interesting that no one has mentioned NWebSec. This library started life adding much needed security enhancements to ASP.NET MVC, and now support ASP.NET Core as well. There's already a pretty complete CSP implementation here, so at the very least, it might be worth referencing.

https://docs.nwebsec.com/en/latest/nwebsec/libraries.html

I'm looking forward to this being part of the aspnet core stack as well. I've experimented a bit with NWebSec but it seems it has not been updated in over a year, issues go unanswered and even their SSL cert for their website expired in Feb 2019 and they have not done anything about it. Seems like an abandoned project to me. https://www.nwebsec.com/

NWebSec is solid, but as @joeaudette said it's under-maintained right now. I would love to see something like @jrestall's approach built in.

NWebSec puts everything into Configure, but it would be easier to compose with things like the Options pattern if configuration happened in ConfigureServices like in James' example.

Hi folks,
My colleague @salcho has been in contact with some of you for adding framework-level support of CSP for ASP.NET. We've created #24548 of our proposed changes, so we will welcome any feedback! Thank you!

Is this issue included: validation summary emitting style="display:none".
[Report Only] Refused to apply inline style because it violates the following Content

<div asp-validation-summary="All" class="text-danger"></div> <div class="text-danger validation-summary-valid" data-valmsg-summary="true"><ul><li style="display:none"></li>

Was this page helpful?
0 / 5 - 0 ratings