与污染 Array 构造函数相关的攻击称为JSON 劫持。这是一个称为跨站点脚本包含(XSSI) 的问题的一个实例,它源于浏览器通常允许网站包含跨域的外部 JS 文件这一事实。
假设您网站上经过身份验证的用户可以在此 API 位置访问一些“秘密令牌”:
https://yoursite.example/api/secret_tokens
假设 API 响应如下所示:
tokens = ["secret123", "secrect456"]
这会造成漏洞。evil.example
攻击者可以通过向登录用户发送包含如下代码的链接来轻松窃取登录用户的令牌:
<script src="https://yoursite.example/api/secret_tokens"></script>
<script>
alert(tokens)
</script>
这样,受害者的浏览器就会被欺骗,将 API 响应解释为外部 JS 文件。API 无意中创建了有效的 JS 代码(将数组分配给全局变量tokens
),从而将登录用户的令牌泄漏到evil.example
.
现在,让我们考虑一下您的 API 响应看起来与此类似的更有可能的情况:
["secret123", "secret456"]
这仍然是有效的 JS 代码,但由于它只是一个没有赋值的表达式,攻击者无法像上面那样轻松访问数组的内容。这里的解决方法是简单地覆盖 Array 构造函数:
Array = function() { foo = this; }
这个特殊的技巧在 2007 年在 Firefox 中被报告并修复,但从那时起,其他技术,例如使用Object.__defineSetter__
or Object.defineProperty
,已经被发现(并修复)。
EMCAScript 5 如何防止这种情况发生?
ES5 规范要求始终使用[]
内置Array
构造函数(同样适用于对象构造函数):
15.4.1.1
数组 ( [ item1 [ , item2 [ , ... ] ] ] )
创建并返回一个新的 Array 对象,就像在具有相同参数Array
的表达式中使用标准内置构造函数 (15.4.2)。new
最后,让我们考虑 API 使用 JSON 对象回复的情况:
{"tokens": ["secret123", "secret456"]}
从未受到攻击的简单原因是它是无效的 JS:花括号中的孤立对象被解析为块而不是对象。因此,您不能将此 JSON 对象作为外部脚本包含在内而不触发错误。从历史上看,触发语法错误一直是防止 XSSI 攻击的便捷方法。
以下是对 JSON 劫持的一些额外参考: