可信类型-一种保护Web应用程序代码免受XSS攻击的新方法

Google 开发了一种 API,允许现代Web应用程序保护其前端免受XSS攻击,更具体地说,防止JavaScript注入DOM(基于DOM的跨站点脚本)。

跨站点脚本(XSS)是与现代Web应用程序漏洞相关的最常见攻击类型。这不仅得到Google的认可,而且整个行业认可。经验表明,开发可抵抗XSS攻击的Web应用程序仍然是一项艰巨的任务,尤其是在涉及复杂项目时。如果开发人员在后端成功解决了这个问题,那么在前端,一切都会变得更加复杂。作为Google漏洞奖励计划的一部分 提出了旨在防御DOM XSS攻击的解决方案的开发人员越来越受到奖励。

您可能会问,我们如何过上这样的生活?大概问。事实是,现代Web应用程序存在两个严重且不相关的问题...

XSS易于嵌入代码


DOM XSS是一种攻击,其中来自用户或其他浏览器API的未经过滤的数据以及嵌入式js注入均落入DOM中。例如,考虑一个旨在为应用程序使用的特定用户界面模板加载样式表的代码段:

const templateId = location.hash.match(/tplid=([^;&]*)/)[1];
// ...
document.head.innerHTML += `<link rel="stylesheet" href="./templates/${templateId}/style.css">`

在这种情况下,攻击者在location.hash中引入的脚本(在这种情况下起数据源的作用,即源)将落入innerHTML中(起数据接收方的作用,即下沉)。使用此漏洞,攻击者可以欺骗受害者将访问的URL:

https://example.com#tplid="><img src=x onerror=alert(1)>

这种注入很容易被忽略,尤其是在代码频繁更改的情况下。例如,很久以前就可以在服务器上生成并验证templateId,此后,任何人都不会再次检查它。当我们用innerHTML编写内容时,我们只能确定其值是一个字符串。但是您应该相信这一行的内容吗?它实际上是哪里来的?

而且,问题不仅限于innerHTML。在典型的DOM中,有60多个具有相同问题的方法和属性。因此,默认情况下,DOM API是不安全的,并且需要一种特殊的方法来防止XSS。

XSS很难跟踪


上面的代码只是一个很好的例子,因此很容易看到那里的错误。实际上,宿的值通常在应用程序的完全不同的部分中。是否有在途中从执行卫生和数据验证的任何功能接收器?但是,如何确定何时调用什么函数?

仅查看源代码,很难看到它是否包含DOM XSS脚本。此外,对于具有区分大小写设置的.js文件,grep是不够的。毕竟,区分大小写的函数通常在各种包装器中使用,因此真实的漏洞被很好地掩盖了,并且可能看起来像这样

有时,即使您仔细阅读了整个代码,也无法确定代码库中是否存在漏洞。看一下这个表达式:

obj[prop] = templateID;

如果obj指向一个位置对象(上面已经讨论过location.hash)并且prop值为'href',那么很可能是XSS DOM。但是,您可能只能在代码执行过程中才能发现。由于您的应用程序的任何部分都可以假设地向DOM写入内容,因此所有代码都必须通过手动安全检查,并且审阅者必须非常小心地检测到错误。但是,由于人为因素尚未消除,因此无法完全依靠这种验证方法。

受信任的类型


Trusted Types是一种新的浏览器API,它将从根本上帮助消除上面列出的问题,包括保护Web应用程序免受DSS XSS的侵害。

Trusted Types API建立在一组类(策略)的基础上,使您可以阻止恶意注入。有了它,DOM不再变得脆弱:它的元素不是通过字符串而是通过特殊对象接收值。为了使用可信类型,您需要在内容安全策略(CSP)HTTP响应的标头中初始化一个特殊字段

Content-Security-Policy: trusted-types *

CSP是安全性的附加层,可以识别和消除某些类型的攻击,例如跨站点脚本(XSS)和数据注入攻击。为了启用CSP,必须配置服务器,使其在响应中使用Content-Security-Policy HTTP标头。

然后没有人能够将任意字符串写入DOM元素:

const templateId = location.hash.match(/tplid=([^;&]*)/)[1];
// typeof templateId == "string"
document.head.innerHTML += templateId //     (TypeError).

现在,您必须创建并使用特殊类型的对象。这些对象只能在特殊设计的功能(称为“受信任的类型策略”)的帮助下创建。

例如:

const templatePolicy = TrustedTypes.createPolicy('template', {
  createHTML: (templateId) => {
    const tpl = templateId;
    if (/^[0-9a-z-]$/.test(tpl)) {
      return `<link rel="stylesheet" href="./templates/${tpl}/style.css">`;
    }
    throw new TypeError();
  }
});

const html = templatePolicy.createHTML(location.hash.match(/tplid=([^;&]*)/)[1]);
// html —    TrustedHTML
document.head.innerHTML += html;

在这里,我们为模板模板创建一个策略,该策略检查传递的ID参数并创建结果HTML。这组create *方法调用相应的用户定义函数,并将结果包装在Trusted Type对象中。例如,templatePolicy.createHTML调用templateId的验证函数,并返回一个包含TrustedHTML类型的对象(请参见第一个示例)。同时,浏览器允许您将数据写入innerHTML中的TrustedHTML类型的对象(理论上期望使用普通的HTML)。

除了TrustedHTML类,您还可以使用TrustedScript,TrustedURL和TrustedScriptURL类。

看来新API的唯一优点是能够添加此检查:

if (/^[0-9a-z-]$/.test(tpl)) { /* allow the tplId */ }

确实,这条线对于防止XSS是必需的。但是实际上,所有事情都更加有趣。使用可信类型,解决DOM漏洞归结为正确执行策略。没有其他代码检查从源到接收器的数据。因此,只有与政客的不正当工作才能导致安全问题。在我们的示例中,templateId的值来自哪里都没有关系,因为该策略始终首先检查其是否通过了验证。因此,在负责特定策略的区域中,将抵制任何XSS攻击:

Uncaught TypeError: Failed to set the ’innerHTML’ property on ’Element’: This document requires `TrustedHTML` assignment.

政策限制


您是否注意到我们在Content-Security-Policy标头中使用的*值?它指示应用程序可以创建任意数量的策略,只要每个策略都有唯一的名称即可。如果应用程序可以自由创建太多策略,则可能导致混乱,并且将更加难以实现针对DOM XSS的保护。

因此,我们可以通过显式指定我们将使用的策略名称列表来限制此数字。例如:

Content-Security-Policy: trusted-types template

这样可以确保仅使用名称模板创建一个策略。然后,我们可以轻松地在源代码中识别它并测试其操作。因此,我们可以确定该应用程序将受到DOM XSS的保护。而且您会幸福的!

实际上,现代Web应用程序需要少量策略。通常的建议是在脚本加载程序,HTML模板库或HTML清理程序中,创建客户端代码生成HTML或URL的策略。与DOM不相关的许多依赖项都不需要使用策略。然后保证可以正确使用Trusted Types以保护我们免受XSS的侵害。

快速启动


以下是对该API的简短介绍。随着时间的流逝,关于如何使用“受信任的类型”编写和重写应用程序的代码示例,教程和文档越来越多。我们认为,Web开发社区早已准备好开始对此进行试验。

要在您的站点上使用新的API,必须在浏览器中直接将其激活如果您只想在本地运行它(从Chrome 73开始),则可以在命令行上启用该功能:

chrome --enable-blink-features=TrustedDOMTypes

要么

chrome --enable-experimental-web-platform-features

如果您使用的是Google Chrome浏览器,则可以在地址栏中输入chrome://flags/#enable-experimental-web-platform-features用于启动API的所有这些选项均使其在一个会话中处于活动状态。

如果遇到故障,请使用--enable-features \ u003d BlinkHeapUnifiedGarbageCollection作为解决方法。有关更多详细信息,请参见错误929601的讨论

还有一个Polyphile,可让您在许多其他浏览器中使用Trusted Types。

您可以在此处讨论该项目它也可以在GitHub上获得。


All Articles