【第738期】WEB 安全:JSONP 沙箱技术实现
前言
本文由作者@糖饼投稿分享。
正文从这开始~
2005 年 12 月,Bob Ippolito 正式提出 JSONP,以实现跨域请求 JSON 数据。目前 JSONP 已经是业界流行的跨域数据获取方案。
JSONP 原理
它的原理是通过 <script> 标签实现跨域加载服务器动态输出的脚本,并执行指定函数回传数据。例子如下:
安全问题
引用站外脚本会让任何内容有机会注入到网站中:
如果 JSONP 源被黑客攻陷,网站也可能会受到攻击。
如果传输过程中被运营商劫持,网站可能会弹出广告(这个问题非常具有中国特色)。
JSONP 到目前为止依然还是一个未列入标准的技术方案,有人提出定义 JSONP 严格安全子集,使浏览器可以对 MIME 类别是 application/json-p 请求做强制处理,严格限定 JSONP 的权限,但是目前依然没有得到浏览器支持。
沙箱技术
iframe
如果利用 <iframe> 创造一个新的执行环境来加载站外脚本,这样可以为 JSONP 提供一层简单的防御。
但是 <iframe> 并非与页面完全隔离,攻击者使用 top 、 parent 等变量可以轻易的拿到页面的全局对象,使得防护失效。
HTML5 sandbox
HTML5 为 <iframe> 提供了 sandbox 属性,通过指令来控制 <iframe> 的权限。sandbox 开启后会隔离与父页面访问,页面与 <iframe> 通讯可以使用 HTML5 parent.postMessage(message, targetOrigin) 通讯。
启用 sandbox 后,如果 JSONP 脚本窃取 Cookie 或者篡改父页面都会被浏览器阻止。
IE 与 sandbox
IE 只有 IE10 以及更高版本支持 sandbox,在低版本 IE 浏览器中可以使用重写技术(override)来实现 sandbox 特性:重写 top、 parent、 docuemnt 等危险全局变量,使得外部脚本无法获取其引用。
黑魔法
在 IE 中删除、重写内置全局变量,常规的操作都将会失败,例如:
但定义同名函数却可以覆盖,这也是目前已知的唯一方式。
由于函数声明后会被“提升”,从而无法引用原始全局变量。
好在 IE 有 execScript() 这个独有的方法可以在全局环境编译字符串运行,结合闭包便可以私有化指定的全局变量。
这样只有内部函数可以获取到父页面引用进行跨窗口通讯,而外部脚本无论如何也拿不到真正的 parent 了,从而实现与父窗口隔离。
为了安全起见还可以全部重写 window 可枚举的属性以绝后患。
上述代码似乎一切都考虑妥当了?然而 IE9 却有个致命的问题:document 与 location 是常量,无法被覆盖。
document
由于 <iframe> 的 Cookie 是与父页面共享的,也就是说通过 document.cookie 可以轻松窃取到 Cookie。
好在 IE9 可以访问 document 的构造函数,而 document.cookie 是其原型上的一个 "getter"、"setter" 属性,如果删除原型的对应属性即可让其失效。
document 的继承关系为:HTMLDocument->Document->Node,清理其原型链即可。
至此,IE9 下 document 已经残废,其不但无法访问 document.cookie 甚至连 document.createElement 等基础 DOM API 都无法使用了。
location
IE9 下可以操作 location,由于 location 无法通过删除原型链的方式进行破坏,这意味着外部脚本有机会将 <iframe> 内部页面导航到新的地址中去。
<iframe> 被重载后,沙箱内的环境也将被重置,所有权限都将会被泄漏。如果新页面是第三方地址,浏览器会使用同源策略保护父页面,限制站外脚本数据获取与页面操作的行为,但是攻击者依然有机会发起钓鱼行为。
虽然无法破坏 location,IE9 却可以使用 unload 事件中实现沙箱“自杀”,从而阻止安全风险。
经过周密防范后,运行在此 <iframe> 的外部脚本几乎只有运行回调函数的权限,达到目标。
后记
基于 sandbox 的 JSONP 加载器代码已经开源:https://github.com/aui/jsonp-sandbox
关于本文
作者:@糖饼
原文:https://github.com/aui/jsonp-sandbox/issues/13
每天早读,三万同行相伴成长
欢迎投稿:181422448@qq.com