在本节中,我们将描述AngularJS沙箱,解释如何从沙箱中逃脱攻击,并阐明如何在AngularJS沙箱的上下文中绕过内容安全策略(CSP)。
AngularJS沙箱是一种机制,用于防止访问AngularJS模板表达式中的潜在危险对象,例如窗口或文档。 它还防止访问具有潜在危险的属性,例如__proto__。 尽管AngularJS团队并未将其视为安全边界,但更广泛的开发人员社区通常会另外考虑。 尽管绕过沙箱最初是一个挑战,但安全研究人员已经发现了许多方法。 结果,它最终从1.6版的AngularJS中删除。 但是,许多遗留应用程序仍使用AngularJS的较旧版本,因此可能容易受到攻击。
沙箱的工作方式是解析一个表达式,重写JavaScript,然后使用各种函数测试重写的代码是否包含任何危险对象。 例如,ensureSafeObject()函数检查给定对象是否引用了自身。 例如,这是检测窗口对象的一种方法。 通过检查构造函数属性是否引用自身,以大致相同的方式检测Function构造函数。
suresafeMemberName()函数检查对象的每个属性访问,如果它包含__proto__或__lookupGetter__之类的危险属性,则该对象将被阻止。 guaranteeSafeFunction()函数可防止调用call(),apply(),bind()或constructor()。
通过访问fiddle并在angular.js文件的第13275行设置一个断点,您可以亲自看到沙箱在运行。变量fnString包含您重写的代码,所以您可以看看AngularJS如何转换它。
沙盒转义包括欺骗沙盒以使其认为恶意表达是良性的。 最著名的转义在表达式中全局使用修改后的charAt()函数:
'a'.constructor.prototype.charAt=[].join
最初发现它时,AngularJS并未阻止此修改。 该攻击通过使用[] .join方法覆盖该函数而起作用,这会使charAt()函数返回发送给它的所有字符,而不是特定的单个字符。 由于AngularJS中isIdent()函数的逻辑,它会将单个字符与多个字符进行比较。 由于单个字符始终少于多个字符,因此isIdent()函数始终返回true,如以下示例所示:
isIdent= function(ch) {
return ('a' <= ch && ch <= 'z' ||
'A' <= ch &&amamp; ch <= 'Z' ||
'_' === ch || ch === '$');
}
isIdent('x9=9a9l9e9r9t9(919)')
一旦对isIdent()函数进行了欺骗,就可以注入恶意JavaScript。 例如,将允许使用诸如$ eval('x = alert(1)')之类的表达式,因为AngularJS将每个字符都视为一个标识符。 请注意,我们需要使用AngularJS的$ eval()函数,因为覆盖charAt()函数仅在执行沙箱代码后才生效。 然后,该技术将绕过沙箱并允许执行任意JavaScript。
因此,您已经了解了基本的沙箱转义是如何工作的,但是您可能会遇到对它们允许使用的字符有更多限制的站点。 例如,一个站点可能会阻止您使用双引号或单引号。 在这种情况下,您需要使用String.fromCharCode()之类的函数来生成字符。 尽管AngularJS禁止访问表达式中的String构造函数,但是您可以通过使用字符串的Constructor属性来解决此问题。 显然,这需要一个字符串,因此要构建这样的攻击,您将需要找到一种创建字符串的方法,而无需使用单引号或双引号。
在标准沙箱转义中,您可以使用$ eval()执行JavaScript有效负载,但是在下面的实验中,$ eval()函数是未定义的。 幸运的是,我们可以改用orderBy过滤器。 orderBy过滤器的典型语法如下:
[123]|orderBy:'Some string'
请注意| 运算符的含义与JavaScript中的含义不同。 通常,这是按位的OR运算,但是在AngularJS中,它表示过滤器运算。 在上面的代码中,我们将左侧的数组[123]发送到右侧的orderBy过滤器。 冒号表示要发送到过滤器的参数,在这种情况下为字符串。 orderBy过滤器通常用于对对象进行排序,但是它也接受表达式,这意味着我们可以使用它传递有效负载。
现在,您应该拥有解决下一个实验室所需的所有工具。
内容安全策略(CSP)旁路工作的方式与标准沙箱转义类似,但通常涉及一些HTML注入。 当AngularJS中的CSP模式处于活动状态时,它将以不同的方式解析模板表达式,并避免使用函数构造函数。 这意味着上述标准沙箱转义将不再起作用。
根据特定策略,CSP将阻止JavaScript事件。 但是,AngularJS定义了自己的事件,可以代替使用。 在事件内时,AngularJS定义了一个特殊的$ event对象,该对象仅引用浏览器事件对象。 您可以使用此对象执行CSP旁路。 在Chrome浏览器上,$ event / event对象上有一个特殊的属性,称为path。 此属性包含导致事件执行的对象数组。 最后一个属性始终是window对象,我们可以使用该对象执行沙箱转义。 通过将此数组传递给orderBy过滤器,我们可以枚举该数组并使用最后一个元素(窗口对象)执行全局功能,例如alert()。 以下代码演示了这一点:
<input autofocus ng-focus="$event.path|orderBy:'[].constructor.from([1],alert)'">
请注意,使用了from()函数,该函数允许您将对象转换为数组,并在该数组的每个元素上调用给定函数(在第二个参数中指定)。 在这种情况下,我们将调用alert()函数。 我们无法直接调用该函数,因为AngularJS沙箱将解析代码并检测到正在使用window对象来调用函数。 相反,使用from()函数可以有效地将窗口对象从沙箱中隐藏起来,从而使我们可以注入恶意代码。
下一个实验采用了长度限制,因此上述向量将不起作用。 为了利用该实验室,您需要考虑各种从AngularJS沙箱中隐藏窗口对象的方法。 一种方法是使用array.map()函数,如下所示:
[1].map(alert)
map()接受一个函数作为参数,并将为数组中的每个项目调用它。 这将绕过沙箱,因为使用了对alert()函数的引用,而没有显式引用该窗口。 要解决此问题,请尝试各种执行alert()的方式,而不会触发AngularJS的窗口检测。
为了防止AngularJS注入攻击,请避免使用不受信任的用户输入来生成模板或表达式。