第1章
Web应用安全
为了给本书后面的技术讨论提供必要的背景知识,我们首先解释清楚安全领域涵盖哪些方面,以及为什么在这个早已被研究得很透彻的领域里,Web应用的安全仍然值得引起额外的关注。那么,让我们开始吧?
1.1信息安全速览
表面上看来,信息安全领域属于计算机科学里很成熟、明确且硕果累累的一个分支,自以为无所不知的专家们通过展现他们那分类清晰、数量庞大的安全漏洞集来标榜这一领域的重要性。至于那些漏洞的责任嘛,就全都归到那些“安全文盲”的程序员们头上好了,而理论家们则会从旁指点,说只要遵从今年最热门的某某安全方法学,早就能把这些问题都防患于未然了云云。安全问题更是带动了一个产业的繁荣,但对用户来讲,从普通计算机用户到庞大的国际公司等,其实并没有带来什么有效的安全保障。
从根本上来说,过去几十年,我们甚至没能构建出一个哪怕原始但至少还算可用的框架来理解和评估现代软件的安全性。除了几篇出色的论文和一些小范围内取得的经验,甚至无法拿出什么有说服力的真实的成功案例。现在的侧重点几乎都放在一些响应性质的、次要的安全方法上(如漏洞管理、恶意软件和攻击的检测、沙盒技术以及其他),要不就是常常对别人代码里的漏洞指指点点。一个令人不安而又秘而不宣的实情是:如果安全系统是由别人开发的,那我们贡献的价值实际上往往乏善可陈;在现代Web这件事情上则更是如此。
让我们来看看以下几种最瞩目的信息安全之道,并尝试分析一下为什么到目前为止,它们也没能走出这一困境。
1.1.1正统之道的尴尬
也许开发一个安全程序最直接的途径就是从算法上证明该程序的运行是正确的。从直觉来说,这个简单的假设听起来还蛮有道理的—那为什么此路不通呢?
首先让我们讨论一下作为形容词时“安全”这两字的含义。准确来说,它到底是什么意思呢?安全(Security)听起来很直观,但在计算机领域,就愣是没法给它下一个确切的定义。没错,我们可以用一些很眩目但基本没啥意义的方式来描述安全,例如,在业界经常被引用的一个关于安全的定义如下,但实际上它也是有问题的:
如果系统能按既定的方式完成任务,不做额外的事情,那这套系统就是安全的。
这个定义很简洁,也大致描述了一个抽象的目标,但它几乎没提到要怎么做才能达成这个目标。虽然这句话的主题是关于计算机科学的,但其笼统程度,和维克多·雨果的一句诗倒有异曲同工之妙:
爱情乃灵魂之一部分,就恍如仙气弥漫于天堂一般。
也许有人会反对说,这个棘手的定义不应该求诸于商业界,那好,我们只管把这个问题抛给学术界吧,但他们也只会给出一个差不多的答案。例如,下面这个常见的学术界对安全的定义,它出自20世纪60年代出版的贝尔–拉帕杜拉安全模型(Bell-La Padula security model,这套规范是企图规范化安全系统需求的诸多努力之一,这是一套针对国家机器的规范1;当然也是最知名的一套规范。)
当且仅当系统开始于安全的状态,而且一直不会落入非安全状态,它才是安全的。
当然,像这些定义安全的文字从根本上来说都是正确的,用于论文的基调甚至政府规范都毫无问题。但实际上,对真实世界里的软件工程而言,由于以下三个原因,以这些理论为基础建立的模型几乎是没用的。
对一个足够复杂的计算机系统来说,没有办法定义什么是正确的行为。对一个操作系统或者Web浏览器来说,没有一个权威机构能定义什么是“应该的方式”或者“安全的状态”。最终用户、系统所有者、数据提供者、业务流程所有者和软件硬件开发商之间的利益是南辕北辙,并且说变就变—如果公司的股东们可以为所欲为,能够不加掩饰地优先考虑自己的利益,就更是如此了。雪上加霜的是,社会学和博弈论的研究表明,把各方利益做简单的叠加运算,实际上并不能产生互赢的结果。这种两难的困局就叫“公地悲剧”,在日后的互联网发展里它将一直是争论的焦点。
美好的想法无法自动转换成规范的约束条件。即使通过给出系统应包含哪些具体用例,我们能就系统该有的行为达成完美的、高级别的共识,但这些用例几乎不可能与可允许的输入数据、程序的状态和这些状态的转换一一对应起来,而要做正式的系统分析却需要这种对应关系。很简单,譬如,一个很常识性的概念“我不希望别人越权读到我的电子邮件”,却没有办法很好地翻译成数据模型。也许有些剑走偏锋的方法的确能把这种模糊的需求部分地转换成规范化的表达,但对软件开发过程带来严重的限制,并产生比验证算法(Validated Algorithm)更复杂的许多规则集和模型。并且,这些方法自身的正确性也还需要进一步验证……这样就走进了一个死循环。
很难令人信服地分析软件的行为。在复杂的真实世界的场景里,完全没有办法令人信服地通过对计算机程序的静态分析,证明程序的运行是符合详细设计规范的(当然,在高度受限的环境下或针对一个非常狭义的目标还是有可能办到的)。很多案例在实际环境中是无解的(因为计算的复杂程度),甚至有可能由于停机问题(halting problem)而完全无法确定其状态。
对安全的这些早期定义既模糊又没用,但令人抓狂的是,尽管几十年过去了,事实上我们取得的进展却非常有限。2001年由Naval Research Laboratory发布的一份学术报告回顾了软件安全领域的早期工作,结果也不过是给软件安全提供了一个更随意的枚举式的定义— 而且这个定义还明确否认其自身并不完善和不完整2。
. 如果系统在处理信息时恰当地保护了数据,使其不会产生未授权的泄漏、未授权的篡改以及能抵抗未授权的大规模压力(也叫拒绝服务,Denial of Service),那系统就是安全的。我们说“恰当地”是因为如果不加上这个限制,真实环境里实际上没法达成这一目标;因为安全从根本上来说只是相对的。
这篇论文也回顾和评估了那些早期的安全定义,并指出BLP模型为了保持理论上的纯洁性而做出了无谓的牺牲。
经验显示,一方面,作为公理的Bell-La Padula模型的限制太多:它禁止了在实际应用中一定会出现的用户操作。另一方面,为了克服某些限制条件,它提供的可信对象机制又变得不够严格……导致的结果是,开发人员不得不为每个系统里受信任过程的合理行为,都要设定一个专门的规范。
最后,不论引入多少这种优雅的彼此竞争的安全模型,它们都期望基于也许注定失败的算法,来理解和评估现实世界里的软件安全性。这让开发人员和安全专家没办法对产品代码的质量进行权威的有预见性的判断。那么,我们还有什么选择呢?
1.1.2进入风险管理
由于缺乏正式的保障,也没有什么实证可用的方法,而商业社会极度依赖的关键性软件又让人心惊肉跳地存在着大量的安全问题,所以业界开始争先恐后地引入另一个抓人眼球的概念:风险管理。
风险管理的理念在保险界大获成功(金融界里的风险管理好像就要逊色那么一点点了),它只是告诉系统所有者应该学会在考虑性价比的情况下,必须接受漏洞必然存在这一现实。一般来说,可以用以下公式计算出为此风险该付出多少代价:
风险 = 出现问题的可能性×最大损失
例如,根据这一公式,假设某台不太重要的工作站每年受到的攻击对生产效率的影响少于1000美金,那么该机构对修复这类损失就应该谨慎投入,不用太费神,无需为此花上譬如10多万美金,来引入额外的安全措施应对意外情况,并制订监控计划以避免这些损失。根据风险管理的精神,主要投资应该花在运行关键性任务的主要设备上,进行隔离、加固和监控,因为这些关键性设备处理着客户所有的付账记录呢。
根据目标安排优先级自是理所当然的。但问题是,风险管理主要是和数字打交道,它对帮助我们理解、覆盖和管理真实世界里的问题并无多大裨益。它反而带来一种错误的危险观念:不到位的措施只要被条理化了就能变得合理了,不足的金钱投入再加上风险管理,就能和充足的安全投入达到相似的安全成效。
想碰运气吗?没门呐。
在互联的系统里,损失是没有上限的,范围也不会只固定在单个设备上。严格意义上的风险管理是评估某个资源被攻破时会遭受到多少常规损失和最大损失。遗憾的是,这么做其实忽略了很多重大的安全攻击事件—如针对TJX和微软的攻击—它们在最开始时都是针对一些相对不重要和被忽略的入口点。但这些开始不起眼的攻击会迅速升级,绕过任何表面的网络隔离,最后几乎完全攻破关键性运营设备。典型的风险管理只关注数字,而和其他节点比起来,初始的事故点往往权重都比较低。同样,因为被利用的可能性较低,能逐步接入敏感资源的内部攻击途径也往往会被忽视了。然而,这两种被忽视的组合会导致代价高昂的损失。
健康系统的贡献实际上弥补不了系统被入侵后的非经济损失。系统被入侵会导致客户信心的受损,业务的中断,甚至可能惹上官司,以及受到监管部门严厉调查等风险,在这些方面很难有什么可靠的保障。至少在理论上,被入侵的后果有可能拖垮一个公司甚至整个行业,浅层地评估它们会带来什么影响,那几乎都只是瞎猜而已。
现有的数据对日后的风险也许完全没意义。与轻微交通事故里的涉事者完全两样,攻击者可不会挺身而出拱手道出事情的原委,更不会事无巨细地记录下事故的损失。除非入侵的程度严重到令人瞠目(因为攻击者的疏怠或攻击的原意就是要搞破坏),通常入侵行动压根就不会被发现。即使行业自身也会报告这类入侵数据,但并没什么方法证实报道的数据是否完整,也搞不清楚会给现在的业务再带来多少风险。
基于统计数据的预测对独立事件来说并不靠谱。尽管普通人在城市里遭到雷击的可能性应该大于被熊袭击,那也不等于我们就该在帽子上戴根避雷针,然后天天泡在蜜糖水里洗澡吧。从单一事件来说,其实无法确定特定的模块会否受到攻击:安全事件几乎是一定存在的,有无数暴露在外的资源,任何服务都有可能受到攻击—所以在企业内部,不太可能说因为哪个特定的服务受到的攻击量特别多,就可以在统计学上预测出有意义的入侵。
1.1.3分类学的启发
以上讨论的两种思想学派有一个相通之处:都认为可以把安全分解成若干可计算的目标,由此就能优雅地得到一套统一的安全体系理论或一套能接受的风险管理模型,进而抽象出一些经过优化的底层行为,然后就能借此设计出完美的应用来。
而某些从业者则鼓吹相反的做法,他们不太考虑理论体系,更偏向自然科学的做法。这些从业者就像信息时代的达尔文,他们通过收集足够多的实验数据进行底层抽象,对这些日益复杂的规律进行观察、重组和记录,以期获得某种安全运算的统一模型。
由美国国土安全局资助的CWE(Common Weakness Enumeration,常见漏洞列表)项目就反映了这种理念。该项目的目标,用它自己的话来说,就是创建一套统一的“漏洞理论”,“以改进对软件缺陷的研究、建模和分类”;并“提供一套通用对话语言,以便于讨论、挖掘和处理软件安全漏洞的成因。”这套体系里的漏洞分类复杂得令人眼花缭乱,它的某些例子可能类似如下这样:
对信息或数据结构的限制不当。
转换不同层次数据时清理不足。
资源标识符控制不当。
对可执行内容的文件名或其他资源名称过滤不充分。
到今时今日,CWE的词典列表里收录了800多条,其中大多数条目在促进交流方面的效果都和上述引用的例子相差无几,并不容易理解。
另一个受自然主义思想影响,但略有差异的学派支持的是CVSS(Common Vulnerability Scoring System)项目,这个项目由商业机构资助,目标是用一套只有机器能读懂的基本参数,精确量化已知的安全问题。一个真实的漏洞描述可能如下表示:
AV:LN / AC:L / Au:M / C:C / I:N / A:P / E:F / RL:T / RC:UR /
CDP:MH / TD:H / CR:M / IR:L / AR:M
机构和研究者期望通过这14项评估因子,根据不同的用例场景,严谨地对潜在漏洞的重要程度打分,达成一个客观、可验证的数字分值(如“42”),这样就完全无需根据主观因素来判断安全漏洞的严重程度。
没错,我是有点儿嘲笑这些项目,但我也完全无意贬低它们的贡献。CWE、CVSS及其相关项目都胸怀远大目标,如为大型机构的安全事务处理提供更可管理的维度。然而,它们都未能在软件安全这个问题上,诞生出一套真正雄伟的理论体系。而我怀疑在可见的未来也不可能出现这样的理论体系了。
1.1.4实际的解决之道
现在所有的迹象都表明安全问题与算法无关。可以理解,业界是心不甘情不愿地接受这一结论的,因为这意味着没法到处去吹嘘我们拥有一劳永逸的解决方案(当然,如果在商业上能获得成功就更称心了);然后一旦被逼急了,安全圈内的人们又只能回退到诸多最基础的老路子上去。这些方法和诸多新的业务管理模式并不是很兼容,但到目前为止,这些招数是唯一还算管用的法子。这些老方法包括:
从失败(最好是别人的失败)中学习。系统设计应尽量避免出现已知的缺陷。尽管没有自动的解决方案(甚至可能连优雅的方案也欠奉),但最好能不断修正程序的设计指引,确保开发人员知道什么地方有可能出现问题,为他们提供一些工具,尽量以最简单的方式来避免任务出错。
开发一些工具来检查和纠正问题。一般而言,安全上有欠缺不会产生明显的副作用,除非它们被恶意的入侵者利用:但这种反馈成本实在太昂贵了。为解决这个问题,我们可以创建各种安全质量保障(QA)工具,以验证程序的实现是否正确,执行定期的审核以检测是否有一些无意中出现的错误(或者系统工程上的缺陷)。
先做最坏的打算。尽管我们已经尽了最大的努力来避免出现问题,但历史也一再教育我们,还是有可能会出现严重的安全事故。所以要引入恰当的组件隔离、访问控制、数据冗余、监控和响应流程,使服务的相关人员可以在事件还未从小事故演变成大灾难前做出及时的响应。
无论如何,信息安全人员都需要具备足够的耐心、创造力和深厚的技术积累。
当然,即使这些最简单和常识性的规则—差不多也就是安全工程的基本要求吧—也会经常被包装成一堆由缩写拼就的闪亮词语以抓人眼球(如CIA:代表“Confidentiality、Integrity、Availability”,意即“保密、完整、可用”),还有各种所谓的“方法学”。一般而言,这些方法学不过是把安全业界里那些叫人无比沮丧的失败涂脂抹粉一番,摇身变为另一个成功故事而已,最后再把一堆号称包治百病的产品或认证卖给那些好骗的冤大头客户。尽管这些产品号称无所不能,但它们实际上还是没法取代技术人员对实际环境的把握和对技术的精通—至少现在办不到。
无论如何,在本书的剩余章节里,我会尽量避免建立或重复前面提到过的任何一种宏伟框架,相反,我会采用不那么理论化的做法。在后文中我们会先回顾现代浏览器的方方面面,讨论如何安全地使用相关工具,Web在哪些方面会被广泛误解,以及出了问题的时候需要怎样控制连带的损失。
我认为这些才是谈论安全工程时的要点。
1.2Web的简明历史
Web 曾遭受过很多次重创,层出不穷的安全问题更是令人瞩目。诚然其中一些问题可以归咎为某个版本的客户端或服务器端的具体实现出了问题,但其中很多是由于浏览器基本机制里各种复杂多变、随心所欲的设计以及它们的相互作用导致的。
我们的Web王国建立在一个摇摇欲坠的基础上,原因何在?可能只是由于当初的短视:毕竟,在早期的纯真年代,谁能预料到当今的网络会变得如此危险,大规模安全攻击还往往涉及强烈的金钱诱因?
尽管这些理由对那些真正古老的机制(如SMTP和DNS)确实适用,但它们却并没有带来那么多麻烦:相对来说,Web还是比较年轻的,Web诞生和成形的环境与当下并无多大差别。实际上,这一谜团的真正原因可能与多年来Web技术那混乱又特殊的演化过程有莫大关系。
所以,请原谅我在此又要简短地打个岔,对Web的发展再追本溯源一下。最早期的Web尽管相当平淡但仍然值得仔细研究一番。
1.2.1史前时期的故事: 1945~1994年
计算机历史学家们都常以美国科学家Vannevar Bush3在1945年虚构出来的一台名为Memex的桌面设备作为Web理念最早期的原型。Memex 用于在微缩胶卷上创建和标注跨文档链接,并按照这些链接而跳转切换到所引用的其他微缩胶卷上,使用方式大略类似于我们现在的书签和超链接。Bush 大胆推测这种简单的功能将革命性地改变知识管理和数据挖掘的前景(令人莞尔的是,直到20世纪90年代初期,偶尔还有人觉得这种想法是愚笨且天真可笑的)。但当时并没有任何实际可用的设计,因此那时候它还仅是个充满未来憧憬的愿景而已,直到晶体管计算机登上了历史舞台的中心,一切才可能成真。
20世纪60年代是下一个历史里程碑。这时候诞生了IBM的GML(Generalized Markup Language,通用标记语言),它用可供机器读取识别的指令作为文档的标识符,以标志每段文本的功用,可以明确地指明“这里是文档的头部”,“这里是几个列表项目”诸如此类。在此后经过20多年的发展,GML(一开始只是用在一些IBM笨重大型机的文本编辑器里)逐渐演变成SGML(Standard Generalized Markup Language,标准通用标记语言)。SGML语言更通用灵活,它把GML原来基于冒号和句号的笨拙语法,改成了我们熟悉的尖括号格式的语法。
在GML进化到SGML的过程中,计算机也越来越强大和用户友好。几位研究人员开始试验Bush的跨链接概念,把它运用到计算机的文档存储和检索上,看是否可以基于某些关键字对一大堆文件进行交互索引。富有探索精神的各家公司和大学也推出了各种先驱型项目,如 ENQUIRE、NLS和 Xanadu,但这些项目中的大多数都失败了,未能产生持续的影响。这些项目的共同问题包括可用性太低,过于复杂,可扩展性差等。
历经10年之后,两位研究人员,Tim Berners-Lee 和Dan Connolly 开始寻找新的跨域引用方案—这个方案必须非常简洁明了。他们先草拟了HTML(HyperText Markup Language,超文本标记语言)规范,这是一套继承自SGML的精简版语言,特别针对带超链接和简单格式的文档进行了设计。在HTML方案之后,他们又进而开发了HTTP协议(HyperText Transfer Protocol ,超文本传输协议),这是一套利用当时已有的IP地址、域名和文件路径等概念,专用于访问HTML资源的非常基础的协议。他们研究工作的总成果就是这个诞生于 1991~1993之间由Tim Berners-Lee开发的World Wide Web 程序(如图1-1所示),这个最原始状态的浏览器可以解析HTML文件,还可以把用户提交的数据显示出来,并且只需要点击一下鼠标,就可以在不同页面之间切换浏览。
图1-1Tim Berners-Lee的World Wide Web
与其他心怀高远目标的竞争项目相比,很多人都觉得HTTP 和HTML 的设计简直是个巨大的倒退。毕竟,很多早期的构想都鼓吹自己包含数据库整合、安全和数字版权管理,或者整合了内容编辑和出版;即使Berners-Lee自己的另一个早期项目ENQUIRE看起来格局也更大一些。然而,因为HTTP和HTML的门槛低,即时可用,扩展性不受限制(正好与那时计算机的运算能力越来越强,价钱更为人接受,互联网也开始普及的时机相吻合),原先毫不起眼的WWW项目突然演变成一波热潮。
好吧好吧,这里的“热潮”是以20世纪90年代的标准来说的。很快,在互联网上就涌现出了10多种的Web服务器。到了1993年HTTP已经占美国科学基金会(National Science Foundation)骨干网总流量的0.1%。在同一年,Mosaic浏览器登场了,它由美国伊利诺伊大学(University of Illinois)开发,是第一款广泛使用的、成熟的Web浏览器。Mosaic 扩展了原先World Wide Web 的代码,增加的功能包括:在HTML文档里添加内嵌的图像,通过表单提交数据,奠定了今天交互式和多媒体应用的基础。
Mosaic使网页浏览变得更美观,也令使用者更容易接受Web方式。在20世纪90年代中期,Mosaic还是另外两个浏览器的基础:Mosaic Netscape(后来改名为Netscape Navigator)和Spyglass Mosaic(后来被微软收购并改名为Internet Explorer)。同时还有好几种非Mosaic引擎的同类竞争产品,包括Opera和其他几个基于文本的浏览器(例如, Lynx 和w3m)。很快,互联网上出现了第一个搜索引擎、在线报纸和约会网站。
1.2.2第一次浏览器大战:1995~1999年
到20世纪90年代中期,很明显Web已经站稳了脚跟,用户也愿意为它放弃许多老旧的技术。此时尽管桌面软件巨鳄微软公司先前对互联网的跟进比较慢,现在也逐渐感到了不安,开始投入巨大的资源开发自己的浏览器,最终从1996年开始,Windows操作系统绑定安装了IE浏览器。微软在这段时间的举动引发了俗称的“浏览器大战”。
浏览器开发商之间的这场军备竞赛主要体现在各竞争产品都在非常快速地开发迭代,以及疯狂加入各种新功能,也就完全无法顾及产品是否符合规范标准,甚至来不及用正儿八经的文档记录下各种新代码新功能。对核心HTML特性的擅自调整包括各种蠢事(如闪烁的文字,这是Netscape的发明创造,但最终沦为笑柄)乃至一些著名的特性,如可更换字样(Typeface)或可以在所谓的框架(Frame)里嵌入外部文档。在各浏览器厂商的产品里,往往还内置对自家编程语言(如JavaScript和Visual Basic)支持,以及可在用户机器上执行跨平台Java或Flash小程序的插件,支持有用但颇诡异的各种HTTP扩展(如Cookie)。这一阶段的浏览器尽管囿于某些专利和商标上的原因,彼此间会有兼容性问题,但这些不兼容大都还比较表面。
随着Web的日益发展壮大和百花齐放,一种隐秘的恶疾悄然在浏览器引擎之间传播开来,尽管表面上还勉强维持着兼容性。这么做最开始的理由听上去还蛮合情合理的:如果浏览器A可以正常显示一个有问题的页面,而浏览器B却拒绝解析这个页面(无论基于何种原因),用户肯定会认为这是浏览器B有问题,而一股脑地选择貌似更强大的浏览器A。为了确保浏览器可以正确地显示任何网页,工程师的开发变得越来越复杂,也没有什么正式的文档来描述浏览器对于网站管理员胡乱提供的网页,是怎么进行主动猜测解析的,而在这些处理过程中往往会牺牲掉安全性,偶尔也会累及兼容性。遗憾的是,这样的变动往往又会进一步纵容各种不靠谱的网页设计观念,迫使其他浏览器开发商为免掉队,也只能亦步亦趋地跟进。当然,相关规范标准的细节缺失,更新也不及时,更是助长了这种恶疾的蔓延。
到1994年,为了解决开发上日益混乱的场面和管理HTML的升级扩展,Tim Berners-Lee和一群资助的公司创建了W3C理事会(World Wide Web Consortium)。遗憾的是,在很长一段时间内这个组织也只能眼睁睁地看着HTML标准被胡乱扩展和修改。最开始的时候W3C 只是想制定一个符合当时实际状况的HTML 2.0 和HTML 3.2 标准,但最后这些规范都只是半成品,因为等到公开发布之日,它们其实早就过时了。W3C也尝试过一些经过深思熟虑的创新项目,如层叠样式表(Cascading Style Sheet,CSS),但要浏览器开发商们接纳还需要一点时日。
另外像欧洲计算机制造者协会(European Computer Manufacturers Association,ECMA)、国际标准组织(International Organization for Standardization ,ISO)和互联网工程任务组(Internet Engineering Task Force ,IETF)等一些组织都企图对一些已实现的技术如HTTP 和 JavaScript做一些标准化和改进的工作。但可惜,各方的努力很少会相互通气协调,一些讨论和设计决策也往往由大公司和股东们控制,这些人根本不关心技术的长期前景。这样只会产生一些僵硬的标准,互相矛盾的建议,以及协议之间需要交互时各种有害的吓人的案例,实际上这些规范本可以设计得更好些—这个问题在第9章讨论到各种内容隔离机制时尤为明显。
1.2.3平淡期:2000~2003年
围绕Web的争论仍然持续不断,由于操作系统绑定策略微软浏览器得以一家独大。十年之后,Netscape Navigator退出市场, Internet Explorer 获得80%的市场占有率—这差不多也是5年前Netscape 浏览器的占有率。在这场新功能的攀比大赛中,牺牲的主要就是安全性和可交互性,既然现在战争已结束,尘埃落定后开发者们想来应该可以搁置彼此的异见,坐下来对过往的混乱局面拨乱反正一下吧。
然而,垄断也滋生了自满:在得逞之后,微软就完全缺乏动力去改进自己的浏览器了。在IE5之前,微软每年发布一个新版本,然后足足过两年才推出IE6,其后更是用了漫长的5年才从IE6升级到IE7。既然微软不感兴趣,其他的浏览器厂商势单力孤,也很难带来什么翻天覆地的变化;而大部分网站也不愿为了极少数的访问者而修改不符合规范的网页。
而另一方面,缓慢的开发进展使W3C得以追上浏览器的实际状况,并认真探索未来Web的一些新概念。在2000年的时候出现的一些新变革包括HTML4(这是经过整理的HTML语言,废弃或禁用了早期HTML版本里的一些累赘功能或策略性错误)和 XHTML 1.1(这是一种格式很严格的结构化XML文档,不会产生模棱两可的解析,也不会需要浏览器主动猜测这类文件的属性问题)。W3C理事会更是对JavaScript 里的DOM(Document Object Model,文档对象模型)和CSS做了重大改良。但遗憾的是,到20世纪结束的时候,由于Web已经步入成熟,导致早期造下的祸端已没法一笔抹去,但同时它又仍处于青春期,所以这些安全问题又貌似还没到那么急迫和突显。尽管这时语法得到了改善,无用的标签被废弃了,各种验证器也写好了,江湖座次也已排定了,但浏览器差不多还是老样子:臃肿、古怪和难以预测。
不久发生了一件有趣的事情:微软推出了一个颇不起眼的专有API,名叫XMLHttpRequest,这个名字也颇让人摸不着头脑。这个新玩意儿本来并不重要,最开始这个API只是在微软Web版的Outlook应用里小试了一把牛刀。但最后XMLHttpRequest却大放异彩,因为它实现了客户端JavaScript 和服务器之间不受限制的异步HTTP通信,而无需额外的时间开销和页面重载。以这种方式,这个API更是对其后出现Web 2.0热潮推波助澜,Web 2.0就包括许多响应式的基于浏览器的复杂应用,用户能轻松使用复杂的数据集,方便地实现群体合作和个人内容出版等,它已经一脚踏入属于传统客户端的神圣领域里了,变成“真正的”软件了。可以理解,它引起了极大轰动。
1.2.4Web 2.0 和第二次浏览器大战:2004年之后
伴随着互联网和各种浏览器的日益流行,XMLHttpRequest也把Web推到了激动人心的新高度,同时,也给我们带来了许多会影响个人和商业界的重大安全隐患。到2002年,蠕虫和浏览器漏洞变成了媒体上经常能看到的主题。由于微软的领导地位和对安全相对疏忽的态度,它承受了最多的公关压力。微软对这些问题一概视而不见,但这些压力积累发酵过后,终于造就了一次小规模的反抗。
到2004年,浏览器舞台上出现了一位新选手:Mozilla Firefox(原网景公司Navigator浏览器的后裔,由开源社区开发),它针对的正是IE糟糕的安全性和与标准的不兼容性。在获得IT专栏作家和安全专家的普遍肯定后,Firefox 很快获得了20%的市场份额。尽管这位后来者很快也被证明和微软浏览器一样,受到各种安全漏洞困扰,但由于Firefox的开源特性,以及无需迎合顽固的企业用户,使它的问题修复较为迅速及时。
注意为什么浏览器开发领域里的竞争如此激烈呢?严格来说,浏览器的市场份额并没有办法直接转化成金钱收入。但专家们认为这关乎权势地位:因为可以通过浏览器来捆绑、推销或边缘化某个在线服务(即使像默认搜索引擎这么简单的服务),也就是说谁控制了浏览器,谁就控制了互联网。
除了Firefox,微软还有别的忧虑。随着越来越多的应用(从文本剪辑器到游戏)转而以Web方式运行,它的旗舰产品微软视窗操作系统正越来越沦为浏览器的工作平台。这显然是个不利的信号。
这些事实连同突然杀入市场的苹果公司浏览器Safari和Opera浏览器在智能手机领域的步步领先,一定使微软的高层深觉头痛不已。他们已经错失了20世纪90年代互联网第一波高潮;当然他们不想再犯同样的错误。微软重新加大了对IE浏览器的投入,发布了有极大提升和在某些方面来说更安全的版本,从IE7、8迅速迭代到了IE9。
IE的竞争对手们拿出了各种新功能与之对抗,甚至声称自己对标准的支持更好(虽然也只是表面上的)、浏览更安全、效率更为提高。XMLHttpRequest出乎意料之外的成功引人瞩目,大家迅速把过去的经验教训抛到了脑后,有时候也会单方面引入不成熟或不安全的设计,如 Firefox浏览器的globalStorage和IE的httponly Cookie,这完全就是在碰运气了。
好像还嫌事情不够混乱,由于对W3C理事会在创新性上的不满,一群参与者创建了一个全新的标准组织,叫网页超文本技术工作小组(Web Hypertext Application Technology Working Group,WHATWG)来主导HTML5协议的开发,这是对现有标准的第一次整体性和把安全也考虑进去的修订,但据报道,他们经常由于专利纷争而没法和微软达成一致。
在Web的整个发展历程中,由于缺乏统一的远景目标和完整的安全规范,其开发模式非常独特,整个发展过程竞争激烈、变幻莫测,与政治牵扯过多,结果错漏百出。这些问题都对浏览器现在的工作方式以及怎样安全地处理用户数据有深远的影响。
但问题在于,在可见的将来这种情况是不会有什么改变。
1.3风险的演化
很明显,Web浏览器以及它们相关的文档格式和通信机制,以一种非同寻常的方式演进变化着。今时今日浏览器安全漏洞数一直居高不下的原因,大概都源自这种演进方式,但光是这些演进方式本身还说明不了这些问题的独特性和重要性。作为本章的收尾,让我们回顾Web领域里一些最具影响力的风险要素,并探讨为什么在Web出现以前,却没有碰到相类似的问题。
1.3.1用户作为安全风险的一个环节
也许浏览器里最突出(完全和技术无关)的一个特点就是大多数使用者完全不懂技术。当然,打从计算机诞生之日起,电脑小白们就一直是挺娱乐、挺无伤大雅的问题。但自从Web日渐深入人们的生活后,由于门槛极低,所以我们碰到了一个新情况:大多数用户对如何安全上网完全没概念。
很长一段时间以来,工程师们在开发普通的软件时,一般来说是完全不会考虑到使用者的计算机水平的。大多数情况下这样做确实没什么大问题;比如某个文本输入框的数值不太对,对整个系统的安全性几乎没任何影响。如果用户的操作有问题,那他也多半玩不转这个软件,这也算是个极好的自我纠错机制了。
但这套规律在Web浏览器上却行不通。和其他复杂的软件不同,哪怕使用者连文本编辑器都用不来,但使用浏览器却完全没问题。但同时,又只有对计算机技术和公开密钥体系(Public-Key Infrastructure,PKI)这样的技术术语相当了解的人,才有可能安全地使用浏览器。不用说,在时下林林总总热门Web应用的目标人群中,绝大部分都不符合这一要求。
而浏览器的许多做法,却弄得它像是由电脑极客们(geek)设计的,专供极客们使用的软件呢,譬如时不时蹦出一些难解又不连贯的出错提示信息,会涉及一堆复杂的配置,还有无数令人困惑的安全警告和提示。由伯克利和哈佛大学研究人员在2006年发布的一份著名的报告中显示,例如像状态栏上是否出现了带锁小图标这样的提示4,普通用户几乎肯定不会留意到这些信息,而开发人员却会非常清楚。在另一份研究中,斯坦福和微软的研究人员在检查新的“绿色URL地址栏” 安全警示标识的功效时也得出了相似的结论。这个机制的本意是为了提供比带锁小图标更明显的安全标识,但结果也往往会误导用户,以为只要见到绿色的特定形状就是可信的,也不管这种绿色标识出现在哪里5。
有些专家认为,不能把普通用户的无知怪罪到软件开发商头上,因为这根本不属于软件工程的问题。但另一种观念是,既然这类软件随处可见且广泛使用,却要求用户必须自行判断许多安全相关问题,而这些问题得需要具备一定的技术知识,可在用户最开始使用软件时又完全未做任何技术水平的限定,这是非常不负责任的做法。但仅指责浏览器开发商们也并不公平;毕竟计算机产业作为一个整体,在这个领域确实没什么靠谱的解决方案,要确保在复杂用户界面(UI)里的用户操作万无一失,也几乎没什么研究成果可供参考。毕竟,我们现在的水平还仅停留在勉强确保ATM级别的界面里不会犯错呢。
1.3.2难以隔离的Web运行环境
Web的另一个古怪特点就是,完全无关的应用和应用相关数据之间的隔离度非常弱。
在过去15年间,个人电脑时代的传统模式里应用层的数据对象(文档)、用户层的代码(应用程序)和操作系统内核之间的边界非常清晰,由操作系统内核负责所有跨应用程序的通信、硬件的输入/输出(I/O)以及通过可配置的安全策略限制应用程序的越界行为。这些边界已经被研究得很透彻,也确实对创建实际可用的安全策略大有助益。在文本编辑器里打开的文件,几乎不可能去窃取你的电子邮件,除非在具体实现上很不幸确实有重大缺陷,导致所有的隔离层都彻底失效了。
但在浏览器的世界里,却压根不存在这种隔离:文档和代码就交织在同一个HTML文件里,完全无关的应用之间最多只能算部分隔离(实际上所有网站使用的是同一个全局JavaScript运行环境),除了要遵守寥寥几个灵活的浏览器级别的安全控制框架,不同网站之间各种交互都是隐式默许的。
从某种程度来说,Web的这种模型与那些没有强壮的内存保护、CPU抢占或多用户支持的非多任务操作系统(如CP/M、DOS等)相类似。但明显不同的是,这些早期系统不太可能出现同时运行多个不受信任且易被攻击的应用的情况,自然也无需特别顾虑安全上的问题。
所以在老系统里不太可能发生文本文件窃取电子邮件这种事情,但令人恼火的是,类似情况在Web上却屡见不鲜。实际上,所有的Web应用都曾为不请自来的恶意跨域访问,付出过沉重的代价,最后只能用一些笨拙的方法勉强分离代码和要显示的数据。所有的Web应用都在这个事情上败下阵来,只是时间早晚而已。许多内容相关的安全问题,如跨站脚本(cross-site scripting)或跨域请求伪造(cross-site request forgery)在Web领域都很常见,但在专用客户端的架构里,却极少碰到类似的情况。
1.3.3缺乏统一的格局
当然,幸运的是,浏览器的安全也不是就完全无药可救了,尽管不同Web应用之间的隔离很有限,但某些安全机制还是为那些最严重的攻击提供了基本保障的。说到这里,也带出了为何Web这一主题如此有意思的另一特点:因为它完全没有一个通用的整体性安全模型。在这方面,我们也没指望能有一个解决世界和平这么宏大的愿景,这里说的只是一些常规通用又灵活的安全范式集合,即使不能适用于所有场合,但可以解决绝大多数相关的安全逻辑就行。例如,在UNIX系统里,rwx方式的用户/属组许可模式就是这么一种高度统一的安全模式。但在浏览器领域里有什么呢?
在浏览器领域里,“同源策略”(same-origin)机制就可算是这类的核心安全范式了,但实际上这套本身就问题多多的机制也仅是跨域交互里一个小的子集而已。即使仅讨论同源策略,也还有不少于7种的使用场景,这使得不同应用之间的安全边界也会略有差异。另外还有若干种机制,它们和同源模式没有什么关系,但控制了浏览器的其他关键行为(这些机制的作者往往是怎么容易实现就怎么来)。
所以结果就是,有诸多这类零零碎碎耍小聪明的调整,但谁都无法担起浏览器的安全大任。由于缺乏真确性,也无从判断单个应用在什么时候结束,新的应用在什么时候开始。在这样的困境之下,到底怎样才算是出现攻击了呢?究竟是需要加载或取消权限许可,还是需要完成某项安全相关的任务呢?我们能做的,往往不过是“双手合十,听天由命,求个平安”而已。
让人觉得奇怪的是,许多原本出于好意希望改善安全机制的努力,最后效果却往往适得其反。为了获得优雅的效果,许多这类机制往往会引入新的安全边界,但这会导致与已混乱不堪的老的安全机制无法相匹配了。如果新的控制机制粒度更细,新机制则很可能会被老机制所拖累,带来的不过是一种虚幻的安全假象;如果新机制的粒度更粗,则可能导致连现有Web机制所依赖的微妙保障也没有了(Adam Barth 和Collin Jackson 在他们的学术论文里讨论过浏览器不同安全机制之间的有害冲突 6)。
1.3.4跨浏览器交互:失败的协同
通常来说,一个包含多种应用程序的生态圈,整体的薄弱程度,可以简单地认为就是每个软件产品问题的叠加。在某些场合里,总体的受影响程度甚至会小于这个叠加值(多样性使得可耐受性也提高了),但一般认为肯定不会超过所有问题的总和。
但Web却再次打破常规。安全圈已经发现当多种浏览器企图彼此交互时,有一系列难以归咎到哪段具体代码头上但又非常严重的漏洞。你没法揪出哪个特定产品就是罪魁祸首:它们都不过是在尽责地完成任务而已,唯一问题是,没有为它们定义一个全体浏览器都理应遵守的公共规范。例如,某个浏览器认为,根据它的安全模式,把某个特定的URL传给外部程序或者存储/读取硬盘上某些类型的数据是安全的。对这类假设,几乎总有某个浏览器完全不认可,并且指望其他浏览器会按自己的规矩办事。而每个厂商也都希望把手伸得尽量长,所以往往会在未告知用户也未得到用户许可的情况下,强行用自家浏览器打开网页。例如,Firefox通过在其他浏览器里注册 firefoxurl: 协议,使得网页强行在它的浏览器里打开;微软则在Firefox里安装自己的.NET网关插件;而Chrome的做法和Firefox如出一辙,它会在IE浏览器里注册cf:协议。
注意:在这种混乱的交互中,指责任意一方都属徒劳。最近有一件和 firefoxurl:协议有关的漏洞,微软和信息安全圈里半数的人在指责Mozilla,而Mozilla和另一半的专家则怪罪于微软7。谁对谁错其实并不重要:反正结果还是一团乱麻。
另一个紧密相关的问题就是,即使浏览器的安全机制表面上看来很相似,实际上却并不兼容,这种情况在Web出现之前很少发生。如果各家浏览器安全模型是不同的,那么某条Web应用开发规范对其中一种浏览器可能是合理的,但对另一种却可能完全不适用且会产生误导。实际上,哪怕一些非常基本的任务,如打开一个用户提供的纯文本文件,在某些浏览器里也无法安全地实现。而这些问题程序开发人员往往意识不到,除非他们正好使用了这种受影响的浏览器—即使这样,也往往需要等他们踩上地雷才会意识得到。
最后,本节描述的问题在安全缺陷分类学里会归到一个很吓人的全新类别里:“无法描述又未被记录的各种问题”,但这类问题实际上随处可见。