[推广] JavaScript Sanitizer API:原生 WEB 安全 API 出现啦

10 月 18 号,W3C 中网络平台孵化器小组( Web Platform Incubator Community Group )公布了 HTML Sanitizer API 的规范草案。这份草案用来解决浏览器如何解决 XSS 攻击问题。

网络安全中比较让开发者们头疼的一类是 XSS 跨站点脚本攻击。这种攻击通常指的是通过利用网页开发时留下的漏洞,即将恶意指令代码注入到网页,使用户加载并执行攻击者恶意制造的网页程序。

这些恶意代码没有经过过滤,与网站的正常代码混在一起,浏览器无法分辨哪些内容是可信的,恶意脚本就会被执行。而 XSS 攻击的核心有两个步骤:1 、处理攻击者提交恶意代码; 2 、浏览器执行恶意代码。

为了解决在这两步恶意攻击中解决这个问题,通常有以下手段,

  1. 增加过滤条件
  2. 只进行纯前端行渲染,将数据和代码内容分开
  3. 对 HTML 充分转义

以上手段这些步骤繁琐,需要注意的内容也很多。为了让开发者更加便捷地解决 XSS 攻击的问题,浏览器现提供了原生的 XSS 攻击消毒能力。

HTML Sanitizer API——这份由谷歌、Mozilla 和 Cure53 联手发起提供的 API 即将最终完成,通过这个浏览器原生 API 我们可以更加轻松地保护 Web 应用程序免受 XSS 的攻击。

接下来我们一起来了解一下这个安全 API 吧。

Sanitizer API 简介

Sanitizer API 可以让浏览器直接从网站动态更新的标记中删除恶意代码。当有恶意 HTML 字符串、和文档或文档片段对象想插入现有 DOM 之中,我们可以使用 HTML Sanitizer API 直接将这些内容清理。有点像电脑的安全卫士应用,可以清除风险内容。

使用 Sanitizer API 有以下三个优点:

  • 减少 Web 应用程序中跨站点脚本的攻击次数
  • 保证 HTML 输出内容在当前用户代理中安全使用
  • Sanitizer API 的可用性很强

Sanitizer API 的特性

Sanitizer API 为 HTML 字符串安全打开新世界大门,将所有的功能大致分类,可以分为以下三个主要特性:

1.对用户输入进行杀毒

Sanitizer API 的主要功能是接受字符串并将其转换为更安全的字符串。这些转换后的字符串不会执行额外的 JavaScript ,并确保应用程序受到 XSS 攻击的保护。

2.浏览器内置

该库在浏览器安装的时候一同预装,并在发现 bug 或出现新的攻击时进行更新。相当于我们的浏览器有了内置的杀毒措施,无需导入任何外部库。

3.使用简洁安全

在使用了 Sanitizer API 之后,浏览器此时就有了一个强大又安全的解析器,作为一个成熟的浏览器,它知道如何处理 DOM 中每个元素的活动。相比之下,用 JavaScript 开发的外部解析器不仅成本高昂,同时很容易跟不上前端大环境的更新速度。

说完了这些使用上的亮点特性,让我们一起来看看这个 API 的具体用法。

Sanitizer API 的使用

Sanitizer API 使用 Sanitizer ()方法构造函数,Sanitizer 类进行配置。

官方提供了三种基础清理方式:

1 、清理隐藏上下文的字符串

Element.setHTML() 用于解析和清理字符串,并立即将其插入 DOM ,这个方法适用于目标 DOM 元素已知且 HTML 内容为字符串的情况。

const $div = document.querySelector('div')
const user_input = `<em>Hello There</em><img src="" onerror=alert(0)>` // The user string.
const sanitizer = new Sanitizer() // Our Sanitizer
// We want to insert the HTML in user_string into a target element with id
// target. That is, we want the equivalent of target.innerHTML = value, except
// without the XSS risks.
$div.setHTML(user_input, sanitizer) // <div><em>Hello There</em><img src=""></div> 

2 、清理给定上下的文字符串

Sanitizer.sanitizeFor() 用于解析、清理和准备稍后准备添加到 DOM 中的字符串。

适用于 HTML 内容是字符串,并且目标 DOM 元素类型已知(例如 div 、span )的情况。

const user_input = `<em>Hello There</em><img src="" onerror=alert(0)>`
const sanitizer = new Sanitizer()
// Later:
// The first parameter describes the node type this result is intended for.
sanitizer.sanitizeFor("div", user_input) // HTMLDivElement <div> 

需要注意的是,HTMLElement 中 .innerHTML 的清理输出结果是字符串格式。

sanitizer.sanitizeFor("div", user_input).innerHTML // <em>Hello There</em><img src="">

3 、清理请理节点

对于已经有用户控制的 DocumentFragment ,Sanitizer.sanitize ()可以直接对 DOM 树节点进行清理。

// Case: The input data is available as a tree of DOM nodes.
const sanitizer = new Sanitizer()
const $userDiv = ...;
$div.replaceChildren(s.sanitize($userDiv)); 

除了以上提到的三种方式之外,SanitizerAPI 通过删除和、过滤属性和标记来修改 HTML 字符串。

举个“栗子”。

  • 删除某些标记(_script, marquee, head, frame, menu, object, etc._)并保留 content 标签。
  • 移除大多属性,只保留<a>标签和colspanson<td>,<th>标签上的 HREF 。
  • 筛选出可能导致风险脚本执行的内容。

默认设置中,这个安全 API 只用来防止 XSS 的出现。但是一些情况下我们也需要自定义自义设置,下面介绍一些常用的配置。

自定义消毒

创建一个配置对象,并在初始化 Sanitizer API 时将其传递给构造函数。

const config = { allowElements: [], blockElements: [], dropElements: [], allowAttributes: {}, dropAttributes: {}, allowCustomElements: true, allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config) 

下面是一些常用方法:

  • allowElements 对指定输入进行保留
  • blockElements blockElements 删除内容中需要保留的部分
  • dropElements dropElements 删除指定内容,包括输入的内容
const str = `hello <b><i>there</i></b>` new Sanitizer().sanitizeFor("div", str)
// <div>hello <b><i>there</i></b></div> new Sanitizer({allowElements: [ "b" ]}).sanitizeFor("div", str)
// <div>hello <b>there</b></div> new Sanitizer({blockElements: [ "b" ]}).sanitizeFor("div", str)
// <div>hello <i>there</i></div> new Sanitizer({allowElements: []}).sanitizeFor("div", str)
// <div>hello there</div> 
  • allowAttributes 和 dropAttributes 这两个参数可以自定义需要保留或者需要删除的部分。
const str = `<span id=foo class=bar style="color: red">hello there</span>` new Sanitizer().sanitizeFor("div", str)
// <div><span id="foo" class="bar" style="color: red">hello there</span></div> new Sanitizer({allowAttributes: {"style": ["span"]}}).sanitizeFor("div", str)
// <div><span style="color: red">hello there</span></div> new Sanitizer({dropAttributes: {"id": ["span"]}}).sanitizeFor("div", str)
// <div><span class="bar" style="color: red">hello there</span></div> 
  • AllowCustomElements 开启是否使用自定义元素
const str = `<elem>hello there</elem>` new Sanitizer().sanitizeFor("div", str);
// <div></div> new Sanitizer({ allowCustomElements: true, allowElements: ["div", "elem"] }).sanitizeFor("div", str);
// <div><elem>hello there</elem></div> 

如果没有进行任何配置,会直接使用默认配置内容。

这个 API 看起来能为我们解决不小少的问题,但是现在浏览器对其的支持还有限,更多功能还在持续完善中。我们也很期待看到功能更加完善的 SanitizerAPI

对它感兴趣的小伙伴在 Chrome93+中可以通过about://flags/#enable-experimental-web-platform-features启用,Firefox 中目前也在实验阶段,可以在about:config 将 dom.security.sanitizer.enabled 设为 true 来启用。

了解更多内容可以查看:https://developer.mozilla.org/en-US/docs/Web/API/HTML_Sanitizer_API

关于数据安全的担忧

根据 Verizon 2020 年数据泄露调查报告( Verizon Business ,2020 年)显示,约 90% 的数据泄露事件是由于跨站点脚本((XSS))和安全漏洞造成的。对于前端开发者而言,面对越发频繁的网络攻击,除了借助 Sanitizer API 等安全机制外,还可以考虑使用”数据与代码分离”的SpreadJS等前端表格控件。

发表评论

您的电子邮箱地址不会被公开。