在Blazor中重新审视内容安全策略(CSP)随机数的使用
这篇博客探讨了在使用Blazor和ASP.NET Core实现的Web应用程序中实施强大的内容安全策略(CSP)。在实施CSP时,我始终推荐使用CSP随机数或至少使用CSP哈希。如果一个技术栈不支持CSP随机数,在实施安全且专业的Web应用程序时,您可能应该避免使用该解决方案。
代码:https://github.com/damienbod/BlazorServerOidc
较早的相关博客
Blazor应用程序类型
在Blazor中实施强大的内容安全策略(CSP)之前,必须确定您正在使用的特定Blazor应用程序类型。Blazor提供了各种形式和渲染模式,因此选择最适合您需求的模式至关重要。
- Blazor Web Server (交互式服务器)
- Blazor Web WASM (交互式WebAssembly)
- Blazor Web 混合模式 (交互式自动)
- 在ASP.NET Core中托管的Blazor WASM (Razor页面主机)
- Blazor WASM 独立应用
- Blazor Server,可以更新为Blazor Web Server (交互式服务器)
我只使用支持CSP随机数的Blazor应用程序类型和渲染模式。目前,只有三种类型的Blazor应用程序提供此支持:
- Blazor Web Server (交互式服务器)
- Blazor Web WASM (交互式WebAssembly)
- Blazor Web 混合模式 (交互式自动)
- 在ASP.NET Core中托管的Blazor WASM (Razor页面主机)
- Blazor WASM 独立应用
- Blazor Server,可以更新为Blazor Web Server (交互式服务器)
Blazor Web设置
当使用最新版本的Blazor时,如果应用程序中安全性很重要,可以使用交互式服务器渲染模式,并且应该避免使用交互式自动渲染模式。可以使用NetEscapades.AspNetCore.SecurityHeaders Nuget包进行设置,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// 添加服务到容器
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddHttpContextAccessor();
// ...
builder.Services.AddSecurityHeaderPolicies()
.SetDefaultPolicy(SecurityHeadersDefinitions
.GetHeaderPolicyCollection(oidcConfig["Authority"],
builder.Environment.IsDevelopment()));
var app = builder.Build();
// ...
app.UseSecurityHeaders();
app.UseHttpsRedirection();
app.UseAntiforgery();
app.UseAuthentication();
app.UseAuthorization();
app.MapStaticAssets();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.MapLoginLogoutEndpoints();
app.Run();
}
}
|
实施安全头
NetEscapades.AspNetCore.SecurityHeaders Nuget包可用于在ASP.NET Core应用程序中实施安全头。这将安全头应用于不同端点的响应。其中一个头是浏览器CSP头。CSP随机数按照最新浏览器的推荐使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
namespace BlazorWebApp;
using Microsoft.AspNetCore.Builder;
public static class SecurityHeadersDefinitions
{
public static HeaderPolicyCollection GetHeaderPolicyCollection(string? idpHost, bool isDev)
{
ArgumentNullException.ThrowIfNull(idpHost);
var policy = new HeaderPolicyCollection()
.AddFrameOptionsDeny()
.AddContentTypeOptionsNoSniff()
.AddReferrerPolicyStrictOriginWhenCrossOrigin()
.AddCrossOriginOpenerPolicy(builder => builder.SameOrigin())
.AddCrossOriginResourcePolicy(builder => builder.SameOrigin())
// #if !DEBUG // 如果使用Visual Studio开发热重载,请移除
.AddCrossOriginEmbedderPolicy(builder => builder.RequireCorp())
// #endif
.AddContentSecurityPolicy(builder =>
{
builder.AddObjectSrc().None();
builder.AddBlockAllMixedContent();
builder.AddImgSrc().Self().From("data:");
builder.AddFormAction().Self().From(idpHost);
builder.AddFontSrc().Self();
builder.AddStyleSrc().Self().UnsafeInline();
builder.AddBaseUri().Self();
builder.AddFrameAncestors().None();
// #if !DEBUG // 为Visual Studio开发移除
builder.AddScriptSrc().WithNonce().UnsafeInline();
// #endif
})
.RemoveServerHeader()
.AddPermissionsPolicyWithDefaultSecureDirectives();
if (!isDev)
{
// maxage = 一年秒数
policy.AddStrictTransportSecurityMaxAgeIncludeSubDomains();
}
return policy;
}
}
|
可以将头添加到服务中。
1
2
|
builder.Services.AddSecurityHeaderPolicies().SetDefaultPolicy(SecurityHeadersDefinitions.GetHeaderPolicyCollection(oidcConfig["Authority"],
builder.Environment.IsDevelopment()));
|
HttpContextAccessor可用于从HTTP上下文中获取头,并用于在UI组件中加载脚本和样式。ImportMap使用随机数进行扩展。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" nonce="@Nonce" />
<link rel="stylesheet" href="@Assets["app.css"]" nonce="@Nonce" />
<link rel="stylesheet" href="@Assets["BlazorWebApp.styles.css"]" nonce="@Nonce" />
<ImportMap AdditionalAttributes="@(new Dictionary<string, object>() { { "nonce", Nonce ?? "" }})" />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />
</head>
<body>
<Routes @rendermode="InteractiveServer" />
<script src="_framework/blazor.web.js" nonce="@Nonce"></script>
</body>
</html>
@code{
public string? Nonce => HttpContextAccessor?.HttpContext?.GetNonce();
[Inject] private IHttpContextAccessor? HttpContextAccessor { get; set; }
}
|
Visual Studio调试
当使用Visual Studio进行调试时,它会添加两个默认被阻止且应该被阻止的脚本。这是一种脚本攻击,在任何部署中都应被阻止。
如果您希望在Visual Studio调试中允许此操作,可以在SecurityHeadersDefinitions类中使用#if !DEBUG来允许以下注入的脚本:
1
2
3
4
|
<!-- Visual Studio Browser Link -->
<script type="text/javascript" src="/_vs/browserLink" async="async" id="__browserLink_initializationData" data-requestId="59852cf479154d149a3db2064a0722e6" data-requestMappingFromServer="false" data-connectUrl="http://localhost:63449/fd8b98433c6f43259bb7df9563900638/browserLink"></script>
<!-- End Browser Link -->
<script src="/_framework/aspnetcore-browser-refresh.js"></script>
|
注意事项
使用CSP随机数可以轻松地应用、更新和维护应用程序,并在所有环境中使用强大的CSP。我在开发、测试和生产设置中都使用这种方法。在构建专业的Web应用程序时,可能应该避免任何不支持CSP随机数的Web技术栈。Blazor InteractiveServer渲染模式有一个很好的解决方案。
链接