3.4 安全


文档摘要

3.4 安全:净化与信任边界 Angular 安全模型的核心是默认净化:模板里的一切插值与属性绑定都经过 Sanitizer 处理,危险内容被剥离;需要放行原始 HTML 时必须显式绕过(bypassSecurityTrust),绕过即自担风险。变更检测视角看安全很直观:每轮检查写入 DOM 的内容都可能是攻击载荷的注入点,净化就是检查管线上的安检门。 先定位,再出发 前三节都在让应用更快更稳,本节先停下来补一门必修课——默认净化策略到底替你挡住了什么: 说出默认净化策略拦截的内容类别与放行的类别。 正确使用三种绕过方法,并评估各自的攻击面。 识别模板注入与脚本注入的真实路径,说明 Angular 编译器的防护。 配置 HTTP 层安全:CSRF 与凭据策略。

3.4 安全:净化与信任边界

Angular 安全模型的核心是默认净化:模板里的一切插值与属性绑定都经过 Sanitizer 处理,危险内容被剥离;需要放行原始 HTML 时必须显式绕过(bypassSecurityTrust*),绕过即自担风险。变更检测视角看安全很直观:每轮检查写入 DOM 的内容都可能是攻击载荷的注入点,净化就是检查管线上的安检门。

先定位,再出发

前三节都在让应用更快更稳,本节先停下来补一门必修课——默认净化策略到底替你挡住了什么:

  1. 说出默认净化策略拦截的内容类别与放行的类别。
  2. 正确使用三种绕过方法,并评估各自的攻击面。
  3. 识别模板注入与脚本注入的真实路径,说明 Angular 编译器的防护。
  4. 配置 HTTP 层安全:CSRF 与凭据策略。

一、默认净化:插值的安检门

import { DomSanitizer } from '@angular/platform-browser'; import { inject } from '@angular/core'; export class RichTextComponent { private sanitizer = inject(DomSanitizer); html = `<img src=x onerror=alert(1)><b>加粗内容</b>`; // 写法一(推荐):直接绑定到 innerHTML,框架自动净化 // 模板:<div [innerHTML]="html"></div> // 结果:onerror 被剥掉,只保留 <b>加粗内容</b> trustIt() { // 写法二(危险示范):显式信任 HTML,绕过安检 return this.sanitizer.bypassSecurityTrustHtml(this.html); // 此时 onerror 会原样进入 DOM——一旦 html 含用户输入即为漏洞 } }

净化器按内容类别分检:HTML 保留结构与安全标签但剥脚本事件与危险属性;URL 检查协议(javascript 伪协议被拒);资源 URL(iframe 的源)默认几乎全拒。绑定到哪个上下文(HTML/样式/URL)决定用哪套规则——同一个值绑到 href 与绑到 innerHTML,检查标准不同。

二、绕过的三种姿势与攻击面评估

// 姿势一:绕过 HTML 检查——展示富文本(CMS 内容) cmsHtml = this.sanitizer.bypassSecurityTrustHtml(articleBody); // 姿势二:绕过资源 URL——嵌入可信来源的 iframe mapFrame = this.sanitizer.bypassSecurityTrustResourceUrl('https://maps.example.com/embed?x=1'); // 姿势三:绕过脚本 URL——极少用,跳转类场景 // jump = this.sanitizer.bypassSecurityTrustUrl(backofficeUrl);

评估准则只有一条:绕过值的内容来源是否完全可信。CMS 富文本若作者可随意编辑,风险取决于编辑权限;URL 类若拼接了任何用户输入(查询参数透传),绕过等于把用户输入送进危险上下文。我见过的真实事故是把用户昵称拼进"信任"的 HTML 串——昵称里带一段脚本标签,论坛式 XSS 复活。

⚠️ 坑:bypass 返回的是包装对象,不是字符串;模板绑定它没问题,但拿去做字符串拼接(再加工)后绕过失效,又会回到净化通道报错或剥内容。

三、模板注入与脚本注入路径

// 危险做法的对照:动态拼模板再编译 // const tpl = `<div>${userInput}</div>`; // userInput 若含模板语法将被求值 // 正确做法:数据永远走绑定,结构永远静态 @Component({ selector: 'app-greeting', template: `<p>你好,{{ user.name }}</p>` // 即使 name 是 <script> 也只显示为文本 }) export class GreetingComponent { user = { name: '<script>alert(1)</script>' }; }

插值走 textContent 写入,天然免疫标签注入;风险集中在两个口:innerHTML 类绑定(靠净化)与 JIT 编译动态模板(新项目禁用,AOT 编译下编译器甚至不随包分发)。这解释了生产构建的另一个安全红利:AOT 产物里没有运行时编译器,动态模板注入路径整体消失

四、HTTP 层的两道配置

import { provideHttpClient, withXsrfStrategy, withCredentials } from '@angular/common/http'; import { CustomXsrfStrategy } from './xsrf'; export const appConfig = { providers: [ provideHttpClient( withCredentials(), // 跨域请求携带凭据(cookie) withXsrfStrategy(new CustomXsrfStrategy()) // 令牌从 cookie 读取并写入请求头 ) ] }; // 拦截器侧的补充防线:拒绝非预期协议的接口地址(防开放重定向类拼接) export const schemeGuardInterceptor: HttpInterceptorFn = (req, next) => { if (!req.url.startsWith('/api/')) { return throwError(() => new Error('接口地址不在白名单内')); } return next(req); };

图:内容从数据到 DOM 的安检流程

图:内容从数据到 DOM 的安检流程

五、案例:富文本公告板的加固过程

背景:站内公告支持运营富文本编辑,直接 bypass 信任曾让测试人员用一张带 onerror 的图片在后台"弹了窗"。
操作:三步加固——编辑器产出限定白名单标签(源头收紧);服务端再过一层同样的白名单净化(纵深防御,防接口被绕过直写);前端展示不再 bypass,改为净化后的服务端产物直接绑定。
结果:onerror 类载荷在三道关全被剥除,展示效果不变。
解读:安全不是前端一个净化器的独角戏,而是"源头限制、服务端复检、前端默认净化"三层同心圆;Angular 的默认净化是最后一道而非唯一一道。
变式:若必须前端信任(例如嵌入自家报表 iframe),把信任粒度缩到最小——只 bypass 那个 iframe 的资源地址,其余内容照常净化。

本节要点回顾

  • 默认净化:按绑定上下文分检,HTML 剥脚本、URL 查协议、资源近乎全拒。
  • 绕过准则:内容来源完全可信才可 bypass;任何用户输入参与拼接即为高危。
  • AOT 红利:产物无运行时编译器,动态模板注入路径整体消失。
  • HTTP 侧:CSRF 令牌策略与凭据策略在应用配置一次成型,白名单拦截器兜底。
  • 纵深防御:源头白名单 + 服务端复检 + 前端默认净化,三层缺一不可。

下一节:国际化与本地化。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U