浏览器家族的安全反击战

干货

观点

案例

资讯

我们



前言


上次我说过,我们浏览器的主要工作就是把 HTML,JavaScript,CSS 等文件从服务器端取下来,然后解析、渲染,展示成美奂美伦的页面呈现给人类。


我们还替人类保护一个叫做 Cookie 的小东西,网站会把 Cookie 发送给浏览器让我们保存起来,等到访问同一个网站的时候,我们再把他发过去。


这个 Cookie 用来证明某个用户已经和服务器交互过,更重要的是证明已经登录过系统,不用再次登录了。


JavaScript 这小子在我们这里承担了越来越重要的职责,从对 DOM 树的改动,到利用 AJAX 访问服务器端,他可以说是风光无限。


(详情参见上一篇文章《浏览器:一个家族的奋斗》)


可是我们都忽视了一个重要的问题:安全,这个东西差点让我们家族遭受灭顶之灾。




Cookie失窃


有一天,我的主人登录了"爱存不存"银行(www.icbc.com),这个银行网站给我发了一个 Cookie,证明主人登录过了, 主人在“爱存不存”银行网站做了一些操作,但是忘记了退出,然后开了一个新的 Tab 页访问了一个叫做 www.beauty.com ,我知道这个网站不怀好意,拼命地提醒主人,但是他仍然经不住网站上那些图片的诱惑,执意把这个网站打开。


我没有办法,只好下载这个不怀好意网站的 HTML,JavaScript,CSS, 让我没有想到的是这里的 JavaScript 竟然想访问“爱存不存”银行的 Cookie。


“这个 Cookie 是爱存不存银行给我的,不属于 www.beauty.com,你为啥要访问?” 我问他。


“没事,我好奇,想看看别的网站的 Cookie 长什么样”  他轻松地回答。


我将信将疑地把 Cookie 给了他,他不知道做了什么花样,似乎是往 www.beauty.com  发了一个请求,然后就把 Cookie 还给了我。


很快我的主人就发现,他在“爱存不存”银行的私房钱不翼而飞了。


FireFox 嘲笑我说:“你这个家伙啊,怎么能够把 Cookie这么重要的东西随随便便地给别人呢? ‘爱存不存’银行的 Cookie 被黑客偷走了,那些黑客不用登录就可以冒充用户在‘爱存不存’网站做操作了。”


“啊? 有这么严重? 可他是 JavaScript,照理说可以访问啊?”


“唉,你要知道,这个 JavaScript 和那个 Cookie 不是同一个网站的,怎么能访问呢。”


由于这件事,主人再我不理我了,从此开始宠幸 FireFox。




密码失窃


FireFox 也没得意很久,他也很快中了招。


这一次,主人还是忍不住去 www.beauty.com  看图片,FireFox 这次很小心,不把任何别的网站的 Cookie 发给这里的 JavaScript。


但这一次 beauty.com 改变了策略,它用 iframe 的方式放置了一个淘宝的登录网页到 beauty.com 页面中,淘宝恰恰是主人最喜欢的,主人一看,不错啊,还有快捷登录方式,于是主人就输入了自己真实的用户名和密码,没想到 Beauty.com 的 JavaScript 已经把这个淘宝登录 Form 的 action 指向了自家网站,等到主人点了登录按钮以后, 用户名和明文的密码就这样被窃取了。


于是 FireFox 也被打入冷宫。





家族会议


黑客猖獗,类似的安全事故不断出现,我们家族的成员纷纷中招,家族赶紧召集会议,商量对策,防止人类把我们家族给废掉。


我和 FireFox 在会议上声讨现在的人类实在是喜欢访问那些不良网站,族长 Mozilla 说没办法这是人类的本性,无论如何也无法改变,如果改了就不是人类了。


“虽然我们控制不了人类的行为,但是我们浏览器家族可以做点改变,增加安全性!” Mozilla 族长充满正义感和使命感,他下达了一个命令:“以后我们家族确定一条铁规:除非两个网页是来自于统一‘源头’, 否则不允许一个网页的 JavaScript 访问另外一个网页的内容,像 Cookie,DOM,LocalStorage 统统禁止访问!” 


我仔细咂摸这句话的含义,其实是说各个网页如果不同源的,就被隔离了,只能在自己的一亩三分地中折腾。


“什么叫同一个源头?” FireFox 问道。


“就是说 {protocol,host,port} 这三个东西必须得一样! 我给你们举个例子, 例如有这么一个网页:http://www.store.com/product/page.html, 下面的表格列出了各种不同情况。



这个同源策略确实严格, 不同源的网页无法访问另外一个网页的 DOM,Cookie, 像 beauty.com 那样的恶意网站想偷走 Cookie/ 密码就不容易了。


我想到了主人之前购物经常访问的 http://www.store.com/, 这个页面中有一段装载 jquery.js 的代码:


<script src="//static.store.com/jquery.js > </script>


这个jquery.js是来自于不同的源(static.store.com), 难道他就没法操作 www.store.com  页面的内容了吗? 如果不能操作,这个 jquery.js 就没有任何用处啦!


我把这个困惑给大家说了下, FireFox马上附和: “没错,难道我们要强制人类把所有的 JavaScript 代码放到 www.store.com 下吗? 人类肯定不能容忍! ”


“嗯,这是个好问题”  Mozilla 族长说,“这样,我给你们开个口子,对于使用<script src='xxxxx'> 加载的 JavaScript,我们认为它的源属于 www.store.com, 而不属于static.store.com,这样就可以操作 www.store.com  的页面了,行不行?”


我和 FireFox 都表示赞同,其实这种“嵌入式”的跨域加载资源的方式还有<img>,<link>等,相当于我们浏览器发起了一次 GET 请求,取到相关资源,然后放到本地而已。


同源策略就这么确定下来了, 我们这些浏览器都开始严格遵守执行,果然,安全事故大大减少。



凡事都有例外


过了一段时间,JavaScript 这小子便跑来抱怨了:“你们这个同源策略实在太严格了,太不方便了。”


我说:“这也是为了大家好啊,省得那些不良网站干坏事。”


JavaScript 说:“好什么好? 现在人类的系统越来越大,大部分都拆分成分布式的了,每个系统都有子域名,像 login.store.com, payment.store.com, 虽然二级域名不同,但是他们属于一个大的系统,由于同源策略的铁规,cookie 不能在这个系统之间共享,麻烦死了。”


这确实是个问题,我带着 JavaScript 找到到 Mozilla 组长,说明情况,希望他网开一面。


族长说:“好吧,我再给你们开个口子,如果两个网页的一级域名是相同的,他们可以共享 cookie, 不过 cookie 的 domain 一定要设置为那个一级域名才可以,例如:”

document.cookie = 'test=true;path=/;domain=store.com'


JavaScript 说:“还有个大问题,你们为什么不让我使用 XMLHttpRequest 访问不同的网站啊!”


我心想 JavaScript 说的可能是 AJAX,可以创建一个 XMLHttpRequest 对象去异步的访问服务器端提供的服务,做到局部刷新页面,用户体验很好。


难道这个 XMLHttpRequest 对象只能访问源服务器(如 book.com),不能访问其他服务器(如 beauty.com)?


族长说:“是啊,XMLHttpReqeust 也要遵守同源策略。”


JavaScript 说:“可是这没有道理啊!我虽然是从 book.com 来的,但是为什么不让我访问 beauty.com?”


族长说:“我这也是为了防止黑客攻击,给你举个例子,假设你的主人登录了 book.com , 然后又去访问 beauty.com,如果这个 beauty.com 是个恶意网站,它也要求你创建一个 XMLHttpRequest 对象,通过这个对象向 book.com(不同源)发起请求,获取你主人的账户信息,会发生什么情况? ”


JavaScript 恍然大悟:“奥,我懂了,由于主人登录过了 book.com,登录 cookie 什么的都在,那 beauty.com 的 JavaScript 向 book.com 发起的 XMLHttpRequest 请求也会成功,我主人的账户信息就会被黑客给获取了。”


我说:“看来对 XMLHttpReqeust 对象施加同源策略也是非常重要的啊!”


JavaScript 沉默了半天说:“那怎么办?”


Mozilla 族长说:“你可以通过服务器端中转啊,例如你是来自 book.com的, 现在想访问 movie.com,那可以让那个 book.com 把请求转发给 movie.com 嘛!人类好像给这种方式起了个名字,叫什么代理模式,那个 book.com 就是代理人。”


JavaScript 急忙说:“不不, 这样太麻烦了,族长你想想,如果我要访问多个不同源的系统,要是都通过 book.com 中转,该多麻烦!”


族长想了想说:“你说得有一定道理,我给你出个主意,既然服务器(domain)之间是互信的,那一个服务器(domain)可以设置一个白名单,里边列出它允许哪些服务器(domain)的AJAX请求假设 movie.com 的白名单中有 book.com, 那当属于 book.com 的 JavaScript 试图访问 movie.com 的时候......”


JavaScript 马上接口说:“ 这时候,我们浏览器做点手脚,悄悄地把当前的源(book.com)发过去,询问下 movie.com, 看看他是否允许我们访问,如何允许,你们就继续访问,否则就报错!”


组长说:“就是这个意思,这样以来,那些黑客就没有办法假冒用户向这些互信的服务器发送请求了, 我把这个方法叫做 Cross Origin Resource Sharing,简称 CORS,只不过这个方法需要服务器的配合了”


(码农翻身注:上面说的是 CORS 的简单请求,对于 Preflight请求,本文不在展开描述)


JavaScript 表示同意,这也算是一个不错的妥协方法了。

(完)




你会感兴趣的内容:


【测试】

Jmeter简单介绍与搭配Jenkins实现自动化测试实践

如何使用 JMeter 实现 API 接口自动化测试?


【姿势】

谈谈Python协程技术的演进


【反爬虫】

常见的反爬虫和应对方法 


UI

UX 必备:我的 MAC 版图片管理器推荐与使用对比




相关文章
相关标签/搜索