Blazor 安全设计:一个 Blazor Admin 后台需要防哪些攻击?

这篇是安全专题的总览,把散落在各篇里的机制汇总成一张"攻击面 → 防护 → 证据"的对照表。目标不是制造紧张感,而是让每一类风险都能指到具体代码。


一、先明确威胁模型

后台系统面对的主要是三类人:

攻击者 手里有什么 想干什么
未登录的外部人员 只知道后台地址 撞库、爆破、探测接口
已登录的低权限用户 合法账号(比如普通员工) 越权看/改不该碰的数据
内网中的其它系统 网络可达性 通过 SSRF 等入口横向移动

下面所有防护都对应这三类。


二、总览:攻击面 → 防护 → 证据

攻击面 防护机制 源码 测试
会话伪造 / 篡改 Data Protection 加密登录票据 + 统一 purpose AdminContext.Auth.cs、EasyAdminLoginTicketAuthenticationHandler.cs AuthenticationTests
后台入口探测 AdminRouteSecret(中间件 404 + 组件二次校验) AdminExtensions.cs、AdminContext.Auth.cs ------
暴力破解 登录失败锁定 + 验证码 SecurityOptions.LoginFailureLimit / LoginLockout、EnableLoginCaptcha ------
CSRF Antiforgery 过滤器(写方法校验) EasyAdminAntiforgeryFilter.cs CsrfTests
路由越权 AuthPath(路由 + 导航拦截) AdminContext.Authorization.cs ------
操作越权 AuthButton + AdminButtonAttribute AdminTable.razor、AdminButtonAttribute.cs AuthorizationTests
数据越权(查询/改/删/导入) ApplyDataPermission + FilterAuthorizedAsync FreeSqlExtensions.cs DataPermissionTests
文件上传绕过 扩展名白/黑名单 + Magic Number + ImageSharp FileSecurityValidator.cs、FileService.cs FileSecurityValidatorTests、FileUploadSecurityTests
路径穿越 SafePathService(拒绝 ../盘符/UNC) SafePathService.cs PathTraversalTests
SSRF 协议白名单 + 逐跳校验 + 连接前锁定 IP RemoteContentDownloader.cs、FileSecurityValidator.cs SsrfTests
越权订阅推送 Hub 不接受客户端 userId NotificationHub.cs NotificationHubTests
越权改消息状态 只取 Id + 当前用户条件 AdminMessageService.cs MessageReadTests
密码泄露 PBKDF2 + Salt + 固定时间比较 PBKDF2Encrypt.cs Pbkdf2UpgradeTests
匿名接口滥用 限流 + 参数校验 + 不泄露异常 WeChatAppController.cs 等 AuthorizationTests
多租户串数据 独立库 + 文件目录 + 缓存/锁前缀 MultiTenantService.cs、AdminContext.Tenant.cs TenantIsolationTests、TenantCacheIsolationTests
异常信息泄露 通用提示 + TraceId ApprovalService.GenericFailure ApprovalConcurrencyTests

三、逐项展开

1. 会话:票据是加密的,且不能跨用途复用

登录成功后写的是加密票据,内容是 userId|loginTime:

csharp 复制代码
var encryptedData = _loginTicketProtector.Protect($"{user.Id}|{user.LoginTime.ToString(LoginTimeFormat, CultureInfo.InvariantCulture)}");

_loginTicketProtector 来自 IDataProtectionProvider.CreateProtector("EasyAdminBlazor.LoginTicket.v1")。测试锁定了两条:

csharp 复制代码
[Fact] public void LoginTicket_InvalidPurpose_CannotBeUnprotected()   // 换个 purpose 解不开
[Fact] public void EphemeralDataProtector_RoundTripsLoginTicket()

还有一条跨文件一致性断言,防止以后有人改了 purpose 导致 Hub 认证失效:

csharp 复制代码
[Theory]
[InlineData("EasyAdminBlazor/Hubs/NotificationHub.cs")]
[InlineData("Extensions/EasyAdminBlazor.Chat/ChatHub.cs")]
[InlineData("EasyAdminBlazor/AdminContext.cs")]
[InlineData("EasyAdminBlazor/Components/Layout/Widget.razor")]
public void LoginTicketProtectorPurpose_IsConsistent(string relativePath)
{
    var source = ReadSource(relativePath);
    source.Should().Contain("EasyAdminBlazor.LoginTicket.v1");
}

2. CSRF:只拦写方法,失败返回 400

csharp 复制代码
[Theory]
[InlineData("GET")] [InlineData("HEAD")] [InlineData("OPTIONS")] [InlineData("TRACE")]
public async Task SafeHttpMethods_AreNotValidated(string method)   // 安全方法放行

[Theory]
[InlineData("POST")] [InlineData("PUT")] [InlineData("PATCH")] [InlineData("DELETE")]
public async Task WriteHttpMethods_AreValidated(string method)     // 写方法必须校验

[Fact]
public async Task PostWithoutToken_IsRejectedWith400()             // 失败是 400,不是 500

三条约束都很关键:

  • 只拦写方法:如果连 GET 都要求令牌,缓存和预取会出问题;
  • 失败返回 400 而不是 500:令牌无效是客户端问题,不该记成服务端错误;
  • AntiforgeryDisabled 时可整体关闭 (默认开启,SecurityOptions.AntiforgeryEnabled = true)。

3. 授权:四个层次缺一不可

这一块第 08、09、24 篇讲透了,这里只列结论:

text 复制代码
路由级  AuthPath            能不能进页面
操作级  AuthButton          能不能点按钮
方法级  AdminButtonAttribute 方法能不能执行
数据级  FilterAuthorizedAsync 能操作哪些行

4. 文件与路径:两套独立的校验

内容防伪(第 23 篇):

csharp 复制代码
if (FileSignatures.TryGetValue(extension, out var signatures))
{
    return await HasAnyPrefixAsync(input, signatures, cancellationToken);
}

return false;   // 未知扩展名 fail-closed

路径防穿越 (SafePathService):

csharp 复制代码
private readonly SafePathService _service = SafePathService.Instance;

[Theory]
[InlineData("..")]
[InlineData("../../etc/passwd")]
[InlineData("a/../../b")]
[InlineData("..\\..\\windows\\system32")]
[InlineData("C:/windows/system32")]
[InlineData("//server/share/file")]
public void NormalizeRelative_TraversalAttempts_Throw(string input)
{
    var act = () => _service.NormalizeRelative(input, allowRoot: false);
    act.Should().Throw<UnauthorizedAccessException>();
}

还有一个容易漏掉的用例:

csharp 复制代码
[Fact]
public void IsContained_SiblingDirectoryWithSamePrefix_ReturnsFalse()
{
    var root = Path.Combine(Path.GetTempPath(), "eab-root");
    // eab-root-evil 以 eab-root 为前缀,但不是子目录
    var sibling = Path.Combine(Path.GetTempPath(), "eab-root-evil", "file.txt");
    _service.IsContained(root, sibling).Should().BeFalse();
}

如果只用 StartsWith(root) 判断包含关系,eab-root-evil 就能绕过。 这是路径校验的经典坑。

5. SSRF:出站请求也要校验目标

第 11 篇讲透了七层防护。核心两条:

  • 每跳重定向都重新校验(禁止自动重定向);
  • ConnectCallback 在建立 TCP 连接前再次校验实际 IP,关闭 DNS Rebinding 的 TOCTOU 窗口。

6. 多租户:数据库隔离 ≠ 系统隔离

第 13、15 篇讲过:数据库、文件目录、缓存键、分布式锁都要带租户维度。测试里有一组专门的候选键清单:

csharp 复制代码
"tenant:main:SysConfig:",
"tenant:main:UserRoles:",
"tenant:main:Dict:",
"tenant:main:chat_messages:",
"tenant:main:save_messages_lock",

7. 信息泄露:错误信息要"对外模糊、对内精确"

csharp 复制代码
private string GenericFailure(Exception ex)
{
    var traceId = Guid.NewGuid().ToString("N")[..16];
    _logger.LogError(ex, "审批处理失败,TraceId:{TraceId}", traceId);
    return LF("审批处理失败,请稍后重试。TraceId:{0}", traceId);
}

用户拿到的是通用提示 + TraceId;数据库异常、表名、SQL 片段只进日志。AuthorizationTests 里还有一条约束:控制器源码里不得出现 ex.Message。

DatabaseLogger 落库时也做了截断(消息 2000 / 异常 12000),并且写日志失败不会影响业务(第 27 篇)。


四、框架已做 vs 需要自己补

项目 框架已做 需要你补
密码哈希 PBKDF2 + 平滑升级 密码强度策略、历史密码限制
登录防护 失败锁定、验证码 二次验证(2FA)、异地登录提醒
授权 路由/按钮/方法/数据四层 权限矩阵的正确配置与验收
文件 类型/内容/路径/大小 病毒扫描、CDR、独立域名下发
SSRF 出站请求完整防护 代理环境下的额外确认
CSRF 写方法令牌校验 自定义接口注册到 MVC 过滤器链
多租户 库/文件/缓存/锁隔离 跨租户统计的权限控制
审计 操作日志 + 登录日志 + 错误日志 日志保留期限、归档与合规要求
传输 ------ HTTPS、证书、HSTS
运维 ------ 备份、WAF/限流、端口暴露面、最小权限账号

这张表的右列不是框架的缺陷,而是产品与运维决策。 比如"是否要求 2FA"取决于客户合规要求,框架不该替业务决定。


五、上线安全清单

结合 SecurityOptions 和部署文档(第 28 篇),上线前逐条确认:

项 默认值 上线动作
AdminRouteSecret i4ByMAX4 换成自定义值
AesKey 示例值 换成长随机串
默认管理员密码 123yyq 首次登录强制改密,确认流程可用
MaxUploadSize 10MB 按业务调整,不要无限放大
MaxRemoteRedirects 3 一般不用改
LoginFailureLimit / LoginLockout 5 次 / 15 分钟 按业务调整
AntiforgeryEnabled true 不要关
RateLimitEnabled true 确认匿名接口都有限流
ExternalApiTimeout 10 秒 按外部服务调整
UseAutoSyncStructure 开发 true 生产关闭
HTTPS ------ 必须启用
wwwroot/uploads 权限 ------ 应用账号可写,不可执行
Redis ------ 设密码、不暴露公网端口

六、小结

后台安全可以拆成六句话:

  1. 身份:票据加密、不能跨用途复用、多端登录可踢。
  2. 入口:路由安全码混淆探测成本,但真正的边界是登录与授权。
  3. 请求:写操作校验 CSRF,匿名接口限流 + 校验参数。
  4. 授权 :路由、操作、方法、数据四层,前端隐藏只是体验。
  5. 内容:上传看内容不看声明,出站请求校验目标,路径拒绝上跳。
  6. 信息:对外模糊(通用提示 + TraceId),对内精确(日志含租户/用户/路径)。

这些机制在仓库里都有对应源码和测试;真正需要你判断的是:权限矩阵怎么配、上线参数怎么改、业务上还有哪些特殊约束。


如果你正在用 .NET 10 + Blazor 做企业后台,可以按这篇的对照表检查自己的项目。EasyAdminBlazor 的安全组件与测试都在仓库里,能直接复用,也能作为自研时的参考清单。

相关推荐
FungLeo1 天前
成为全栈·Next.js 网站前台篇·同源 BFF 代理:API、附件、Cookie 与跨站写入保护
react·csrf·next.js·bff·成为全栈·route handler
szial2 天前
网络爬虫与 CDP 实战(二):浏览器能访问,Python 却被拒绝?拆开登录态与 CSRF
爬虫·python·csrf
gudufy3 天前
EasyAdminBlazor 审批模块实战:从提交、审批到最终完成
工作流·blazor审批·admin blazor·blazor admin·easyadminblazor
szephyr14 天前
前端鉴权实战:Token 存哪、怎么刷新、怎么防 XSS 与 CSRF
鉴权·xss·csrf·jwt·token
zander25817 天前
知识分享平台项目复习第二天:登录模块中的双令牌与刷新安全(2)
xss·csrf·cors·登录模块·cookie的三个属性
DsirNg22 天前
登录后的那一个小时:把前端会话续期做成可收敛的控制面
xss·csrf·token·cookie·refresh token·前端安全·登录态
三8441 个月前
SSRF 打内网 Redis:gopher 原理与实操
redis·web安全·bootstrap·ssrf
三8441 个月前
Redis未授权访问与四种 getshell 路径(原理 → 实操,因果不断层)
redis·web安全·ssrf·getshell
三8441 个月前
SSRF 从入门到实战:原理、危害与基础利用
服务器·网络安全·ssrf·#web安全