Open javiercn opened 7 years ago
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.
Microsoft.AspNetCore.Csp Microsoft.AspNetCore.Mvc.Csp
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"))
);
}
}
}
[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:
```csharp
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
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" />
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?
Please don't forget about the validation summary (inline style
-element) mentioned in https://github.com/aspnet/AspNetCore/issues/4817#issuecomment-308058241.
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.
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
`
@javiercn , I do not see a real benefit of having a csp as a directive. Rather, I think it is better to make the templates more Csp compliant for those who already are implementing Csp (with or without a library) and who may find themselves in using unsafe keywords in the Csp directive set:
One example is the style in the default generator as mentioned above https://github.com/dotnet/aspnetcore/blob/ff51fd7105a9003841215f1f3b0b8fc9e2998a67/src/Mvc/Mvc.ViewFeatures/src/DefaultHtmlGenerator.cs#L28
Another one is the injection of javascript during development, see @damienbod here https://github.com/dotnet/aspnetcore/issues/34428
Regarding the library, from my own experience with Csp at least, I see most benefit in having enable/disable attributes, filters and a global Csp/ReportOnly. For Spa's, I don't know yet, so probably meta tags with hashes (?).
Please don't forget about the validation summary (inline
style
-element) mentioned in #4817 (comment).
Narrator: They did forget about it.
I created a library for CSP because we needed it in production apps and existing libraries including NWebsec did not fully fulfill our conditions. It is based on a pragmatic and performance-oriented design philosophy which I believe is worth sharing after a brief exchange with @damienbod here https://github.com/dotnet/aspnetcore/issues/34428. This lead us to introduce the concept of a CSP Policy Group. This class encompasses the Content-Security-Policy
and Content-Security-Policy-Report-Only
headers in one entity, because they can and should be used in tandem.
So I go along by copy pasting some text, and you can get extensive details here https://github.com/Ponant/Galebra.Security/tree/master/src/Galebra.Security.Headers.Csp , in particular the design section, together with a bunch of references. Critics and/or questions are always welcome.
All terminology is explained after. The library does not use EndPoint
routing, so you can invoke the UseContentSecurityPolicy
middleware before or after UseRouting
.
app.UseContentSecurityPolicy();
It can be placed before UseStaticFiles
if you need CSP headers to be delivered with peculiar content such as SVG
.
You configure CSP via appsettings.json or via an Action
in Program.cs.
When using appsetting.json:
using Galebra.Security.Headers.Csp;
builder.Services.AddContentSecurityPolicy(builder.Configuration.GetSection("Csp"));
There are two services registered, a Singleton
, essentially holding the configuration, and a Scoped
service for nonce generation.
In the following, three policy groups are registered:
"Csp": {
"IsDisabled": false,//default: Will apply the default policy everywhere until overriden by attributes or filters
"PolicyGroups": {
"PolicyGroup1": {
"Csp": {
"Fixed": "default-src 'none' 'sha256-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc=';script-src 'self'"
},
"IsDefault": false,
"NumberOfNonceBytes": 16//default
},
"PolicyGroup2": {
"Csp": {
"Fixed": "default-src 'self';base-uri 'self';form-action 'self';object-src;frame-ancestors;connect-src ws://localhost:65412",
"Nonceable": [
"style-src 'self'"
]
},
"CspReportOnly": {
"Fixed": "default-src;form-action 'self';base-uri;object-src;frame-ancestors;sandbox",
"Nonceable": [
"style-src",
"script-src"
]
},
"IsDefault": true,//default
"NumberOfNonceBytes": 8
},
"PolicyGroup3": {
"Csp": {
"Nonceable": [
"style-src"
]
},
"IsDefault": false,
"NumberOfNonceBytes": 3
}
}
},
The first policy group does not require nonces (hence fixed, see below) and only requires the Content-Security-Policy
header to be set.
The second policy group configures the two headers, CSP and CSP-Report-Only, and requires nonces for each of these headers.
This policy is the default policy, IsDefault=true
. The IsDisabled
property in Line 1 is set to false
(default),
which means that the default policy named PolicyGroup2 will be applied globally unless overridden by attributes or filters.
The third policy uses only nonces, for styles. The default value for nonce generation is 16 bytes.
We used connect-src ws://localhost:65412
in this example to allow /_framework/aspnetcore-browser-refresh.js
to work properly.
Also, we disabled CSS Hot Reload in Visual Studio, see https://github.com/dotnet/aspnetcore/issues/36085, to avoid
a weak CSP configuration just for development.
Alternatively, you can configure everything in code:
builder.Services.AddContentSecurityPolicy(c =>
{
c.IsDisabled = false;
c.Add("Policy1", g =>
{
g.Csp.Fixed = "default-src 'self';connect-src ws://localhost:65412";
g.Csp.Nonceable.Add("style-src 'self'");
g.CspReportOnly.Nonceable.Add("script-src");
g.IsDefault = true;
g.NumberOfNonceBytes = 32;
});
c.Add("Policy2", g =>
{
g.Csp.Fixed = "default-src 'self';connect-src ws://localhost:65412";
g.CspReportOnly.Fixed="default-src";
g.IsDefault = false;
});
});
Add nonces to the body by importing the TagHelpers
@addTagHelper *, Galebra.Security.Headers.Csp
And add to styles, scripts or link tags the TagHelper:
nonce-add=true
For example, for PolicyGroup3
, that restricts you to use only nonced styles,
you would allow loading bootstrap like so:
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" nonce-add=true/>
As you may agree, this logical structure actually is powerful.
The following design principles, detailed below, have been followed:
Action
should also be possible in Program.csscript
, style
, and link
tagsHttpContext
to avoid memory allocationConsider the following arguments. CSP is a set of two headers, whose names can be:
Each policy in those headers is defined by an ordered set of directives.
Each directive is a name/value pair; name is non-empty, but value may be empty.
For example, script-src
is a directive's name and the value can be, for instance, 'self'
or a url or a set of these.
Another directive can be default-src 'none'
, where default-src
is another directive name.
The directives forming a policy are separated by semicolons.
For this example, the Content Security Policy would be the header:
Content-Security-Policy: default-src 'none';script-src 'self'
which is equivalent to the shorter version
Content-Security-Policy: default-src;script-src 'self'
In theory, you can have multiple CSP policies for the same header name in the same response header; however, we believe this should not be recommended because it makes your policy more convoluted and rather highlights a design flaw, notwithstanding that you send more bytes to the user's browser. Consequently, the library outputs only one CSP header for a given header name. In practice, we assign the value for the header key (name) rather than adding a new item in the dictionary. This also has the consequence of efficient write.
So, with this library, you cannot have the following:
Content-Security-Policy: Policy1
Content-Security-Policy: Policy2
However, you can have both a CSP and a CSP-Report-Only policy, and usually this is recommended. Some libraries do not support this configuration. This library does. For example you can do something like this:
Content-Security-Policy: default-src 'self'
Content-Security-Policy-Report-Only: default-src;style-src 'self'
In this scenario, the website would run without issues if, loosely speaking, all styles and scripts are loaded from the server
('self'
), but the browser will report to you what would break if you disable scripts ('none') and other fetch directives,
except for styles which use 'self'
.
Thus, this allows you to enforce a policy and at the same time fine-tune another one which ultimately will become
the enforcing policy when you are ready.
Allowing for both CSP and CSP-Report-Only headers to coexist introduces the CspPolicyGroup
class.
It is this group class that you use to configure your policies. This class contains two properties, Csp
and CspReportOnly
, each of
which is a CspPolicy
class. You can leave one of these properties empty; for example if you want to have only browser reports,
you would build only the property CspReportOnly
.
The usual route to library design is to use the so-called fluent-api. This gives elegant code, but with CSP this is unnecessary complication and makes your Program.cs (or Startup.cs) rather long. In addition, CSP configuration ultimately boils down to outputting one or two strings in the headers, plus the possibility of nonces in those headers and pages. A developer will ultimately look at the CSP output in the browser's tools to see if the formatting is as expected and nonces properly generated and positioned. There is already a lot of resources and services in the pipeline, so we decided to keep it simple and focus on performance.
This led us to the following observation. A CSP header value is always divided into two groups.
The first group is a fixed, possibly empty, string, i.e. a string that does not change upon requests on a given page,
and another string that is dynamically generated upon each request to include one or more nonces.
Therefore the CspPolicy
class has two public properties, a string Fixed
and a list of strings called Nonceable
.
For example, the policy:
default-src;style-src 'self'
would be set as a CspPolicy.Fixed
string because a nonce is not required. In appsettings.json, this would be:
"Fixed": "default-src;style-src 'self'",
A sha256-myshacode
, if needed, would be included in the Fixed
string.
If, now, you want to have this policy, but in addition produce a nonce for styles, then you would need to split your string
and populate the CspPolicy.Nonceable
list. In appsettings.json, the split would be like this:
"Fixed": "default-src",
"Nonceable": [
"style-src 'self'"
]
The style-src
is here short, but generally is longer to include for example urls from a Bootstrap CDN. The most important is to
identify the directive's name, here style-src
.
To use the nonce on the style in this example, you would invoke the Tag Helper nonce-add
, e.g.
<style nonce-add=true>
.myclass{
background:lightgreen;
}
</style>
<h4 class="myclass">I am lightgreen, thank you nonce!</h4>
When you use a nonce
, common libraries are confined to the script
and style
tags, but link
tags are also possible.
Nonce generation defaults to the spec-recommended 16 bytes, which gives 256^16=2^128 possibilities,
or a 22 long web-encoded base64 string. You can override this with the property CspPolicyGroup.NumberOfNonceBytes
,
e.g. in appsettings.json, and the nonce will apply to the entire group.
"NumberOfNonceBytes": 8
When you implement CSP on a website, often you need several CspPolicyGroup
objects depending on the page where the user lands.
For example, you would have a global CSP policy on all pages, but when processing a payment on a page or Razor Pages folder,
or a Controller, you will want another CSP policy (for example to accept connections to a payment API such as PayPal or Stripe).
This library allows you to configure many policies and invoke them when needed, through attributes and filters.
When you use the library, unless you override the default described below, the default CspPolicyGroup
is applied to all pages as soon as you inject the Middleware in the pipeline.
You can apply a given policy to an entire Folder in Razor Pages, e.g. in the Movies folder:
//Apply on specific folders
builder.Services.AddRazorPages(options =>
{
options.Conventions.AddFolderApplicationModelConvention(
"/Movies",
model => model.Filters.Add(new EnableCspPageFilter { PolicyGroupName = "PolicyGroup1" }));
});
You can also disable CSP in a folder:
model => model.Filters.Add(new DisableCspPageFilter { EnforceMode = false }));
Where EnforceMode
is true
by default and is discussed below.
In areas, you could do something like this:
options.Conventions.AddAreaFolderApplicationModelConvention("Identity", "/Account",
model => model.Filters.Add(new EnableCspPageFilter { PolicyGroupName = "PolicyGroup1" }));
options.Conventions.AddAreaPageApplicationModelConvention("Identity", "/Account/Manage/ChangePassword",
model => model.Filters.Add(new EnableCspPageFilter { PolicyGroupName = "PolicyGroup3" }));
In a Razor Page or Action or Controller, you can override the default CspPolicyGroup
with an attribute:
[EnableCsp(PolicyGroupName="PolicyGroup1")]
[DisableCsp]
The [DisableCsp]
always wins unless you set the init
property DisableCsp.EnforceMode
to false.
The enforcement rule is useful in a scenario where an entire folder or a controller needs to have CSP disabled on all routes
except for those where the enable attribute is present. For example, in the following we use the DisableCspPageFilter
to disable CSP in the Movies folder:
builder.Services.AddRazorPages(options =>
{
options.Conventions.AddFolderApplicationModelConvention(
"/Movies",
model => model.Filters.Add(new DisableCspPageFilter { EnforceMode = false }));
});
Because we have set EnforceMode = false
, we can set CSP on a page inside the Movies folder with an attribute,
e.g. in Movies/index [EnableCsp(PolicyGroupName = "PolicyGroup3")]
. The same applies for a controller; you would disable CSP
with EnforceMode=false
and use the EnableCsp
attribute on an action:
[DisableCsp(EnforceMode = false)]
public class BooksController : Controller
{
public ActionResult Index()
{
//CSP is disabled here
return View();
}
// GET: BooksController/Details/5
[EnableCsp(PolicyGroupName ="PolicyGroup3")]
public ActionResult Details(int id)
{
//CSP works here owing to EnforceMode=false
return View();
}
}
By default, the library applies the default CspPolicyGroup
to all delivered pages until overwritten by attributes or filters.
You can override this global behaviour in Program.cs or in appsettings.json where you can set at the top level
"IsDisabled": true,
. This will result in CSP not being applied globally, at all, until you invoke it via attributes or filters.
This would be really nice to be built in. Two other libraries support this as well https://github.com/andrewlock/NetEscapades.AspNetCore.SecurityHeaders and https://github.com/juunas11/aspnetcore-security-headers
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
Usage from MVC
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