forked from aspnet/AspLabs
-
Notifications
You must be signed in to change notification settings - Fork 1
Middleware and TagHelpers for CSP support in ASP.NET #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
a304dbb
Content Security Policy implementation
salcho 21042df
add READM.md file
salcho 57e9b7c
minor fix in READM.md file
salcho 87ebc81
rel path and co-author fix in READM.md file
salcho dd14bf0
moved CSP to top-level directory and fixed README file
salcho File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "solution": { | ||
| "path": "..\\..\\..\\AspNetCore.sln", | ||
| "projects": [ | ||
| "src\\Middleware\\CSP\\src\\Microsoft.AspNetCore.Csp.csproj", | ||
| "src\\Middleware\\CSP\\test\\UnitTests\\Microsoft.AspNetCore.Csp.Test.csproj", | ||
| "src\\Middleware\\CSP\\test\\testassets\\CspMiddlewareWebSite.csproj", | ||
| ] | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| # CSP | ||
|
|
||
| ## Description | ||
|
|
||
| This directory contains .NET Core middleware for Content Security Policy (CSP). CSP is a very popular security mitigation against XSS and other injection vulnerabilities. CSP comes in many flavours, but we've chosen to add support for the most robust of them: nonce-based, strict-dynamic CSP. | ||
|
|
||
| There was a previous discussion about CSP in .NET [here](https://github.com/dotnet/aspnetcore/issues/6001), that we have considered for our design. | ||
gchatz22 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ## Contributions | ||
| This directory includes the following changes: | ||
|
|
||
| * Allow configuration of whether CSP enabled in reporting or enforcement modes. | ||
| * Allows configuration of a report URI, for violation reports sent by the browser. | ||
| * CSP middleware generates a nonce-based, strict-dynamic policy. | ||
| * Middleware adds thepolicy to HTTP responses according to the configuration. | ||
gchatz22 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * Custom <script> TagHelper to set nonce attribute on script blocks automatically. | ||
| * Provides a default implementation of a CSP violation report collection endpoint. | ||
| * Example app that uses our CSP middleware and corresponding basic unit tests. | ||
|
|
||
| ## Usage: | ||
|
|
||
| ``` | ||
| // CSP configuration. Must come first because other middleware might skip any following middleware. | ||
|
|
||
| app.UseCsp(policyBuilder => | ||
| policyBuilder.WithCspMode(CspMode.ENFORCING) | ||
|
|
||
| .WithReportingUri("/csp")); | ||
| ``` | ||
| You can find the sample app under `./test/testassets/CspApplication/` directory. | ||
|
|
||
| ## Authors | ||
| * Co-authored-by: Aaron Shim - [email protected] | ||
| * Co-authored-by: Santiago Diaz - [email protected] | ||
gchatz22 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System; | ||
| using System.Text; | ||
|
|
||
| namespace Microsoft.AspNetCore.Csp | ||
| { | ||
| /// <summary> | ||
| /// A greedy Content Security Policy generator | ||
| /// </summary> | ||
| public class ContentSecurityPolicy | ||
| { | ||
| private readonly string _baseAndObject = "base-uri 'none'; object-src 'none'"; | ||
| private readonly Func<INonce, string> policyBuilder; | ||
|
|
||
| private readonly CspMode _cspMode; | ||
| private readonly bool _strictDynamic; | ||
| private readonly bool _unsafeEval; | ||
| private readonly string _reportingUri; | ||
|
|
||
| /// <summary> | ||
| /// Instantiates a new <see cref="ContentSecurityPolicy"/>. | ||
| /// </summary> | ||
| /// <param name="cspMode">Represents whether the current policy is in enforcing or reporting mode.</param> | ||
| /// <param name="strictDynamic">Whether the policy should enable nonce propagation.</param> | ||
| /// <param name="unsafeEval">Whether JavaScript's eval should be allowed to run.</param> | ||
| /// <param name="reportingUri">An absolute or relative URI representing the reporting endpoint</param> | ||
| public ContentSecurityPolicy( | ||
| CspMode cspMode, | ||
| bool strictDynamic, | ||
| bool unsafeEval, | ||
| string reportingUri | ||
| ) | ||
| { | ||
| _cspMode = cspMode; | ||
| _strictDynamic = strictDynamic; | ||
| _unsafeEval = unsafeEval; | ||
| _reportingUri = reportingUri; | ||
|
|
||
| // compute the static directives of the policy up front to avoid doing so on every request | ||
| var policyFormat = new StringBuilder() | ||
| .Append("script-src") | ||
| .Append(" 'nonce-{0}' ") // nonce | ||
| .Append(_strictDynamic ? "'strict-dynamic'" : "") | ||
| .Append(_unsafeEval ? "'unsafe-eval'" : "") | ||
| .Append(" https: http:;") // fall-back allowlist-based CSP for browsers that don't support nonces | ||
| .Append(_baseAndObject) | ||
| .Append("; ") // end of script-src | ||
| .Append(_reportingUri != null ? "report-uri " + _reportingUri : "") | ||
| .ToString(); | ||
|
|
||
| policyBuilder = nonce => string.Format(policyFormat, nonce.GetValue()); | ||
| } | ||
|
|
||
| public string GetHeaderName() | ||
| { | ||
| return _cspMode == CspMode.REPORTING ? CspConstants.CspReportingHeaderName : CspConstants.CspEnforcedHeaderName; | ||
| } | ||
| public string GetPolicy(INonce nonce) | ||
| { | ||
| return policyBuilder.Invoke(nonce); | ||
| } | ||
| } | ||
|
|
||
| public enum CspMode | ||
| { | ||
| NONE, | ||
| REPORTING, | ||
| ENFORCING | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace Microsoft.AspNetCore.Csp | ||
| { | ||
| /// <summary> | ||
| /// Allows customizing content security policies | ||
| /// </summary> | ||
| public class ContentSecurityPolicyBuilder | ||
| { | ||
| private CspMode _cspMode; | ||
| private bool _strictDynamic; | ||
| private bool _unsafeEval; | ||
| private string _reportingUri; | ||
| private LogLevel _logLevel = LogLevel.Information; | ||
|
|
||
| public ContentSecurityPolicyBuilder WithCspMode(CspMode cspMode) | ||
| { | ||
| _cspMode = cspMode; | ||
| return this; | ||
| } | ||
|
|
||
| public ContentSecurityPolicyBuilder WithStrictDynamic() | ||
| { | ||
| _strictDynamic = true; | ||
| return this; | ||
| } | ||
|
|
||
| public ContentSecurityPolicyBuilder WithUnsafeEval() | ||
| { | ||
| _unsafeEval = true; | ||
| return this; | ||
| } | ||
| public ContentSecurityPolicyBuilder WithReportingUri(string reportingUri) | ||
| { | ||
| // TODO: normalize URL | ||
| _reportingUri = reportingUri; | ||
| return this; | ||
| } | ||
|
|
||
| public ContentSecurityPolicyBuilder WithLogLevel(LogLevel logLevel) | ||
| { | ||
| _logLevel = logLevel; | ||
| return this; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Whether the policy specifies a relative reporting URI. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// If this method returns true, a handler for the reporting endpoint will be automatically added to this application. | ||
| /// </remarks> | ||
| public bool HasLocalReporting() | ||
| { | ||
| return _reportingUri != null && _reportingUri.StartsWith("/"); | ||
| } | ||
|
|
||
| public CspReportLogger ReportLogger(ICspReportLoggerFactory loggerFactory) | ||
| { | ||
| return loggerFactory.BuildLogger(_logLevel, _reportingUri); | ||
| } | ||
|
|
||
| public ContentSecurityPolicy Build() | ||
| { | ||
| if (_cspMode == CspMode.NONE) | ||
| { | ||
| // TODO: Error message | ||
| throw new InvalidOperationException(); | ||
| } | ||
|
|
||
| if (_cspMode == CspMode.REPORTING && _reportingUri == null) | ||
| { | ||
| // TODO: Error message | ||
| throw new InvalidOperationException(); | ||
| } | ||
|
|
||
| return new ContentSecurityPolicy( | ||
| _cspMode, | ||
| _strictDynamic, | ||
| _unsafeEval, | ||
| _reportingUri | ||
| ); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| namespace Microsoft.AspNetCore.Csp | ||
| { | ||
| /// <summary> | ||
| /// CSP-related constants. | ||
| /// </summary> | ||
| public static class CspConstants | ||
| { | ||
| /// <summary> | ||
| /// CSP header name in enforcement mode. | ||
| /// </summary> | ||
| public static readonly string CspEnforcedHeaderName = "Content-Security-Policy"; | ||
| /// <summary> | ||
| /// CSP header name in reporting mode. | ||
| /// </summary> | ||
| public static readonly string CspReportingHeaderName = "Content-Security-Policy-Report-Only"; | ||
| /// <summary> | ||
| /// Expected content type for requests containing CSP violation reports. | ||
| /// </summary> | ||
| public static readonly string CspReportContentType = "application/csp-report"; | ||
| /// <summary> | ||
| /// Possible violated directive value used to create textual representations of violation reports. | ||
| /// </summary> | ||
| public static readonly string ScriptSrcElem = "script-src-elem"; | ||
| /// <summary> | ||
| /// Possible blocked URI value used to create textual representations of violation reports. | ||
| /// </summary> | ||
| public static readonly string BlockedUriInline = "inline"; | ||
| /// <summary> | ||
| /// Possible violated directive value used to create textual representations of violation reports. | ||
| /// </summary> | ||
| public static readonly string ScriptSrcAttr = "script-src-attr"; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System.Threading.Tasks; | ||
| using Microsoft.AspNetCore.Http; | ||
|
|
||
| namespace Microsoft.AspNetCore.Csp | ||
| { | ||
| /// <summary> | ||
| /// Middleware for supporting CSP. | ||
| /// </summary> | ||
| public class CspMiddleware | ||
| { | ||
| private readonly RequestDelegate _next; | ||
| private readonly ContentSecurityPolicy _csp; | ||
|
|
||
| /// <summary> | ||
| /// Instantiates a new <see cref="CspMiddleware"/>. | ||
| /// </summary> | ||
| /// <param name="next">The next middleware in the pipeline.</param> | ||
| /// <param name="csp">A content security policy generator.</param> | ||
| public CspMiddleware(RequestDelegate next, ContentSecurityPolicy csp) | ||
| { | ||
| _next = next; | ||
| _csp = csp; | ||
| } | ||
|
|
||
| public Task Invoke(HttpContext context, INonce nonce) | ||
| { | ||
| context.Response.Headers[_csp.GetHeaderName()] = _csp.GetPolicy(nonce); | ||
| return _next(context); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
|
||
| using System; | ||
| using Microsoft.AspNetCore.Builder; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
|
|
||
| namespace Microsoft.AspNetCore.Csp | ||
| { | ||
| /// <summary> | ||
| /// Extends <see cref="IApplicationBuilder"/> to add CSP middleware support. | ||
| /// </summary> | ||
| public static class CspMiddlewareExtensions | ||
| { | ||
| /// <summary> | ||
| /// Adds a CSP middleware to this web application pipeline that will add a custom policy to responses and collect CSP violation reports sent by user agents. | ||
| /// </summary> | ||
| /// <param name="app">The IApplicationBuilder passed to the Configure method</param> | ||
| /// <param name="configurePolicy">A delegate to build a custom content security policy</param> | ||
| /// <returns>The original app parameter</returns> | ||
| public static IApplicationBuilder UseCsp(this IApplicationBuilder app, Action<ContentSecurityPolicyBuilder> configurePolicy) | ||
| { | ||
| if (app == null) | ||
| { | ||
| throw new ArgumentNullException(nameof(app)); | ||
| } | ||
|
|
||
| var policyBuilder = new ContentSecurityPolicyBuilder(); | ||
| configurePolicy(policyBuilder); | ||
|
|
||
| if (policyBuilder.HasLocalReporting()) | ||
| { | ||
| var loggerFactory = app.ApplicationServices.GetService<ICspReportLoggerFactory>(); | ||
| var reportLogger = policyBuilder.ReportLogger(loggerFactory); | ||
| app.UseWhen( | ||
| context => context.Request.Path.StartsWithSegments(reportLogger.ReportUri), | ||
| appBuilder => appBuilder.UseMiddleware<CspReportingMiddleware>(reportLogger)); | ||
| } | ||
|
|
||
| return app.UseMiddleware<CspMiddleware>(policyBuilder.Build()); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds the necessary bindings for CSP. Namely, allows adding nonces to script tags automatically and provides a custom logging factory. | ||
| /// </summary> | ||
| /// <param name="app">The IApplicationBuilder passed to the Configure method</param> | ||
| /// <returns>The original services parameter</returns> | ||
| public static IServiceCollection AddCsp(this IServiceCollection services) | ||
| { | ||
| if (services == null) | ||
| { | ||
| throw new ArgumentNullException(nameof(services)); | ||
| } | ||
|
|
||
| services.AddScoped<INonce, Nonce>(); | ||
| services.AddSingleton<ICspReportLoggerFactory, CspReportLoggerFactory>(); | ||
|
|
||
| return services; | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.