Browse Source

Merge pull request #16695 from abpframework/salihozkara/csp

Prevent breaking change
pull/16699/head
Halil İbrahim Kalkan 3 years ago
committed by GitHub
parent
commit
ca52e7d6c1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 51
      docs/en/UI/AspNetCore/Security-Headers.md
  2. 27
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersMiddleware.cs
  3. 5
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersOptions.cs
  4. 1
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Security/Headers/SecurityHeadersTestController_Tests.cs

51
docs/en/UI/AspNetCore/Security-Headers.md

@ -19,31 +19,13 @@ ABP Framework allows you to add frequently used security headers into your appli
Configure<AbpSecurityHeadersOptions>(options =>
{
options.UseContentSecurityPolicyHeader = true; //false by default
options.ContentSecurityPolicyValues["object-src"] = new string[] { "'none'" };
options.ContentSecurityPolicyValues["form-action"] = new string[] { "'self'" };
options.ContentSecurityPolicyValues["frame-ancestors"] = new string[] { "'self'" };
options.ContentSecurityPolicyValues["script-src"] = new string[] { "'self'", "'unsafe-inline'", "'unsafe-eval'" };
//adding script-src nonce
options.UseContentSecurityPolicyScriptNonce = true; //false by default
//ignore script nonce source for these paths
options.IgnoredScriptNoncePaths.Add("/my-page");
//ignore script nonce by Elsa Workflows and other selectors
options.IgnoredScriptNonceSelectors.Add(context =>
{
var endpoint = context.GetEndpoint();
return Task.FromResult(endpoint?.Metadata.GetMetadata<PageRouteMetadata>()?.RouteTemplate == "/{YOURHOSTPAGE}");
});
options.ContentSecurityPolicyValue = "object-src 'none'; form-action 'self'; frame-ancestors 'none'"; //default value
//adding additional security headers
options.Headers["Referrer-Policy"] = "no-referrer";
});
```
> Using the script nonce feature will automatically add the nonce value to your script tags. There is no need to add it manually. However, if you still need to add it manually, you can use 'Html.GetScriptNonce()' to add the nonce value or 'Html.GetScriptNonceAttribute()' to add the nonce attribute value.
> If the header is the same, the additional security headers you defined take precedence over the default security headers. In other words, it overrides the default security headers' values.
## Security Headers Middleware
@ -61,3 +43,34 @@ app.UseAbpSecurityHeaders();
After that, you have registered the `UseAbpSecurityHeaders` middleware into the request pipeline, the defined security headers will be shown in the response headers as in the figure below:
![](../../images/security-response-headers.png)
## Content Security Policy Script Nonce
Abp Framework provides a property to add a dynamic script-src nonce value to the Content-Security-Policy header. With this feature, it automatically adds a dynamic nonce value to the header side. And with the help of the script tag helper, it adds this [`script nonce`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce) value to the script tags on your pages(The `ScriptNonceTagHelper` in the `Volo.Abp.AspNetCore.Mvc.UI.Bundling` namespace must be attached as a taghelper.).
> If you need to add the nonce script manually, you can use 'Html.GetScriptNonce()' to add the nonce value or 'Html.GetScriptNonceAttribute()' to add the nonce attribute value.
This feature is disabled by default. You can enable it by setting the `UseContentSecurityPolicyScriptNonce` property of the `AbpSecurityHeadersOptions` class to `true`.
### Ignore Script Nonce
You can ignore the script nonce for some pages or some selectors. You can use the `IgnoredScriptNoncePaths` and `IgnoredScriptNonceSelectors` properties of the `AbpSecurityHeadersOptions` class.
**Example:**
```csharp
Configure<AbpSecurityHeadersOptions>(options =>
{
//adding script-src nonce
options.UseContentSecurityPolicyScriptNonce = true; //false by default
//ignore script nonce source for these paths
options.IgnoredScriptNoncePaths.Add("/my-page");
//ignore script nonce by Elsa Workflows and other selectors
options.IgnoredScriptNonceSelectors.Add(context =>
{
var endpoint = context.GetEndpoint();
return Task.FromResult(endpoint?.Metadata.GetMetadata<PageRouteMetadata>()?.RouteTemplate == "/{YOURHOSTPAGE}");
});
});
```

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

@ -101,35 +101,28 @@ public class AbpSecurityHeadersMiddleware : IMiddleware, ITransientDependency
protected virtual string BuildContentSecurityPolicyValue(HttpContext context)
{
var cspValue = Options.Value.ContentSecurityPolicyValue.IsNullOrWhiteSpace() ? DefaultValue : Options.Value.ContentSecurityPolicyValue;
if (!(Options.Value.UseContentSecurityPolicyScriptNonce &&
context.Items.TryGetValue(AbpAspNetCoreConsts.ScriptNonceKey, out var nonce) &&
nonce is string nonceValue && !string.IsNullOrEmpty(nonceValue)))
{
return ContentSecurityPolicyValuesToCSPString();
return cspValue;
}
var scriptSrcValue = "";
if (Options.Value.ContentSecurityPolicyValues.TryGetValue(ScriptSrcKey, out var scriptSrc))
{
scriptSrcValue = string.Join(" ", scriptSrc);
}
scriptSrcValue += $" 'nonce-{nonceValue}'";
var nonceStr = $" 'nonce-{nonceValue}'";
return ContentSecurityPolicyValuesToCSPString(true) + $"; {ScriptSrcKey} {scriptSrcValue}";
}
var scriptSrcValue = Options.Value.ContentSecurityPolicyValue.Split(';')
.FirstOrDefault(x => x.Trim().StartsWith(ScriptSrcKey))?.Trim();
protected virtual string ContentSecurityPolicyValuesToCSPString(bool ignoreScriptSrc = false)
{
if (Options.Value.ContentSecurityPolicyValues.Any())
if (scriptSrcValue.IsNullOrWhiteSpace())
{
return string.Join("; ",
Options.Value.ContentSecurityPolicyValues.WhereIf(ignoreScriptSrc, x => x.Key != ScriptSrcKey)
.Select(x => $"{x.Key} {string.Join(" ", x.Value)}"));
return cspValue.EnsureEndsWith(';') + $" {ScriptSrcKey}{nonceStr};";
}
return DefaultValue;
var newScriptSrcValue = scriptSrcValue + nonceStr;
return Options.Value.ContentSecurityPolicyValue.Replace(scriptSrcValue, newScriptSrcValue);
}
protected virtual void AddHeader(HttpContext context, string key, string value, bool overrideIfExists = false)
{

5
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Security/AbpSecurityHeadersOptions.cs

@ -10,8 +10,8 @@ public class AbpSecurityHeadersOptions
public bool UseContentSecurityPolicyHeader { get; set; }
public bool UseContentSecurityPolicyScriptNonce { get; set; }
public Dictionary<string, IEnumerable<string>> ContentSecurityPolicyValues { get; }
public string ContentSecurityPolicyValue { get; set; }
public Dictionary<string, string> Headers { get; }
@ -22,7 +22,6 @@ public class AbpSecurityHeadersOptions
public AbpSecurityHeadersOptions()
{
Headers = new Dictionary<string, string>();
ContentSecurityPolicyValues = new Dictionary<string, IEnumerable<string>>();
IgnoredScriptNonceSelectors = new List<Func<HttpContext, Task<bool>>>();
IgnoredScriptNoncePaths = new List<string>();
}

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

@ -29,7 +29,6 @@ public class SecurityHeadersTestController_Tests : AspNetCoreMvcTestBase
responseMessage.Headers.ShouldContain(x => x.Key == "X-XSS-Protection" & x.Value.First().ToString() == "1; mode=block");
responseMessage.Headers.ShouldContain(x => x.Key == "X-Frame-Options" & x.Value.First().ToString() == "SAMEORIGIN");
responseMessage.Headers.ShouldContain(x => x.Key == "X-Content-Type-Options" & x.Value.First().ToString() == "nosniff");
responseMessage.Headers.ShouldContain(x => x.Key == "Content-Security-Policy" & x.Value.First().ToString() == "object-src 'none'; form-action 'self'; frame-ancestors 'none'");
}
[Fact]

Loading…
Cancel
Save