抖音技术能力大揭密!钜惠大礼、深度体验,尽在火山引擎增长沙龙,就等你来! 立即报名>> 了解详情
写点什么

Dropbox 的 Web 安全防护策略之三:意外 Eval 操作

2015 年 12 月 08 日

【编者的话】Dropbox 的 Web 安全防护措施之一是使用基于内容的安全策略(CSP)。Dropbox 的安全工程师 Devdatta Akhawe 通过四篇文章,介绍了 CSP 在 Dropbox 中推广的细节和经验。Dropbox 的 CSP 原则大大减少了 XSS 和内容注入攻击。不过,大规模使用比较严苛的 CSP 规则将面临诸多挑战。我们希望通过这四篇 CSP 系列文章,将 Dropbox 在实践 CSP 过程中的收获分享给广大开发社区朋友。第一篇文章主要介绍如何在规则中设置报表筛选管线来标记错误;第二篇介绍 Dropbox 如何在上述规则中配置随机数及缓解unsafe-inline带来的安全风险;第三篇介绍如何降低unsafe-eval造成的风险,以及介绍 Dropbox 所开发的开源补丁;最后一篇介绍在权限分离机制下,如何减小第三方软件整合时的风险。本篇是该系列文章的第三篇,主要讨论如何降低 CSP 中的 unsafe-eval 指令所带来的风险,以及介绍 Dropbox 为此而开发的开源补丁。

在之前的两篇文章中,我们讨论了 Dropbox 如何大规模配置 CSP 来防止注入攻击。第一篇文章主要讨论的是如何筛选错误报告,获得噪声较小的白名单,从而限制应用中运行的代码源;第二篇文章主要讨论,随机数源如何缓解内容注入带来的XSS 攻击。尽管如此,CSP 规则中的另一个关键字 unsafe-eval允许字符串转代码的用法(比如,evalnew FunctionsetTimeout等),这又留下了 XSS 攻击的隐患。

显然,上面这个问题必须解决。但由于旧式JS 模板在Dropbox 客户端代码中的大量使用,全面禁止eval 并不容易。在我们用React 替换旧式模板的过程中,我们也在思考 unsafe-eval造成危险的确切机理和解决方法。

unsafe-eval乍看起来并不像致命的不安全指令。unsafe-eval只决定浏览器是否允许 eval,其变量类似于new Function。但是,如果一次攻击中可以调用 eval,那么这次攻击就已经到达了代码执行的程度,这必会造成损失。与unsafe-inline不同,在unsafe-eval造成的漏洞中,攻击插入字符串并随字符串进入 eval“槽”,而unsafe-inline则允许攻击者将简单的 HTML 注入漏洞转变为代码注入漏洞。

不幸的是,在更深入的探索中我们意识到,上述解释并不正确。产生攻击漏洞的主要原因是我们使用了 jQuery、Prototype 之类的库。事实上,使用 jQuery、Prototype 时,unsafe-eval抵消了移除unsafe-inline所带来的优势。我们会深入讨论 jQuery,类似的问题也同样存在于 Prototype 或其他库中。

请看以下两行 HTML 代码,似乎它们的运行结果相同:

复制代码
document.getElementById("notify").innerHTML = untrusted_input
jQuery("#notify").html(untrusted_input)

不允许内联脚本的 CSP 规则中,untrusted_input可能含有全局中所有的onclicks,浏览器不会执行它们。这一点对于两行代码都适用。但是untrusted_input包含一个内联脚本标记(如,alert(1)),这就使得两行代码大不相同了。

在第一行代码中,innerHTML不支持内联脚本标记,alert不会执行。而在第二行代码中,jQuery 将解析脚本标记,直接用innerHTML设置untrusted_input并不会起作用。jQuery 会解析脚本标记,并直接在脚本标记中 eval 代码。更糟糕的是,如果untrusted_input https://attacker.com/foo.js ,那么 jQuery 会 XHR 注入那个 foo.js 文件并 eval 它,内容源对脚本的限制甚至会失效。完成这一动作的代码在 jQuery 核心的 domManip 函数中。jQuery 代码在几乎所有 DOM 操作(插入、追加、html 等)中,都会调用该函数。

此类问题的另一个例子是jQuery.ajax函数。这个函数看起来是一个普通的用来产生 XHR 请求的函数,但 jQuery 从设计上赋予了它的 ajax 函数更多功能。特别当 XHR 请求的应答中包含内容型脚本时,jQuery 会 eval 应答(参见 GitHub 讨论)。这意味着,只要是攻击者可以控制目标 ajax URI 的地方,都将成为代码注入漏洞。

不允许 eval 的 CSP 规则中,浏览器会阻止上述的漏洞。但实施这种 CSP 规则代价巨大。为了减少此类风险,我们开发了一项 jQuery 顶层“安全补丁”,以防止非安全操作。我们很乐意将我们的 jQuery 补丁开源来帮助解决以上“意外的 eval 操作”,希望广大的社区开发者们可以从中受益。如果开发者朋友们发现其他地方需要打补丁,也请和我们分享!

补丁中有两个重要的组成部分。首先,通过添加以下代码,移除了 ajax 中的隐式 eval。这行代码使用一个 no-op 代替了脚本应答的默认处理器(放置在 jQuery 代码使用 eval 的地方)。

复制代码
jQuery.ajaxSettings.converters["text script"] = true

第二,重写了默认的domManip函数,在执行前检测脚本标记及随机数的正确性。补丁仅仅重新实现了domManip函数(完全从 jQuery 中复制出来),不过补丁的关键之处在函数的第 183 行:

复制代码
// line 181:
for (i = 0; i < hasScripts; i++) {
node = scripts[i];
if ((window.CSP_SCRIPT_NONCE != null) &&
(window.CSP_SCRIPT_NONCE !== node.getAttribute('nonce')) {
console.error("Refused to execute script because CSP_SCRIPT_NONCE" +
" is defined and the nonce doesn't match.");
continue;
}

另外一种解决方案是完全删除可能造成误操作的代码,或使用 jPurify 等插件清理 jQuery 所有的 DOM 操作。但文章这里的重点是,如果配置的 CSP 规则允许unsafe-eval,那么减小 XSS 攻击风险的措施便十分重要。

可信任 eval 的使用

正如之前所提到的那样,因为我们早先的代码仍在使用不安全的 eval,我们无法完全移除规则中的 unsafe-eval。特别地,当使用 JavaScript Microtemplates 时,我们还需要 unsafe-eval。本质上,该模板库使用new Function来对“text/template”内容型脚本标记中的模板进行 eval 操作。例如下面这个模板

复制代码
<script type="text/html" id="user_tmpl">
<% for ( var i = 0; i < users.length; i++ ) { %>
<li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
<% } %>
</script>

模板代码使用 id 参数查找模板,然后在上面的脚本标记中调用new Function函数,但这样也使得攻击者可以用 HTML 注入漏洞插入恶意模板。这里的模板是由我们的模板库 eval 所得。

为了解决这个问题,我们在所有模板脚本标记中插入随机数属性,并修正了模板库,检查模板节点的随机数属性。这类似于浏览器检查脚本节点的随机数属性。

复制代码
<script id=test type=text/template nonce=1234>
...// template library only processes this if
...// window.CSP_SCRIPT_NONCE equals 1234
</script>
<script type=text/template>
...//the templating library will ignore this
</script>

我们遇到的另一个问题是,有时客户端代码会在网页载入之后下载模板。由于服务器每次生成一个新的随机数,网页载入后下载下来的模板中的随机数会和主页中的随机数不一样。我们通过修改服务器端的代码解决了这个问题。以前每次载入都要产生随机数,现在替代的方法是,网页的脚本随机数是 CSRF 令牌的 hash 值(CSRF 令牌已经是一个不可预测的随机值了)。这个方法将随机数安全性简化为 CSRF 令牌安全性。不过,如果攻击者知道使用的是 CSRF 令牌,可能对用户进行 CSRF 攻击。

最后再一次提醒读者,CSP 是一个缓解风险的措施,属于深度防御,并不是网页应用安全的第一道防线。最适合 XSS 的防御方法是安全搭建 HTML,使用的框架应当能够自动规避非信任数据,同时使用性能较好的 DOM 清理器作为第二道防线。

在下一篇文章中,我们将讨论 CSP 和第三方软件整合的问题,及其相关的风险。

查看英文原文: [CSP] The Unexpected Eval

编后语

《他山之石》是 InfoQ 中文站新推出的一个专栏,精选来自国内外技术社区和个人博客上的技术文章,让更多的读者朋友受益,本栏目转载的内容都经过原作者授权。文章推荐可以发送邮件到 editors@cn.infoq.com。


感谢魏星对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群(已满),InfoQ 读者交流群(#2))。

2015 年 12 月 08 日 17:341148
用户头像

发布了 268 篇内容, 共 104.0 次阅读, 收获喜欢 19 次。

关注

评论

发布
暂无评论
发现更多内容

【大厂面试05期】说一说你对MySQL中锁的理解?

NotFound9

Java MySQL 后端

ARTS-Week Four

shepherd

Java algorithm

架构师训练营-课后作业-Week-2

Chasedreamer

Spring-资源加载

CoderLi

Java spring 程序员 后端 Java 25 周年

Spring 获取单例流程(一)

CoderLi

Java spring 程序员 源码分析 后端

一文讲透SpringMVC

知春秋

spring springmvc Servlet

编译Spring5.2.0源码

CoderLi

Java spring 程序员 后端 Java 25 周年

重学 Java 设计模式:实战享元模式「基于Redis秒杀,提供活动与库存信息查询场景」

小傅哥

设计模式 小傅哥 重构 代码坏味道 代码优化

618 将至,融云通信云技术如何助力电商销售

Geek_116789

Spring 容器的初始化

CoderLi

Java spring 程序员 源码分析 后端

架构师训练营第二周总结

一剑

以太坊颠覆了以太坊:引入密码学实现2.0性能突破

安比实验室SECBIT

以太坊 分布式系统 节点 密码学

Spring 获取单例流程(二)

CoderLi

Java spring 程序员 源码分析 后端

面试官:线程池如何按照core、max、queue的执行循序去执行?(内附详细解析)

一枝花算不算浪漫

jdk源码 线程池 Java 面试

架构师训练营第二周作业

一剑

小师妹学JVM之:JVM的架构和执行过程

程序那些事

Java JVM 「Java 25周年」 小师妹 性能调优

漫画 | 啊哈,给我一碗孟婆汤

码农神说

程序员 测试 互联网人 设计师

Websocket直播间聊天室教程 - GoEasy快速实现聊天室

GoEasy消息推送

直播 websocket 即时通讯 聊天室 弹幕

作为CEO你比员工厉害吗?

Neco.W

创业 创业者 CEO

架构师训练营第 2 周——学习总结

在野

极客大学架构师训练营

CDN百科第四讲 | 如何优雅地在云上“摆摊”——做直播带货,你不得不关注的技术

阿里云Edge Plus

CDN 边缘计算 直播 直播带货

Spring-AliasRegistry

CoderLi

Java spring 程序员 源码分析 后端

架构师训练营第二周 - 作业

Eric

极客大学架构师训练营

谈谈程序链接及分段那些事

helloworld

c++

Flink on Zeppelin (1)入门篇

章剑锋_Jeff

大数据 flink 流计算 Zeppelin

为什么你的简历石沉大海,offer 了无音讯?

非著名程序员

程序员 程序人生 提升认知 简历优化 简历

软件开发:软件设计的基本原则

WANDEFOUR

极客大学架构师训练营

LinkedList竟然比ArrayList慢了1000多倍?(动图+性能评测)

王磊

Java 数据结构 性能优化 性能 链表

数字产品开发那些事

涛哥

产品开发 数字化

别教我女儿该怎么穿,教你儿子别去强奸

小天同学

教育 日常思考 个人感悟 自我保护

Spring 获取单例流程(三)

CoderLi

Java spring 程序员 源码分析 后端

Study Go: From Zero to Hero

Study Go: From Zero to Hero

Dropbox的Web安全防护策略之三:意外Eval操作-InfoQ