W#eb 的困境(全)
原文:The Tangled Web
译者:飞龙
协议:CC BY-NC-SA 4.0
前言
只是在十五年前,Web 既简单又无关紧要:一种奇特的机制,允许少数学生和一些社交障碍、地下室居住的极客访问彼此的科学、宠物或诗歌主页。今天,它是编写复杂、交互式应用的优选平台(从邮件客户端到图像编辑器到计算机游戏),并且是一种覆盖全球数亿普通用户的媒介。它也是商业的重要工具,重要性足以在 1999 年至 2001 年的互联网泡沫破裂时被归因于引发经济衰退。
这种从默默无闻到无处不在的进步速度之快,即使是按照我们今天信息时代习惯的标准来看也是惊人的——其上升速度带来了一个意想不到的问题。万维网的设计缺陷和实施不足是那种从未渴望达到当前地位、从未有机会停下来回顾以前错误的技术。由此产生的问题迅速成为今天数据安全最重大和最普遍的威胁之一:事实证明,人们应用于充满跳舞仓鼠的灰黑色主页的协议设计标准,并不一定适用于每年处理数百万张信用卡交易的在线商店。
当回顾过去十年时,难以避免地会感到些许失望:迄今为止,几乎所有值得注意的在线应用在 Web 早期阶段都不得不为节省的代价付出代价。嘿,xssed.com,一个专注于跟踪与 Web 相关的安全漏洞的狭窄子集的网站,在运营大约三年后积累了约 50,000 条条目。然而,浏览器供应商对此大多无动于衷,而安全社区本身也几乎没有提供关于如何应对这种普遍痛苦的见解或建议。相反,许多安全专家坚持构建复杂的漏洞分类法,并习惯性地进行模糊的手腕扭动,关于这种混乱的所谓原因。
问题的一部分在于,这些专家长期以来一直对整个 Web 安全问题不屑一顾,无法理解其本质。他们迅速将 Web 安全漏洞标记为困惑的副手问题^([1])或其他三十年前在贸易杂志上概述的吸引人的标签的微不足道的表现。他们为什么要关心 Web 安全呢?与传统的系统妥协漏洞的严重性相比,在无聊的宠物主题主页上注入的令人厌恶的评论有什么影响?
回顾过去,我相当确信我们大多数人都忍住了。不仅互联网的重要性远远超出了最初的预期,而且我们没有注意到一些基本特征,这些特征使它远远超出了我们的舒适区。毕竟,即使是设计得最好、最彻底审计过的网络应用程序,其问题也比非网络应用程序多得多,而且出现频率也更高。
我们都犯了错误,现在是忏悔的时候了。为了忏悔,网络迷踪试图迈出一小步,朝着急需的正常化前进,因此,它可能是第一本提供对网络应用程序安全领域当前状况的系统性和彻底分析的作品。在这个过程中,它旨在揭示我们——安全工程师、网络开发人员和用户——每天都要面对的安全挑战的独特性。
本书的设计布局集中在探索一些最突出、最高级别的浏览器构建块和由此产生的各种与安全相关的话题。我采取这种方法,因为它似乎比简单地使用任意选择的分类法(许多其他信息安全书籍中见到的做法)更有信息量和直观性。我也希望这种方法会使网络迷踪成为一本更好的读物。
对于寻求快速答案的读者,我决定在许多章节的末尾包含快速工程速查表。这些速查表概述了处理网络应用程序设计中遇到的一些最常见问题的合理方法。此外,本书的最后一部分提供了一个快速词汇表,列出了可能遇到的知名实现漏洞。
致谢
《网络迷踪》的许多部分都源于为谷歌的《浏览器安全手册》所进行的研究,这是一份我在 2008 年整理的技术维基,并在 Creative Commons 许可下公开发布。您可以在code.google.com/p/browsersec/在线浏览原始文档。
我很幸运能加入一家允许我追求这个项目的公司,并且很高兴能与一群才华横溢的同行合作,他们为使《浏览器安全手册》更加有用和准确提供了出色的建议。特别是,感谢 Filipe Almeida、Drew Hintz、Marius Schilder 和 Parisa Tabriz 在他们的协助。
我也自豪地站在巨人的肩膀上。这本书在很大程度上归功于信息安全社区成员对浏览器安全的研究。特别感谢 Adam Barth、Collin Jackson、Chris Evans、Jesse Ruderman、Billy Rios 和 Eduardo Vela Nava 在这个领域的理解上的进步。
感谢大家——并继续做好工作。
^([1]) 混乱代理问题 是信息安全中的一个通用概念,用于指代一类广泛的设计或实现缺陷。这个术语描述了任何允许攻击者欺骗程序滥用某些“权限”(访问权限)以未预期的方式操纵资源的向量——假设这种未预期的方式对攻击者有利,无论这种利益如何定义。短语“混乱代理”通常由学术界的安全研究人员引用,但由于在某种程度上抽象考虑时,几乎所有现实世界的安全问题都可以归入这个类别,因此这个术语几乎毫无意义。
第一章. Web 应用程序世界中的安全
为了为书中稍后技术讨论提供适当的背景,首先解释安全工程领域试图实现的目标,然后概述为什么在这个其他方面已经得到充分研究的背景下,Web 应用程序值得特别对待似乎是明智的。那么,我们就这样做吗?
信息安全概述
表面上看,信息安全领域似乎是一个成熟、定义明确且成就显著的计算机科学分支。驻场专家们通过指出大量精心分类的安全漏洞来热情地宣称他们专业领域的重要性,这些漏洞不可避免地归因于缺乏安全知识的开发者,而他们的同行理论家则指出,所有这些问题本可以通过遵循今年最热门的安全方法来预防。一个商业产业在附近蓬勃发展,向所有人提供各种非约束性的安全保证,从普通计算机用户到大型国际公司。
然而,几十年来,我们基本上完全未能提出甚至最基础的、可用的框架来理解和评估现代软件的安全性。除了几篇杰出的论文和有限的实验之外,我们甚至没有可以分享的真实世界成功案例。重点几乎完全集中在反应性、次要的安全措施(如漏洞管理、恶意软件和攻击检测、沙箱等)以及可能是有选择地指出他人代码中的缺陷。令人沮丧的是,这个秘密被嫉妒地保守着:当涉及到使他人能够开发安全系统时,我们提供的价值远低于预期;现代网络也不例外。
让我们来看看确保信息安全的一些最吸引人的方法,并试图弄清楚为什么它们至今没有产生任何影响。
与形式化解决方案调情
建立安全程序的最明显工具可能是通过算法证明它们的行为完全正确。这是一个简单的假设,直观上应该属于可能性的范畴——那么为什么这种方法没有给我们带来太多成果呢?
好吧,让我们从形容词安全本身开始:它究竟要传达什么?安全似乎是一个直观的概念,但在计算的世界里,它逃避了所有试图有用地定义它的尝试。当然,我们可以用吸引人的方式重述问题,但你知道当从业者最常引用的定义之一是这样的:
一个系统是安全的,如果它的行为完全符合预期——并且不做任何其他事情。
这个定义简洁明了,模糊地概述了一个抽象的目标,但它很少讲述如何实现它。这是计算机科学,但在具体性方面,它与维克多·雨果的一首诗有着惊人的相似之处:
爱是灵魂的一部分,它与天堂之气的天体呼吸具有相同的本质。
可以争论说,从业者不是被要求提供细微定义的人,但即使向一群学者提出相同的问题,他们也会给出大致相同的答案。例如,以下常见的学术定义可以追溯到 20 世纪 60 年代发表的贝尔-拉帕杜拉安全模型。(这是大约十几次尝试形式化安全系统要求之一的尝试;^([86])它也是最引人注目的一次之一。)
一个系统只有在它从安全状态开始并且不能进入不安全状态时才是安全的。
沿着这些定义路线,当然基本上是真实的,可能作为论文或甚至几项政府拨款的基础。但在实践中,建立在这些基础上的模型注定对于广义、现实世界的软件工程几乎毫无用处,至少有三个原因:
-
为足够复杂的计算机系统定义理想行为是没有办法的。没有任何单一权威机构可以定义操作系统或网络浏览器的“预期方式”或“安全状态”应该是什么。用户、系统所有者、数据提供者、业务流程所有者以及软件和硬件供应商的利益往往差异很大,并且迅速变化——当利益相关者能够并且愿意清楚地、诚实地披露他们的利益时。更糟糕的是,社会学和博弈论表明,计算这些特定利益的简单总和可能实际上不会产生有益的结果。这个被称为“公地悲剧”的困境是许多关于互联网未来争议的核心。
-
愿望思维并不自动映射到形式约束。即使我们可以在某些情况下就系统的行为达成完美、高级的协议,但将这种期望形式化为允许的输入、程序状态和状态转换的集合,这对于几乎每种形式分析都是先决条件,这几乎是不可能的。简单地说,像“我不想让我的邮件被他人阅读”这样的直观概念并不特别适合转化为数学模型。几种异质的方法至少可以部分地形式化这样的模糊要求,但它们对软件工程过程施加了沉重的约束,并且往往导致规则集和模型比经过验证的算法本身更加复杂。而且,反过来,它们可能还需要证明它们自己的正确性……无穷无尽。
-
软件行为很难进行最终分析。以证明计算机程序将始终按照详细规范行事为目的的静态分析是一项没有人能够在复杂、现实场景中可信地证明的任务(尽管,正如你所期望的,在高度受限的环境或非常狭窄的目标下取得有限的成就是可能的)。许多案例在实践中可能无法解决(由于计算复杂性),甚至可能因为停机问题而完全不可解。()
比早期定义的模糊和无用性更令人沮丧的是,随着几十年的过去,在向更好的方向取得进展方面几乎没有任何进展。事实上,2001 年海军研究实验室发布的一篇学术论文对早期工作进行了回顾,并得出了一种更为随意、列举式的软件安全定义——该定义明确否认其不完善和不完整。()
一个系统如果能够充分保护其处理的信息免受未经授权的披露、修改和保留(也称为服务拒绝),则该系统是安全的。我们说“充分”,因为没有任何实际系统能够在没有任何限制的情况下实现这些目标;安全性本质上是相对的。
该论文还对早期努力进行了回顾性评估,以及为了保持这些模型的理论纯洁性所做出的不可接受牺牲:
经验表明,一方面,Bell-La Padula 模型的公理过于严格:它们禁止用户在实际应用中需要的操作。另一方面,受信任的主体,这是为了克服这些限制而提供的机制,限制不足。因此,开发人员不得不为每个系统中的受信任进程的期望行为制定专门的规范。
最后,无论引入了多少优雅、竞争的模型,所有试图使用算法基础来理解和评估现实世界软件安全性的尝试似乎注定会失败。这使开发人员和安全专家没有方法来做出权威、前瞻性的声明,关于所产生代码的质量。那么,还有什么其他选择呢?
进入风险管理
在没有正式保证和可证明的指标的情况下,考虑到现代社会所依赖的关键软件中存在令人担忧的安全漏洞,企业纷纷转向另一个吸引人的概念:风险管理。
风险管理的理念,成功应用于保险业(在金融世界中可能稍逊一筹),简单地说,就是系统所有者应该学会与无法以成本效益的方式解决的安全漏洞共存,并且通常应根据以下公式调整努力:
例如,根据这一教条,如果每年有一些不重要的工作站被破坏不会使公司损失超过 1000 美元的生产力,那么该组织应该只是为这种损失进行预算并继续前进,而不是花费比如说 10 万美元在额外的安全措施或应急和监控计划上以防止损失。根据风险管理教条,这笔钱最好用于隔离、保护和监控为所有客户生成账单记录的至关重要的主机的安全。
自然,优先考虑安全工作是很谨慎的。问题是,当风险管理严格按数字进行时,它对我们理解、控制和处理现实世界问题帮助甚微。相反,它引入了一个危险的谬误:结构性的不足几乎和充足一样好,而且资金不足的安全努力加上风险管理几乎和充分资金的安全工作一样好。
猜猜看?没有用。
-
在互联系统中,损失没有上限,也不与任何资产挂钩。严格的风险管理依赖于估计与资源妥协相关的典型和最大成本的能力。不幸的是,唯一的方法是忽略这样一个事实:许多最引人注目的安全漏洞——例如对 TJX^([4])或 Microsoft^([5])的攻击——都是从相对不重要且被忽视的入口点开始的。这些初始入侵很快升级,最终导致关键基础设施几乎完全被破坏,绕过了它们在路上的任何表面网络隔离。在典型的按数字进行的风险管理中,初始入口点被赋予较低的权重,因为它与其他节点相比价值较低。同样,通往更敏感资源的内部升级路径被轻视,因为它被低估为被滥用的可能性很低。然而,忽视这两者都证明是一个爆炸性的组合。
-
入侵的非货币成本很难通过健康系统的价值来抵消。用户信心丧失、业务连续性中断,以及诉讼前景和监管审查的风险,都难以进行有意义的保险。这些影响,至少在原则上,可以使公司或整个行业陷入困境,或者甚至完全崩溃,而对这些结果的任何表面评估几乎纯粹是投机性的。
-
现有数据可能并不代表未来的风险。与轻微碰撞的参与者不同,攻击者不会主动报告入侵,也不会详尽地记录造成的损害。除非入侵令人痛苦地明显(由于攻击者的粗心大意或破坏意图),否则它通常会被忽视。尽管行业范围内的自报数据可能可用,但根本无法可靠地判断其完整性或当前业务实践可能增加多少额外风险。
-
统计预测并不是个体结果的稳健预测器。仅仅因为平均而言,城市中的人被雷击的可能性比被熊袭击的可能性更大,并不意味着你应该在你的帽子上安装避雷针,然后泡在蜜里。在个体层面上,与特定组件相关的妥协的可能性在很大程度上是不相关的:安全事件几乎是必然发生的,但在数千个暴露的非平凡资源中,任何服务都可以被用作攻击向量——而且没有任何一种服务可能会看到足以在单个企业范围内使统计预测有意义的事件量。
通过分类学获得启迪
上文讨论的两种思想流派有一些共同之处:两者都假设可以将安全定义为可计算的目标的集合,并且由此产生的安全系统统一理论或可接受风险模型将优雅地向下渗透,从而产生实现应用设计完美所需的底层行动的最优集合。
一些从业者宣扬相反的方法,这种方法与其说是基于哲学,不如说是基于自然科学。这些从业者认为,就像信息时代的查尔斯·达尔文一样,通过收集足够的低级实验数据,我们将能够观察、重建和记录越来越复杂的法则,以便达到某种统一的计算安全模型。
这种世界观带来了像国土安全部资助的通用弱点枚举(CWE)这样的项目,该项目的目标,用组织自己的话说,是开发一个统一的“漏洞理论”;“改进软件缺陷的研究、建模和分类”;以及“提供一个共同的语言来讨论、发现和处理软件安全漏洞的原因。”一个典型的、令人愉快的巴洛克式分类学例子可能是这个:
信息或数据结构执行不当
未将数据净化到不同的平面
资源标识符控制不当
对可执行内容中的文件和其他资源名称过滤不足
今天,CWE 词典中有大约 800 个名称,其中大多数与这里引用的名称一样,具有促进对话的能力。
自然主义思想的一个略有不同的学派在诸如通用漏洞评分系统(CVSS)等项目中表现出来,这是一个由企业支持的协作项目,旨在通过一组基本、机器可读的参数严格量化已知的安全问题。一个现实世界的漏洞描述符示例可能是这样的:
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 和相关项目服务于崇高的目标,例如为大型组织实施的一些安全流程带来更可管理的维度。然而,没有一个产生了关于安全软件的伟大理论,我怀疑这样的框架在眼前。
向实用方法迈进
所有迹象都表明,目前安全问题在很大程度上是一个非算法问题。行业可以理解地不愿意公开接受这一观点,因为这暗示了没有银弹解决方案可以宣扬(或者更好的是,商业化);然而,当压力足够大时,最终安全领域的每个人都会退回到一套基本的、经验性的食谱。这些食谱与许多商业模式深深不兼容,但它们是我们迄今为止真正有效的方法。它们如下:
-
从(最好是别人的)错误中学习。系统应该被设计成预防已知的错误类别。在没有自动(或者甚至只是优雅)解决方案的情况下,这个目标最好通过提供持续的设计指导来实现,确保开发者知道可能出错的地方,并给他们提供以最简单的方式执行可能出错的任务的工具。
-
开发用于检测和纠正问题的工具。安全缺陷通常在恶意方发现之前没有明显的副作用:一个相当昂贵的反馈循环。为了解决这个问题,我们创建了安全质量保证(QA)工具来验证实施情况,并定期进行审计以检测偶然的错误(或系统性的工程缺陷)。
-
计划让一切都被破坏。历史告诉我们,尽管我们尽了最大的努力来预防,但重大事件仍会发生。实施适当的组件分离、访问控制、数据冗余、监控和响应程序非常重要,这样服务所有者可以在一个最初微不足道的小故障变成圣经般规模的灾难之前对事件做出反应。
在所有情况下,所有信息安全人员都需要大量的耐心、创造力和真正的技术专长。
自然地,即使这样简单、常识性的规则——本质上是一种基本的工程严谨性——也常常被包装成口号,大量使用各种缩写(例如 CIA:保密性、完整性、可用性),然后被称为“方法论”。通常,这些方法论只是试图将安全行业最令人沮丧的失败之一作为另一个成功故事来推销,最终向易受骗的客户出售另一种万能产品或认证。但尽管有相反的声明,这样的产品并不能替代街头智慧和技术专长——至少不是在今天。
在任何情况下,在这本书的剩余部分,我将避免尝试建立或重新使用上述任何宏伟的哲学框架,而是满足于适量的反智主义。我将回顾现代浏览器暴露的表面,讨论如何安全地使用可用的工具,哪些 Web 部分通常被误解,以及当事情变得糟糕时如何控制附带损害。
这基本上是我能想到的关于安全工程的最佳看法。
^([2]) 这句话最初归功于伊万·阿尔塞,一位著名的漏洞猎人,大约在 2000 年左右;从那时起,它被 Crispin Cowan、Michael Howard、Anton Chuvakin 和其他众多安全专家所使用。
^([3]) 1936 年,艾伦·图灵表明(略有引申)不可能设计出一个算法,可以一般性地决定其他算法的结果。当然,有些算法可以通过进行特定案例的证明而被决定,但并非所有算法都可以。
^([4]) 大约在 2006 年,一些入侵者,据称由阿尔伯特·冈萨雷斯领导,攻击了一个零售场所未加密的无线网络,并随后进入了零售巨头的企业网络。他们复制了约 4600 万客户的信用卡数据,以及约 45 万人的社会保障号码、家庭地址等。与攻击有关的 11 人被起诉,其中一人自杀。
^([5]) 微软正式未公开且平淡无奇的演示文稿《针对和保护微软内部网络威胁》概述了 2003 年的一次攻击,该攻击始于一名工程师的家庭工作站被入侵,该工作站与公司内部保持了长期的 VPN 会话。随后进行了有计划的升级尝试,最终攻击者获得了对内部源代码存储库的访问权限,并泄露了数据。至少对公众来说,肇事者仍然是个谜。
网络简史
互联网一直受到大量令人困惑的安全问题的困扰,这些问题种类繁多。当然,其中一些问题可以归因于特定客户端或服务器实现中的一时疏忽,但许多问题是由于任性的、通常是任意的设计决策造成的,这些决策决定了基本机制在浏览器端的操作和协同工作方式。
我们的帝国建立在摇摇欲坠的基础上——但为什么呢?或许是因为简单的短视:毕竟,在那些天真无邪的日子里,谁会预测到当代网络的风险以及今天大规模安全攻击背后的经济激励呢?
不幸的是,虽然这种解释对于像 SMTP 或 DNS 这样真正古老的机制是有道理的,但它在这里并不完全适用:互联网相对较年轻,并在与我们今天所看到的环境不太不同的环境中形成了目前的形状。相反,这个谜题的关键可能在于相关技术演变过程中的动荡和异常方式。
因此,请原谅我再次短暂地偏离主题,当我们回到根源时。网络的史前时期相当平凡,但仍值得仔细研究。
石器时代的故事:1945 年至 1994 年
计算机历史学家经常引用一个假设的桌面大小的设备,称为 Memex,作为最早的化石记录之一,这是 1945 年由范内瓦·布什提出的。^([88) Memex 旨在通过一种类似于现代书签和超链接的技术,使创建、注释和跟踪缩微胶片中的跨文档链接成为可能。布什大胆地预测,这种简单的功能将彻底改变知识管理和数据检索领域(有趣的是,这种说法直到 20 世纪 90 年代初偶尔还会被嘲笑为无知的和天真的)。然而,任何有用的设计实现在当时都遥不可及,因此,除了未来主义的愿景之外,直到基于晶体管的计算机成为主流之前,并没有发生太多的事情。
下一个有形的里程碑是在 20 世纪 60 年代,IBM 推出了通用标记语言(GML),它允许使用机器可读的指令对文档进行注释,指示每块文本的功能,实际上是在说“这是一个标题”,“这是一个项目编号列表”,等等。在接下来的 20 年左右,GML(最初仅由少数 IBM 文本编辑器在笨重的主机计算机上使用)成为了标准通用标记语言(SGML)的基础,这是一种更通用、更灵活的语言,它用熟悉的尖括号语法取代了笨拙的冒号和句点语法。
当 GML 发展成 SGML 时,计算机变得更加强大和用户友好。几位研究人员开始尝试布什的交叉链接概念,将其应用于基于计算机的文档存储和检索,试图确定是否有可能基于某种关键字来交叉引用大量文档。一些冒险的公司和大学追求了开创性的项目,如 ENQUIRE、NLS 和 Xanadu,但大多数未能产生持久的影响。关于这些项目的常见抱怨围绕着它们的有限实用性、过度复杂性和较差的可扩展性。
到了十年末,两位研究人员,蒂姆·伯纳斯-李和丹·康诺利,开始致力于解决跨领域引用挑战的新方法——该方法侧重于简单性。他们通过起草超文本标记语言(HTML),一个 SGML 的基本后代,专门设计用于通过超链接和基本格式化标注文档,启动了这个项目。他们随后继续开发超文本传输协议(HTTP),这是一个极其基本的、专门用于使用现有的互联网协议(IP)地址、域名和文件路径概念访问 HTML 资源的方案。他们工作的成果,在 1991 年和 1993 年之间,是蒂姆·伯纳斯-李的世界万维网(图 1-1
图 1-1.蒂姆·伯纳斯-李的世界万维网
对于许多人来说,HTTP 和 HTML 的设计似乎是对竞争项目更高目标的重大倒退。毕竟,许多早期的努力都吹嘘数据库集成、安全性和数字版权管理,或者协作编辑和发布;事实上,甚至伯纳斯-李自己的项目 ENQUIRE,似乎也比他目前的工作更有雄心。然而,由于其低门槛要求、即时可用性和不受限制的可扩展性(这恰好与强大且价格合理的计算机的问世以及互联网的扩张相吻合),这个不起眼的万维网项目意外地成为了一项热门。
好吧,好吧,按照 1990 年代中期的标准,这显然是一个“热门”产品。很快,互联网上就有数十个网络服务器在运行。到 1993 年,HTTP 流量占美国国家科学基金会骨干网络的 0.1%带宽。同年,也见证了 Mosaic 的到来,这是第一个相对流行且复杂的网络浏览器,由伊利诺伊大学开发。Mosaic 通过添加将图像嵌入 HTML 文档和通过表单提交用户数据等功能,扩展了原始万维网代码,从而为今天的交互式、多媒体应用铺平了道路。
Mosaic 让浏览变得更加美观,有助于推动消费者对网络的采用。到 1990 年代中期,它成为其他两个浏览器的基石:Mosaic Netscape(后来更名为 Netscape Navigator)和 Spyglass Mosaic(最终被微软收购并更名为 Internet Explorer)。还出现了一些竞争的非 Mosaic 引擎,包括 Opera 和几个基于文本的浏览器(如 Lynx 和 w3m)。第一个搜索引擎、在线报纸和约会网站紧随其后。
第一次浏览器战争:1995 年至 1999 年
到 1990 年代中期,很明显,网络将长期存在,并且用户愿意放弃许多旧技术,转而选择新的竞争者。在那个时期,微软,这个之前对互联网反应迟缓的桌面软件巨头,开始感到不舒服,并开始为其自己的浏览器分配大量的工程资源,最终在 1996 年将其捆绑到 Windows 操作系统上.^([6])微软的行动引发了一个被俗称为“浏览器战争”的时期。
浏览器供应商之间由此产生的军备竞赛,其特点是在竞争产品中迅速发展和部署新功能,这一趋势往往违背了所有试图标准化或甚至正确记录所有新增代码的尝试。核心 HTML 调整从愚蠢的(使文本闪烁的能力,这是网景的一个发明,成为笑柄,也是错误网络设计的明显标志)到显著的,例如改变字体或在外部框架中嵌入外部文档的能力。供应商发布了包含嵌入式编程语言(如 JavaScript 和 Visual Basic)、用于在用户机器上执行平台无关的 Java 或 Flash 小程序的插件,以及如 cookies 之类的有用但棘手的 HTTP 扩展。只有有限程度的表面兼容性,有时受到专利和商标的限制,^([7])才能得到维持。
随着网络的日益庞大和多样化,一种狡猾的疾病在浏览器引擎中以容错为幌子传播开来。起初,这种推理似乎完全合理:如果浏览器 A 能够显示一个设计糟糕、损坏的页面,而浏览器 B 拒绝显示(无论什么原因),用户必然会将浏览器 B 的失败视为该产品的缺陷,并成群结队地涌向看似更强大的客户端,即浏览器 A。为了确保他们的浏览器能够正确显示几乎任何网页,工程师们开发了越来越复杂且未经文档记录的启发式方法,旨在猜测马虎的网站管理员的本意,在这个过程中往往牺牲了安全性和偶尔甚至兼容性。不幸的是,每一次这样的改变都进一步强化了不良的网页设计实践^([8]),并迫使剩余的厂商跟上混乱的局面以保持浮出水面。当然,缺乏足够详细、最新的标准并没有帮助遏制这种疾病的传播。
1994 年,为了减轻工程无序的传播并管理 HTML 的扩展,蒂姆·伯纳斯-李和少数几家企业赞助商创建了万维网联盟(W3C)。不幸的是,对于这个组织来说,在很长的一段时间里,它只能无助地旁观格式被随意扩展和调整。W3C 对 HTML 2.0 和 HTML 3.2 的最初工作仅仅试图跟上现状,结果产生了半成品规范,这些规范在发布给公众时已经很大程度上过时了。该联盟还试图从事一些新颖且相当周密考虑的项目,例如层叠样式表(Cascading Style Sheets),但很难获得厂商的支持。
其他旨在标准化或改进已实施机制的尝试,最显著的是 HTTP 和 JavaScript,是由欧洲计算机制造商协会(ECMA)、国际标准化组织(ISO)和互联网工程任务组(IETF)等其他机构推动的。遗憾的是,这些努力很少同步进行,有些讨论和设计决策被厂商或其他不太关心技术长期前景的利益相关者主导。结果是产生了一系列死标准、相互矛盾的建议,以及几个令人担忧的例子,展示了原本设计精良的协议之间有害的交叉交互——当我们在第九章(ch09.html "第九章。内容隔离逻辑")讨论各种内容隔离机制时,这个问题将尤为明显。
无聊时期:2000 至 2003 年
随着努力控制网络的努力失败,微软凭借其操作系统捆绑策略的统治地位日益增强。到新世纪的开始,网景导航器正在走向尽头,而互联网浏览器占据了令人印象深刻的 80%市场份额——这个数字大约与五年前网景所持有的市场份额相当。在双方阵营,安全和互操作性是特征战争中最显著的受害者,但现在人们可以希望战斗已经结束,开发者可以放下分歧,共同努力修复混乱。
相反,主导地位滋生了自满:微软在出色地实现了其目标后,几乎没有动力在浏览器上大量投资。尽管从第五版开始,互联网浏览器(IE)的主要版本每年都会发布,但第六版的出现却花了两年时间,然后又用了整整五年时间才将互联网浏览器 6 更新到互联网浏览器 7。没有微软的兴趣,其他供应商几乎没有能力进行颠覆性的改变;大多数网站都不愿意进行改进,这些改进只能为极少数访客带来好处。
从积极的一面来看,浏览器开发的放缓使得万维网联盟(W3C)能够赶上并仔细探索一些关于网络未来的新概念。在 2000 年左右完成的新举措包括 HTML 4(一种清理过的语言,废弃或禁止了早期版本中许多冗余或政治上不正确的特性)和 XHTML 1.1(一种严格且结构良好的基于 XML 的格式,更容易明确解析,不允许使用专有启发式方法)。联盟还对 JavaScript 的文档对象模型和层叠样式表(CSS)进行了重大改进。遗憾的是,到世纪末,网络已经足够成熟,可以随意纠正一些旧时代的错误,但还太年轻,安全问题并不紧迫,也不够明显,以至于所有人都看得见。语法得到了改进,标签被废弃,编写了验证器,甲板椅被重新排列,但浏览器基本上还是老样子:臃肿、古怪、不可预测。
但不久之后,发生了一件有趣的事情:微软向世界提供了一个看似微不足道、名为XMLHttpRequest的专有 API,名称令人困惑。这个微不足道的机制原本意义不大,仅仅是为了解决微软 Outlook 网络版的一个小问题。但XMLHttpRequest最终证明意义非凡,它允许客户端 JavaScript 与服务器之间进行大量不受约束的异步 HTTP 通信,而无需进行耗时且破坏性的页面转换。在这个过程中,该 API 促进了后来被称为Web 2.0的出现——一系列复杂、响应速度异常快、基于浏览器的应用程序,使用户能够操作复杂的数据集、协作和发布内容等,在这个过程中侵入了“真实”的、可安装的客户端软件的神圣领域。可以理解,这引起了很大的轰动。
Web 2.0 和第二次浏览器战争:2004 年及以后
XMLHttpRequest,结合互联网的普及和浏览器的广泛可用性,推动了网络走向一些新的、令人兴奋的领域——并带来了一系列影响个人用户和企业的安全漏洞。大约在 2002 年,蠕虫和浏览器漏洞成为媒体上经常讨论的主题。凭借其市场主导地位和相对轻视的安全态度,微软承担了大部分由此产生的公关压力。公司轻描淡写地贬低问题,但这一趋势最终营造了一种有利于小规模反抗的氛围。
2004 年,浏览器战争中出现了一位新的竞争者:Mozilla Firefox(Netscape Navigator 的社区支持后代)发起了攻势,特别针对 Internet Explorer 糟糕的安全记录和标准合规性。Firefox 受到了 IT 记者和安全专家的赞扬,迅速占据了 20%的市场份额。尽管这位新来者很快证明其同样受到安全漏洞的困扰,但其开源性质和无需迎合固执的 corporate users 的自由使得开发者能够更快地修复问题。
备注
为什么供应商会如此激烈地竞争呢?严格来说,在浏览器世界中拥有特定的市场份额并不能赚钱。然而,专家们长期以来一直猜测,这关乎权力:通过捆绑、推广或降低某些在线服务(甚至像默认搜索引擎这样简单的东西),控制浏览器的人就能在很大程度上控制互联网。
除了 Firefox 之外,微软还有其他原因感到不安。其旗舰产品 Windows 操作系统越来越多地被用作浏览器的(可消耗的?)发射台,越来越多的应用程序(从文档编辑到游戏)转移到网络上。这可不是什么好事。
这些事实,加上苹果 Safari 浏览器的突然出现,以及可能在智能手机领域取得进展的 Opera,肯定让微软的高管们感到困惑。他们在 20 世纪 90 年代错过了互联网重要性的早期迹象;当然,他们不能犯同样的错误。微软再次加大了对 Internet Explorer 开发的力度,迅速发布了大幅改进且相对更安全的版本 7、8 和 9。
竞争对手通过新增功能和声称更好的(尽管仍然表面化)标准合规性、更安全的浏览和性能改进来反击。在XMLHttpRequest意外成功面前措手不及,并且很快忘记了过去的教训,供应商们也大胆地尝试新想法,有时甚至单方面推出半成品或有些不安全的方案,如 Firefox 中的globalStorage或 Internet Explorer 中的httponly cookies,只是为了试试运气。
为了进一步复杂化情况,由于与 W3C 在创意上的分歧,一群贡献者创建了一个全新的标准机构,称为 Web 超文本应用技术工作组(WHATWG)。WHATWG 在 HTML5 的发展中发挥了关键作用,这是现有标准的第一次全面和注重安全的修订,但据报道,由于专利政策争议,它被微软所排斥。
在其大部分历史中,网络都享有一种独特、高度竞争、快速、往往过于政治化和反复无常的发展模式,没有统一的愿景和一套固定的安全原则。这种状况对浏览器当前的运作方式以及浏览器处理用户数据的安全性留下了深刻的印记。
很有可能,这种情况不会很快改变。
^([6]) 有趣的是,这个决定最终证明是非常有争议的。一方面,可以认为,通过这样做,微软极大地促进了互联网的普及。另一方面,它削弱了竞争对手浏览器的地位,可能被视为反竞争。最终,这种策略导致了一系列关于公司可能滥用垄断地位的长期法律斗争,例如美国诉微软。
^([7]) 例如,微软不想与 Sun 协商 JavaScript(这种语言之所以这样命名是为了促销目的,而不是因为它与 Java 有任何关系)的商标许可,因此它选择了将其几乎但并不完全相同的版本命名为“JScript”。微软的官方文档仍然使用这个名字。
^([8]) 一些被误导且最终致命的浏览器功能例子是内容识别和字符集嗅探机制,这两者都将在第十三章(内容识别机制)中讨论。
威胁的演变
显然,网络浏览器及其相关的文档格式和通信协议是以一种非常规的方式演化的。这种演化可能解释了我们看到的大量安全问题,但仅凭这一点几乎无法证明这些问题是独特或值得注意的。为了总结本章,让我们快速看一下最常见的在线安全威胁背后的非常特殊的特点,并探讨为什么这些威胁在互联网出现之前并没有特别好的对应物。
用户作为安全漏洞
也许网络浏览器最引人注目(而且完全是技术性的)的特性是,使用它们的大多数人都是技能不足的。当然,非熟练用户自从计算机诞生以来一直是一个有趣且边缘的问题。但互联网的普及,加上其惊人的低门槛,意味着我们面临一个新的敌人:大多数用户对安全的了解不足,无法保持安全。
很长一段时间以来,从事通用软件开发的工程师们对于用户所需的最基本的计算机技能水平做出了看似随意的假设。这些假设中的大多数都没有产生严重的后果;例如,文本编辑器的错误使用通常对系统安全的影响微乎其微。能力不足的用户根本无法完成他们的工作,这是一个美妙的自纠正问题。
然而,网络浏览器并不是这样工作的。与某些复杂的软件不同,它们可以被几乎没有计算机培训的人成功使用,这些人甚至可能不知道如何使用文本编辑器。但与此同时,只有对计算机技术和相关术语有相当了解的人才能安全地操作浏览器,包括诸如公钥基础设施等主题。不用说,大多数今天最成功的网络应用的用户都没有达到这个先决条件。
浏览器仍然看起来和感觉像是被极客们为极客们设计的,包括偶尔出现的晦涩和不一致的错误信息、复杂的配置设置,以及令人困惑的安全警告和提示。2006 年,加州大学伯克利分校和哈佛大学的研究人员进行的一项显著研究表明,普通用户几乎普遍对那些对开发者来说肯定是有意义的信号视而不见,例如状态栏中是否存在锁形图标。^([[89]) 在另一项研究中,斯坦福大学和微软的研究人员在对现代“绿色 URL 栏”安全指示符的影响进行了考察后,得出了相似的结论。该机制旨在提供一种更直观的替代锁形图标的方案,但实际上它通过教会观众信任特定色调的绿色,无论这种颜色出现在哪里,都使得欺骗用户变得更加容易。^([[90])
一些专家认为,普通用户的无能不是软件供应商的过错,因此根本不是工程问题。其他人指出,在创建如此易于访问和广泛分布的软件时,强迫用户做出依赖于技术专长(而这些技术专长原本不是操作程序所必需的)的安全关键决策是不负责任的。然而,仅仅责怪浏览器供应商也是不公平的:整个计算行业在这个领域没有强大的答案,而且关于如何以安全的方式设计类似复杂用户界面(UI)的研究非常少。毕竟,我们连 ATM 机都很难做到完全正确。
云端,或集体生活的乐趣
网络的另一个奇特特征是无关应用程序及其处理的数据之间戏剧性地低估的分离。
在过去 15 年左右几乎所有个人计算机遵循的传统模型中,高级数据对象(文档)、用户级代码(应用程序)和仲裁所有跨应用程序通信以及硬件输入/输出(I/O)的操作系统内核之间有非常清晰的边界,并在应用程序出现异常时强制执行可配置的安全规则。这些边界得到了很好的研究,并且对于构建实用的安全方案很有用。在你文本编辑器中打开的文件不太可能窃取你的电子邮件,除非非常不幸的实现缺陷同时破坏了所有这些分离层。
在浏览器世界中,这种分离几乎不存在:文档和代码作为同一混合的 HTML 块的一部分存在,完全无关的应用程序之间的隔离最多是部分的(所有网站名义上共享一个全局 JavaScript 环境),并且网站之间的大多数交互都是隐式允许的,几乎没有灵活的、浏览器级别的安全仲裁框架。
在某种意义上,这种模式让人联想到 CP/M、DOS 以及其他主要非多任务操作系统,它们没有强大的内存保护、CPU 抢占或多用户功能。明显的区别是,很少有用户依赖于这些早期的操作系统来同时运行多个不受信任、由攻击者提供的应用程序,因此没有特别的理由感到恐慌。
最后,看似不可能的文本文件窃取你的电子邮件的情况,实际上在网络上是一个令人沮丧的常见模式。几乎所有的网络应用程序都必须大量补偿未经请求、恶意的跨域访问,并采取繁琐的步骤来保持代码和显示数据之间至少有一些分离。而且迟早,几乎所有的网络应用程序都会失败。与内容相关的安全问题,如跨站脚本或跨站请求伪造,非常普遍,在专门的、分区的客户端架构中几乎没有对应的例子。
视野的非收敛
幸运的是,浏览器安全领域并非完全无望,尽管网络应用程序之间的分离有限,但一些选择性的安全机制为最明显的攻击提供了基本的保护。但这又引出了使网络成为一个有趣主题的另一个特征:没有共享的、整体的安全模型可以把握和遵循。请注意,我们不是在寻找世界和平的宏伟愿景,而只是寻找一套适用于大多数,如果不是所有相关安全逻辑的灵活范式。例如,在 Unix 世界中,rwx 用户/组权限模型就是这样一种强大的统一主题。但在浏览器领域呢?
在浏览器领域,一种称为同源策略的机制可以被认为是一个核心安全范式的候选,但只有直到人们意识到它只控制了跨域交互的一个非常小的子集。抛开这个细节不谈,即使在它的范围内,它也有不少于七种不同的类型,每种类型都在应用程序之间以略微不同的位置设置安全边界。^([9]) 与同源模型无关的数十种其他机制控制着浏览器行为的其他关键方面(本质上实现了每个作者认为当天最佳的安全控制方法)。
事实上,数百个小巧的漏洞并不一定能够组合成一个合格的安全作品。这种异常缺乏完整性使得甚至很难决定一个应用程序的结束和另一个应用程序的开始。考虑到这一现实,一个人如何评估攻击面、授予或撤销权限,或者完成几乎任何其他以安全为导向的任务?太经常了,“只靠交叉手指”是我们能给出的最佳回应。
奇怪的是,许多出于好意的尝试通过定义新的安全控制措施来提高安全性,反而使问题变得更糟。许多这样的方案创建了新的安全边界,为了追求优雅,它们并不完美地与现有的复杂边界对齐。当新的控制措施更加细粒度时,它们很可能会被遗留机制所削弱,从而产生一种虚假的安全感;而当它们更加粗粒度时,它们可能会消除网络目前所依赖的一些微妙的安全保证。(亚当·巴思和柯林·杰克逊在他们学术工作中探讨了浏览器安全策略之间破坏性干扰的话题。)^([91])
跨浏览器交互:失败的协同
由多个不同的软件产品组成的生态系统的整体易受攻击性可能预期等于每个应用程序贡献的缺陷的简单总和。在某些情况下,由此产生的暴露可能更少(多样性提高了弹性),但人们不会期望它更多。
Web 再次成为规则的例外。安全社区已经发现了一系列问题,这些问题不能归因于任何特定的代码片段,但当各种浏览器尝试相互交互时,它们会成为一个真正的威胁。无法轻易将责任归咎于任何特定产品:它们都在做自己的事情,唯一的问题是没有人费心为它们定义一个共同的行为准则。
例如,一个浏览器可能认为,根据其自身的安全模型,将某些 URL 传递给外部应用程序或从磁盘存储或读取某些类型的数据是安全的。对于这样的假设,很可能至少存在一个浏览器强烈反对,期望其他方遵循其规则。由于供应商希望打开市场并尝试在用户知情同意的情况下动态切换网页到他们的浏览器,这些问题可利用性大大加剧。例如,Firefox 允许通过注册firefoxurl:协议在浏览器中打开页面;Microsoft 在 Firefox 中安装了自己的.NET 网关插件;Chrome 通过名为cf:的协议以同样的方式对 Internet Explorer 进行操作。
注意
尤其是在这种交互的情况下,将责任归咎于任何特定的一方都是徒劳的。在最近一个与firefoxurl:相关的错误案例中,Microsoft 和一半的信息安全社区指责 Mozilla,而 Mozilla 和另一半专家则指责 Microsoft.^([92]) 谁对谁错并不重要:结果仍然是一个非常真实的混乱局面。
另一组与之密切相关的问题(在 Web 出现之前几乎闻所未闻)是每个浏览器中实施的表面上类似的安全机制的不兼容性。当安全模型不同时,一个产品中的良好网络应用程序工程实践可能在另一个产品中不足且误导。实际上,一些基本任务,如提供用户提供的明文文件,在某些浏览器中根本无法安全实现。然而,除非开发者在受影响的浏览器中工作,否则这一事实对他们来说可能并不明显——即使如此,他们也需要恰好击中正确的位置。
最后,本节中概述的所有特征共同导致了一种全新的安全漏洞类别,一个分类学爱好者可能会称之为“未能考虑未记录的多样性”。这个类别在今天非常丰富。
客户端-服务器分界的崩溃
信息安全研究人员喜欢静态、明确分配的角色世界,这在映射复杂世界中安全交互时是一个熟悉的知识点。例如,我们谈论 Alice 和 Bob,两位诚实、勤奋的用户想要进行通信,以及 Mallory,一个狡猾的攻击者,他想要对他们下手。然后我们有客户端软件(本质上是无知的、有时是恶意 I/O 终端,随意请求服务)和谦逊的服务器,小心翼翼地满足客户端的愿望。开发者学习这些角色并参与其中,在这个过程中构建了相当可理解和可测试的网络计算环境。
互联网最初是一个典型的客户端-服务器架构的例子,但客户端和服务器之间的功能边界很快就被侵蚀了。罪魁祸首是 JavaScript,这种语言为 HTTP 服务器提供了一种将应用程序逻辑委托给浏览器(“客户端”)的方法,并给了它们两个非常有说服力的理由去做这件事。首先,这样的转变通常会导致用户界面更加响应迅速,因为服务器不需要同步参与每个可想象的微小 UI 状态变化。其次,当全球各地的个人工作站共同分担大量工作时,服务器端的 CPU 和内存需求(以及因此提供服务的成本)可以大幅降低。
客户端-服务器扩散过程开始时相当无辜,但很快,第一个安全机制也跟随到了客户端,以及其他所有平凡的功能。例如,当数据仅在客户端机器上通过 JavaScript 动态渲染时,在服务器端仔细清洗 HTML 有什么意义呢?
在某些应用程序中,这种趋势被推向了极端,最终服务器几乎只是一个哑存储设备,几乎所有解析、编辑、显示和配置任务都移到了浏览器本身。在这种设计中,甚至可以通过使用离线 Web 扩展(如 HTML5 持久存储)完全切断对服务器的依赖。
整个应用程序魔法发生位置的简单转变并不一定是件大事,但并非所有的安全责任都可以轻易地委托给客户端。例如,即使是在服务器作为哑存储的情况下,客户端也不能无差别地访问服务器上存储的其他用户的所有数据,也不能信任它们执行访问控制。最终,由于不希望将所有应用程序安全逻辑保留在服务器端,并且完全迁移到客户端又是不可能的,大多数应用程序最终占据了某种任意的中间地带,客户端和服务器组件之间没有明显的和逻辑上的职责分离。由此产生的不熟悉的设计和应用行为在优雅和纯洁的安全角色扮演世界中没有任何有用的对应物。
这种情况不仅仅导致了设计层面的混乱;它还导致了不可还原的复杂性。在传统的客户端-服务器模型中,具有明确指定的 API,人们可以很容易地评估服务器的行为,而不必查看客户端,反之亦然。此外,在每一个这些组件内部,可以轻松地隔离更小的功能块,并对它们的预期操作做出假设。在新模型中,结合 Web 上常见的模糊的、一次性的应用程序 API,这些分析工具以及由此产生的关于系统安全性的推理便利性,都被残酷地剥夺了。
标准化安全建模和测试协议的意外失败是另一个问题,这使得 Web 在信息安全领域占据了非常特殊——并且令人恐惧——的位置。
^(9]) 本书第二部分“浏览器安全特性”中讨论的主要七种类型包括 JavaScript DOM 访问的安全策略;XMLHttpRequest API;HTTP cookies;本地存储 API;以及 Flash、Silverlight 或 Java 等插件。
全球浏览器市场份额,2011 年 5 月
| 供应商 |
浏览器名称 |
市场份额 |
| Microsoft |
Internet Explorer 6 |
10% |
| Internet Explorer 7 |
7% |
|
| Internet Explorer 8 |
31% |
|
| Internet Explorer 9 |
4% |
|
| Mozilla |
Firefox 3 |
12% |
| Firefox 4+ |
10% |
|
| Google |
Chrome |
13% |
| Apple |
Safari |
7% |
| Opera Software |
Opera |
3% |
来源:数据来源于公开的 Net Applications 报告。^([93)]
第一部分。网络解剖学
本书的第一部分专注于支配网络浏览器操作的原理性概念,即使一切运转的协议、文档格式和编程语言。因为所有现代浏览器中使用的熟悉、用户可见的安全机制都与这些内部工作紧密相连,所以在深入森林之前,我们应该给予这些裸露的内部相当多的关注。
第二章。一切从 URL 开始
网络最显著的特征是被称为统一资源定位符(URL)的简单文本字符串。每个格式良好、完全合格的 URL 旨在最终确定地定位和唯一标识远程服务器上的单个资源(并在这样做的同时实现一些相关的辅助功能)。URL 语法是地址栏的基石,是每个浏览器中最重要用户界面(UI)安全指示器。
除了用于内容检索的真实 URL 之外,还有几类伪 URL使用类似的语法来提供方便地访问浏览器级功能,包括集成脚本引擎、几个特殊的文档渲染模式等。也许不出所料,这些伪 URL 操作可以对决定链接到它们的任何网站的安全性产生重大影响。
确定特定 URL 将被浏览器如何解释及其副作用的能力,是人类和 Web 应用尝试的最基本和最常见的安全任务之一,但它可能是一个有问题的任务。通用 URL 语法,由蒂姆·伯纳斯-李的工作,主要在 RFC 3986 中编码;^([94])其 Web 上的实际用途在 RFC 1738,^([95]) 2616,^([96])和其他几个不那么重要的标准中概述。这些文件非常详细,导致了一个相当复杂的解析模型,但它们并不足够精确,以至于在所有客户端软件中都能导致和谐、兼容的实现。此外,各个软件供应商出于自己的原因选择偏离规范。
让我们更详细地看看这个谦逊的 URL 在实际中是如何工作的。
统一资源定位符结构
图 2-1 显示了完全合格的绝对 URL的格式,它指定了访问特定资源所需的所有信息,并且不依赖于导航开始的任何位置。相比之下,相对 URL,例如../file.php?text=hello+world省略了一些信息,并且必须在与当前浏览上下文相关的基础 URL 的上下文中进行解释。
![绝对 URL 的结构]()
图 2-1. 绝对 URL 的结构
绝对 URL 的各个部分看起来很直观,但每个部分都伴随着一些陷阱,所以现在让我们来回顾一下。
方案名称
方案名称 是一个不区分大小写的字符串,以单个冒号结尾,表示用于检索资源的协议。有效 URL 方案的官方注册由 互联网数字分配机构 (IANA) 维护,该机构更广为人知的是其对于 IP 地址空间的管理。97] IANA 当前的有效方案名称列表包括数十个条目,如 http:、https: 和 ftp:;在实践中,更广泛的方案集合被常见的浏览器和第三方应用程序非正式地认可,其中一些方案具有特殊的网络安全后果。(特别值得注意的是几种伪 URL 类型,如 data: 或 javascript:,这些将在本章后面和本书的其余部分进行讨论。)
在进行任何进一步的解析之前,浏览器和 Web 应用程序需要区分完全限定的绝对 URL 和相对 URL。地址前存在有效方案的存在意味着这是关键的区别,如 RFC 1738 中定义:在一个符合规范的绝对 URL 中,只有字母数字“+”、“−”和“.”可以出现在必需的“:”之前。然而,在实践中,浏览器对此指导有一些偏差。所有浏览器都忽略了前导的新行和空白字符。Internet Explorer 忽略了 ASCII 码 0x01 到 0x1F 的非打印字符范围。Chrome 还跳过了 0x00,即空字符。大多数实现也忽略了方案名称中间的新行和制表符,而 Opera 接受字符串中的高位字符。
由于这些不兼容性,依赖于区分相对和绝对 URL 的应用程序必须保守地拒绝任何异常的语法——但正如我们很快就会发现的,即使这样也不够。
层次化 URL 的指示符
为了符合 RFC 1738 中规定的通用语法规则,每个绝对、层次化 URL 都必须包含固定字符串“//”,位于授权部分之前。如果该字符串缺失,该规范中剩余 URL 的格式和功能是未定义的,必须将其视为不透明的、方案特定的值。
注意
一个非层次化 URL 的例子是 mailto: 协议,用于指定电子邮件地址和可能的主题行(mailto:user@example.com?subject=Hello+world)。此类 URL 在传递给默认邮件客户端时不会进行任何进一步的解析尝试。
一个通用、分层的 URL 语法的概念在理论上是非常优雅的。它应该能够使应用程序在不知道特定方案如何工作的情况下提取关于地址的一些信息。例如,在没有预先设定的关于wacky-widget:协议的概念,仅通过应用通用 URL 语法的概念,浏览器可以决定example.com/test1/和wacky-widget://example.com/test2/引用的是同一个,可信任的远程主机。
很遗憾,该规范有一个有趣的缺陷:上述 RFC 没有提及当遇到方案已知为非分层但仍然出现“//”前缀的 URL 时,实现者应该做什么,反之亦然。实际上,RFC 1630 中提供的一个参考解析器实现中包含了一个无意中的漏洞,给后一类 URL 赋予了反直觉的含义。在几年后发布的 RFC 3986 中,作者尴尬地承认了这个缺陷,并允许实现者出于兼容性原因尝试解析这样的 URL。因此,许多浏览器以意想不到的方式解释了以下示例:
-
http:example.com/ 在 Firefox、Chrome 和 Safari 中,当没有完全限定的基本 URL 上下文存在时,这个地址可能被处理成与example.com/相同,当有有效的基本 URL 时,它被视为对名为example.com的目录的相对引用。
-
javascript://example.com/%0Aalert(1) 这个字符串在所有现代浏览器中被解释为有效的非分层伪 URL,并且 JavaScript alert(1)代码将被执行,显示一个简单的对话框窗口。
-
mailto://user@example.com Internet Explorer 接受这个 URL 作为对电子邮件地址的有效非分层引用;“//”部分被简单地跳过。其他浏览器持不同意见。
资源访问凭据
URL 的凭据部分是可选的。此位置可以指定一个用户名,也许还需要一个密码,这些信息可能是从服务器检索数据所必需的。这些凭据交换的方法不是抽象 URL 语法的组成部分,并且总是特定于协议。对于不支持认证的协议,带有凭据的 URL 的行为是未定义的。
当没有提供凭据时,浏览器将尝试匿名获取资源。在 HTTP 和几个其他协议的情况下,这意味着不发送任何认证数据;对于 FTP,这涉及到使用名为ftp的虚假密码登录到访客账户。
大多数浏览器接受除了一般 URL 部分分隔符之外几乎任何字符,在这个部分有两个例外:由于不明原因,Safari 拒绝了一组更广泛的字符,包括“<”,“>”,“{”,和“}”,而 Firefox 也拒绝换行符.^([10])
服务器地址
对于所有完全限定的分层 URL,服务器地址部分必须指定一个不区分大小写的 DNS 名称(例如 example.com),一个原始 IPv4 地址(例如 127.0.0.1),或一个方括号中的 IPv6 地址(例如 [0:0:0:0:0:0:0:1]),以指示托管请求资源的服务器位置。Firefox 也将接受方括号中的 IPv4 地址和主机名,但其他实现会立即拒绝它们。
虽然 RFC 只允许 IP 地址的规范表示法,但大多数应用程序使用的标准 C 库要宽松得多,接受混合八进制、十进制和十六进制表示的非规范 IPv4 地址,或者将某些或所有八位字节连接成一个单一整数。因此,以下选项被视为等效:
类似的轻松态度也可以在 DNS 名称中看到。理论上,DNS 标签需要符合一个非常窄的字符集(具体来说,是字母数字、“.”和“-”,如 RFC 1035 定义),但许多浏览器都会愉快地请求底层操作系统的解析器查找几乎任何内容,而操作系统通常也不会对此大惊小怪。主机名和传递给解析器的字符集的确切集合因客户端而异。Safari 最为严格,而 Internet Explorer 最为宽容。值得注意的是,0x0A-0x0D 和 0xA0-0xAD 范围内的几个控制字符在大多数浏览器中在这个 URL 部分被忽略。
注意
一个令人着迷的行为是所有主流浏览器中的 URL 解析器都愿意将字符“
”(汉字句号,Unicode 点 U+3002)与主机名中的句点同等对待,但在 URL 的其他地方则不是。据报道,这是因为某些中文键盘映射使得输入这个符号比预期的 7 位 ASCII 值要容易得多。
服务器端口
这个服务器端口是一个可选部分,描述了在之前指定的服务器上连接的非标准网络端口。几乎所有的浏览器和第三方应用程序支持的应用层协议都使用 TCP 或 UDP 作为底层传输方法,而 TCP 和 UDP 都依赖于 16 位端口号来区分运行在单个机器上的无关服务之间的流量。每个方案都与该协议服务器通常运行的默认端口相关联(HTTP 为 80,FTP 为 21 等),但默认值可以在 URL 级别被覆盖。
注意
这个特性的一个有趣且未预料到的副作用是,浏览器可以被欺骗,向随机网络服务发送攻击者提供的数据,而这些服务并不使用浏览器期望它们使用的协议。例如,可以将浏览器指向 mail.example.com:25/,其中 25 是 Simple Mail Transfer Protocol (SMTP)服务使用的端口,而不是 HTTP。这一事实导致了各种安全问题,并促使许多不完美的解决方案,这些解决方案在本书的第二部分中进行了更详细的讨论。
层次化文件路径
URL 的下一部分,即层次化文件路径,设想为识别要从服务器检索的特定资源的一种方式,例如 /documents/2009/my_diary.txt。规范相当明确地建立在 Unix 目录语义之上,要求解析路径中的“/../”和“/./”段,并为非完全限定的 URL 中的相对引用提供基于目录的方法。
在 20 世纪 90 年代,使用文件系统模型似乎是一个自然的选择,当时网络服务器只是作为一组静态文件和偶尔的执行脚本的简单网关。但自那时起,许多当代网络应用程序框架已经切断了与文件系统的任何剩余联系,直接与数据库对象或驻留程序代码中注册的位置进行交互。将这些数据结构映射到良好的 URL 路径是可能的,但并不总是实践或谨慎实践。所有这些都使得自动内容检索、索引和安全测试比应有的要复杂。
查询字符串
查询字符串是一个可选部分,用于将任意、非层次化的参数传递给路径中先前标识的资源。一个常见的例子是将用户提供的术语传递给实现搜索功能的服务器端脚本,例如:
http://example.com/`search.php?query=Hello+world`
大多数网络开发者习惯于查询字符串的特定布局;这种熟悉的格式是由浏览器在处理基于 HTML 的表单时生成的,遵循以下语法:
name1=value1&name2=value2...
令人惊讶的是,这种布局在 URL RFCs 中并没有被强制要求。相反,查询字符串被视为一个不透明的数据块,最终接收者可以按照自己的理解进行解释,而且与路径不同,它没有特定的解析规则的限制。
常用格式的提示可以在信息 RFC 1630 中找到,这是一个与邮件相关的 RFC 2368,以及处理表单的 HTML 规范中。([[99])]([[100])]^([[101])] 这一切都是非约束性的,因此,尽管这可能不太礼貌,但网络应用程序为 URL 的这一部分使用任意格式并不是错误。
片段 ID
片段 ID 是一个不透明的值,其角色类似于查询字符串,但为客户端应用程序提供可选的指令而不是服务器。(实际上,这个值根本不应该发送到服务器。)片段 ID 的格式和功能在 RFCs 中没有明确规定,但暗示它可以用来定位检索到的文档中的“子资源”或提供其他文档特定的渲染提示。
实际上,在浏览器中,片段标识符只有一个官方用途:指定文档中锚点 HTML 元素的名称,用于文档内导航。逻辑很简单。如果 URL 中提供了锚点名称并且可以找到匹配的 HTML 标签,则文档将滚动到该位置进行查看;否则,不会发生任何操作。由于信息编码在 URL 中,因此可以轻松与他人共享或书签这种长文档的特定视图。在这种情况下,片段 ID 的含义仅限于滚动现有文档,因此当仅更新 URL 的这部分以响应用户操作时,无需从服务器检索任何新数据。
这个有趣的属性导致了另一种更近期的、完全临时的使用:存储客户端脚本所需的杂项状态信息。例如,考虑一个地图浏览应用程序,它将当前查看的地图坐标放在片段标识符中,以便在链接被书签或分享时知道从该位置恢复。与更新查询字符串不同,即时更改片段 ID 不会触发耗时的页面重新加载,这使得这种数据存储技巧成为一个杀手级功能。
再次将所有内容组合在一起
如上所述的每个 URL 段落都由某些保留字符分隔:斜杠、冒号、问号等。为了使整个方法可用,这些分隔字符不应出现在 URL 的任何其他目的中。基于这个假设,想象一个示例算法,将绝对 URL 分割成上述功能部分,至少在某种程度上与浏览器完成此任务的方式一致。这样一个相当合理的算法示例可能是:
步骤 1:提取方案名称。
查找第一个“:”字符。URL 左侧的部分是方案名称。如果方案名称不符合预期的字符集,则退出;如果这样,URL 可能需要被当作相对 URL 处理。
步骤 2:消费层次 URL 标识符。
方案名称之后应该跟着字符串“//”。如果找到它,则跳过;如果没有找到,则退出。
注意
在某些解析上下文中,为了提高可用性,实现可能会接受零个、一个甚至三个或更多斜杠而不是两个。同样,从其诞生之初,Internet Explorer 就接受在 URL 的任何位置使用反斜杠 () 代替斜杠,这可能是为了帮助不熟练的用户.^([11]) 除了 Firefox 之外的所有浏览器最终都跟随了这个趋势,并识别了如 *http:\example.com* 这样的 URL。
步骤 3:获取权限部分。
扫描下一个“/”、“?”或“#”,以从 URL 中提取权限部分。如上所述,大多数浏览器也会接受“\”作为正斜杠的替代分隔符,这可能需要考虑。分号 (😉 也是除 Internet Explorer 和 Safari 之外浏览器中可接受的权限分隔符;做出这一决定的原因尚不清楚。
步骤 3A:查找凭证(如果存在)。
一旦提取了权限部分,在子字符串中定位到符号 (@)。如果找到,则前导片段构成登录凭证,应进一步在第一个冒号(如果存在)处进行标记,以分割登录和密码数据。
步骤 3B:提取目标地址。
权限部分的其余部分是目标地址。寻找第一个冒号以将主机名与端口号分开。对于括号内的 IPv6 地址也需要一个特殊情况。
步骤 4:识别路径(如果存在)。
如果权限部分后面紧跟着一个正斜杠——或者在某些实现中,如前所述,一个反斜杠或分号,扫描下一个“?”、“#”或字符串末尾,以先到者为准。中间的文本构成路径部分,应根据 Unix 路径语义进行标准化。
步骤 5:提取查询字符串(如果存在)。
如果最后一个成功解析的段后面跟着一个问号,扫描下一个“#”字符或字符串末尾,以先到者为准。中间的文本是查询字符串。
步骤 6:提取片段标识符(如果存在)。
如果最后一个成功解析的段后面跟着“#”,则从该字符到字符串末尾的所有内容都是片段标识符。无论如何,你就可以完成了!
这个算法可能看起来很平凡,但它揭示了即使是经验丰富的程序员通常也不会考虑的微妙细节。它还说明了对于普通用户来说,理解特定 URL 的解析方式极其困难。让我们从一个相当简单的情况开始:
http://example.com&gibberish=1234@167772161/
这个 URL 的目标——一个解码为 10.0.0.1 的连续 IP 地址——对于一个非专家来说并不明显,许多用户会认为他们正在访问 example.com,而不是其他地址.^([12]) 好吧,那是一个简单的例子!那么,让我们来看看这个语法:
http://example.com\@coredump.cx/
在 Firefox 中,该 URL 将用户带到 coredump.cx,因为 *example.com* 被解释为登录字段的合法值。在几乎所有其他浏览器中,“\” 被解释为路径分隔符,用户将到达 example.com。
对于 Internet Explorer,还有一个更加令人沮丧的例子。考虑以下情况:
http://example.com;.coredump.cx/
微软的浏览器允许在主机名中使用“;”,并且由于 coredump.cx 域的适当配置,成功解析了这个标签。大多数其他浏览器会自动更正 URL 为 http://example.com/;.coredump.cx 并将用户带到 example.com(Safari 除外,那里的语法会导致错误)。如果这看起来很混乱,请记住,我们只是刚开始了解浏览器的工作方式!
^([10]) 这可能是出于对 FTP 的担忧,FTP 在传输用户凭据时没有进行编码;在这个协议中,作为 FTP 命令开始的换行符会被服务器错误地解释。其他浏览器可能会以不合规的百分号编码形式传输 FTP 凭据,或者简单地删除任何有问题的字符。
^([11]) 与基于 UNIX 的操作系统不同,Microsoft Windows 使用反斜杠而不是斜杠来分隔文件路径(例如,c:\windows\system32\calc.exe)。微软可能试图通过用户在网络上需要输入不同类型的斜杠而感到困惑的可能性来补偿,或者希望解决与 file: URL 和类似机制的其他可能的矛盾,这些机制将直接与本地文件系统交互。然而,其他 Windows 文件系统特定的内容(如不区分大小写)并未复制。
^([12]) 这种基于 @ 的技巧很快就被接受,以方便各种针对普通用户的在线欺诈。减轻其影响的尝试从重手和奇怪的具体措施(例如,在 Internet Explorer 中禁用基于 URL 的身份验证或在 Firefox 中通过警告来削弱它)到相当合理的措施(例如,在几个浏览器的地址栏中突出显示主机名)。
保留字符和百分号编码
上一个章节中概述的 URL 解析算法依赖于这样一个假设,即某些保留的、用于语法分隔的字符不会以任何其他形式出现在 URL 中(也就是说,它们不会是用户名、请求路径的一部分等)。这些通用的、破坏语法的分隔符是:
: / ? # [ ] @
RFC 还提到了一些低级别的分隔符,但没有为它们指定任何特定用途,这可能是为了允许在顶级部分的任何部分实现方案或应用特定的功能:
! $ & ' ( ) * + , ; =
所有上述字符原则上都是禁止使用的,但在某些合法情况下,人们可能希望将它们包含在 URL 中(例如,为了适应用户输入的任意搜索词,并通过查询字符串传递给服务器)。因此,而不是禁止它们,标准提供了一种方法来编码所有这些值的虚假出现。这种方法简单地称为百分编码或URL 编码,用百分号(%)后跟表示匹配 ASCII 值的两个十六进制数字来替换字符。例如,/将被编码为%2F(大写是惯例但不是强制性的)。因此,为了避免歧义,裸百分号本身必须编码为%25。任何处理现有 URL 的中介(包括浏览器和 Web 应用程序)都进一步被迫不得尝试解码或编码传递的 URL 中的保留字符,因为这样的 URL 的含义可能会突然改变。
殊为遗憾,现有 URL 中保留字符的不可变性与其需要响应任何技术上非法的 URL 的需求相矛盾,因为这些 URL 错误地使用了这些字符,并且在浏览器中遇到。这个主题在规范中完全没有涉及,这迫使浏览器供应商进行临时改进,并导致跨实现不一致。例如,URL http://a@b@c/ 应该被转换为 http://a@b%40c/ 还是可能转换为 http://a%40b@c/?Internet Explorer 和 Safari 认为前者更有意义;其他浏览器则支持后者观点。
在保留集合之外的字符在 URL 语法本身中不应有任何特定的意义。然而,其中一些(如不可打印的 ASCII 控制字符)显然与 URL 应该是人类可读和传输安全的理念不相符。因此,RFC 概述了一个名为未保留字符的子集(包括字母数字,-,.,_和“~”),并表示只有这个子集以及它们在预期用途中的保留字符正式允许出现在 URL 中。
注意
奇怪的是,这些未保留字符仅允许以未转义的形式出现;它们不是必须这样做。用户代理可以随意编码或解码它们,这样做并不会改变 URL 的含义。这种属性提出了另一种混淆用户的方法:使用未规范化的未保留字符表示。具体来说,以下所有表示都是等效的:
一些本应非保留的可打印字符被排除在所谓的非保留集之外。因此,严格来说,RFC 要求它们无条件地进行百分编码。然而,由于浏览器没有明确负责执行这项规则,所以它并没有被认真对待。特别是,所有浏览器都允许“^”、“{”、“|”和“}”出现在 URL 中而不进行转义,并将这些字符原样发送到服务器。Internet Explorer 进一步允许“<”、“>”和“`”通过;Internet Explorer、Firefox 和 Chrome 都接受“\”;Chrome 和 Internet Explorer 将允许双引号;而 Opera 和 Internet Explorer 都允许非打印字符 0x7F(DEL)原样通过。
最后,与 RFC 中明确要求的内容相反,大多数浏览器甚至根本不对片段标识符进行编码。这对依赖于这个字符串并期望某些潜在不安全的字符永远不会以字面形式出现的客户端脚本构成了一个意想不到的挑战。我们将在第六章中重新探讨这个话题。
非 US-ASCII 文本的处理
全球许多语言依赖于基本 7 位 ASCII 字符集之外的字符,或者依赖于所有 PC 兼容系统传统上使用的默认 8 位代码页(CP437)。实际上,一些语言依赖于根本不是基于拉丁字母的字母表。
为了满足一个经常被忽视但强大的非英语用户群体的需求,在 Web 出现之前就设计了各种带有替代高比特字符集的 8 位代码页:ISO 8859-1、CP850 和 Windows 1252 用于西欧语言;ISO 8859-2、CP852 和 Windows 1250 用于东欧和中欧;KOI8-R 和 Windows 1251 用于俄罗斯。而且,由于几个字母表无法适应 256 字符的空间,我们看到了复杂变宽编码的兴起,例如 Shift JIS 用于片假名。
这些字符映射的不兼容性使得在不同代码页配置的计算机之间交换文档变得困难。到 20 世纪 90 年代初,这个日益增长的问题导致了Unicode的创建——一种几乎可以包含人类已知所有区域脚本和专业象形文字的通用字符集。Unicode 之后是 UTF-8,这是一种相对简单、变宽的字符表示形式,理论上对能够处理传统 8 位格式的所有应用程序都是安全的。不幸的是,UTF-8 编码高比特字符所需的字节数比大多数竞争对手都要多,对许多用户来说,这似乎是浪费且不必要的。由于这种批评,UTF-8 在 Web 上获得认可花了十多年时间,而且是在所有相关协议都巩固之后才实现的。
这个不幸的延迟对处理包含用户输入的 URL 的处理产生了一定的影响。浏览器需要很早就适应这种使用,但当开发者转向相关标准时,他们发现没有有意义的建议。甚至多年以后,在 2005 年,RFC 3986 也只有以下内容:
在本地或区域环境中,以及随着技术的改进,用户可能会从能够使用更广泛的字符中受益;这种使用并未由本规范定义。
百分比编码的八位字节……可以在 URI 中使用,以表示超出 US-ASCII 编码字符集范围的字符,如果这种表示由方案或 URI 引用中的协议元素允许。这样的定义应指定用于将那些字符映射到八位字节并在进行 URI 百分比编码之前进行编码的字符编码。
悲哉,尽管有这种美好的愿望,但剩余的标准都没有解决这个问题。始终可以将原始高位字符放入 URL 中,但如果没有知道它们应该解释的代码页,服务器将无法判断那个%B1是否应该表示“±”、“ą”或用户母语脚本中的一些其他波浪形字符。
很遗憾,浏览器供应商没有采取主动行动,提出一个一致的解决方案来解决这个问题。大多数浏览器在内部将 URL 路径段转码为 UTF-8(或者在足够的情况下,为 ISO 8859-1),但随后它们在引用页面的代码页中生成查询字符串。在某些情况下,当 URL 是手动输入或传递给某些专用 API 时,高位字符也可能被降级为其 7 位 US-ASCII 相似字符,用问号替换,或者由于实现缺陷而完全损坏。
无论实现得多么糟糕,将非英语字符传递到查询字符串和路径中的能力都满足了明显的需求。传统的百分比编码方法只让一个 URL 段完全处于不利地位:在指定目标服务器名称时,不允许直接使用高位输入,因为至少在原则上,已经建立的标准 DNS 仅允许点分隔的字母数字和破折号出现在域名中——尽管没有人遵守规则,但例外情况因名称服务器而异。
一个敏锐的读者可能会想知道为什么这种限制很重要;也就是说,为什么在非拉丁字母表中也要有本地化域名?这个问题现在可能很难回答。简单地说,有几个人认为缺乏这些编码将阻止全球的企业和个人完全拥抱和享受互联网——而且,无论对错,他们都决心让它成为现实。
这种追求导致了应用程序中国际化域名(IDNA)的形成。首先,RFC 3490,^([102])概述了一个相当复杂的方案,用于使用字母数字和破折号对任意 Unicode 字符串进行编码,然后是 RFC 3492,^([103]),它描述了使用称为Punycode的格式将此编码应用于 DNS 标签的方法。Punycode 看起来大致如下:
xn--[US-ASCII part]-[encoded Unicode data]
一个合规的浏览器在遇到一个技术上非法的 URL,其中在任何位置包含一个非 US-ASCII 字符时,应该将名称转换为 Punycode,然后再执行 DNS 查找。因此,当在现有 URL 中遇到 Punycode 时,它应该在地址栏中显示该字符串的解码、可读形式。
注意
将所有这些不兼容的编码策略结合起来,可能会产生有趣的混合。考虑以下一个虚构的波兰语毛巾店的示例 URL:
![无标题图片]()
在所有基于 URL 的编码方法中,IDNA 很快证明是最有问题的。本质上,浏览器地址栏中显示的 URL 中的域名是 Web 上最重要的安全指示之一,因为它使用户能够快速区分他们信任并与之进行过交易的网站与互联网上的其他网站。当浏览器显示的域名由 38 个熟悉且独特的字符组成时,只有相当粗心的受害者才会被骗,认为他们最喜欢的example.com域名和冒充的examp1e.com网站是同一件事。但 IDNA 随意且不加区分地将这 38 个字符扩展到 Unicode 支持的约 10 万个符号中,其中许多符号看起来几乎相同,并且仅基于功能差异而彼此分离。
这有多糟糕?以西里尔字母为例。这个字母表有几个与拉丁字母表中的字母几乎相同的同形异义词,但它们的 Unicode 值完全不同,并且解析为完全不同的 Punycode DNS 名称:
| 拉丁 |
a |
c |
e |
i |
j |
o |
p |
s |
x |
y |
| U+0061 |
U+0063 |
U+0065 |
U+0069 |
U+006A |
U+006F |
U+0070 |
U+0073 |
U+0078 |
U+0079 |
|
| 西里尔 |
a |
c |
e |
i |
j |
o |
p |
s |
x |
y |
| U+0430 |
U+0441 |
U+0435 |
U+0456 |
U+0458 |
U+043E |
U+0440 |
U+0455 |
U+0445 |
U+0443 |
|
当 IDNA 被提出并在浏览器中首次实现时,没有人认真考虑这个问题带来的后果。浏览器供应商显然认为 DNS 注册商将阻止人们注册类似的名字,而注册商认为在地址栏中保持清晰的视觉是浏览器供应商的问题。
在 2002 年,所有相关方终于认识到了这个问题的严重性。那年,叶夫根尼·加布里洛维奇和亚历克斯·冈特马克赫发表了“同音攻击”^([104]),一篇详细探讨漏洞的论文。他们指出,任何注册级别的工作方案,即使实施,也会有一个致命的缺陷。攻击者总是可以购买一个健全的顶级域名,然后在自己的域名服务器上设置一个子域名记录,应用 IDNA 转换后,会解码成一个与example.com/(最后一个字符仅是一个非功能的类似 ASCII 斜杠的替代品)视觉上相同的字符串。结果将是:
![无标题图片]()
注册商无法采取任何措施来防止这种攻击,球现在在浏览器供应商的场地上。但他们到底有什么选择呢?
实际上,并不多。我们现在意识到,构思不佳的 IDNA 标准无法以简单和痛苦的方式修复。浏览器开发者通过在用户的区域设置与特定 DNS 标签中看到的脚本不匹配时回退到难以理解的 Punycode 来应对这种风险(这在使用外国网站或使用导入或配置错误的计算机时会导致问题);仅在某些特定国家的顶级域名中允许使用 IDNA(排除了在.com和其他高知名度 TLD 中使用国际化域名);并列入某些类似斜杠、点、空格等“坏”字符的黑名单(鉴于全球使用的字体数量,这是一项徒劳的任务)。
这些措施足够激进,足以严重阻碍国际化域名的采用,可能到了这种程度,标准的持续存在带来的安全问题比它为非英语用户带来的实际可用性好处还要多。
^([13]) 类似的不规范编码被广泛用于各种类型的社会工程攻击,因此,多年来部署了各种对策。像往常一样,其中一些对策是破坏性的(例如,Firefox 明确拒绝在主机名中使用百分号编码的文本),而另一些则相当不错(例如,通过解码所有不必要的编码文本以用于显示目的,强制执行地址栏的“规范转换”)。
常见 URL 方案及其功能
让我们抛开奇特的 URL 解析世界,回到基础。在本章的早期,我们暗示某些方案可能会有意外的安全后果,并且由于这个原因,任何处理用户提供的 URL 的 Web 应用程序都必须谨慎。为了更好地解释这一点,回顾在典型浏览器环境中通常支持的 URL 方案是有用的。这些可以组合成四个基本组。
浏览器支持的文档获取协议
这些方案由浏览器内部处理,提供了一种使用特定传输协议检索任意内容,然后使用常见的浏览器级渲染逻辑显示它的方法。这是 URL 的最基本和最预期的功能。
在这个类别中,常见支持的方案列表出奇地短:http:(RFC 2616),在 Web 上使用的首选传输模式,也是本书下一章的重点;https:,HTTP 的加密版本(RFC 2818^([105]));以及 ftp:,一种较老的文件传输协议(RFC 959^([106]))。所有浏览器也支持 file:(以前也称为 local:),一种特定于系统的访问本地文件系统或 NFS 和 SMB 共享的方法。(这个最后的方案通常不能通过源自互联网的页面直接访问。)
还有两个额外的、不为人知的案例也值得简要提及:内置对 gopher: 方案的支持,这是 Web 的一个失败的先驱(RFC 1436^([107])),仍然存在于 Firefox 中,以及 shttp:,一种对 HTTPS 的替代、失败的尝试(RFC 2660^([108])),仍然在 Internet Explorer 中被认可(但今天,它简单地被别名为 HTTP)。
第三方应用程序和插件声明的协议
对于这些方案,匹配的 URL 简单地被发送到外部、专门的应用程序,这些应用程序实现了媒体播放、文档查看或 IP 电话等功能。到此,浏览器(主要)的参与就结束了。
目前存在大量外部协议处理器,要涵盖它们所有内容需要另一本厚厚的书。其中一些最常见例子包括 acrobat: 方案,可预测地路由到 Adobe Acrobat Reader;callto: 和 sip: 方案被各种即时通讯软件和电话软件所声称;daap:, itpc:, 和 itms: 方案由 Apple iTunes 使用;mailto:, news:, 和 nntp: 协议由邮件和 Usenet 客户端声称;mmst:, mmsu:, msbd:, 和 rtsp: 协议用于流媒体播放器;等等。浏览器有时也会包含在列表中。之前提到的 firefoxurl: 方案可以在另一个浏览器中启动 Firefox,而 cf: 则可以从 Internet Explorer 访问 Chrome。
对于这些方案,当它们出现在 URL 中时,通常对允许它们通过的 Web 应用程序的安全性没有影响(尽管这并不保证,尤其是在插件支持的内容的情况下)。值得注意的是,第三方协议处理器往往臭名昭著地存在漏洞,有时被滥用以损害操作系统。因此,限制导航到神秘协议的能力是对任何合理可信网站用户的一种常见礼节。
非封装伪协议
一系列协议被保留,以提供方便地访问浏览器的脚本引擎和其他内部功能,而无需实际检索任何远程内容,也许无需建立独立的文档上下文来显示结果。许多这些伪协议非常特定于浏览器,要么不能直接从互联网访问,要么无法造成伤害。然而,有几个重要的例外。
最著名的例外可能是 javascript: 方案(在早年,在 Netscape 浏览器中也可通过别名如 livescript: 或 mocha: 访问)。此方案允许在当前查看的网站上下文中访问 JavaScript 编程引擎。在 Internet Explorer 中,vbscript: 通过专有的 Visual Basic 界面提供类似的功能。
另一个重要的情况是 data: 协议(RFC 2397^([109])),它允许创建简短的、内联的文档,而无需任何额外的网络请求,有时它们的大部分操作上下文都来自引用页面。一个 data: URL 的例子是:
*`data:text/plain,Why,%20hello%20there!`*
这些外部可访问的伪 URL 对于站点安全至关重要。当导航到这些 URL 时,其有效载荷可能在原始域的上下文中执行,可能窃取敏感数据或更改受影响用户的页面外观。我们将在第六章中讨论浏览器脚本语言的特定功能,但正如你所预期的那样,它们是相当重要的。(另一方面,URL 上下文继承规则是第十章的重点。)
封装伪协议
这一类特殊的伪协议可以用来作为任何其他 URL 的前缀,以强制对检索到的资源进行特殊的解码或渲染模式。或许最著名的例子是 Firefox 和 Chrome 支持的 view-source: 方案,用于显示 HTML 页面的格式化源代码。此方案的使用方式如下:
view-source:http://www.example.com/
其他功能类似的协议包括 jar:,它允许在 Firefox 中动态提取 ZIP 文件中的内容;wyciwyg: 和 view-cache:,分别提供 Firefox 和 Chrome 中缓存页面的访问权限;一个奇特的 feed: 方案,旨在访问 Safari 中的新闻源;^([110]) 以及与 Windows 帮助子系统和其他 Microsoft Windows 组件相关的一大批文档不充分的协议(hcp:, its:, mhtml:, mk:, ms-help:, ms-its:, 和 ms-itss:)。
许多封装协议的共同特性是它们允许攻击者隐藏浏览器最终将解释的实际 URL,从而避开天真过滤器:view-source:javascript:(甚至view-source:view-source:javascript:)后跟恶意代码是完成这一点的简单方法。可能存在一些安全限制来限制这种诡计,但不应依赖于它们。另一个显著的问题,尤其是在微软的mhtml:中反复出现,是使用该协议可能会忽略服务器在 HTTP 级别提供的某些内容指令,这可能导致普遍的不幸.^([111])
关于方案检测的结束语
伪协议的数量众多是为什么 Web 应用需要仔细筛选用户提供的 URL 的主要原因。古怪且特定于浏览器的 URL 解析模式,加上支持方案列表的开放性,意味着简单地黑名单已知的坏方案是不安全的;例如,如果这个关键字与制表符或换行符拼接,替换为vbscript:,或者以另一个封装方案为前缀,那么对javascript:的检查可能会被规避。
相对 URL 的解析
相对 URL 在章节的前几部分已经被提到,现在也需要给予更多的关注。它们存在的原因是,在互联网上的几乎每个网页上,都会有相当数量的 URL 引用同一服务器上托管的资源,可能是在同一个目录下。每次需要此类引用时都要求出现完全限定的 URL 将是不方便且浪费的,因此使用简短的相对 URL(如../other_file.txt)代替。缺失的细节可以从引用文档的 URL 中推断出来。
由于相对 URL 可以出现在任何绝对 URL 可能出现的完全相同的场景中,因此浏览器内部需要有一种方法来区分这两种 URL。Web 应用也受益于这种区分能力,因为大多数类型的 URL 过滤器可能只想仔细检查绝对 URL,并允许本地引用按原样通过。
规范可能使这项任务看起来非常简单:如果 URL 字符串不以有效的方案名称开头,后面跟一个分号和,最好是有效的“//”序列,则应将其解释为相对引用。如果没有解析此类相对 URL 的上下文,则应拒绝。其他一切都是安全的相对链接,对吧?
预计这并不像看起来那么简单。首先,如前几节所述,有效方案名称中接受的字符集,以及代替“//”接受的模式,因实现而异。也许更有趣的是,一个常见的误解是相对链接只能指向同一服务器上的资源;实际上存在许多其他不那么明显的相对 URL 变体。
让我们快速浏览一下已知的相对 URL 类别,以更好地说明这种可能性。
有方案,但没有权限 (http:foo.txt)
这种臭名昭著的漏洞在 RFC 3986 中有所暗示,并归因于早期规范中的一个疏忽。虽然这些规范描述性地将这些 URL 分类为(无效)绝对引用,但它们还提供了一个对它们进行错误解释的贪婪引用解析算法。
在后一种解释中,这些 URL 将设置新的协议和路径、查询或片段标识符,但权限部分将从引用位置复制。这种语法被几个浏览器接受,但并不一致。例如,在某些情况下,http:foo.txt 可能被视为相对引用,而 https:example.com 可能被解析为绝对引用!
没有方案,但有权限 (//example.com)
这是一种臭名昭著但至少有良好文档记录的怪癖。虽然 /example.com 是对当前服务器上本地资源的引用,但标准强制浏览器将 //example.com 视为一个非常不同的情况:对当前协议的不同权限的引用。在这种情况下,方案将从引用位置复制,所有其他 URL 细节都将从相对 URL 派生。
没有方案,没有权限,但有路径 (../notes.txt)
这是相对链接最常见的变体。协议和权限信息是从引用 URL 复制的。如果相对 URL 不以斜杠开头,路径也将复制到最右侧的“/”。例如,如果基本 URL 是 www.example.com/files/,则路径相同,但在 www.example.com/files/index.html 中,文件名被截断。然后,将新路径附加到上面,并在连接值上执行标准路径规范化。查询字符串和片段标识符仅从相对 URL 派生。
没有方案,没有权限,没有路径,但有查询字符串 (?search=bunnies)
在此场景中,协议、权限和路径信息直接从引用 URL 复制。查询字符串和片段标识符是从相对 URL 派生出来的。
仅存在片段标识符 (#bunnies)
除了片段标识符之外的所有信息都是直接从引用 URL 复制的;只有片段标识符被替换。遵循此类相对 URL 在正常情况下不会导致页面重新加载,如前所述。
由于在处理这些类型的相对引用时,应用级 URL 过滤器与浏览器之间可能存在潜在误解的风险,因此,始终直接输出用户提供的相对 URL 是一个良好的设计实践。在可行的情况下,它们应被明确重写为绝对引用,并且所有安全检查都应针对生成的完全合格地址进行。
安全工程速查表
当基于用户输入构建全新的 URL 时
-
如果您允许用户在路径、查询或片段 ID 中提供数据: 如果某个部分分隔符在没有适当转义的情况下成功通过,URL 可能会产生与您预期不同的效果(例如,将一个用户可见的 HTML 按钮链接到错误的服务器端操作)。谨慎行事是正确的:当插入攻击者控制的字段值时,您只需对非字母数字字符进行百分号转义即可。
-
如果您允许用户提供的方案名称或授权部分: 这是一种主要的代码注入和钓鱼风险!应用以下概述的相关输入验证规则。
当设计 URL 输入过滤器时
-
相对 URL: 禁止或显式重写它们为绝对引用以避免麻烦。其他任何内容都极有可能是不安全的。
-
方案名称: 仅允许已知的前缀,例如 http://、https:// 或 ftp://。不要使用黑名单;这极其不安全。
-
授权部分: 主机名应仅包含字母数字、“-”和“.”,并且只能由“/”、“?”、“#”或字符串末尾跟随。允许任何其他内容都会适得其反。如果您需要检查主机名,请确保进行适当的右侧子字符串匹配。
在罕见的情况下,您可能需要考虑 IDNA、IPv6 括号表示法、端口号或 HTTP 凭证在 URL 中的情况。如果是这样,您必须完全解析 URL,验证所有部分,拒绝异常值,并将它们重新序列化为一个非歧义、规范、正确转义的表示形式。
当解码通过 URL 接收到的参数时
- 不要假设任何特定字符会因为标准如此规定或因为您的浏览器这样做而被转义。在回显任何由 URL 派生的值或将它们放入数据库查询、新 URL 等之前,仔细清除危险字符。
第三章. 超文本传输协议
我们接下来需要讨论的下一个基本概念是超文本传输协议(HTTP):Web 的核心传输机制,以及服务器和客户端之间交换 URL 引用文档的首选方法。尽管 HTTP 的名字中有超文本,但 HTTP 和实际的超文本内容(HTML 语言)通常独立存在。话虽如此,它们有时以令人惊讶的方式交织在一起。
HTTP 的历史为作者们的雄心和互联网日益增长的相关性提供了有趣的见解。蒂姆·伯纳斯-李(Tim Berners-Lee)1991 年最早的协议草案(HTTP/0.9^([112]))仅有不到一页半长,并且未能考虑到甚至是最直观的未来需求,例如传输非 HTML 数据所需的扩展性。
五年后,经过几个版本的规范迭代,第一个官方的 HTTP/1.0 标准(RFC 1945^([113]))在约 50 页密集的文字中试图纠正许多这些不足。快进到 1999 年,在 HTTP/1.1(RFC 2616^([114]))中,七位认可的作者试图预测几乎该协议的每一种可能的使用,创作了一部超过 150 页的杰作。不仅如此:截至本文撰写时,对 HTTPbis 的工作,^([115]))基本上是 HTTP/1.1 规范的替代品,达到了大约 360 页。虽然其中大部分逐渐积累的内容对现代网络来说并不相关,但这种进展清楚地表明,添加新特性的愿望远远超过了修剪失败特性的愿望。
现在,所有客户端和服务器都支持 HTTP/1.0 的一个不完全准确的超集,并且大多数都能说一口相当完整的 HTTP/1.1 方言,并附加了一些扩展。尽管实际上没有这样的需求,但一些 Web 服务器和所有常见的浏览器也保持了与 HTTP/0.9 的向后兼容性。
HTTP 流量基本语法
初看之下,HTTP 是一种相当简单、基于文本的协议,它建立在 TCP/IP 之上.^([14]) 每个 HTTP 会话都是通过建立到服务器的 TCP 连接来启动的,通常是端口 80,然后发出一个概述请求 URL 的请求。作为回应,服务器返回请求的文件,在最基本的使用案例中,随后立即终止 TCP 连接。
原始的 HTTP/0.9 协议为参与方之间交换任何额外的元数据没有提供空间。客户端请求始终由一行组成,以 GET 开头,后跟 URL 路径和查询字符串,并以单个 CRLF 换行符(ASCII 字符 0x0D 0x0A;服务器也被建议接受单独的 LF)。一个示例 HTTP/0.9 请求可能看起来像这样:
GET /fuzzy_bunnies.txt
作为对此消息的回应,服务器会立即返回适当的 HTML 有效负载。(规范要求服务器将返回文档的行包装在 80 个字符处,但这些建议并没有真正得到遵循。)
HTTP/0.9 方法存在许多实质性的缺陷。例如,它为浏览器提供不了与用户语言偏好进行通信的方式,提供支持的文档类型列表等。它也使服务器无法通知客户端请求的文件找不到,它已移动到不同的位置,或者返回的文件根本不是 HTML 文档。最后,该方案对服务器管理员来说并不友好:当传输的 URL 信息仅限于路径和查询字符串时,服务器无法在单个 IP 地址下托管多个网站,这些网站通过它们的域名区分开来——而且与 DNS 记录不同,IP 地址并不便宜。
为了解决这些不足(并为未来的调整留出空间),HTTP/1.0 和 HTTP/1.1 标准采用了略微不同的对话格式:请求的第一行被修改以包含协议版本信息,其后跟随零个或多个 name: value 对(也称为 headers),每个对占据一行。此类请求中常见的请求头包括 User-Agent(浏览器版本信息)、Host(URL 主机名)、Accept(支持的 MIME 文档类型^([15])))、Accept-Language(支持的语言代码)和 Referer(一个拼写错误的字段,表示请求的起始页面,如果已知)。
这些头部以一个单独的空行结束,之后可以跟随客户端希望传递给服务器的任何有效负载(其长度必须通过额外的 Content-Length 头部明确指定)。从协议本身的角度来看,有效负载的内容是透明的;在 HTML 中,这个位置通常用于以几种可能的格式提交表单数据,尽管这并不是一个要求。
总体而言,一个简单的 HTTP/1.1 请求可能看起来像这样:
POST /fuzzy_bunnies/bunny_dispenser.php HTTP/1.1
Host: www.fuzzybunnies.com
User-Agent: Bunny-Browser/1.7
Content-Type: text/plain
Content-Length: 17
Referer: http://www.fuzzybunnies.com/main.html
I REQUEST A BUNNY
服务器预计会通过一行来响应这个查询,该行指定支持的协议版本、一个数值状态码(用于指示错误条件和其他特殊情况),以及一个可选的、可读的状态消息。接下来是一组自解释的头部,以一个空行结束。响应接着是请求资源的正文:
HTTP/1.1 200 OK
Server: Bunny-Server/0.9.2
Content-Type: text/plain
Connection: close
BUNNY WISH HAS BEEN GRANTED
RFC 2616 也允许在传输过程中使用三种支持的方法(gzip、compress、deflate)压缩响应,除非客户端通过提供合适的 Accept-Encoding 头部明确退出。
支持 HTTP/0.9 的后果
尽管 HTTP/1.0 和 HTTP/1.1 做出了改进,但不受欢迎的“愚蠢”HTTP/0.9 协议的遗留问题仍然存在,即使它通常隐藏在视线之外。HTTP/1.0 的规范部分应为此负责,因为它要求所有未来的 HTTP 客户端和服务器支持原始的、半成品草案。具体来说,第 3.1 节说:
HTTP/1.0 客户端必须 . . . 理解任何有效的 HTTP/0.9 或 HTTP/1.0 格式的响应。
在后来的年份里,RFC 2616 试图撤销这一要求(第 19.6 节:“协议规范的范围之外是强制遵守先前版本。”),但根据早期的建议,所有现代浏览器仍然继续支持这个遗留协议。
为了理解这种模式为何危险,回想一下 HTTP/0.9 服务器只回复请求的文件。没有任何迹象表明响应方实际上理解 HTTP 并愿意提供 HTML 文档。考虑到这一点,让我们分析一下如果浏览器向运行在 example.com 端口 25 的不知情的 SMTP 服务发送 HTTP/1.1 请求会发生什么:
GET /`<html><body><h1>Hi!` HTTP/1.1
Host: example.com:25
...
因为 SMTP 服务器不理解发生了什么,它很可能会这样响应:
220 example.com ESMTP
500 5.5.1 Invalid command: "GET /`<html><body><h1>Hi!` HTTP/1.1"
500 5.1.1 Invalid command: "Host: example.com:25"
...
421 4.4.1 Timeout
所有愿意遵循 RFC 的浏览器被迫将这些消息作为有效 HTTP/0.9 响应的正文,并假设返回的文档确实是 HTML。这些浏览器将解释出现在错误消息中的一个引用的攻击者控制的片段,就像它来自 example.com 的合法网站所有者一样。这严重干扰了本书第二部分(Part II)中讨论的浏览器安全模型,因此,这是相当糟糕的。
换行处理怪癖
除了 HTTP/0.9 和 HTTP/1.0 之间的根本变化之外,后来还进行了一些其他核心语法的调整。最值得注意的是,与早期版本的字面意思相反,HTTP/1.1 要求客户端不仅尊重 CRLF 和 LF 格式中的换行符,还要识别单独的 CR 字符。尽管这个建议被两个最受欢迎的 Web 服务器(IIS 和 Apache)忽视,但所有浏览器(除了 Firefox)都在客户端遵循了这一建议。
结果的不一致性使得应用程序开发者更容易忘记,不仅 LF,而且 CR 字符必须从 HTTP 标题中任何攻击者控制的值中删除。为了说明这个问题,考虑以下服务器响应,其中用户提供的、未充分清理的值出现在一个标题中,如粗体所示:
HTTP/1.1 200 OK[CR][LF]
Set-Cookie: last_search_term=`[CR][CR]<html><body><h1>Hi!`[CR][LF]
[CR][LF]
Action completed.
对于 Internet Explorer 来说,这个响应可能看起来是这样的:
HTTP/1.1 200 OK
Set-Cookie: last_search_term=
`<html><body><h1>Hi!`
Action completed.
事实上,与 HTTP 标题换行符潜入相关的漏洞类别——无论是由于这种不一致性,还是仅仅因为未能过滤任何类型的换行符——已经足够常见,以至于有了自己的名称:标题注入或响应分割。
另一个鲜为人知且可能影响安全的调整是支持多行标题,这是一个在 HTTP/1.1 中引入的变更。根据标准,任何以空格开头的标题行都被视为上一行的延续。例如:
X-Random-Comment: This is a very long string,
so why not wrap it neatly?
IIS 和 Apache 会识别客户端发出的多行标题,但 Internet Explorer、Safari 或 Opera 不支持这种语法。因此,任何依赖于或简单地允许这种语法在攻击者影响的环境中的实现可能会遇到麻烦。幸运的是,这种情况很少发生。
代理请求
代理被许多组织和互联网服务提供商用来代表用户拦截、检查和转发 HTTP 请求。这可能是为了提高性能(通过允许某些服务器响应在附近的系统上缓存),为了执行网络使用策略(例如,防止访问色情),或者为了提供受监控和认证的网络环境访问。
传统的 HTTP 代理依赖于显式的浏览器支持:应用程序需要配置为向代理系统发送修改后的请求,而不是尝试与目标目的地通信。要通过此类代理请求 HTTP 资源,浏览器通常会发送如下请求:
GET http://www.fuzzybunnies.com/ HTTP/1.1
User-Agent: Bunny-Browser/1.7
Host: www.fuzzybunnies.com
...
与上述示例和常规语法之间的关键区别在于请求的第一行中存在一个完全限定的 URL(www.fuzzybunnies.com/),指示代理代表用户连接到何处。鉴于Host头已经指定了主机名,此信息有些冗余;这种重叠的唯一原因是这些机制独立发展。为了避免被共谋的客户端和服务器欺骗,代理应纠正任何不匹配的Host头以匹配请求 URL,或者将缓存的内容与特定的 URL-Host对相关联,而不仅仅是这些值之一。
许多 HTTP 代理还允许浏览器请求非 HTTP 资源,例如 FTP 文件或目录。在这些情况下,代理将响应包装在 HTTP 中,并在适当的情况下将其转换为 HTML,然后再将其返回给用户。^([[16])] 话虽如此,如果代理不理解请求的协议,或者它不适合窥探交换的数据(例如,在加密会话中),则必须使用不同的方法。为此目的保留了一种特殊类型的请求,即 CONNECT,但在 HTTP/1.1 RFC 中未进一步解释。相关的请求语法在 1998 年的一份单独的、仅草案的规范中概述。^([[116])] 它看起来是这样的:
CONNECT www.fuzzybunnies.com:1234 HTTP/1.1
User-Agent: Bunny-Browser/1.7
...
如果代理愿意并且能够连接到请求的目标,它将通过特定的 HTTP 响应代码来确认此请求,此时该协议的作用结束。在此之后,浏览器将开始在建立的 TCP 流中发送和接收原始二进制数据;代理则预期将无差别地转发两个端点之间的流量。
注意
搞笑的是,由于草案规范中的一个细微遗漏,许多浏览器错误地处理了在尝试建立加密连接期间返回的非加密、代理起源的错误响应。受影响的实现将此类明文响应解释为似乎来自目标服务器通过安全通道。这个故障实际上消除了与在网络上使用加密通信相关的所有保证。这个缺陷花了十多年才被发现并纠正。^([[117])]
其他一些低级代理类不使用 HTTP 直接与浏览器通信,但仍然检查交换的 HTTP 消息以缓存内容或强制执行某些规则。这种做法的典型例子是透明代理,它在 TCP/IP 级别静默拦截流量。透明代理采取的方法异常危险:任何这样的代理都可以查看被拦截连接中的目标 IP 和Host头部,但它无法立即判断该目标 IP 是否真正与指定的服务器名称相关联。除非进行额外的查找和关联,否则共谋的客户端和服务器可以利用这种行为。如果没有这些额外的检查,攻击者只需连接到他的或她的家用服务器,并发送一个误导性的Host: www.google.com头部,就可以让其他所有用户的响应被缓存,就像真正来自www.google.com一样。
重复或冲突头部的解决
尽管 RFC 2616 相对冗长,但它对符合规范的解析器如何解决请求或响应数据中可能存在的歧义和冲突解释得并不好。本 RFC 的第 19.2 节(“宽容的应用”)建议在“明确”情况下对某些字段进行宽松和容错解析,但这个术语本身的意义,可以说,并不特别明确。
例如,由于缺乏规范级别的建议,大约一半的浏览器将优先考虑特定 HTTP 头部的第一次出现,而其余的将优先考虑最后一次出现,确保几乎每个头部注入漏洞,无论多么受限,都能至少被一部分目标用户利用。在服务器端,情况同样随机:Apache 将尊重看到的第一个Host头部,而 IIS 将完全拒绝包含多个此字段实例的请求。
相关地,相关的 RFC 没有明确禁止混合可能冲突的 HTTP/1.0 和 HTTP/1.1 头部,也没有要求 HTTP/1.0 服务器或客户端忽略所有 HTTP/1.1 语法。正因为这种设计,很难预测 HTTP/1.0 和 HTTP/1.1 指令之间间接冲突的结果,而这些指令负责相同的事情,例如Expires和Cache-Control。
最后,在某些罕见的情况下,规范中对头部冲突的解决方法描述得非常清楚,但允许这种冲突最初出现的目的很难理解。例如,HTTP/1.1 客户端要求在所有请求中发送Host头部,但服务器(不仅仅是代理!)也要求识别请求的第一行中的绝对 URL,而不是传统的路径和查询方法。这条规则允许这种好奇心:
GET http://www.fuzzybunnies.com/ HTTP/1.1
Host: www.bunnyoutlet.com
在这种情况下,RFC 2616 的第 5.2 节指示客户端忽略非功能性的(但仍然是强制性的!)Host标题,许多实现都遵循这一建议。问题是底层应用程序可能不会意识到这个怪癖,而可能会基于检查的标题值做出一些重要的决定。
注意
当抱怨 HTTP RFCs 中的遗漏时,重要的是要认识到替代方案可能同样有问题。在该 RFC 中概述的几个场景中,明确要求处理某些边缘情况的需求导致了明显荒谬的结果。一个这样的例子是关于解析某些 HTTP 标题中日期的建议,这是根据 RFC 1945 第 3.3 节的要求提出的。结果实现(Firefox 代码库中的 prtime.c 文件^([118]))包含接近 2,000 行极其混乱且难以阅读的 C 代码,只是为了以足够容错的方式解析指定的日期、时间和时区(例如,用于决定缓存内容过期)。
分号分隔的标题值
几个 HTTP 标题,例如 Cache-Control 或 Content-Disposition,使用分号分隔的语法,将多个单独的 name=value 对压缩到一行中。允许这种嵌套记法的原因尚不清楚,但可能是由这样的信念驱动的:它将比使用多个单独的标题更高效或更直观,而这些标题总是必须一起使用。
RFC 2616 中概述的一些用例允许 quoted-string 作为此类对中的右侧参数。quoted-string 是一种语法,其中一系列任意可打印字符被双引号包围,这些引号充当分隔符。自然地,引号本身不能出现在字符串中,但——重要的是——分号或空白字符可以,允许许多其他情况下可能有问题值被原样发送。
不幸的是,对于开发者来说,Internet Explorer 并不擅长处理 quoted-string 语法,实际上使这种编码方案变得无用。浏览器将以意想不到的方式解析以下行(其目的是指示响应是可下载的文件而不是内联文档):
Content-Disposition: attachment; filename="`evil_file.exe;`.txt"
在 Microsoft 的实现中,文件名将在分号字符处截断,并看起来像 evil_file.exe。这种行为对任何依赖于检查或向攻击者控制的文件名添加“安全”文件扩展名并正确检查此字符串中的引号字符和新行的应用程序构成了潜在的风险。
注意
提供了一种额外的 quoted-pair 机制,允许在字符串前加上反斜杠时安全地使用引号(以及任何其他字符)。然而,这种机制似乎被错误地指定,并且除了 Opera 以外,没有任何主流浏览器支持。为了使 quoted-pair 正确工作,需要禁止 quoted-string 中的“\”字符,但在 RFC 2616 中并非如此。Quoted-pair 还允许任何 CHAR-type 令牌被引用,包括换行符,这与其他 HTTP 解析规则不兼容。
值得注意的是,当在单个 HTTP 标头中找到重复的分号分隔字段时,它们的优先级顺序在 RFC 中并未定义。在 Content-Disposition 中的 filename= 的情况下,所有主流浏览器都使用第一次出现。但其他地方的一致性很少。例如,当从 Refresh 标头(用于在指定时间后强制重新加载页面)中提取 URL= 值时,Internet Explorer 6 将回退到最后一个实例,而所有其他浏览器都会选择第一个。当处理 Content-Type 时,Internet Explorer、Safari 和 Opera 将使用第一个 charset= 值,而 Firefox 和 Chrome 将依赖于最后一个。
注意
思考食物:Julian Reschke 维护的一页上可以找到与单个 HTTP 标头——Content-Disposition——处理相关的数十个不一致性的迷人但主要与安全无关的调查。greenbytes.de/tech/tc2231/
标题字符集和编码方案
与为 URL 处理奠定基础的文档一样,所有后续的 HTTP 规范在很大程度上都避免了处理标头值中非 US-ASCII 字符的话题。有几个合理的场景下,非英语文本可能合法地出现在这个上下文中(例如,Content-Disposition 中的文件名),但在这方面,预期的浏览器行为基本上是未定义的。
最初,RFC 1945 允许 TEXT 令牌(一种广泛用于定义其他字段语法的原始令牌)包含 8 位字符,提供了以下定义:
OCTET = <any 8-bit sequence of data>
CTL = <any US-ASCII control character
(octets 0 - 31) and DEL (127)>
TEXT = <any OCTET except CTLs,
but including LWS>
RFC 随后给出了神秘的建议:当在 TEXT 字段中遇到非 US-ASCII 字符时,客户端和服务器可能将它们解释为 ISO-8859-1,即标准的西欧代码页,但不必这样做。后来,RFC 2616 复制并粘贴了相同的 TEXT 令牌规范,但增加了一个注释,指出非 ISO-8859-1 字符串必须使用 RFC 2047 中概述的格式进行编码,([119])该格式最初是为电子邮件通信创建的。公平地说;在这个简单的方案中,编码的字符串以“=?”前缀开始,后面跟一个字符集名称,一个“?q?”或“?b?”编码类型指示器(*quoted-printable*([17])或base64^([18])),最后是编码的字符串本身。序列以“?=”终止符结束。一个例子可能是:
Content-Disposition: attachment; filename="=`?utf-8?q?Hi=21.txt?=`"
注意
RFC 还应指出,任何虚假的“=?...?=”模式都不应直接允许出现在相关头中,以避免对实际上并未编码的值进行意外的解码。
可惜,对这种 RFC 2047 编码的支持参差不齐。Firefox 和 Chrome 在某些头中识别它,但其他浏览器合作较少。Internet Explorer 选择在Content-Disposition字段中识别 URL 风格的百分编码,这种情况也被 Chrome 采纳)并默认使用 UTF-8。另一方面,Firefox 和 Opera 更喜欢支持 RFC 2231 中提出的独特百分编码语法,^([120)这与 HTTP 语法应有的样子有显著差异:
Content-Disposition: attachment; filename*=`utf-8'en-us'Hi%21.txt`
聪明的读者可能会注意到,没有一种编码方案被所有浏览器同时支持。这种情况促使一些 Web 应用程序开发者求助于在 HTTP 头中使用原始的高位值,通常解释为 UTF-8,但这样做是相当不安全的。例如,在 Firefox 中,一个长期存在的漏洞导致 UTF-8 文本在放入Cookie头时被破坏,允许攻击者注入的 cookie 分隔符出现在意想不到的地方.^([121)。换句话说,没有简单且健壮的解决方案来解决这个问题。
当讨论字符编码时,处理空字符(0x00)的问题可能值得提及。这个字符在许多编程语言中用作字符串终止符,在技术上禁止出现在 HTTP 头中(除了上述功能不正常的quoted-pair语法),但正如你可能记得的,解析器被鼓励要有容忍度。当这个字符被允许通过时,它可能会产生意外的副作用。例如,Content-Disposition头在 NUL 处被 Internet Explorer、Firefox 和 Chrome 截断,但不是 Opera 或 Safari。
引用头行为
如本章前面所述,HTTP 请求可能包括一个Referer标题。此标题包含以某种方式触发当前导航的文档的 URL。它的目的是帮助进行某些故障排除任务,并通过强调相关网页之间的交叉引用来促进网络的增长。
不幸的是,此标题也可能向某些不友好方透露有关用户浏览习惯的信息,并且它可能泄露在引用页面 URL 查询参数中编码的敏感信息。由于这些担忧以及随后的缓解这些担忧的糟糕建议,该标题经常被误用于安全或政策执行目的,但它并不胜任这项任务。主要问题是无法区分一个客户端没有提供标题是因为用户隐私偏好,一个客户端没有提供标题是因为正在进行的导航类型,以及一个客户端被恶意引用网站故意欺骗以隐藏这些信息。
通常,此标题包含在大多数 HTTP 请求中(并在 HTTP 级别重定向中保留),但在以下场景中除外:
-
在有机地将新 URL 输入地址栏或打开书签页面后。
-
当导航来自伪 URL 文档时,例如data:或javascript:。
-
当请求是由Refresh标题控制的重定向结果(但不是基于Location的重定向)。
-
当引用网站加密但请求的页面未加密时。根据 RFC 2616 第 15.1.2 节,这是出于隐私原因,但这并没有太多意义。当从一个加密域导航到不相关的加密域时,Referer字符串仍然会向第三方披露,请放心,加密的使用并不等同于可信性。
-
如果用户决定通过调整浏览器设置或安装以隐私为导向的插件来阻止或伪造此标题。
如应明显,这五个条件中有四个可以通过任何恶意网站故意诱导。
^([14]) 传输控制协议(TCP)是互联网的核心通信协议之一,为构建在其之上的任何应用协议提供传输层。TCP 为网络主机之间提供合理可靠的、对等确认的、有序的、基于会话的连接。在大多数情况下,该协议对其他非本地主机尝试的盲目数据包伪造攻击也相当有弹性。
^([15]) MIME 类型(也称为Internet 媒体类型)是一个简单的两分量值,用于标识任何给定计算机文件的类别和格式。该概念起源于 RFC 2045 和 RFC 2046,当时它被用作描述电子邮件附件的方式。官方值注册表(如text/plain或audio/mpeg)目前由 IANA 维护,但临时类型相当常见。
^([16]) 在这种情况下,客户端提供的某些 HTTP 头部可能被代理内部使用,但它们不会被传输到非 HTTP 终端,这创造了一些有趣但与安全无关的协议歧义。
^([17]) Quoted-printable 是一种简单的编码方案,它将任何不可打印或非法字符替换为等号 (=) 后跟要编码的 8 位字符值的 2 位十六进制表示。输入文本中的任何多余的等号都必须替换为“=3D”。
^([18]) Base64 是一种非人类可读的编码,它使用大小写敏感的字母数字、加号(“+”)和斜杠(“/”)的 6 位字母表来编码任意 8 位输入。每 3 个字节的输入映射到 4 个字节的输出。如果输入不以 3 个字节的边界结束,则通过在输出字符串末尾附加一个或两个等号来表示。
HTTP 请求类型
原始 HTTP/0.9 草案提供了一个用于请求文档的单个方法(或“动词”):GET。随后的提案尝试了一系列越来越奇怪的方法,以允许除了检索文档或运行脚本之外的其他交互,包括诸如 SHOWMETHOD、CHECKOUT 或——为什么不呢——SPACEJUMP 这样的奇特方法.^([122])
大多数这些思想实验在 HTTP/1.1 中已被放弃,它确定了一套更易于管理的八种方法。只有前两种请求类型——GET 和 POST——对大多数现代 Web 具有任何意义。
GET
GET 方法旨在表示信息检索。在实践中,它被用于正常浏览会话期间几乎所有客户端-服务器交互。常规 GET 请求不携带浏览器提供的有效负载,尽管它们并不严格禁止这样做。
预期 GET 请求不应具有,引用 RFC 中的说法,“采取除检索之外行动的意义”(也就是说,它们不应对应用程序的状态造成持久性更改)。在现代网络应用中,这一要求越来越没有意义,因为应用程序的状态通常甚至不在服务器端完全管理;因此,这一建议被应用程序开发者广泛忽视.^([19])
注意
在 HTTP/1.1 中,客户端可以通过在 GET 请求(以及较少见的其他请求类型)中指定 Range 头部来请求目标文档的任何一组可能非连续或重叠的片段。服务器没有义务遵守,但如果机制可用,浏览器可以使用它来恢复中断的下载。
POST
POST 方法旨在将信息(主要是 HTML 表单)提交给服务器进行处理。由于 POST 操作可能具有持久性副作用,许多浏览器在重新加载使用 POST 获取的任何内容之前都会要求用户确认,但就大部分而言,GET 和 POST 以准可互换的方式使用。
POST 请求通常伴随着一个负载,其长度由 Content-Length 头部指示。在纯 HTML 的情况下,负载可能由 URL 编码或 MIME 编码的表单数据组成(格式在第四章第四章。超文本标记语言中详细说明),尽管再次强调,在 HTTP 层面上,其语法并没有任何特殊的限制。
HEAD
HEAD 是一种很少使用的请求类型,它与 GET 实质上相同,但只返回请求内容的 HTTP 头部,而不是实际的负载。浏览器通常不会自行发出 HEAD 请求,但该方法有时会被搜索引擎爬虫和其他自动化工具使用,例如,用于探测文件的存在或检查其修改时间。
OPTIONS
OPTIONS 是一个元请求,它返回特定 URL(或“*”,表示服务器本身)在响应头中支持的集合方法。在实际情况中,OPTIONS 方法几乎从不使用,除了用于服务器指纹识别;由于其价值有限,返回的信息可能并不非常准确。
注意
为了完整性,我们需要指出,OPTIONS 请求也是一项提议的跨域请求授权方案的基础,因此它们可能会很快获得一些重视。我们将在第十六章中重新审视这个方案,并探讨许多即将推出的浏览器安全功能。
PUT
PUT 请求的目的是允许将文件上传到指定的目标 URL 所在的服务器。由于浏览器不支持 PUT,故有意文件上传功能几乎总是通过 POST 到服务器端脚本实现,而不是使用这种理论上更优雅的方法。
话虽如此,一些非 Web HTTP 客户端和服务器可能会出于自己的目的使用 PUT。同样有趣的是,一些 Web 服务器可能配置错误,会无差别地处理 PUT 请求,从而造成明显的安全风险。
DELETE
DELETE 是一种自解释的方法,它补充了 PUT(在实际情况中同样不常见)。
TRACE
TRACE 是一种“ping”请求的形式,它返回有关处理请求所涉及的所有代理跳转的信息,并回显原始请求。TRACE 请求不是由 Web 浏览器发出的,并且很少用于合法目的。TRACE 的主要用途是安全测试,它可能会揭示远程网络中 HTTP 服务器内部结构的有趣细节。正是出于这个原因,服务器管理员通常会禁用此方法。
CONNECT
CONNECT 方法是为通过 HTTP 代理建立非 HTTP 连接而保留的。它不是直接发送给服务器的。如果某个服务器意外启用了 CONNECT 请求的支持,它可能会通过为攻击者提供一种将 TCP 流量隧道到本应受保护的网络中的方式,从而构成安全风险。
其他 HTTP 方法
许多其他请求方法可能被其他非浏览器应用程序或浏览器扩展使用;最受欢迎的一组 HTTP 扩展可能是 WebDAV,这是一个在 RFC 4918 中描述的作者和版本控制协议.^([123])
此外,XMLHttpRequest API 名义上允许客户端 JavaScript 以几乎任意的方法向原始服务器发出请求——尽管这种最后的功能在某些浏览器中受到严格限制(我们将在第九章内容隔离逻辑中探讨这一点)。
^([19]) 有一个关于不幸的网站管理员约翰·布雷克曼的轶事(也许甚至是真的)。据故事所说,约翰的网站被搜索引擎索引机器人意外删除。机器人无意中发现了约翰为他的网站构建的一个未经身份验证的基于 GET 的行政界面……并且高兴地跟随它找到的每一个“删除”链接。
服务器响应代码
RFC 2616 的第十部分列出了服务器在构建响应时可能选择的近 50 个状态代码。其中大约有 15 个在实际生活中使用,其余的用于表示越来越奇怪或不寻常的状态,例如“402 需要付款”或“415 不支持的媒体类型”。大多数 RFC 列出的状态与现代 Web 应用程序的行为不匹配;它们存在的唯一原因是有人希望它们最终会这样。
一些代码值得记忆,因为它们很常见或具有特殊含义,如下所述。
200-299:成功
这个状态代码范围用于表示请求的成功完成:
200 OK
这是对成功 GET 或 POST 的正常响应。浏览器将显示随后返回的有效载荷给用户,或以某种其他上下文特定的方式处理它。
204 无内容
此代码有时用于表示一个成功的请求,不需要详细的响应。204 响应会中止触发它的 URL 的导航,并保持用户在原始页面上。
206 部分内容
此代码类似于 200,但它是服务器在响应范围请求时返回的。浏览器必须已经拥有文档的一部分(否则它不会发出范围请求),并且通常会在进一步处理之前检查Content-Range响应头以重新组装文档。
300-399:重定向和其他状态消息
这些代码用于传达各种状态,这些状态不表示错误,但需要在浏览器端进行特殊处理:
301 永久移动,302 找到,303 查看其他
这个响应指示浏览器在新位置重试请求,该位置由 Location 响应头指定。尽管 RFC 中有明确的区分,但当遇到这些响应代码中的任何一个时,所有现代浏览器都会自动将 POST 替换为 GET,删除有效载荷,然后重新提交请求。
注意
重定向消息可能包含有效载荷,但如果包含,除非重定向不可行(例如,由于缺少或不支持的 Location 值),否则此消息不会显示给用户。实际上,在某些浏览器中,即使在那种情况下,也可能抑制消息的显示。
304 未修改
这个非重定向响应指示客户端,请求的文档与客户端已有的副本相比没有修改。这个响应通常出现在带有 If-Modified-Since 等头的条件请求之后,这些请求是为了重新验证浏览器文档缓存。响应体不会显示给用户。(如果服务器对无条件请求以这种方式响应,结果将是浏览器特定的,可能非常有趣;例如,Opera 将弹出非功能性的下载提示。)
307 临时重定向
与 302 类似,但与其它重定向模式不同,当遵循 307 重定向时,浏览器不会将 POST 降级为 GET。这个代码在 Web 应用程序中不常用,一些浏览器在处理它时表现并不一致。
400-499: 客户端错误
这个代码范围用于指示由客户端行为引起的错误条件:
400 错误请求(及相关消息)
服务器无法或不愿意以某种未指定原因处理请求。响应有效载荷通常会在一定程度上解释问题,并且通常会被浏览器像 200 响应一样处理。
存在更多具体的变体,例如“411 长度要求”,“405 方法不允许”或“414 请求-URI 太长”,也都有。为什么在需要指定 Content-Length 时有专门的 411 响应代码,而未指定 Host 只需要通用的 400 响应代码,这谁也说不清。
401 未授权
这个代码意味着用户需要提供协议级别的 HTTP 认证凭据才能访问资源。浏览器通常会提示用户输入登录信息,并且只有在认证过程失败时才会呈现响应体。这个机制将在稍后的 HTTP 认证 中详细介绍。
403 禁止访问
请求的 URL 存在,但由于除不正确的 HTTP 认证之外的其他原因无法访问。可能的原因包括文件系统权限不足、阻止此请求处理的配置规则,或某些类型的凭证不足(例如,无效的 cookie 或未识别的源 IP 地址)。通常,响应将显示给用户。
404 未找到
请求的 URL 不存在。通常,响应体将显示给用户。
500-599:服务器端错误
这是一类响应服务器端问题的错误消息:
500 内部服务器错误,503 服务不可用,等等
服务器遇到问题,阻止其满足请求。这可能是一种暂时状态,是配置错误的结果,或者仅仅是请求一个意外位置的效果。通常,响应会显示给用户。
HTTP 状态码信号的一致性
由于返回大多数 2xx、4xx 和 5xx 状态码之间没有立即可观察的差异,因此这些值没有用任何特殊的热情来选择。特别是,Web 应用程序因在发生应用程序错误并在结果页面上进行通信时返回“200 OK”而臭名昭著。(这是使 Web 应用程序的自动化测试比实际需要的更困难的多因素之一。)
在罕见的情况下,会为特定用途发明新的、不一定合适的 HTTP 状态码。其中一些是标准化的,例如在 WebDAV RFC 中引入的几条消息.^([124]) 其他,如微软的 Microsoft Exchange “449 重试”状态,则不是。
持久连接
最初,HTTP 会话旨在一次性发生:为每个 TCP 连接发送一个请求,冲洗,然后重复。反复完成三次 TCP 握手(以及在传统的 Unix 服务器设计模型中启动一个新进程)的开销很快证明是一个瓶颈,因此 HTTP/1.1 标准化了持久连接的想法。
现有的协议已经让服务器理解客户端请求的结束位置(一个空行,可选地后跟 Content-Length 字节的数据),但为了继续使用现有的连接,客户端也需要了解返回的文档相同的信息;连接的终止不能再作为指示器。因此,持久连接需要响应包括一个 Content-Length 标头,始终指定后续数据的数量。一旦收到这么多有效载荷字节,客户端就知道可以发送第二个请求并开始等待另一个响应。
虽然从性能角度来看非常有益,但这种机制的设计加剧了 HTTP 请求和响应拆分错误的影响。客户端和服务器在同步哪个响应属于哪个请求时很容易出现误导。为了说明这一点,让我们考虑一个认为自己在发送单个 HTTP 响应的服务器,其结构如下:
HTTP/1.1 200 OK[CR][LF]
Set-Cookie: term=`[CR]Content-Length: 0[CR][CR]HTTP/1.1 200 OK[CR]Gotcha: Yup`[CR][LF]
Content-Length: 17[CR][LF]
[CR][LF]
Action completed.
另一方面,客户端可能会看到两个响应,并将第一个响应与其最新的请求关联起来,将第二个响应与尚未发出的查询^([20])(这甚至可能指向同一 IP 上的不同主机名)关联起来:
HTTP/1.1 200 OK
Set-Cookie: term=
Content-Length: 0
`HTTP/1.1 200 OK`
`Gotcha: Yup`
`Content-Length: 17`
Action completed.
如果缓存 HTTP 代理看到这个响应,错误的结果也可能被全局缓存并返回给其他用户,这真的是一个坏消息。一个更安全的 keepalive 会话设计应该包括提前指定头和负载的长度,或者使用随机生成且不可预测的边界来界定每个响应。遗憾的是,该设计并没有做到这一点。
在 HTTP/1.1 中,除非明确关闭(Connection: close),否则默认使用 keepalive 连接,并且当启用Connection: keep-alive头时,许多 HTTP/1.0 服务器也支持它。服务器和浏览器都可以限制每个连接可以服务的并发请求数量,并可以指定空闲连接保持的最大时间。
^([20]) 从原则上讲,客户端可以被设计成在 keepalive 会话中发出任何后续请求之前,吸收任何未经请求的服务器响应数据,从而限制攻击的影响。然而,由于 HTTP 管道化的实践;出于性能原因,一些客户端被设计成一次丢弃多个请求,而不等待接收完整响应。
分块数据传输
基于Content-Length的 keepalive 会话的一个重大限制是服务器需要提前知道返回响应的确切大小。当处理静态文件时,这是一个相当简单的工作,因为信息已经在文件系统中可用。当提供动态生成数据时,问题变得更加复杂,因为输出必须在发送到客户端之前完整地缓存。如果负载非常大或逐渐生成(例如,实时视频流),挑战变得无法克服。在这些情况下,预先缓存以计算负载大小根本不可行。
针对这一挑战,RFC 2616 第 3.6.1 节赋予服务器使用Transfer-Encoding: chunked的能力,这是一种在数据可用时将其分部分发送的方案。文档每一部分的长度都通过一个单独的行上的十六进制整数提前声明,但直到看到一个长度为零的最终块,文档的总长度是不确定的。
一个示例分块响应可能看起来像这样:
HTTP/1.1 200 OK
Transfer-Encoding: chunked
...
5
Hello
6
world!
0
支持分块数据传输没有显著的缺点,除了可能导致浏览器代码中的整数溢出或需要解决 Content-Length 和块长度之间的不匹配的可能性(尽管任何尝试优雅地处理这种情况似乎都是不明智的)。所有流行的浏览器都正确处理这些条件,但新的实现需要小心行事。
缓存行为
由于性能和带宽节约的原因,HTTP 客户端和一些中间代理渴望缓存 HTTP 响应以供以后重用。在 Web 的早期,这似乎是一个简单的任务,但随着 Web 包含越来越多的敏感、用户特定信息,以及这些信息更新得越来越频繁,这变得越来越充满危险。
RFC 2616 第 13.4 节指出,对于以一系列 HTTP 代码(最显著的是“200 OK”和“301 Moved Permanently”)响应的 GET 请求,在没有其他服务器提供的指令的情况下,可以隐式缓存。这样的响应可以无限期地存储在缓存中,并且可以用于任何涉及相同请求方法和目标 URL 的未来请求,即使其他参数(如 Cookie 头部)不同。禁止缓存使用 HTTP 认证的请求(参见 HTTP 认证),但规范中未认可其他认证方法,如 cookies。
当响应被缓存时,实现方式可以选择在重用之前对其进行重新验证,但大多数情况下并不需要这样做。重新验证是通过带有特殊条件头部的请求来实现的,例如 If-Modified-Since(后面跟着之前缓存的响应中记录的日期)或 If-None-Match(后面跟着服务器与较早副本一起返回的不透明 ETag 头部值)。服务器可能会以“304 Not Modified”代码响应,或者返回资源的较新副本。
注意
当 Date/If-Modified-Since 和 ETag/If-None-Match 头部对与 Cache-Control: private 结合使用时,为网站提供了一个方便且完全意外的存储长期、唯一令牌的方法,在浏览器中。^([125]) 同样,也可以通过在可缓存的 JavaScript 文件中存储一个唯一令牌,并对所有未来的针对生成令牌位置的带条件请求返回“304 Not Modified”,来实现这一点。与专门构建的机制,如 HTTP cookies(在下节中讨论)不同,用户对存储在浏览器缓存中的信息、存储条件以及存储时间几乎没有任何控制权。
隐式缓存问题很大,因此,服务器几乎总是应该求助于使用显式的 HTTP 缓存指令。为此,HTTP/1.0 提供了一个 Expires 头部,指定缓存副本应丢弃的日期;如果此值等于服务器提供的 Date 头部,则响应不可缓存。除了这个简单的规则之外,Expires 和 Date 之间的关系是不明确的:不清楚 Expires 是否应该与缓存系统的系统时钟进行比较(如果客户端和服务器时钟不同步,则可能存在问题)或者基于 Expires - Date 差值进行评估(这更稳健,但如果 Date 误被省略,则可能停止工作)。Firefox 和 Opera 使用后一种解释,而其他浏览器则更喜欢前者。在大多数浏览器中,无效的 Expires 值也会阻止缓存,但依赖它是风险很大的。
HTTP/1.0 客户端还可以包含一个 Pragma: no-cache 请求头部,这可以被代理解释为获取请求资源的新的副本,而不是返回现有的副本。一些 HTTP/1.0 代理也识别一个非标准的 Pragma: no-cache 响应头部作为不制作文档副本的指令。
与之相反,HTTP/1.1 对缓存指令采用了更为实质性的方法,引入了一个新的 Cache-Control 头部。该头部包含诸如 public(文档可以公开缓存)、private(代理不允许缓存)、no-cache(有点令人困惑——响应可以被缓存但不应用于未来的请求)^([21]) 和 no-store(绝对不缓存)等值。公共和私有缓存指令可以伴随一个限定符,例如 max-age,指定旧副本应保留的最大时间,或者 must-revalidate,请求在内容重用之前进行条件请求。
不幸的是,服务器通常需要返回 HTTP/1.0 和 HTTP/1.1 缓存指令,因为某些类型的旧版商业代理无法正确理解 Cache-Control。为了可靠地防止通过 HTTP 缓存,可能需要使用以下响应头集合:
Expires: [current date]
Date: [current date]
Pragma: no-cache
Cache-Control: no-cache, no-store
当这些缓存指令不一致时,行为难以预测:一些浏览器将优先考虑 HTTP/1.1 指令,并优先考虑 no-cache,即使它错误地跟在 public 之后;而其他浏览器则不是这样。
HTTP 缓存的一个风险与不安全的网络有关,例如公共 Wi-Fi 网络,这些网络允许攻击者拦截对某些 URL 的请求,并在对受害者的请求中返回修改后的、长时间可缓存的 内容。如果这种受污染的浏览器缓存随后在受信任的网络中使用,注入的内容将意外地再次出现。奇怪的是,受害者甚至不必访问目标应用程序:攻击者可以将对精心选择的敏感域的引用注入到其他上下文中。目前还没有好的解决方案来解决这个问题;在访问星巴克后清除浏览器缓存可能是一个非常不错的选择。
^([21]) RFC 在这方面有些模糊,但看起来意图是允许缓存的文档用于诸如在浏览器中操作“后退”和“前进”导航按钮等目的,但不适用于请求正确页面加载的情况。Firefox 采用这种方法,而所有其他浏览器认为 no-cache 和 no-store 大致相同。
HTTP Cookie 语义
HTTP cookies 并不是 RFC 2616 的一部分,但它们是 Web 上使用的重要协议扩展之一。cookie 机制允许服务器通过发送 Set-Cookie 响应头来在浏览器中存储短而透明的 name=value 对,并在未来的请求中通过客户端提供的 Cookie 参数接收它们。cookie 是迄今为止维护会话和验证用户请求最受欢迎的方式;它们是 Web 上四种规范形式的 环境权限^([22]) 之一(其他形式包括内置的 HTTP 认证、IP 检查和客户端证书)。
该机制最初由 Lou Montulli 在 1994 年左右在 Netscape 实现,并在一份简短的四页草案文档中描述,^([126]) 在过去 17 年中,该机制并未在适当的标准中进行概述。1997 年,RFC 2109^([127]) 试图记录现状,但有些令人费解地,它还提出了一系列广泛的变更,这些变更至今仍使该规范与任何现代浏览器的实际行为存在实质性不兼容。另一个雄心勃勃的努力——Cookie2——出现在 RFC 2965^([128]) 中,但十年后,它几乎在浏览器级别上没有支持,这种情况不太可能改变。在本书出版前不久,一项新的努力——RFC 6265^([129])——完成了对合理准确的 cookie 规范的编写,最终结束了这一规范相关的痛苦。
由于长期缺乏任何真正的标准,实际的实现以非常有趣且有时不兼容的方式发展。在实践中,可以使用 Set-Cookie 头部后跟一个单个的 name=value 对和一系列可选的分号分隔的参数来设置新的 cookie,这些参数定义了 cookie 的作用域和生存期。
过期时间
以类似于 Date 或 Expires HTTP 报头使用的格式指定 cookie 的过期日期。如果 cookie 没有提供明确的过期日期,它通常会被保存在内存中,直到浏览器会话结束(特别是在具有挂起功能的便携式计算机上,这可以轻松跨越几周)。具有明确过期日期的 cookie 可能会被常规地保存到磁盘上,并在会话之间持续存在,除非用户的隐私设置明确阻止这种可能性。
Max-age
这种替代的、RFC 建议的过期机制在 Internet Explorer 中不受支持,因此在实践中没有使用。
域名
此参数允许 cookie 范围限定到比返回 Set-Cookie 报头的 hostname 更宽泛的域名。此范围机制的精确规则和安全后果将在 第九章 中探讨。
注意
与 RFC 2109 中暗示的不同,使用此参数时无法将 cookie 范围限定到特定的主机名。例如,domain=example.com 总是会匹配 www.example.com。省略 domain 是创建主机范围 cookie 的唯一方法,但即使这种方法在 Internet Explorer 中也没有按预期工作。
路径
允许 cookie 范围限定到特定的请求路径前缀。这不是一个可行的安全机制,原因已在 第九章 中解释,但它可能用于方便起见,防止应用不同部分中使用的同名 cookie 发生冲突。
Secure 属性
防止生成的 cookie 通过非加密连接发送。
HttpOnly 属性
移除了通过 JavaScript 中的 document.cookie API 读取 cookie 的能力。这是一个微软的扩展,尽管现在所有主流浏览器都支持它。
当向在 cookie jar 中找到有效 cookie 的域名发出未来请求时,浏览器会将所有适用的 name=value 对组合成一个单独的分号分隔的 Cookie 报头,不包含任何其他元数据,并将其返回给服务器。如果某个请求需要发送过多的 cookie,服务器强制性的报头大小限制将被超过,请求可能会失败;没有从这种状态恢复的方法,除了手动清空 cookie jar。
奇怪的是,没有明确的方法让 HTTP 服务器删除不必要的 cookie。然而,每个 cookie 都由一个独特的名称-域名-路径三元组(忽略 secure 和 httponly 属性)唯一标识,这允许简单地覆盖已知范围的旧 cookie。此外,如果覆盖的 cookie 有一个过去的 expires 日期,它将立即被丢弃,这实际上提供了一种人为清除数据的方法。
虽然 RFC 2109 要求在单个 Set-Cookie 头中接受多个逗号分隔的 cookie,但这种做法是危险的,并且不再被任何浏览器支持。Firefox 允许通过 document.cookie JavaScript API 在单个步骤中设置多个 cookie,但不可思议的是,它要求使用换行符作为分隔符。没有浏览器使用逗号作为 Cookie 分隔符,在服务器端识别它们应被视为不安全的。
规范与现实之间另一个重要的区别是,cookie 值应使用 HTTP 规范中概述的 quoted-string 格式(参见 Semicolon-Delimited Header Values),但只有 Firefox 和 Opera 在实际中识别这种语法。因此,依赖 quoted-string 值是不安全的,允许攻击者控制的 cookie 中存在散乱的引号字符也是如此。
Cookies 并不保证特别可靠。用户代理对每个域允许的 cookie 数量和大小执行适度设置,并且作为一种错误的隐私功能,它们还可能限制它们的生存期。由于可以通过其他方式,如前一部分中概述的 ETag/If-None-Match 行为,以同样可靠的方式实现用户跟踪,因此限制基于 cookie 的跟踪的努力可能弊大于利。
^([22]) 环境权限 是一种基于请求实体全局和持久属性的一种访问控制形式,而不是任何仅对特定操作有效的明确授权形式。在向远程站点发出的每个请求中不加区分地包含用于识别用户的 cookie,而不考虑为什么发起这个请求,就属于这一类别。
HTTP 认证
根据 RFC 2617 指定的 HTTP 认证,是最初为 Web 应用程序设想的一种凭证处理机制,现在几乎已经完全灭绝。这种结果的原因可能包括与浏览器级别 UI 相关的僵化性、难以适应更复杂的非基于密码的认证方案,或者可能是因为无法控制凭据的缓存时间以及它们共享的其他域。
在任何情况下,基本方案相当简单。它始于浏览器发起一个未经身份验证的请求,服务器随后响应一个“401 未授权”代码.^([23]) 服务器还必须包含一个 WWW-Authenticate HTTP 头,指定请求的认证方法、realm 字符串(一个任意标识符,输入的凭据应与其绑定),以及适用的方法特定参数。
客户端应通过某种方式获取凭据,将它们编码在授权头中,并包含此头重试原始请求。根据规范,出于性能原因,相同的授权头也可以包含在随后的对同一服务器路径前缀的请求中,无需第二次WWW-Authenticate挑战。如果realm字符串和认证方法匹配,也可以在服务器上的其他任何WWW-Authenticate挑战中重用相同的凭据。
在实践中,这些建议并没有被严格遵守:除了 Safari 和 Chrome 之外,大多数浏览器忽略了realm字符串,或者对路径匹配采取了宽松的态度。另一方面,所有浏览器都将缓存的凭据范围不仅限于目标服务器,还包括特定的协议和端口,这种做法提供了一些安全优势。
原始 RFC 中指定的两种凭据传递方法被称为基本和摘要。第一种方法本质上是以base64编码的明文发送密码。另一种方法计算出一个一次性加密散列,保护密码不被以明文形式查看,并防止授权头在之后被重放。不幸的是,现代浏览器支持这两种方法,并且没有以任何明显的方式区分它们。因此,攻击者可以简单地将在初始请求中将单词摘要替换为基本,一旦用户完成身份验证对话框,就可以立即获得一个干净的明文密码。令人惊讶的是,RFC 的第 4.8 节预测了这种风险,并提供了一些有用的但最终被忽视的建议:
用户代理应考虑采取一些措施,例如在请求凭据时提供视觉指示,说明将要使用的认证方案,或者记住服务器请求过的最强认证方案,并在使用较弱的方案之前发出警告信息。也许用户代理还应配置为在一般情况下或从特定站点要求摘要认证。
除了这两种 RFC 指定的认证方案之外,一些浏览器还支持不太常见的方法,例如微软的NTLM和协商,这些方法用于与 Windows 域凭据无缝认证。^[[131]
虽然 HTTP 认证在互联网上很少遇到,但它仍然对某些类型的 Web 应用投下了长长的阴影。例如,当外部攻击者提供的图像被包含在论坛的一个帖子中,而托管该图像的服务器突然决定在某些请求上返回“401 未授权”时,查看该帖子的用户会突然看到一个有些神秘的密码提示。在仔细检查地址栏后,许多人可能会将提示误认为是输入论坛凭证的请求,并将这些凭证立即转发给攻击者的图像托管服务器。哎呀。
^([23]) 在这个 RFC 中,似乎将“认证”和“授权”这两个术语互换使用,但在信息安全的其他地方,它们有着独特的含义。“认证”通常用来指证明你的身份的过程,而“授权”则是确定你之前建立的凭证是否允许你执行特定的特权操作。
协议级加密和客户端证书
如现在所应显而易见,HTTP 会话中的所有信息都在网络上以明文形式交换。在 20 世纪 90 年代,这不会成为什么大问题:当然,明文会暴露你的浏览选择给好奇的 ISP,也许还会给办公室网络中的另一个淘气的用户或一个过于热情的政府机构,但这似乎并不比 SMTP、DNS 或其他常用应用协议的行为更糟。然而,随着 Web 作为商业平台的日益流行,这种风险加剧了,而公共无线网络固有的不安全性导致的网络安全大幅倒退,又给这个棺材钉上了另一颗钉子。
在几次不太成功的黑客攻击之后,RFC 2818 提出了一个直接解决这个问题的方法:为什么不将正常的 HTTP 请求封装在几年前开发的现有、多用途的传输层安全性(TLS,也称为 SSL)机制中?这种传输方法利用公钥密码学^([24])在两个端点之间建立一个保密、认证的通信通道,而不需要任何 HTTP 级别的调整。
为了让网络服务器证明其身份,每个启用了 HTTPS 的网页浏览器都附带了一组庞大的公钥,这些公钥属于各种证书颁发机构。证书颁发机构是那些被浏览器供应商信任的组织,它们通过密码学方式证明特定的公钥属于特定的网站,希望是在验证请求这种证明的人的身份以及验证他对相关域的主张之后。
受信任的组织集合是多样化的、任意的,并且没有特别好的文档记录,这往往引发有效的批评。但最终,系统通常能合理地完成工作。到目前为止,只有少数几个错误被记录下来(包括最近一家名为 Comodo 的公司的高调妥协[133]),并且没有记录广泛滥用 CA 特权的案例。
关于实际实现,当建立新的 HTTPS 连接时,浏览器从服务器接收一个签名后的公钥,验证签名(如果没有访问 CA 的私钥,签名是无法伪造的),检查证书中签名的cn(通用名称)或subjectAltName字段表明此证书是为浏览器想要与之通信的服务器签发的,并确认该密钥不在公共撤销列表上(例如,由于被破坏或欺诈性地获得)。如果一切检查无误,浏览器就可以通过使用该公钥加密消息到服务器,并确信只有那个特定的当事人能够解密它们。
通常情况下,客户端保持匿名:它生成一个临时的加密密钥,但这个过程并不能证明客户端的身份。尽管如此,这样的证明是可以安排的。某些组织内部接受客户端证书,并在世界上的几个国家达到国家层面(例如,用于电子政府服务)。由于客户端证书的通常目的是提供有关用户现实世界身份的一些信息,因此出于隐私原因,浏览器通常在将它们发送到新遇到的网站之前会提示用户;除此之外,证书还可以作为另一种环境权威的形式。
值得注意的是,尽管 HTTPS 本身是一个能够抵御被动和主动攻击者的健全方案,但它对隐藏访问先前公开信息的证据几乎没有什么帮助。它不会掩盖典型浏览会话中的粗略 HTTP 请求和响应大小、流量方向和时间模式,这使得未受训练的、被动的攻击者能够推断出,例如,受害者通过加密通道正在查看维基百科上的哪个令人尴尬的页面。实际上,在一种极端情况下,微软研究人员展示了使用这种数据包分析来重建在线应用程序中的用户按键的方法[134])。
扩展验证证书
在 HTTPS 的早期阶段,许多公共证书机构在签署证书之前,会进行相当繁琐和复杂的用户身份和域名所有权检查。不幸的是,为了追求便利和降低价格,一些现在只需要一张有效的信用卡和将文件上传到目标服务器的能力,就可以完成验证过程。这种方法使得除了cn和subjectAltName之外的大多数证书字段都不可信。
为了解决这个问题,一种新的证书类型正在以显著更高的价格进行推广,这种证书使用特殊的标志进行标记:扩展验证 SSL (EV SSL)。这些证书不仅预期可以证明域名所有权,而且通过手动验证过程,还能更可靠地证明请求方的身份。EV SSL 通过使地址栏的一部分变为蓝色或绿色而被所有现代浏览器所识别。尽管拥有这一级别的证书很有价值,但将更高价格的证书与一个含糊暗示“更高安全级别”的指示器相结合的想法,通常被批评为一种巧妙伪装的赚钱手段。
错误处理规则
在一个理想的世界里,涉及可疑证书错误(如严重不匹配的主机名或未知的证书机构)的 HTTPS 连接应该简单地导致连接失败。不太可疑的错误,如最近到期的证书或主机名不匹配,可能只需伴随一个温和的警告。
不幸的是,大多数浏览器不加区分地将理解问题的责任委托给了用户,努力(但最终失败)用通俗易懂的语言解释密码学,并要求用户做出二选一的决定:你真的想看到这个页面吗?(图 3-1 显示了这样一个提示。)
![在仍然流行的 Internet Explorer 6 中的示例证书警告对话框]()
图 3-1. 在仍然流行的 Internet Explorer 6 中的示例证书警告对话框
SSL 警告的语言和外观在多年中逐渐演变为越来越简单(但仍然有问题)的问题解释和更复杂的绕过警告所需采取的行动。这种趋势可能是错误的:研究表明,即使是令人恐惧和破坏性的警告,也有超过 50%的人会点击通过。 虽然容易责怪用户,但最终,我们可能是在问他们错误的问题,并提供了完全错误的选择。简单来说,如果认为在某些情况下点击通过警告是有利的,提供以清晰标记的“沙盒”模式打开页面的选项,其中危害有限,将是一个更合理的解决方案。如果没有这样的信念,任何覆盖能力都应该完全消除(这是严格传输安全的目标,一个将在第十六章中讨论的实验性机制)。
安全工程速查表
当处理内容处置头中的用户控制文件名时
-
如果您不需要非拉丁字符: 删除或替换除字母数字、“.”、“-”和“_”之外的所有字符。为了保护您的用户免受可能有害或误导性的文件名的影响,您还可能希望确认至少第一个字符是字母数字的,并将除了最右侧的点之外的所有字符替换为其他内容(例如,下划线)。
请记住,允许引号、分号、反斜杠和控制字符(0x00-0x1F)将引入漏洞。
-
如果您需要非拉丁字母的名字: 您必须以浏览器依赖的方式使用 RFC 2047、RFC 2231 或 URL 风格的百分号编码。确保过滤掉控制字符(0x00-0x1F),并转义任何分号、反斜杠和引号。
当将用户数据放入 HTTP Cookie 中时
- 除了字母数字之外的所有内容都需要进行百分号编码。更好的做法是使用 base64。多余的引号字符、控制字符(0x00-0x1F)、高位字符(0x80-0xFF)、逗号、分号和反斜杠可能会允许注入新的 cookie 值,或者改变现有 cookie 的意义和范围。
当发送用户控制的地理位置头时
当发送用户控制的重定向头时
- 遵循关于位置的提示。请注意,分号在这个头中是不安全的,并且无法可靠地转义,但它们在某些 URL 中也有特殊含义。您的选择是完全拒绝这样的 URL,或者对“;”字符进行百分号编码,从而违反 RFC 规定的语法规则。
在构建其他类型的用户可控请求或响应时
^([24]) 公钥密码学依赖于非对称加密算法来创建一对密钥:一个私有的,由所有者保密并用于解密消息,另一个公有的,向全世界广播,仅用于加密发送给该接收者的流量,而不是解密。
第四章:超文本标记语言
超文本标记语言(HTML)是编写在线文档的主要方法。关于这种语言的最早书面记录之一是蒂姆·伯纳斯-李在 1991 年发布在互联网上的一篇简要总结。^([[136]) 他的提案概述了一种基于 SGML 的语法,允许文本文档通过内联超链接和几种类型的布局辅助工具进行标注。在接下来的几年里,在伯纳斯-李爵士和丹·康诺利的指导下,该规范逐渐演变,但直到 1995 年,在第一次浏览器大战开始时,该语言的合理全面规范(HTML 2.0)才出现在 RFC 1866 中。^([[137])]
从那时起,一切变得混乱不堪:接下来的几年里,竞争的浏览器供应商不断推出各种花哨的、以展示为导向的功能,并根据自己的喜好调整了语言。已经尝试对原始 RFC 进行多次修订,但最终,由 IETF 管理的标准化方法证明过于僵化。新成立的万维网联盟接管了该语言的维护工作,并于 1997 年最终发布了 HTML 3.2 规范。^([[138])]
新规范试图弥合浏览器实现之间的差异,同时采纳了许多吸引公众的铃声和哨声,如可定制的文本颜色和可变字体。然而,最终,HTML 3.2 在语言的清晰度上退步了,并且在追赶事实方面只取得了有限的成效。
在接下来的几年里,对 HTML 4 和 4.01 的工作主要集中在修剪 HTML 中所有累积的冗余,以及更好地解释文档元素应该如何被解释和渲染。它还定义了一种基于 XML 的替代,严格的 XHTML 语法,这更容易一致地解析,但编写起来更具惩罚性。然而,尽管所有这些工作,然而,互联网上所有网站中只有一小部分真正声称符合这些标准,客户端在解析模式和错误恢复方面几乎看不到任何一致性。因此,一些改进核心语言的工作变得毫无成效,W3C 将注意力转向了样式表、文档对象模型以及其他更抽象或前瞻性的挑战。
在 2000 年代后期,一些底层工作在 HTML5 的旗帜下得到复兴,HTML5 是一个雄心勃勃的项目,旨在规范语言语法的几乎每个方面,定义所有相关 API,并更严格地监控浏览器的一般行为。时间将证明它是否成功;在此之前,语言本身以及四个主要的解析引擎,^([25])都带有它们自己的一套令人沮丧的怪癖。
HTML 文档的基本概念
从纯粹理论的角度来看,HTML 依赖于相当简单的语法:标签的分层结构、name=value 标签参数,以及介于其中的文本节点(构成实际文档主体)。例如,一个简单的文档,包含标题、标题和超链接,可能看起来像这样:
<html>
<head>
<title>Hello world</title>
</head>
<body>
<h1>Welcome to our example page</h1>
<a href="http://www.example.com/">Click me!</a>
</body>
</html>
这种语法对参数值或文档主体内部可能出现的元素施加了一些限制。五个字符——尖括号、单引号、双引号和和号——被保留为 HTML 标记的构建块,当它们用于其预期功能之外时,需要避免或以某种方式转义。最重要的规则是:
-
在 HTML 文档的大部分部分中,不应出现多余的和号(&)。
-
两种类型的尖括号在标签内显然是问题性的,除非它们被正确引用。
-
左尖括号(<)在文本节点内是一个危险因素。
-
出现在标签内的引号字符可能会根据它们的确切位置产生不良影响,但在文本节点中是无害的。
为了允许这些字符在不引起副作用的情况下出现在问题位置,提供了一个基于和号(&)的编码方案,这在实体编码中有所讨论,该内容位于 HTML 解析生存技巧。
注意
当然,这种编码方案的可用性并不能保证其被使用。在显示用户控制的数据时未能正确过滤或转义保留字符,是导致一系列极其常见且致命的 Web 应用程序安全漏洞的原因。一个特别著名的例子是跨站脚本(XSS),这是一种攻击,恶意攻击者提供的 JavaScript 代码在 HTML 标记中无意中被回显,从而有效地使攻击者完全控制目标网站的外观和操作。
文档解析模式
对于任何 HTML 文档,可以使用顶层<!DOCTYPE>指令来指示浏览器以至少表面上符合官方定义标准的方式解析文件;在更有限的程度上,相同的信号也可以通过Content-Type头传递。在所有可用的解析模式中,XHTML 和传统 HTML 之间的差异最为显著。在传统模式下,解析器将尝试从大多数类型的语法错误中恢复,包括不匹配的开启和关闭标签。此外,标签和参数名称将被视为不区分大小写,参数值不一定需要引用,并且某些类型的标签,如
,将被隐式关闭。换句话说,以下输入将被勉强接受:
<hTmL>
<BODY>
<IMG src="/hello_world.jpg">
<a HREF=http://www.example.com/>
Click me!
</oops>
</html>
相反,XML 模式是严格的:所有标签都需要仔细平衡,使用正确的命名大小写,并且必须显式关闭。(允许使用 XML 特定的自闭合标签语法,例如
。)此外,大多数语法错误,即使是微不足道的错误,也会导致错误,并阻止文档显示。
与常规的 HTML 版本不同,基于 XML 的文档可以优雅地结合使用其他 XML 兼容的标记格式,例如 MathML,这是一种数学公式标记语言。这是通过为特定标签指定不同的xmlns命名空间设置来实现的,无需进行一次性的语言级别黑客攻击。
值得在此提及的最后一个重要区别是,传统的 HTML 解析策略具有一组特殊的模式,这些模式在遇到某些标签后进入,并在看到特定的终止字符串时退出;介于两者之间的所有内容都被解释为非 HTML 文本。这类特殊标签的例子包括, , 或类似的匹配值;任何此类块内的其他标记都不会被解释为 HTML。(有趣的是,有一个官方上已经废弃的标签
</em>, 完全无法退出;它将一直有效到文档的末尾。)</p>
<p>与之相比,XML 模式更可预测。它通常禁止在文档内部出现散乱的“<”和“&”字符,但它提供了一种特殊的语法,以“<![CDATA[”开始,以“]]>”结束,作为在任意标签内封装任何原始文本的方法。例如:</p>
<pre><code><script>
<![CDATA[
alert('>>> Hello world! <<<');
]]>
</script>
</code></pre>
<p>在 XHTML 和普通 HTML 中都可用的一种值得注意的特殊解析模式是注释块。在 XML 中,它非常简单,以“<!--”开始,以“-->”结束。在 Firefox 版本 4 之前的传统 HTML 解析器中,任何出现“--”,随后是“>”的情况也被认为足够好。</p>
<h2 id="语义之战">语义之战</h2>
<p>除了语言的底层语法之外,HTML 也是一场迷人的概念斗争的主题:在线世界的意识形态与现实之间的冲突。蒂姆·伯纳斯-李一直倡导<em>语义网</em>的愿景,这是一个相互连接的文档系统,其中每个功能块,如引用、代码片段、邮寄地址或标题,都由适当的机器可读标签(例如,<em><cite></em>, <em><code></em>, <em><address></em>, 或 <em><h1>到<h6></em>)解释其含义。</p>
<p>他和其他支持者认为,这种方法将使机器更容易以有意义的方式爬取、分析和索引内容,并且在未来不久,它将使计算机能够利用人类知识的总和进行推理。根据这种哲学,标记语言应该提供一种方式来美化文档的外观,但只能作为次要考虑。</p>
<p>伯纳斯-李爵士从未放弃过这个梦想,但在这一方面,HTML 的实际使用证明与他所期望的非常不同。网络开发者迅速地将 HTML 3.2 的精髓提炼成几个改变呈现但语义中性的标签,如<em><font></em>, <em><b></em>, 和 <em><pre></em>, 并没有看到进一步解释他们文档结构的理由。W3C 试图对抗这种趋势,但效果有限。尽管<em><font></em>这样的标签已经被成功废弃,并大量被 CSS 所取代,但这仅仅是因为样式表提供了更强大和一致的视觉控制。借助 CSS,开发者开始依赖一大堆语义无关的<em><span></em>和<em><div></em>标签来构建从标题到用户可点击按钮的一切,所有这些对任何自动内容提取工具来说都是完全透明的。</p>
<p>尽管对语言的设计产生了持久的影响,但在某些方面,语义网的观念可能正在变得过时:在线内容越来越不映射到单一可查看文档的概念,HTML 通常被简化为提供方便的绘图表面和图形原语,供 JavaScript 应用程序构建界面。</p>
<hr>
<p>^([25]) 为了处理 HTML 文档,Internet Explorer 使用 Trident 引擎(也称为 MSHTML);Firefox 和一些派生产品使用 Gecko;Safari、Chrome 以及其他几个浏览器使用 WebKit;而 Opera 则依赖于 Presto。除了 WebKit,这是一个由几家供应商维护的协作开源努力,其他引擎主要是由各自的浏览器团队内部开发的。</p>
<h1 id="理解-html-解析器行为">理解 HTML 解析器行为</h1>
<p>在前几节中概述的 HTML 语法基础通常足以理解良好形成的 HTML 和 XHTML 文档的意义。当使用 XHTML 词汇时,故事也就到此为止了:解析器的最小容错性意味着异常语法几乎总是导致解析错误。然而,情况与传统的、放松的 HTML 解析器截然不同,即使在非常模糊或可能有害的情况下,这些解析器也会积极猜测页面开发者的意图。</p>
<p>由于准确理解用户提供的标记对于设计许多类型的网络安全过滤器至关重要,让我们快速了解一下这些行为和特性。首先,考虑以下参考片段:</p>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages949999.png.jpg" alt="无标题图片" loading="lazy"></p>
<p>网页开发者通常会对这种语法可以在不改变其对浏览器意义的情况下被大幅修改感到惊讶。例如,Internet Explorer 允许在标记为 <img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950001.png" alt="" loading="lazy"> 的位置插入 NUL 字符(0x00),这种变化可能会让所有天真的 HTML 过滤器迷失方向。而且,很少有人知道在 <img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950003.png" alt="" loading="lazy"> 和 <img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950005.png" alt="" loading="lazy"> 的空白字符可以用不常见的垂直制表符(0x0B)或换页符(0x0C)字符在所有浏览器中替换,在 Opera 中则可以用非断行 UTF-8 空格(0xA0)替换.^([26]) 哦,还有一件真正令人惊讶的事情:在 Firefox 中,<img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950003.png" alt="" loading="lazy"> 的空白字符也可以被替换为一个普通的单个斜杠——然而在 <img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950005.png" alt="" loading="lazy"> 的位置则不能。</p>
<p>继续前进,标记为 <img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950007.png" alt="" loading="lazy"> 的位置也值得关注。在这个位置,大多数解析器会忽略 NUL 字符,以及许多类型的空白字符。不久前,WebKit 浏览器接受在这个位置使用斜杠,但最近的解析器改进已经消除了这个特性。</p>
<p>引号字符又是另一个引人入胜的话题。网站开发者知道可以使用单引号和双引号将包含空格或尖括号的字符串放入 HTML 参数中,但通常会让人们感到惊讶的是,在标记为<img src="http://atomoreilly.com/source/nostarch/images/950009.png" alt="图片链接" loading="lazy">的位置,Internet Explorer 也尊重反引号(`)而不是真正的引号。同样,很少有人意识到在任何浏览器中,在引号参数之后都会插入一个隐式的空格,因此在<img src="http://atomoreilly.com/source/nostarch/images/950011.png" alt="图片链接" loading="lazy">处的显式空格可以省略,而不会改变标签的意义。</p>
<p>这些模式对安全性的影响并不总是容易理解,但考虑一个 HTML 过滤器,其任务是清除带有攻击者控制的<em>title</em>参数的<em><img></em>标签。假设在输入标记中,如果该参数不包含空格和尖括号,则不会对其进行引号处理——这种设计可以在一个流行的博客网站上看到。这种做法可能一开始看起来很安全,但在以下两种情况下,恶意注入的<em>onerror</em>参数将出现在标签内部:</p>
<pre><code><img ... title=""onerror="alert(1)">
</code></pre>
<p>和</p>
<pre><code><img ... title=``onerror=`alert(1)`>
</code></pre>
<p>另一个在 Internet Explorer 中非常有趣的与引号相关的怪癖使得这项工作变得更加复杂。虽然大多数浏览器仅在参数值的开头使用引号时才识别引号,但 Internet Explorer 只是检查任何等于号(=)后跟引号的出现,并以一种相当意想不到的方式解析这种语法:</p>
<pre><code><img src=test.jpg?value=">Yes, we are still inside a tag!">
</code></pre>
<h2 id="多个标签之间的交互">多个标签之间的交互</h2>
<p>解析单个标签可能是一项艰巨的任务,但正如你可能想象的那样,多个 HTML 标签的非正常排列将更加不可预测。考虑以下简单的例子:</p>
<pre><code><i <b>
</code></pre>
<p>当遇到这样的语法时,大多数浏览器只会解释<em><i></em>,并将“<b”字符串视为无效的标签参数。然而,Firefox 版本在 4 之前,在遇到尖括号时会自动先关闭<em><i></em>标签,最终会解释<em><i></em>和<em><b></em>。本着容错的精神,直到最近 WebKit 也遵循了这一模式。</p>
<p>在处理包含无效字符(在这种情况下,是等于号)的标签名时,可以在 Firefox 的早期版本中观察到类似的行为。解析器不会尽力忽略整个块,而是简单地重置并解释引号内的标签:</p>
<pre><code><i="<b>">
</code></pre>
<p>在文件末尾之前未关闭的标签的处理同样引人入胜。例如,以下片段将导致大多数浏览器解释<em><i></em>标签或忽略整个字符串,但 Internet Explorer 和 Opera 使用不同的回溯方法,并将看到<em><b></em>:</p>
<pre><code><i foo="<b>" [EOF]
</code></pre>
<p>事实上,Firefox 版本在 4 之前,如果特定的特殊标签(如<em><title></em>)在文档末尾之前没有关闭,就会进行牵强的重新解析:</p>
<pre><code><title>This text will be interpreted as a title
<i>This text will be shown as document body!
[EOF]
</code></pre>
<p>最后两个解析怪癖在任何攻击者可能能够提前中断页面加载的场景中都有有趣的后果。即使标记本身相当干净,文档的意义可能会以非常意想不到的方式改变。</p>
<h2 id="显式和隐式条件">显式和隐式条件</h2>
<p>为了进一步复杂化 HTML 解析的工作,一些浏览器表现出一些行为,可以用来有条件地跳过文档中的一些标记。例如,为了帮助微软 Active Server Pages 开发平台的初级用户,Internet Explorer 将 <code><% ... %></code> 块视为一个完全非标准的注释,隐藏这两个字符序列之间的任何标记。另一个 Internet Explorer 特有的功能是解析器解释的显式条件表达式,并偷偷地放在标准的 HTML 注释块中:</p>
<pre><code><!--[if IE 6]>
Markup that will be parsed only for Internet Explorer 6
<![endif]—>
</code></pre>
<p>许多其他这类怪癖都与 SGML 和 XML 的特性有关。例如,由于前面提到的旁注中的注释处理行为,浏览器在解析 !- 和 ?-指令(如 <code><!DOCTYPE></code> 或 <code><?xml></code>)方面存在分歧,是否允许在非 XHTML 模式下使用 XML 风格的 CDATA 块,以及如何确定重叠的特殊解析模式标签(如 <code><style><!-- </style> --></code>)的优先级。</p>
<h2 id="html-解析生存技巧">HTML 解析生存技巧</h2>
<p>前面几节讨论的解析行为远非详尽无遗。实际上,已经有整本书是关于这个主题的:建议好奇的读者获取 Mario Heiderich、Eduardo Alberto Vela Nava、Gareth Heyes 和 David Lindsay 所著的《Web 应用混淆》(Syngress,2011)——然后为人类的命运感到悲伤。底线是,构建试图阻止已知危险模式的 HTML 过滤器,并允许剩余的标记保持原样,这根本不可行。</p>
<p>标签清理的唯一合理方法是使用一个现实的解析器将输入文档转换为层次结构的内存文档树,然后清理这个表示形式中所有未识别的标签和参数,以及任何不希望的标签/参数/值配置。到那时,这个树可以被仔细地重新序列化为格式良好、转义良好的 HTML,不会在浏览器本身中激活任何错误纠正功能。许多开发者认为应该有一个更简单的设计方案,但最终他们以艰难的方式发现了现实。</p>
<hr>
<p>^([26]) Opera 表现出的行为尤其狡猾:Unicode 空白字符不被许多用于服务器端 HTML 清理器的标准库函数识别,例如 libc 中的 <code>isspace(...)</code>。这增加了实现故障的风险。</p>
<h1 id="实体编码">实体编码</h1>
<p>让我们再次谈谈字符编码。正如本章第一页所注明的,某些保留字符在文本节点和标签参数值内通常是危险的,并且它们往往会直接导致 XHTML 中的语法错误。为了允许安全地使用这些字符(并允许方便地嵌入高位文本),开发人员可以使用一个简单的以&符号开头、以分号结尾的编码方案,称为实体编码。</p>
<p>这种编码方法最熟悉的使用是包含某些预定义的命名实体。在 XML 中,只有少数这些实体被指定,但在 HTML 规范中有数百个更多,并且所有现代浏览器都支持。在这种情况下,<em><</em>用于插入左尖括号;<em>></em>替换为右尖括号;<em>&</em>替换为自身;而例如<em>→</em>是一个很好的 Unicode 箭头。</p>
<h3 id="注意-18">注意</h3>
<p>在 XHTML 文档中,可以使用<em><!ENTITY></em>指令定义额外的命名实体,并将其解析为内部定义的字符串或外部文件 URL 的内容。(如果允许处理不受信任的内容时使用此选项,则显然是不安全的;这种攻击有时被称为<em>外部 XML 实体</em>,或简称 XXE。)</p>
<p>除了命名实体之外,还可以使用十进制<em>&#number;</em>表示法插入任意 ASCII 或 Unicode 字符。在这种情况下,<em><</em>映射到左尖括号;<em>></em>替换为右尖括号;而<em>😹</em>实际上是一个名为“笑脸猫脸带泪”的 Unicode 6.0 字符。如果数字前缀为“x”,则也可以使用十六进制表示法。在这种情况下,左尖括号变为<em><</em>等。</p>
<p>HTML 解析器识别文本节点和参数值内的实体编码,并在构建文档树内存表示时透明地解码它。因此,以下两种情况在功能上是相同的:</p>
<pre><code><img src="http://www.example.com">
</code></pre>
<p>和</p>
<pre><code><img src="ht&#x74;p&#x3a;//www.example.com">
</code></pre>
<p>另一方面,以下两个示例将不会按预期工作,因为编码会干扰标签本身的结构:</p>
<pre><code><img src&#x3d;"http://www.example.com">
</code></pre>
<p>和</p>
<pre><code><img s&#x72;c="http://www.example.com">
</code></pre>
<p>实体编码的这种很大程度上透明的行为使得在做出关于文档内容的任何安全决策之前正确解析它变得很重要,如果适用,在稍后对清洗后的输出进行正确恢复。为了说明,以下语法必须被识别为对<em>javascript:</em>伪 URL 的绝对引用,而不是对名为“./javascript&”的相对资源内的神秘片段 ID 的引用:</p>
<pre><code><a href="javascript&#x3a;alert(1)">
</code></pre>
<p>不幸的是,即使是识别和解析 HTML 实体的简单任务也可能很棘手。例如,在传统的解析中,即使尾随的分号被省略,只要下一个字符不是字母数字,实体通常也会被接受。(在 Firefox 中,连字符和点也接受在实体名称中。)数字实体甚至更成问题,因为它们可能有任意数量的尾随零的过长表示法。此外,如果数值大于许多计算机架构中整数的标准大小 232,相应的字符可能被错误计算。</p>
<p>使用 XHTML 的工作开发者也应该意识到该方言中存在的一个潜在陷阱。尽管在大多数特殊解析模式中不识别 HTML 实体,但 XHTML 与传统 HTML 的不同之处在于,如 <em><script></em> 和 <em><style></em> 这样的标签不会自动切换到特殊的解析模式。相反,需要任何脚本或样式的 <em><![CDATA[...]]></em> 块来实现类似的效果。因此,以下带有攻击者控制的字符串(否则会清除尖括号、引号、反斜杠和新行)的片段在 HTML 中是安全的,但在 XHTML 中则不是:</p>
<pre><code><script>
var tmp = 'I am harmless! &#x27;+alert(1);// Or am I?';
...
</script>
</code></pre>
<h1 id="httphtml-集成语义">HTTP/HTML 集成语义</h1>
<p>从第三章中,我们回忆起 HTTP 头部可能给整个响应赋予新的意义(如 <em>Location</em>、<em>Transfer-Encoding</em> 等),改变有效载荷的呈现方式(如 <em>Content-Type</em>、<em>Content-Disposition</em>),或者以其他辅助方式影响客户端环境(如 <em>Refresh</em>、<em>Set-Cookie</em>、<em>Cache-Control</em>、<em>Expires</em> 等)。</p>
<p>但如果 HTML 文档是通过非 HTTP 协议发送或从本地文件加载的,会怎样呢?显然,在这种情况下,没有简单的方法来表示或保留这些信息。我们可以轻松地放弃其中的一些,但 MIME 类型或字符集等参数是必不可少的,失去它们迫使浏览器在以后进行即兴创作。(例如,考虑 UTF-7、UTF-16 和 UTF-32 这样的字符集与 ASCII 不兼容,因此,HTML 文档甚至在没有确定需要使用哪种转换的情况下都无法解析。)</p>
<p>在第十三章中,将详细探讨用于检测字符集和文档类型的浏览器级启发式方法的安全后果。同时,通过特殊的 HTML 指令 <em><meta http-equiv=...></em>,以一种多少有些尴尬的方式解决了在文档中保留协议级信息的问题。当浏览器检查标记时,许多内容处理决策可能已经做出,但仍有一些调整可以做出;例如,可能调整字符集到一个普遍兼容的值,或者指定 <em>Refresh</em>、<em>Set-Cookie</em> 和缓存指令。</p>
<p>作为允许的语法的示例,考虑以下指令,当出现在 8 位 ASCII 文档中时,将向浏览器阐明文档的字符集是 UTF-8,而不是,比如说,ISO-8859-1:</p>
<pre><code><meta http-equiv="Content-Type" content="text/html;charset=utf-8">
</code></pre>
<p>相反,以下所有指令都将失败,因为此时切换到不兼容的 UTF-32 编码、更改文档类型为视频格式或执行重定向而不是解析文件都太晚了:</p>
<pre><code><meta http-equiv="Content-Type" content="text/html;charset=utf-32">
<meta http-equiv="Content-Type" content="video/mpeg">
<meta http-equiv="Location" content="http://www.example.com">
</code></pre>
<p>请注意,当 <em>http-equiv</em> 值相互冲突或与之前从服务器接收到的 HTTP 头信息相矛盾时,它们的行为并不一致,不应依赖。例如,第一个支持的 <em>charset=</em> 值通常占主导地位(在这种情况下,HTTP 头信息优先于 <em><meta></em>),但有几个冲突的 <em>Refresh</em> 值时,行为高度依赖于浏览器。</p>
<h3 id="注意-19">注意</h3>
<p>一些浏览器会在实际解析文档之前尝试推测性地提取 <meta http-equiv> 信息,这可能会导致尴尬的错误。例如,Firefox 4 最近修复的一个安全漏洞导致浏览器将以下声明解释为字符集声明:<em><meta http-equiv="Refresh" content="10;http://www.example.com/charset=utf-7"></em>。^([141])</p>
<h1 id="超链接和内容包含">超链接和内容包含</h1>
<p>HTML 最重要的和与安全相关的特性之一,不出所料,是链接到和嵌入外部内容的能力。除了 HTTP 级别的功能(如 <em>Location</em> 和 <em>Refresh</em>)之外,这可以通过几种简单的方式实现。</p>
<h2 id="平常链接">平常链接</h2>
<p>以下标记示例展示了在文档内引用外部内容最熟悉和最基本的方法:</p>
<pre><code><a href="http://www.example.com/">Click me!</a>
</code></pre>
<p>此超链接可能指向浏览器识别的任何方案,包括伪 URL(如 <em>data:</em>、<em>javascript:</em> 等)以及由外部应用程序(如 <em>mailto:</em>)处理的协议。点击嵌套在 <em><a href=...></em> 块内的文本(或任何 HTML 元素)通常会导致浏览器从链接文档导航离开,并前往指定的位置,如果该协议有意义的话。</p>
<p>可以使用可选的 <em>target</em> 参数来针对其他窗口或文档视图进行导航。该参数必须指定目标视图的名称。如果找不到该名称,或者访问被拒绝,通常的默认行为是打开一个新窗口。可能被拒绝访问的条件是第十一章的主题。</p>
<p>也可以使用四个特殊的目标名称(如图 4-1 左侧所示):<em>_blank</em> 总是打开一个全新的窗口,<em>_parent</em> 导航到一个包含链接文档的更高层级视图(如果有的话),而 <em>_top</em> 总是导航到顶级浏览器窗口,无论中间有多少文档嵌入层级。对了,第四个特殊目标,<em>_self</em>,与不指定任何值相同,且没有任何原因存在。</p>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950013.png.jpg" alt="预定义的超链接目标" loading="lazy"></p>
<p>图 4-1. 预定义的超链接目标</p>
<h2 id="表单和表单触发的请求">表单和表单触发的请求</h2>
<p>可以将 HTML 表单视为一个信息收集的超链接:当点击“提交”按钮时,会动态地从任何数量的输入字段收集数据,即时构建一个动态请求。表单允许用户输入和文件上传到服务器,但在几乎所有其他方面,提交表单的结果与跟随正常链接相似。</p>
<p>简单的表单标记可能看起来像这样:</p>
<pre><code><form method=GET action="/process_form.cgi">
Given name: <input type=text name=given>
Family name: <input type=text name=family>
...
<input type=submit value="Click here when done!">
</form>
</code></pre>
<p><em>action</em> 参数的工作方式与用于正常链接的 <em>href</em> 值类似,只有一个细微的差别:如果值不存在,表单将被提交到当前文档的位置,而任何无目标 <em><a></em> 链接将根本无法工作。还可以指定一个可选的 <em>target</em> 参数,其行为将在上一节中概述。</p>
<h3 id="注意-20">注意</h3>
<p>不寻常的是,与 <em><a></em> 标签不同,表单不能嵌套在彼此内部,在这种情况下,只有顶级 <em><form></em> 标签将保持可用。</p>
<p>当 <em>method</em> 值设置为 GET 或根本不存在时,所有嵌套的字段名称及其当前值都将使用在第二章中概述的熟悉百分编码方案进行转义,但有两大相当任意的差异。首先,空格字符(0x20)将被加号替换,而不是编码为“%20”。其次,由此产生的任何现有加号都需要编码为“%2B”,否则它们将被错误地解释为空格。</p>
<p>编码的 <em>name=value</em> 对然后用 ampersands 分隔,并合并成一个字符串,例如:</p>
<pre><code>given=Erwin+Rudolf+Josef+Alexander&family=Schr%C3%B6dinger
</code></pre>
<p>结果值被插入到目标 URL 的查询部分(替换该部分的任何现有内容)并提交给服务器。然后,接收到的响应将在目标视口中显示给用户。</p>
<p>如果将 <em>method</em> 参数设置为 POST,情况会稍微复杂一些。对于这种类型的 HTTP 请求,有三种数据提交格式可用。在默认模式(称为 <em>application/x-www-form-urlencoded</em>)下,消息的构建方式与 GET 相同,但它在请求有效载荷中传输,从而保留查询字符串和目标 URL 的所有其他部分.^([27])</p>
<p>第二种 POST 提交模式的存在,由在 <em><form></em> 标签上指定 <em>enctype="text/plain"</em> 触发,很难证明其合理性。在此模式下,字段名称和值将根本不进行百分编码(但,取决于浏览器,可能使用加号来代替空格),并且将使用换行符代替和号。生成的格式基本上是无用的,因为它无法被明确解析:来自表单的新行和等号与浏览器插入的无法区分。</p>
<p>最后一种模式由 <em>enctype="multipart/form-data"</em> 触发,并且必须在通过表单提交用户选择的文件时使用(这可以通过特殊的 <em><input type="file"></em> 标签实现)。生成的请求体由一系列短 MIME 消息组成,对应于每个提交的字段.^([28]) 这些消息由客户端选择的随机、唯一的边界标记分隔,该标记在其他情况下不应出现在封装的数据中:</p>
<pre><code>POST /process_form.cgi HTTP/1.1
...
Content-Type: multipart/form-data; boundary=random1234
--random1234
Content-Disposition: form-data; name="given"
Erwin Rudolf Josef Alexander
--random1234
Content-Disposition: form-data; name="family"
Schrödinger
--random1234
Content-Disposition: form-data; name="file"; filename="cat_names.txt"
Content-Type: text/plain
(File contents follow)
--random1234--
</code></pre>
<p>尽管该标签的语法看似开放,但任何浏览器都不支持其他请求方法或提交格式,并且这种情况不太可能改变。HTML5 标准曾试图在表单中引入 PUT 和 DELETE 方法,但这个提议很快就被否决了。</p>
<h2 id="框架">框架</h2>
<p>框架是一种标记形式,允许一个 HTML 文档的内容在另一个嵌入页面的矩形区域内显示。现代浏览器支持几种框架标签,但实现这一目标最常见的方式是使用无需麻烦且灵活的内联框架:</p>
<pre><code><iframe src="http://www.example.com/"></iframe>
</code></pre>
<p>在传统的 HTML 文档中,此标签将解析器置于一种特殊的解析模式,并且所有在开始和结束标签之间的文本在框架感知浏览器中都将被简单地忽略。在不支持 <em><iframe></em> 的旧浏览器中,开始和结束标签之间的标记将被正常处理,然而,提供了一种明确低成本的条件渲染指令。这种条件行为通常用于提供有洞察力的建议,例如“此页面必须在支持框架的浏览器中查看。”</p>
<p>框架是一个完全独立的文档视图,在许多方面与新的浏览器窗口相同。(它甚至拥有自己的 JavaScript 执行上下文。)像浏览器窗口一样,框架可以配备一个 <em>name</em> 参数,然后可以从 <em><a></em> 和 <em><form></em> 标签中定位。</p>
<p>对于框架内容的 <em>src</em> URL 的限制大致类似于对常规链接实施的规则。这包括将框架指向 <code>javascript:</code> 或加载外部处理的协议,这些协议会留空框架并在新进程中打开目标应用程序。</p>
<p>框架对网络安全性特别感兴趣,因为它们允许几乎不受限制的类型的内容从无关网站组合到单个页面上。我们将在第十一章中再次审视与此行为相关的问题。</p>
<h2 id="类型特定的内容包含">类型特定的内容包含</h2>
<p>除了内容无关的链接导航和文档框架之外,HTML 还提供了多种方法以更轻量级的方式包含几种预定义类型的外部内容。</p>
<p><strong>图像</strong></p>
<p>可以使用<code><img></code>标签、通过样式表以及通过诸如<code><body></code>或<code><table></code>等标记的遗留<code>background=</code>参数来检索和显示页面上的图像文件。</p>
<p>互联网上最受欢迎的图像类型是损失但非常高效的 JPEG 文件,其次是无损且功能更丰富(但速度较慢)的 PNG。一个日益过时的无损 GIF 格式也受到每个浏览器的支持,同样,很少遇到的通常未压缩的 Windows 位图文件(BMP)也得到支持。越来越多的渲染引擎支持 SVG,这是一个基于 XML 的矢量图形和动画格式,但通过<code><img></code>标签包含此类图像受到额外的限制。</p>
<p>可以将已识别的图像类型列表总结为一些零散的项目,如 Windows 元文件(WMF 和 EMF)、Windows 媒体照片(WDP 和 HDP)、Windows 图标(ICO)、动画 PNG(APNG)、TIFF 图像,以及最近出现的 WebP。然而,浏览器对这些格式的支持远非普遍。</p>
<p><strong>层叠样式表</strong></p>
<p>这些基于文本的文件可以用<code><link rel=stylesheet href=...></code>标签加载——尽管<code><style src=...></code>可能是一个更直观的选择——并且可以重新定义其父文档中几乎所有其他 HTML 标签的视觉方面(在某些情况下,甚至可以包含嵌入的 JavaScript)。CSS 的语法和功能是第五章的主题。</p>
<p>如果下载的样式表的<code>Content-Type</code>头中没有适当的<code>charset</code>值,包含方可以通过<code><link></code>标签的<code>charset</code>参数指定此子资源的解释编码。</p>
<p><strong>脚本</strong></p>
<p>脚本是包含在<code><script></code>标签中的基于文本的程序,并以一种赋予它们对宿主文档完全控制的方式执行。网络的主要脚本语言是 JavaScript,尽管在 Internet Explorer 中也支持嵌入的 Visual Basic 版本,并且可以随意使用。第六章深入探讨了客户端脚本及其功能。</p>
<p>与 CSS 一样,在没有有效的<code>Content-Type</code>数据的情况下,脚本解释所依据的字符集可能由包含方控制。</p>
<p><strong>插件内容</strong></p>
<p>这个类别涵盖了与 <em><embed></em> 或 <em><object></em> 标签一起包含的或通过过时的、Java 特定的 <em><applet></em> 标签包含的各类二进制文件。浏览器插件内容遵循其自身的安全规则,这在 第八章 和 第九章 中有所探讨。在许多情况下,可以将插件支持的内容视为等同于或比 JavaScript 更强大。</p>
<h3 id="注意-21">注意</h3>
<p>标准允许某些类型的浏览器支持的文档,如 <em>text/html</em> 或 <em>text/plain</em>,通过 <em><object></em> 标签加载,在这种情况下,它们与 <em><iframe></em> 形成紧密的等价物。这种功能在实际中并未使用,其背后的理由也难以理解。</p>
<p><strong>其他补充内容</strong></p>
<p>这个类别包括各种可能或可能不被浏览器尊重的渲染提示;它们最常见的是通过 <em><link></em> 指令提供的。例如包括网站图标(称为“favicons”)、页面的替代版本和章节导航链接。</p>
<p>一些曾经支持的内容包含方法,如用于背景音乐的 <em><bgsound></em> 标签,在过去很常见,但现在已经不再受欢迎。另一方面,作为 HTML5 的一部分,新的标签如 <em><video></em> 和 <em><audio></em> 预计很快会流行起来。</p>
<p>在接受类型特定的内容检索的 URL 方案方面,相对一致性较少。应该预期,路由到外部应用程序的协议将被拒绝,因为在这个上下文中它们没有合理的意义,但除此之外,不应做出太多假设。作为安全预防措施,大多数浏览器在加载图像和样式表时也会拒绝与脚本相关的方案,尽管 Internet Explorer 6 和 Opera 不遵循这一做法。截至本文撰写时,<em>javascript:</em> URL 在 Firefox 中的 <em><embed></em> 和 <em><applet></em> 标签上也是允许的,但例如在 <em><img></em> 标签上则不允许。</p>
<p>对于几乎所有类型特定的内容包含方法,服务器提供的 <em>Content-Type</em> 和 <em>Content-Disposition</em> 头部信息通常会被忽略(或许除了 <em>charset=</em> 值之外),HTTP 响应码本身也可能如此。最好假设,当任何服务器提供的资源内容被模糊地识别为该节中列举的数据格式之一时,它可能被解释为该格式。</p>
<h2 id="关于跨站请求伪造的说明">关于跨站请求伪造的说明</h2>
<p>在所有类型的跨域导航中,浏览器将透明地包含任何环境凭证;因此,对于服务器来说,一个合法源自其客户端代码的请求将大致与源自恶意第三方网站的请求相同,并且可能会被赋予相同的权限。</p>
<p>在处理任何敏感的、状态改变请求时未能考虑到这种可能性的应用程序被认为容易受到<em>跨站请求伪造</em>(<em>XSRF</em>或<em>CSRF</em>)的攻击。这种漏洞可以通过多种方式缓解,其中最常见的是在请求中包含一个秘密的用户和会话特定值(作为一个额外的查询参数或隐藏表单字段)。攻击者将无法获取此值,因为跨域文档的读取访问受到同源策略的限制(参见第九章)。</p>
<p>安全工程备忘单</p>
<p>所有 HTML 文档的良好工程卫生</p>
<ul>
<li>总是输出一致、有效且浏览器支持的<em>内容类型</em>和<em>字符集</em>信息,以防止文档被解释为与您的原始意图相反。</li>
</ul>
<p>在生成带有攻击者控制部分的 HTML 文档时</p>
<p>在整个 Web 应用程序中一致地执行此任务很困难,这是 Web 应用程序安全漏洞的最重要来源之一。考虑使用上下文敏感的自动转义框架,如<em>JSilver</em>或<em>CTemplate</em>来自动化它。如果这不可能,请继续阅读。</p>
<ul>
<li>
<p><strong>文本主体中的用户提供的内联内容:</strong>始终对“<”,“>”,和“&”进行实体编码。请注意,某些其他模式在非 ASCII 兼容的输出编码中可能是危险的。如果适用,请参阅第十三章。</p>
<p>请记住,某些 Unicode 元字符(例如,U+202E)会改变后续文本的方向或流程。在特别敏感的使用中,可能需要删除它们。</p>
</li>
<li>
<p><strong>特定标签的样式</strong>和<strong>参数:</strong>需要多级转义。这种做法极其容易出错,意味着这不是一个真正值得尝试的事情。如果绝对不可避免,请查阅第五章和第六章中的备忘单。</p>
</li>
<li>
<p><strong>所有其他 HTML 参数值:</strong>始终在攻击者控制的输入周围使用引号。对“<”,“>”,“&”和任何多余的引号进行实体编码。请记住,某些参数需要额外的验证。对于 URL,请参阅第二章中的备忘单。</p>
<p>永远不要尝试在 URL 或任何其他参数中黑名单已知的不良值;这样做会适得其反,可能导致脚本执行漏洞。</p>
</li>
<li>
<p><strong>特殊解析模式(例如</strong>,<script> <strong>和</strong> <style> <strong>块):</strong>对于出现在引号字符串内的值,将引号字符、反斜杠、“<”,“>”和所有非打印字符替换为语言适当的转义代码。对于出现在字符串外的值,请采取极端谨慎的态度,并仅允许经过仔细验证的已知字母数字值。</p>
<p>在 XHTML 模式下,请记住将整个脚本部分包裹在 CDATA 块中。避免需要多级编码的情况,例如使用攻击者提供的字符串构建传递给 JavaScript <em>eval(...)</em> 函数的参数。永远不要将用户控制的数据放在 HTML 注释、!-type 或?-type 标签以及其他非必要或异常解析的块中。</p>
</li>
</ul>
<p>当将 HTML 转换为纯文本时</p>
<ul>
<li>一个常见的错误是仅删除格式良好的标签。请记住,所有左尖括号都必须删除,即使找不到匹配的右尖括号。为了最大限度地减少错误的风险,在生成的输出中也总是对尖括号和和符号进行实体转义。</li>
</ul>
<p>当编写用于用户内容的标记过滤器时</p>
<ul>
<li>
<p>仔细阅读本章。使用一个合理健壮的 HTML 解析器来构建内存中的文档树。遍历树,删除任何未识别或不必要的标签和参数,并清除任何不希望出现的标签/参数/值组合。</p>
<p>完成后,重新序列化文档,确保对参数值和文本内容应用适当的转义规则。(参见本备忘单上的第一个提示。)注意特殊解析模式的影响。</p>
</li>
<li>
<p>由于与 JavaScript 的命名空间交互有些反直觉,不允许在用户提供的标记中使用 <em>name</em> 和 <em>id</em> 参数——至少在未首先阅读第六章之前不要这样做。</p>
</li>
<li>
<p>不要尝试就地清理现有的、序列化的文档。这样做不可避免地会导致安全问题。</p>
</li>
</ul>
<hr>
<p>^([27]) 这可能导致混淆,因为相同的参数可能同时出现在查询字符串和 POST 有效负载中。各种服务器端 Web 应用程序框架解决这种冲突的方式并不一致。</p>
<p>^([28]) MIME(多用途互联网邮件扩展)是一种数据格式,旨在封装和通过电子邮件消息安全地传输各种类型的文档。该格式在浏览器世界中出现了几次意想不到的情况。例如,<em>Content-Type</em> 文件格式标识符也有明确的 MIME 根。</p>
<h1 id="第五章层叠样式表">第五章。层叠样式表</h1>
<p>随着 Web 在 20 世纪 90 年代的发展,网站开发者越来越需要一个一致且灵活的方式来控制 HTML 文档的外观;当时可用的随机、供应商特定的标签参数集合根本无法满足需求。在审查了几个竞争性提案后,W3C 最终决定采用<em>层叠样式表(CSS)</em>,这是一种由 Håkon Wium Lie 提出的相当简单的基于文本的页面外观描述语言。</p>
<p>CSS 1.0 的初始规范于 1996 年底问世,^([142]) 但对该文档的进一步修订一直持续到 2008 年。CSS 2.0 的初始草案于 1998 年 12 月发布,截至 2011 年仍未最终确定。对最新版本,即 3.0 版本的研发始于 2005 年,并且至今仍在继续。尽管经过多年的试验和错误,CSS2 和 CSS3 所设想的大部分单个功能都已由所有现代浏览器采用,但许多细微的细节在不同实现之间差异很大,而缺乏一个最终标准可能正是导致这种情况的原因。</p>
<p>尽管不同浏览器之间存在差异,CSS 仍然是一个非常强大的工具。仅通过几个限制,样式表允许几乎任意地缩放、定位和装饰几乎每一个 HTML 标签,从而克服了底层标记语言最初对其施加的限制;在某些实现中,JavaScript 程序还可以嵌入到 CSS 展示指令中。因此,将用户控制的值放置在样式表中,或重新编码任何外部提供的 CSS,对网络应用安全来说具有极大的兴趣。</p>
<h1 id="基本-css-语法">基本 CSS 语法</h1>
<p>样式表可以通过三种方式放置在 HTML 文档中:通过 <em><style></em> 块内联全局应用于整个文档,通过 <em><link rel=stylesheet></em> 指令从外部 URL 获取,或者使用 <em>style</em> 参数附加到特定的标签。此外,基于 XML 的文档(包括 XHTML)还可以利用一个鲜为人知的 <em><?xml-stylesheet href=... ?></em> 指令来实现相同的目标。</p>
<p>包含的前两种方法需要包含任何数量的选择器(描述以下规则集将应用于哪些 HTML 标签的指令)的完全合格的样式表,后跟花括号内分号分隔的 <em>name: value</em> 规则。以下是一个此类语法的简单示例,定义了 <em><img></em>、<em><span></em> 和 <em><div></em> 标签的外观:</p>
<pre><code>img {
border-size: 1px;
border-style: solid;
}
span, div {
color: red;
}
</code></pre>
<p>选择器可以引用特定类型的标签(如 <em>img</em>),以点前缀命名的标签类名(例如,<em>.photos</em>,它将应用于所有具有内联 <em>class=photos</em> 参数的标签),或者两者的组合(<em>img.company_logo</em>)。类似于 <em>:hover</em> 或 <em>:visited</em> 的选择器后缀也可以用来使选择器仅在特定情况下匹配,例如当鼠标悬停在内容上,或者当特定的显示超链接已经被访问过之前。</p>
<p>所说的 <em>复杂选择器</em>^([143]) 是 CSS2 中引入的一个有趣特性,并在 CSS3 中得到扩展。它们允许给定的规则集仅应用于参数值中包含特定字符串的标签,或者位于其他标记的特定关系中的标签。这样一个选择器的例子如下:</p>
<pre><code>a[href^="ftp:"] {
/* Styling applicable only to FTP links. */
}
</code></pre>
<h3 id="注意-22">注意</h3>
<p>哦,顺便说一下:正如这个例子所示,C 样式的 <em><code>/*...*/</code></em> 注释块可以在 CSS 语法中任何非引号字符串之外的地方使用。另一方面,<em><code>//</code></em>-样式的注释则完全不识别。</p>
<h2 id="属性定义">属性定义</h2>
<p>在跟随选择器的 <em>{ ... }</em> 块内部,以及附加到特定标签的 <em>style</em> 参数内部,可以使用任意数量的 <em>name: value</em> 规则来重新定义受影响标记的显示的几乎所有方面。可见性、形状、颜色、屏幕位置、渲染顺序、本地或远程字体,甚至任何附加文本(某些伪类支持的内容属性)和鼠标光标形状都可以重新定义。此外,通过 CSS 规则还可以提供简单的自动化类型,例如编号列表的计数器。^([29])</p>
<p>属性值可以格式化为以下形式:</p>
<ul>
<li>
<p><strong>原始文本</strong> 此方法主要用于指定数值(可选单位)、RGB 向量和命名颜色,以及其他预定义的关键字(“绝对”、“左”、“中心”等)。</p>
</li>
<li>
<p><strong>引号字符串</strong> 应该将任何非关键字值放在单引号或双引号内,但如何执行此规则并不一致。例如,字体名称或某些 URL 的使用不需要引号,但对于前面提到的 <em>content</em> 属性则是必需的。</p>
</li>
<li>
<p><strong>函数表示法</strong> 原始 CSS 规范中提到了两个与参数相关的伪函数:<em>rgb(...)</em>,用于将单个 RGB 颜色值转换为单个颜色代码,以及 <em>url(...)</em>,在大多数但不是所有上下文中都是必需的。除此之外,近年来还推出了几个更多的伪函数,包括 <em>scale(...)</em>、<em>rotate(...)</em> 或 <em>skew(...)</em>。</p>
<p>在 Internet Explorer 中也提供了一个专有的 <em>expression(...)</em> 函数;它允许在 CSS 中插入 JavaScript 语句。这个函数是攻击者控制的样式表可能构成严重安全风险的最重要原因之一。</p>
</li>
</ul>
<h2 id="-指令和-xbl-绑定">@ 指令和 XBL 绑定</h2>
<p>除了选择器和属性之外,在独立样式表中还识别了几个以 @- 前缀的指令。所有这些指令都会修改样式表的意义;例如,通过指定样式表应该应用到的命名空间或显示媒体。但有两个特殊的指令也会影响解析过程的行为。第一个是 <em>@charset</em>,它设置当前 CSS 块的字符集;另一个是 <em>@import</em>,它将外部文件插入到样式表中。</p>
<p><em>@import</em> 指令本身是 CSS 解析特性的一个很好的例子;解析器认为以下所有示例都是等效的:</p>
<pre><code>@import "foo.css";
@import url('foo.css');
@import'foo.css';
</code></pre>
<p>在 Firefox 中,外部内容指令,包括 JavaScript 代码,也可以通过使用<em>-moz-binding</em>属性从外部源加载,这是一种特定于供应商的方法,用于将 XML 绑定语言^([144])文件(一种提供 XML 内容自动化的晦涩方法)编织到文档中。关于在其他浏览器中也支持 XBL 的讨论,此时属性的名称将改变,XSS 风险可能以某种方式得到解决,也可能没有得到解决。</p>
<h3 id="注意-23">注意</h3>
<p>如预期的那样,处理伪 URLs 在@import、url(...)*和其他基于 CSS 的内容包含方案中是一个潜在的安全风险。虽然大多数当前浏览器在这些上下文中不接受与脚本相关的方案,但 Internet Explorer 6 允许它们而不加限制,如果 URL 没有经过足够的验证,就会创建一个代码注入向量。</p>
<h2 id="与-html-的交互">与 HTML 的交互</h2>
<p>从上一章的讨论可以得出,对于任何内联在 HTML 文档中的样式表,HTML 解析首先执行,并且完全独立于 CSS 语法规则。因此,在 CSS 属性中放置某些 HTML 语法字符是不安全的,如下例所示,即使正确地引用了它们。一个常见的错误是允许这样做:</p>
<pre><code><style>
some_descriptor {
background: url('http://www.example.com/`</style><h1>Gotcha!'`);
}
</style>
</code></pre>
<p>我们将在稍后讨论在样式表中编码问题字符的方法,但首先,让我们快速看一下 CSS 的另一个非常独特的属性。</p>
<hr>
<p>^([29]) 使用任意位图重新定义鼠标光标的做法,如预期的那样,导致了一些安全漏洞。一个过大的光标结合基于脚本的鼠标位置跟踪,可能被用来隐藏或替换浏览器 UI 中的重要元素,并诱使用户执行危险的操作。</p>
<h1 id="解析器重新同步风险">解析器重新同步风险</h1>
<p>一种无疑受到 HTML 启发的行为,使 CSS 与大多数其他语言区分开来,是符合规范的解析器在遇到语法错误后应继续执行,并在下一个匹配的大括号处重新启动(规范要求进行一些表面上的嵌套级别跟踪)。特别是,以下样式表片段,尽管显然格式不正确,但仍将指定的边框样式应用于所有<em><img></em>标签:</p>
<pre><code>a {
$$$ This syntax makes absolutely no sense $$$
!(@*#)!!@ 123
}
img {
border: 1px solid red;
}
</code></pre>
<p>这种不寻常的行为为以有趣的方式利用解析器不兼容性提供了机会:如果存在任何方法可以通过对其他解析器看似有效但特定 CSS 实现无效的输入来使 CSS 实现脱轨,那么重新同步逻辑可能会导致被攻击的浏览器在错误的位置恢复解析,例如在攻击者提供的字符串中间。</p>
<p>对此问题的简单说明可能是 Internet Explorer 对多行字符串字面量的支持。在这个浏览器中,似乎在用户提供的 CSS 字符串中不删除 CR 和 LF 字符是安全的,因此一些网站管理员可能会允许这样做。不幸的是,相同的模式会导致任何其他浏览器在意外的偏移处恢复,并解释<em>evil_rule</em>规则集:</p>
<pre><code>some_benign_selector {
content: `'Attacker-controlled text...`
`} evil_rule { margin-left: −1000px; }';`
}
</code></pre>
<p>多行字符串的支持是微软特有的扩展,上述问题可以通过从一开始就避免这种不合规的语法来轻松解决。不幸的是,标准本身引入了其他不同步的风险。例如,回想一下复杂的选择器:这种 CSS3 语法对预 CSS3 解析器来说没有意义。在以下示例中,较旧的实现可能会在遇到意外的尖括号后退出,并从攻击者提供的 <em>evil_rule</em> 重新开始解析:</p>
<pre><code>a[href^=`'} evil_rule { margin-left: −1000px; }'`] {
/* Harmless, validated rules here. */
}
</code></pre>
<p>仍然流行的浏览器 Internet Explorer 6 会容易受到这种技巧的影响。</p>
<h1 id="字符编码">字符编码</h1>
<p>为了在字符串内部引用保留字符或其他问题字符,CSS 提供了一种非常规的转义方案:一个反斜杠(\)后跟一到六个十六进制数字。例如,根据此方案,字母 <em>e</em> 可以编码为“\65”、“\065”或“\000065”。然而,只有最后的语法“\000065”在下一个字符恰好是有效的十六进制数字时才会是明确的;将“teak”编码为“t\65ak”不会按预期工作,因为转义序列会被解释为“\65A”,这是 Unicode 字符集中的阿拉伯符号。</p>
<p>为了避免这个问题,规范采取了一个尴尬的折衷方案:反斜杠序列之后可以跟一个空格,它将被解释为终止符,然后从字符串中移除(例如,“t\65 ak”)。遗憾的是,更熟悉且可预测的固定长度 C 风格转义序列,如<em>\ x65</em>,不能被用来代替。</p>
<p>除了数值转义方案之外,还可以在非十六进制有效数字字符之前放置一个反斜杠。在这种情况下,后续的字符将被视为字面量。这种机制对于编码引号字符和反斜杠本身很有用,但不应用于转义 HTML 控制字符,如尖括号。上述 HTML 解析优先于 CSS 解析的优先级使得这种方法不适用。</p>
<p>在一个奇怪的转折中,由于 W3C 草案中的指导有些含糊不清,许多 CSS 解析器在其他位置也识别任意转义序列。更糟糕的是,在 Internet Explorer 中,这些序列的替换似乎在解析伪函数语法之前发生,实际上使得以下两个示例等效:</p>
<pre><code>color: expression(alert(1))
</code></pre>
<pre><code>color: expression\028 alert \028 1 \029 \029
</code></pre>
<p>更令人困惑的是,为了维护容错性,微软的实现不识别 <em>url(...)</em> 值内的反斜杠转义代码;这又一次是为了避免伤害那些在指定 URL 时输入错误类型斜杠的用户感情。</p>
<p>这些以及类似的怪癖使得检测已知的危险 CSS 语法极其容易出错。</p>
<p>安全工程速查表</p>
<p>当加载远程样式表时</p>
<ul>
<li>
<p>您正在将您站点的安全性链接到样式表的原始域名。即使在不支持在样式表中使用 JavaScript 表达式的浏览器中,也可以使用条件选择器和<em>url(...)</em>引用等特性来泄露您站点的部分内容.^([145])</p>
</li>
<li>
<p>当不确定时,最好创建数据的一个本地副本。</p>
</li>
<li>
<p>在 HTTPS 站点上,要求样式表也通过 HTTPS 提供。</p>
</li>
</ul>
<p>当将攻击者控制的值放入 CSS</p>
<ul>
<li>
<p><strong>独立块内的字符串和 URL</strong>。始终使用引号。使用数字代码转义所有控制字符(0x00-0x1F)、“\”、“<”、“>”、“{”、“}”和引号。还最好转义高位字符。对于 URL,请参考第二章中的速查表,以避免代码注入漏洞。</p>
</li>
<li>
<p><strong>样式参数中的字符串</strong>。涉及多级转义。这个过程容易出错,所以除非绝对必要,否则不要尝试。如果不可避免,首先应用上述 CSS 转义规则,然后对结果字符串应用 HTML 参数编码。</p>
</li>
<li>
<p><strong>非字符串属性</strong>。只允许白名单字母数字关键字和经过仔细验证的数值。不要尝试拒绝已知的坏模式。</p>
</li>
</ul>
<p>当过滤用户提供的 CSS</p>
<ul>
<li>
<p>移除所有功能规则集之外的内容。不要保留或生成用户控制的注释块、@-指令等。</p>
</li>
<li>
<p>仔细验证选择器语法,只允许字母数字;下划线;空格;以及在“{”之前正确位置的分号、句号和逗号。不允许复杂的文本匹配选择器;它们是不安全的。</p>
</li>
<li>
<p>解析并验证{ ... }块中的每一条规则。只允许具有良好理解后果的白名单属性,并确认它们采用预期的、已知的、安全的值。请注意,传递给某些属性的字符串有时即使没有<em>url(...)</em>包装器也可能被解释为 URL。</p>
</li>
<li>
<p>使用本节前面概述的规则对每个参数值进行编码。在出现任何语法异常时退出。</p>
</li>
<li>
<p>请记住,除非明确阻止,否则 CSS 可能会将用户内容定位在预期的绘制区域之外或重新定义应用程序 UI 的任何部分的显示。避免此问题的最安全方法是,在单独的框架内显示不受信任的内容。</p>
</li>
</ul>
<p>当允许在 HTML 标记上指定用户定义的类值</p>
<ul>
<li>确保用户提供的内联内容不能重用用于应用程序 UI 任何部分的类名。如果没有使用单独的框架,建议维护单独的命名空间前缀。</li>
</ul>
<h1 id="第六章浏览器端脚本">第六章。浏览器端脚本</h1>
<p>第一个浏览器脚本引擎在 1995 年左右首次出现在 Netscape Navigator 中,归功于布伦丹·艾奇的工作。最初被称为集成 Mocha 语言,它赋予了网络开发者操作 HTML 文档、显示简单的系统级对话框、打开和重新定位浏览器窗口以及以无烦恼的方式使用其他基本类型的客户端自动化的能力。</p>
<p>在迭代测试 beta 版本的过程中,Netscape 最终将 Mocha LiveScript 改名为 JavaScript,并在与 Sun Microsystems 达成一项尴尬的品牌合作后,选择了 JavaScript 作为最终名称。布伦丹的 Mocha 与 Sun 的 Java 之间的相似之处寥寥无几,但 Netscape 公司押注这种由市场营销驱动的奇特联姻将确保 JavaScript 在更具利润空间的网络服务器领域占据主导地位。它在一份著名的、令人困惑的 1995 年新闻稿中表达了这种观点,该新闻稿向世界介绍了这种语言,并立即试图将其与一系列令人印象深刻的随机商业产品联系起来:^([146])</p>
<blockquote>
<p><strong>Netscape 和 Sun 宣布 JavaScript,面向企业网络和互联网的开放、跨平台对象脚本语言</strong></p>
<p>[ . . . ]</p>
<p>Netscape Navigator Gold 2.0 允许开发者创建和编辑 JavaScript 脚本,而 Netscape LiveWire 允许 JavaScript 程序在 Netscape 服务器上安装、运行和管理,无论是在企业内部还是在互联网上。Netscape LiveWire Pro 增加了连接到 Illustra、Informix、Microsoft、Oracle 和 Sybase 的高性能关系数据库的支持。Java 和 JavaScript 的支持正在集成到所有 Netscape 产品中,以提供统一的前后端、客户端/服务器/工具环境,用于构建和部署实时在线应用程序。</p>
</blockquote>
<p>尽管 Netscape 对 Java 的喜爱有些过度,但 JavaScript 在客户端编程中的价值似乎很明确,包括对竞争对手来说也是如此。1996 年,微软通过在 Internet Explorer 3.0 中发布 JavaScript 的几乎完全相同的副本,并提出了自己的反提案:一种名为 VBScript 的 Visual Basic 衍生语言。可能是因为它来得太晚,也可能是因为 VBScript 的语法更笨拙,微软的替代品未能获得显著的关注或任何跨浏览器的支持。最终,JavaScript 在市场上确立了其地位,部分原因是微软的失败,自那时起,主流浏览器中就没有尝试过新的脚本语言。</p>
<p>在 JavaScript 语言的流行推动下,Netscape 将维护它的一部分责任交给了独立机构,即欧洲计算机制造商协会(ECMA)。新的监管机构成功地在 1999 年发布了 ECMAScript 的第三版^([147]),但在继续前进方面遇到了相当大的困难。第四版,对语言的全面改革,在供应商之间经过几年的争吵后最终被放弃,并于 2009 年发布了一个规模较小的第五版^([148)),但仍然只得到了有限的(尽管持续改善的)浏览器支持。从 2008 年开始的“和谐”新版本的研发工作至今尚未完成。在没有演变和广泛接受的标准的情况下,语言的供应商特定扩展很常见,但它们通常只会带来痛苦。</p>
<h1 id="javascript-的基本特征">JavaScript 的基本特征</h1>
<p>JavaScript 是一种相当简单的语言,旨在在运行时进行解释。它具有受 C 语言影响的语法(除了指针运算);一个简单的无类对象模型,据说受到了一个名为 Self 的鲜为人知的编程语言的启发;自动垃圾回收;以及弱动态类型。</p>
<p>作为一种语言,JavaScript 没有内置的 I/O 机制。在浏览器中,通过一组预定义的方法和属性提供有限的与宿主环境交互的能力,这些方法和属性映射到浏览器内部的本地代码,但与许多其他编程语言中看到的情况不同,这些接口相当有限且专为特定目的而设计。</p>
<p>JavaScript 的大部分核心功能相当普通,应该对已经熟悉 C、C++或在一定程度上熟悉 Java 的开发者来说很熟悉。一个简单的 JavaScript 程序可能看起来像这样:</p>
<pre><code>var text = "Hi mom!";
function display_string(str) {
alert(str);
return 0;
}
// This will display "Hi mom!".
display_str(text);
</code></pre>
<p>由于本书的范围不涉及对 JavaScript 语法的更详细概述,我们将在本章后面简要总结其一些更独特和安全相关的属性。对于寻求对语言有更系统介绍的人来说,Marijn Haverbeke 的《优雅的 JavaScript》(No Starch Press,2011 年)是一个不错的选择。</p>
<h2 id="脚本处理模型">脚本处理模型</h2>
<p>在浏览器中显示的每个 HTML 文档——无论是单独的窗口还是框架中——都会有一个独立的 JavaScript 执行环境实例,包括由加载的脚本创建的所有全局变量和函数的独立命名空间。在特定文档上下文中执行的脚本共享这个公共沙盒,并且也可以通过浏览器提供的 API 与其他上下文进行交互。这种跨文档交互必须以非常明确的方式进行;意外干扰的可能性很小。表面上,脚本隔离规则让人联想到现代多任务操作系统的进程隔离模型,但包容性要小得多。</p>
<p>在特定的执行上下文中,所有遇到的 JavaScript 块都是单独处理的,并且几乎总是按照一个明确的顺序进行。每个代码块必须由任意数量的自包含、格式良好的语法单元组成,并且将按照三个不同的、连续的步骤进行处理:解析、函数解析和代码执行。</p>
<h3 id="解析">解析</h3>
<p>解析阶段验证脚本块的语法,并且通常将其转换为中间的二进制表示形式,这可以在随后的执行中以更合理的速度执行。代码在完成此步骤之前没有全局效果。在出现语法错误的情况下,整个有问题的块将被放弃,解析器继续处理下一个可用的代码块。</p>
<p>为了说明符合规范的 JavaScript 解析器的行为,考虑以下 HTML 片段:</p>
<pre><code><script> **block #1:**
var my_variable1 = 1;
var my_variable2 =
</script>
<script> **block #2:**
2;
</script>
</code></pre>
<p>与在 C 中受过教育的开发者可能习惯的做法相反,上述顺序与以下代码片段并不等价:</p>
<pre><code><script>
var my_variable1 = 1;
var my_variable2 = 2;
</script>
</code></pre>
<p>这是因为 <em><script></em> 块在解析之前并没有被连接。相反,第一个脚本段将简单地导致语法错误(一个缺少右侧值的赋值),导致整个块被忽略,并且不会达到执行阶段。整个段落在能够产生任何全局副作用之前就被放弃的事实也意味着原始示例与这个示例并不等价:</p>
<pre><code><script>
var my_variable1 = 1;
</script>
<script>
2;
</script>
</code></pre>
<p>这使得 JavaScript 与许多其他脚本语言(如 Bash)不同,在 Bash 中,解析阶段并没有如此强烈地与执行阶段分离。</p>
<p>在本节前面提供的原始示例中,将会发生的情况是第一个块将被忽略,但第二个块 (<em><script>2;</script></em>) 将被正确解析。然而,当执行时,第二个块将相当于一个无操作(no-op),因为它使用了一个纯数值表达式作为代码语句。</p>
<h3 id="函数解析">函数解析</h3>
<p>一旦解析阶段成功完成,下一步涉及注册解析器在当前处理的块中找到的每个命名全局函数。在此之后,每个找到的函数都将可以从随后执行的代码中访问。由于这个额外的预执行步骤,以下语法将完美工作(与程序员在 C 或 C++ 中可能习惯的做法相反,<em>hello_world()</em> 将在执行第一个代码语句(即对该函数的调用)之前被注册):</p>
<pre><code><script>
hello_world();
function hello_world() {
alert('Hi mom!');
}
</script>
</code></pre>
<p>另一方面,下面修改后的示例将不会产生预期的效果:</p>
<pre><code><script>
hello_world();
</script>
<script>
function hello_world() {
alert('Hi mom!');
}
</script>
</code></pre>
<p>这种修改后的案例将因运行时错误而失败,因为代码块不是同时处理的,而是根据它们提供给 JavaScript 引擎的顺序来处理的。当第一个块已经开始执行时,定义 <em>hello_world()</em> 的块尚未被解析。</p>
<p>为了进一步复杂化这幅图景,这里概述的略显尴尬的全局名称解析模型仅适用于函数,而不适用于变量声明。变量在执行时按顺序注册,方式类似于其他解释型脚本语言。因此,以下代码示例,它只是将我们的全局 <em>hello_world()</em> 替换为一个分配给全局变量的未命名函数,将不会按预期工作:</p>
<pre><code><script>
hello_world();
var hello_world = function() {
alert('Hi mom!');
}
</script>
</code></pre>
<p>在这种情况下,对 <em>hello_world</em> 变量的赋值将不会在尝试调用 <em>hello_world()</em> 时完成。</p>
<h3 id="代码执行">代码执行</h3>
<p>一旦函数解析完成,JavaScript 引擎通常会继续按顺序执行函数块之外的所有语句。脚本在此处可能由于未处理的异常或其他一些更神秘的原因而失败。然而,如果遇到此类错误,则受影响代码块中的任何已解析函数仍然可以调用,并且已执行代码的任何效果将保留在当前脚本上下文中。</p>
<p>以下长篇但有趣的代码片段展示了异常恢复和其他几个 JavaScript 执行特性:</p>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950015.png.jpg" alt="无标题图片" loading="lazy"></p>
<p>尝试自己跟随这个例子,看看你是否同意右侧提供的注释。</p>
<p>如此练习所示,任何意外且未处理的异常都有不寻常的后果:它们可能会使应用程序处于不一致但仍然可能可执行的状态。由于异常旨在防止由未预料到的错误引起的错误传播,这种设计显得很奇怪——尤其是在许多其他方面(如禁止 <em>goto</em> 语句),JavaScript 表现出更基要主义的立场。</p>
<h2 id="执行顺序控制">执行顺序控制</h2>
<p>为了正确分析某些常见网络应用程序设计模式的安全属性,了解 JavaScript 引擎的执行顺序和计时模型非常重要。幸运的是,这个模型非常合理。</p>
<p>几乎所有存在于特定执行上下文中的 JavaScript 都是以同步方式执行的。由于外部事件,代码在执行过程中无法重新进入,并且没有支持能够同时修改任何共享内存的线程。当执行引擎忙碌时,事件处理、计时器、页面导航请求等处理将被推迟;在大多数情况下,整个浏览器,或者至少是 HTML 渲染器,也将保持大部分无响应。只有当执行停止并且脚本引擎进入空闲状态时,排队事件的处理才会继续。此时,JavaScript 代码可以再次进入。</p>
<p>此外,JavaScript 没有提供 <em>sleep(...)</em> 或 <em>pause(...)</em> 函数来暂时释放 CPU 并稍后从相同位置恢复执行。相反,如果程序员希望推迟脚本的执行,则必须注册一个定时器,稍后启动新的执行流程。这个流程需要从指定的处理函数的开始(或设置定时器时提供的自包含代码片段的开始)开始。尽管这些设计决策可能令人烦恼,但它们在很大程度上减少了代码中发生竞争条件的风险。</p>
<h3 id="注意-24">注意</h3>
<p>在这个同步执行模型中存在几个可能并非故意的漏洞。其中之一是在调用 <em>alert(...)</em> 或 <em>showModalDialog(...)</em> 后,暂时挂起另一段 JavaScript 执行时可能发生代码执行的可能性。尽管这些边缘情况并不经常出现。</p>
<p>忙碌的 JavaScript 循环的破坏性、阻止浏览器行为需要浏览器级别的缓解措施。我们将在 第十四章 中详细探讨这些缓解措施。目前,只需说它们还有一个非常不寻常的后果:任何无限循环实际上都可能终止,类似于抛出一个未处理的异常。然后,引擎将返回空闲状态,但仍然处于运行状态,有问题的代码仍然可以调用,所有定时器和事件处理程序都将保持原位。</p>
<p>当攻击者故意触发时,意外终止 CPU 密集型代码的执行可能会通过中断作者预期总是成功完成的操作,使应用程序处于不一致的状态。而且,这还不是全部:这些语义的另一个密切相关的后果将在 JavaScript 对象表示法和其他数据序列化 中变得明显。JavaScript 对象表示法和其他数据序列化。</p>
<h2 id="代码和对象检查功能">代码和对象检查功能</h2>
<p>JavaScript 语言为检查任何非本地函数的反编译源代码提供了一个基本的规定,只需在开发者希望检查的任何函数上调用 <em>toString()</em> 或 <em>toSource()</em> 方法即可。除了这个能力之外,检查程序流程的机会是有限的。应用程序可以利用对其宿主文档内存表示的访问来查找所有内联的 <em><script></em> 块,但无法直接看到任何远程加载或动态生成的代码。也可以通过非标准的 <em>caller</em> 属性获得对调用栈的一些了解,但也没有办法知道当前正在执行哪一行代码或下一行将执行哪一行。</p>
<p>动态创建新 JavaScript 代码的能力是语言中更突出的一部分。可以指示引擎同步解释传递给内置的 <em>eval(...)</em> 函数的字符串。例如,这将显示一个警告对话框:</p>
<pre><code>eval("alert(\"Hi mom!\")")
</code></pre>
<p>任何提供给 <em>eval(...)</em> 的输入文本中的语法错误都会导致此函数抛出异常。同样,如果解析成功,由解释的代码抛出的任何未处理的异常将被传递给调用者。最后,在没有语法错误或运行时问题的情况下,执行器在执行提供的代码时评估的最后一条语句的值将被用作 <em>eval(...)</em> 本身的返回值。</p>
<p>除了这个功能之外,还可以利用其他浏览器级别的机制来安排在执行器返回空闲状态后延迟解析和执行新的 JavaScript 块。这类机制的例子包括计时器(<em>setTimeout</em>、<em>setInterval</em>)、事件处理器(<em>onclick</em>、<em>onload</em> 等)以及 HTML 解析器的接口(<em>innerHTML</em>、<em>document.write(...)</em> 等)。</p>
<p>虽然检查代码的能力有些隐蔽,但 JavaScript 中的运行时对象自省能力已经相当成熟。应用程序被允许使用简单的 <em>for ... in</em> 或 <em>for each ... in</em> 迭代器枚举几乎任何对象的方法或属性,并且可以利用诸如 <em>typeof</em>、<em>instanceof</em> 或“严格等于”(===)这样的运算符以及诸如 <em>length</em> 这样的属性来深入了解每个发现项的身份。</p>
<p>所有上述功能使得在同一上下文中运行的脚本之间很难保守秘密。该功能还使得在文档上下文中保守秘密变得更加困难,这是一个浏览器厂商长期以来必须应对的问题——正如你将在第十一章中了解到的那样,这仍然不是完全过去的事情。</p>
<h2 id="修改运行时环境">修改运行时环境</h2>
<p>尽管 JavaScript 语言相对简单,但执行脚本有许多不寻常的方式来深刻地操纵其自身的 JavaScript 沙盒的行为。在某些罕见的情况下,这些行为可能会影响其他文档。</p>
<h3 id="覆盖内置函数">覆盖内置函数</h3>
<p>一个恶意脚本可用的更不寻常的工具是删除、覆盖或隐藏大多数内置 JavaScript 函数和几乎所有浏览器提供的 I/O 方法的能力。例如,考虑以下代码的行为:</p>
<pre><code>// This assignment will not trigger an error.
eval = alert;
// This call will unexpectedly open a dialog prompt.
eval("Hi mom!");
</code></pre>
<p>这只是乐趣的开始。在 Chrome、Safari 和 Opera 中,可以使用 <em>delete</em> 运算符完全删除 <em>eval(...)</em> 函数。令人困惑的是,在 Firefox 中尝试同样的操作将恢复原始的内置函数,从而撤销了原始覆盖的效果。最后,在 Internet Explorer 中,删除尝试将生成一个似乎在那个点上没有实际意义的延迟异常。</p>
<p>沿着这些思路进一步,几乎每个对象,包括如 <em>String</em> 或 <em>Array</em> 这样的内置对象,都有一个可以自由修改的原型。这个原型是一个主对象,所有现有和未来的对象实例都从中继承其方法和属性(形成了一种在功能更全面的编程语言中存在的类继承的粗略等效)。修改对象原型的能力可能导致新创建的对象产生相当反直觉的行为,如下所示:</p>
<pre><code>Number.prototype.toString = function() {
return "Gotcha!";
};
// This will display "Gotcha!" instead of "42":
alert(new Number(42));
</code></pre>
<h3 id="设置器和获取器">设置器和获取器</h3>
<p>在当代 JavaScript 方言中,对象模型的一些更有趣的特性是 <em>设置器</em> 和 <em>获取器</em>:提供自定义代码以处理宿主对象的属性读取或设置的方式。尽管它们不如 C++中的运算符重载强大,但它们可以用来使现有对象或对象原型以更令人困惑的方式表现。在下面的代码片段中,设置对象属性和稍后读取它的行为都很容易被颠覆:</p>
<pre><code>var evil_object = {
set foo() { alert("Gotcha!"); },
get foo() { return 2; }
};
// This will display "Gotcha!" and have no other effect.
evil_object.foo = 1;
// This comparison will fail.
if (evil_object.foo != 1) alert("What's going on?!");
</code></pre>
<h3 id="注意-25">注意</h3>
<p>设置器和获取器最初是作为供应商扩展开发的,但现在在 ECMAScript 第 5 版中得到了标准化。该功能在所有现代浏览器中都是可用的,但在 Internet Explorer 6 或 7 中不可用。</p>
<h3 id="对语言潜在用途的影响">对语言潜在用途的影响</h3>
<p>由于前两节中讨论的技术,在某个环境中执行脚本,该环境曾经被任何其他不受信任的内容污染,脚本将没有可靠的方式来检查其操作环境或采取纠正措施;即使是简单的条件表达式或循环的行为也不一定能依赖。语言提出的增强功能可能会使情况变得更加复杂。例如,ECMAScript 第 4 版的失败提案包括完整的运算符重载,这个想法可能会回归。</p>
<p>更有趣的是,这些设计决策还使得从页面沙盒外部检查任何执行上下文变得困难。例如,盲目依赖潜在敌对文档的 <em>location</em> 对象的可靠性,导致了浏览器插件、基于 JavaScript 的扩展和几类客户端网络应用安全特性中相当数量的安全漏洞。这些漏洞最终导致了浏览器级别的解决方案的开发,旨在部分保护这个特定的对象免受破坏,但大多数剩余的对象层次结构仍然处于争夺之中。</p>
<h3 id="注意-26">注意</h3>
<p>在 ECMAScript 第 5 版的“严格”模式下,对自身执行上下文的篡改能力受到限制。然而,截至本文撰写时,任何浏览器都不完全支持这种模式,它旨在是一个可选的、任意的机制。</p>
<h2 id="javascript-对象表示法和其他数据序列化">JavaScript 对象表示法和其他数据序列化</h2>
<p>在 JavaScript 中,一个非常重要的语法结构是其非常紧凑且方便的现场对象序列化,称为 JavaScript 对象表示法,或 JSON(RFC 4627^([149]))。这种数据格式依赖于对花括号符号 ({) 的重载。当这样的花括号用于打开一个完全限定的语句时,它以熟悉的方式处理,作为嵌套代码块的开始。然而,在表达式的情况下,它被假定为序列化对象的开始。以下示例说明了这种语法的正确使用,并将显示一个简单的提示:</p>
<pre><code>var impromptu_object = {
"given_name" : "John",
"family_name" : "Smith",
"lucky_numbers" : [ 11630, 12067, 12407, 12887 ]
};
// This will display "John".
alert(impromptu_object.given_name);
</code></pre>
<p>与数字、字符串或数组的不明确序列化相比,花括号的重载意味着当作为独立语句使用时,JSON 块将无法正确识别。这看起来可能微不足道,但这是一个优点:它防止了任何符合这种语法的服务器提供的响应通过 <em><script src=...></em> 在域之间有意义地包含。^([30]) 下面的列表将导致语法错误,表面上是因为在解释器试图将其视为代码标签的地方存在非法引号 (<img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950001.png" alt="" loading="lazy">)),并且将没有可测量的副作用:</p>
<pre><code><script>
{
"given_name" : "John",
"family_name" : "Smith",
"lucky_numbers" : [ 11630, 12067, 12407, 12887 ]
};
</script>
</code></pre>
<h3 id="注意-27">注意</h3>
<p>无法通过 <em><script src=...></em> 包含 JSON 是一个有趣的特性,但同时也非常脆弱。特别是,将响应包裹在括号或方括号中,或者移除标签周围的引号,将使语法在独立块中易于执行,这可能会产生可观察的副作用。鉴于 JavaScript 语法的快速演变,依赖这种特定的代码布局在未来的几年里始终导致解析错误是不明智的。尽管如此,在许多非关键用途中,这种程度的保证已经足够作为简单的安全机制来依赖。</p>
<p>一旦通过如 <em>XMLHttpRequest</em> 这样的通道检索到 JSON 序列化,就可以使用所有常见浏览器中的 <em>JSON.parse(...)</em> 函数快速且轻松地将它转换为内存中的对象,除了 Internet Explorer。不幸的是,为了与 Internet Explorer 兼容,有时仅仅是出于习惯,许多开发者求助于一个同样快速但危险得多的黑客手段:</p>
<pre><code>var parsed_object = eval("(" + json_text + ")");
</code></pre>
<p>这个语法的问题是,用于计算 JSON 表达式“值”的 <em>eval(...)</em> 函数不仅允许纯 JSON 输入,还允许任何其他有效的 JavaScript 语法出现在字符串中。这可能导致不希望看到的全局副作用。例如,这个伪造 JSON 响应中嵌入的函数调用将会执行:</p>
<pre><code>{ "given_name": `alert("Hi mom!")`
}
</code></pre>
<p>这种行为给网络开发者带来了额外的负担,他们必须只接受来自可信来源的 JSON 有效载荷,并且始终正确转义他们自己的服务器端代码生成的数据流。可以预见的是,未能这样做已经导致了许多应用程序级别的安全漏洞。</p>
<h3 id="注意-28">注意</h3>
<p>获取 <em>eval(...)</em> 正确性的难度体现在 JSON 规范(RFC 4627)本身:该文档中包含的声称安全的解析器实现无意中允许恶意的 JSON 响应自由增加或减少任何仅由字母“a”、“e”、“f”、“l”、“n”、“r”、“s”、“t”、“u”以及数字组成的程序变量;这足以构成“不安全”以及大约 1,000 个其他常见的英语单词。在这个 RFC 中合法化的有缺陷的正则表达式遍布整个互联网,并将继续这样做。</p>
<p>由于它们易于使用,JSON 序列化在现代所有网络应用中的服务器到客户端通信中无处不在。这种格式仅被其他更不安全的字符串或数组序列化以及 JSONP 所匹敌.^([32]) 然而,所有这些方案都与 <em>JSON.parse(...)</em> 不兼容,并且必须依赖不安全的 <em>eval(...)</em> 来转换为内存中的数据。这些格式的另一个特性是,与正确的 JSON 不同,当在第三方页面上使用 <em><script src=...></em> 加载时,它们可以正确解析。这种特性在某些罕见情况下是有利的,但大多数情况下它仅仅构成了一种不明显风险。例如,考虑即使通过 <em><script></em> 标签加载数组序列化通常没有可测量的副作用,攻击者至少直到最近,可以修改 <em>Array</em> 原型的设置器来检索提供的数据。一种常见的但通常不够充分的预防措施是在响应前加上一个 <em>while(1);</em> 循环来防止这种攻击,但如果记住无限循环可能在 JavaScript 中终止,这种做法可能会以有趣的方式产生反效果。</p>
<h2 id="e4x-和其他语法扩展">E4X 和其他语法扩展</h2>
<p>与 HTML 一样,JavaScript 正在迅速发展。多年来对其所做的某些更改相当激进,最终可能将以前被解析器拒绝的文本格式转换为有效的 JavaScript 代码。这反过来可能导致意外的数据泄露,尤其是在本章前面讨论的广泛代码和对象检查以及修改能力,以及使用 <em><script src=...></em> 加载跨域代码的能力。</p>
<p>这种趋势的一个更显著的例子是 <em>ECMAScript for XML</em> (E4X),^([150]) 这是一个完全不必要的但优雅的计划,将 XML 语法直接纳入 JavaScript,作为 JSON 样式序列化的替代方案。在任何 E4X 兼容的引擎中,例如 Firefox,以下两个代码片段大致等价:</p>
<pre><code>// Normal object serialization
var my_object = { "user": {
"given_name": "John",
"family_name": "Smith",
"id": make_up_value()
} };
// E4X serialization
var my_object = <user>
<given_name>John</given_name>
<family_name>Smith</family_name>
<id>{ make_up_value() }</id>
</user>;
</code></pre>
<p>E4X 的意外后果是,在这种机制下,任何格式良好的 XML 文档突然变成了一个有效的 <em><script src=...></em> 目标,它将被解析为一个表达式-语句块。此外,如果攻击者能够在包含的页面上战略性地放置“{”和“}”字符,或者更改正确对象原型的设置器,攻击者可能能够提取在无关文档中显示的用户特定文本。以下示例说明了风险:</p>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950017.png.jpg" alt="无标题图片" loading="lazy"></p>
<p>值得赞扬的是,在忍受了几年这个缺陷之后,Firefox 开发者决定禁止任何跨越整个解析脚本的 E4X 语句,这在一定程度上关闭了这个漏洞。尽管如此,语言的灵活性是显而易见的,这让人对使用 JSON 响应作为防御跨域脚本包含的手段的稳健性产生了一些怀疑。一旦“{”符号被赋予第三个含义,或者引号作为标签开始发挥作用,这种服务器到客户端数据交换格式的安全性将大大降低。务必提前做好规划。</p>
<hr>
<p>^([30]) 与大多数其他提供给脚本的(如 <em>XMLHttpRequest</em>)内容包含方案不同,<em><script src=...></em> 不受第九章中概述的跨域安全限制的影响。因此,当服务器使用环境权限凭据(如 cookies)动态生成用户特定 JavaScript 代码时,这种机制就是一个安全风险。这类漏洞被毫无想象力地称为 <em>跨站脚本包含</em>,或 <em>XSSI</em>。</p>
<p>^([31]) 比较意外的是,JavaScript 支持 C 样式的标签语句,例如 <em>my_label: alert(“Hi mom!”)</em>。这很有趣,因为出于哲学原因,该语言不支持 <em>goto</em>,因此在这种情况下,这样的标签通常无法有意义地引用。</p>
<p>^([32]) JSONP 实际上意味着“带有填充的 JSON”,它代表被一些补充代码包裹的 JSON 序列化,这些代码将其转换为一个有效的、独立的 JavaScript 语句,以便于使用。常见的例子可能包括函数调用(例如,<em>callback_function({ ...JSON 数据... })</em>)或变量赋值(<em>var return_value = { ...JSON 数据... }</em>)。</p>
<h1 id="标准对象层次结构">标准对象层次结构</h1>
<p>JavaScript 执行环境是围绕一个隐式根对象构建的,该对象用作程序创建的所有全局变量和函数的默认命名空间。除了几个语言强制性的内置函数外,此命名空间还预先填充了一个函数层次结构,这些函数在浏览器环境中实现输入和输出功能。这些功能包括操作浏览器窗口(<em>open(...)</em>、<em>close()</em>、<em>moveTo(...)</em>、<em>resizeTo(...)</em>、<em>focus()</em>、<em>blur()</em> 等);配置 JavaScript 定时器(<em>setTimeout(...)</em>、<em>setInterval(...)</em> 等);显示各种 UI 提示(<em>alert(...)</em>、<em>prompt(...)</em>、<em>print(...)</em>);以及执行各种供应商特定的和经常有风险的功能,例如访问系统剪贴板、创建书签或更改主页。</p>
<p>顶级对象还提供了对属于相关上下文的根对象的 JavaScript 引用,包括父框架(<em>parent</em>)、当前浏览器窗口中的顶级文档(<em>top</em>)、创建当前窗口的窗口(<em>opener</em>)以及当前文档的所有子框架(<em>frames[]</em>)。还包括对当前根对象本身的几个循环引用——例如,<em>window</em> 和 <em>self</em>。在非 Firefox 浏览器中,具有指定 <em>id</em> 或 <em>name</em> 参数的元素也将自动注册在此命名空间中,允许使用如下语法:</p>
<pre><code><img id="hello" src="http://www.example.com/">
...
<script>
alert(hello.src);
</script>
</code></pre>
<p>幸运的是,在 JavaScript 变量或内置函数发生名称冲突的情况下,<em>id</em> 数据不会优先考虑,这很大程度上避免了清洁的用户提供的标记和文档内脚本之间可能发生的任何干扰。</p>
<p>顶级层次结构的其余部分主要由几个具有特色的子对象组成,这些对象按主题分组浏览器 API 功能:</p>
<p>位置 <strong>对象</strong></p>
<p>这是一个属性和方法集合,允许程序读取当前文档的 URL 或启动对新文档的导航。在大多数情况下,此操作对调用者来说是致命的:当前的脚本上下文将在不久之后被销毁并替换为一个新的上下文。更新仅片段标识符(<em>location.hash</em>)是此规则的例外,如第二章所述。</p>
<p>注意,当使用 <em>location.</em>* 数据构建新的字符串(特别是 HTML 和 JavaScript 代码)时,假设它以任何特定方式转义是不安全的。Internet Explorer 将保持 <em>location.search</em> 属性(对应于 URL 查询字符串)中的角度括号不变。另一方面,Chrome 会转义它们,但会忽略双引号(<code>"</code>)或反斜杠。大多数浏览器也不会对片段 ID 应用任何转义。</p>
<p>history <strong>对象</strong></p>
<p>这个层次结构提供了几个不常使用的方法,用于在窗口浏览历史中移动,其方式类似于在浏览器 UI 中点击“后退”和“前进”按钮。无法直接检查任何之前访问的 URL;唯一的选择是通过提供数值偏移量,如 <em>history.go(-2)</em>,盲目地导航到历史记录。(关于这个层次结构的一些最近添加的内容将在第十七章 Chapter 17 中讨论。)</p>
<p>screen <strong>对象</strong></p>
<p>一个基本的 API 用于检查屏幕和浏览器窗口的尺寸,监视 DPI、颜色深度等。这有助于网站优化特定显示设备的页面展示。</p>
<p>navigator <strong>对象</strong></p>
<p>一个用于查询浏览器版本、底层操作系统和已安装插件列表的接口。</p>
<p>document <strong>对象</strong></p>
<p>这是最复杂的层次结构之一,是进入当前页面文档对象模型(Document Object Model)的入口;我们将在下一节中查看这个模型。在 <em>document</em> 层次结构下还出现了一些与文档结构无关的函数,通常是由于任意的设计决策。例如,<em>document.cookie</em> 用于操作 cookie,<em>document.write(...)</em> 用于将 HTML 附加到当前页面,以及 <em>document.execCommand(...)</em> 用于执行某些所见即所得(WYSIWYG)编辑任务。</p>
<h3 id="注意-29">注意</h3>
<p>有趣的是,通过 <em>navigator</em> 和 <em>screen</em> 对象可获得的信息足以以高置信度唯一标识许多用户。这个早已为人所知的属性通过电子前沿基金会(Electronic Frontier Foundation)的项目 <em>Panopticlick</em> 得到强调:<a href="https://panopticlick.eff.org/" target="_blank"><code>panopticlick.eff.org/</code></a>。</p>
<p>几种其他由语言强制规定的对象提供了简单的字符串处理或算术功能。例如,<em>Math.random()</em> 实现了一个不安全、可预测的伪随机数生成器(遗憾的是,在大多数浏览器中目前还没有安全的 PRNG 替代方案^([33])),而 <em>String.fromCharCode()</em> 可以用来将数值转换为 Unicode 字符串。在特权执行上下文中,这些上下文无法被普通网络应用程序访问,还将出现许多其他特定任务的对象。</p>
<h3 id="注意-30">注意</h3>
<p>当访问任何浏览器提供的对象时,重要的是要记住,虽然 JavaScript 不使用以 NUL 结尾的 ASCIZ 字符串,但底层浏览器(用 C 或 C++ 编写)有时会这样做。因此,将包含 NUL 的字符串分配给各种 DOM 属性,或提供给原生函数的结果可能是不可预测和不一致的。几乎所有浏览器都会在 <em>location.</em> 的分配处截断,但只有一些引擎在处理 DOM <strong>.innerHTML</strong> 时会这样做。</p>
<h2 id="文档对象模型">文档对象模型</h2>
<p>文档对象模型(Document Object Model),通过 <em>document</em> 层级结构可访问,提供了当前文档的结构化、内存表示,该表示由 HTML 解析器映射。生成的对象树公开了页面上的所有 HTML 元素、它们的特定于标签的方法和属性以及相关的 CSS 数据。这种表示,而不是原始的 HTML 源代码,被浏览器用于渲染和更新当前显示的文档。</p>
<p>JavaScript 可以以非常直接的方式访问 DOM,类似于任何普通对象。例如,以下代码片段将访问文档的 <em><body></em> 块内的第五个标签,查找第一个嵌套子标签,并将该元素的 CSS 颜色设置为红色:</p>
<pre><code>document.body.children[4].children[0].style.color = "red";
</code></pre>
<p>为了避免在 DOM 树中跋涉以到达特定的深层嵌套元素,浏览器提供了几个全局查找函数,例如 <em>getElementById(...)</em> 和 <em>getElementsByTagName(...)</em>,以及部分冗余的分组机制,如 <em>frames[]</em>, <em>images[]</em>, 或 <em>forms[]</em>。这些功能允许以下两行代码的语法,这两行代码都直接引用一个元素,无论它在文档层次结构中的位置如何:</p>
<pre><code>document.getElementsByTagName("input")[2].value = "Hi mom!";
document.images[7].src = "/example.jpg";
</code></pre>
<p>由于历史原因,某些 HTML 元素(如 <em><img></em>, <em><form></em>, <em><embed></em>, <em><object></em>, 和 <em><applet></em>) 的名称也直接映射到 <em>document</em> 命名空间,如下面的代码片段所示:</p>
<pre><code><img name="hello" src="http://www.example.com/">
<script>
alert(document.hello.src);
</script>
</code></pre>
<p>与全局命名空间中更合理的 <em>name</em> 和 <em>id</em> 映射的情况不同(见上一节),这样的 <em>document</em> 条目可能会覆盖内置函数和对象,如 <em>getElementById</em> 或 <em>body</em>。因此,允许用户指定的标签名称,例如用于构建表单,可能是不安全的。</p>
<p>除了提供对文档的抽象表示的访问外,许多 DOM 节点还可能公开 <em>innerHTML</em> 和 <em>outerHTML</em> 等属性,这些属性允许将文档树的一部分读取为格式良好的、序列化的 HTML 字符串。有趣的是,相同的属性可以写入以替换 DOM 树的任何部分,并用解析脚本提供的 HTML 片段的结果来替换。以下是一个该用途的例子:</p>
<pre><code>document.getElementById("output").innerHTML = "`<b>Hi mom!</b>`";
</code></pre>
<p>每个对 <em>innerHTML</em> 的赋值都必须涉及一个格式良好且自包含的 HTML 块,该块不会改变替换片段之外的文档层次结构。如果不符合此条件,输入将在替换之前被强制转换为格式良好的语法。因此,以下示例将不会按预期工作;也就是说,它不会以粗体显示“Hi mom!”,也不会将文档的其余部分以斜体显示:</p>
<pre><code>some_element.innerHTML = "<b>Hi";
some_element.innerHTML += " mom!</b><i>";
</code></pre>
<p>相反,这两个赋值将分别处理和纠正,从而产生相当于以下的行为:</p>
<pre><code>some_element.innerHTML = "<b>Hi</b> mom!<i></i>";
</code></pre>
<p>需要注意的是,<em>innerHTML</em> 机制应谨慎使用。除了如果未观察到适当的 HTML 转义,其本身容易受到标记注入的影响外,浏览器对 DOM 到 HTML 序列化算法的实现通常也不完美。以下是一个近期(现已修复)的 WebKit 中的此类问题示例:</p>
<pre><code><textarea>
&lt;/textarea&gt;&lt;script&gt;alert(1)&lt;/script&gt;
</textarea>
</code></pre>
<p>由于对 <em><textarea></em> 语义的混淆,这个看似明确的输入标记,在解析为 DOM 树并通过 <em>innerHTML</em> 访问时,会被错误地读取为:</p>
<pre><code><textarea>
</textarea><script>alert(1)</script>
</textarea>
</code></pre>
<p>在这种情况下,即使执行此序列化的无操作赋值(如 <em>some_element.innerHTML += ""</em>),也可能导致意外的脚本注入。类似的问题也常常困扰其他浏览器。例如,正在处理 <em>innerHTML</em> 代码的 Internet Explorer 开发者没有意识到 MSHTML 将反引号(`)识别为引号字符,因此最终处理错误。在他们的实现中,以下标记:</p>
<pre><code><img src="test.jpg" alt="``onload=alert(1)">
</code></pre>
<p>将被重新序列化为以下形式:</p>
<pre><code><img src=test.jpg alt=``onload=alert(1)>
</code></pre>
<p>除去个别错误,关于 <em>innerHTML</em> 的情况相当严峻:HTML5 当前草案的 10.3 节只是承认某些由脚本创建的 DOM 结构完全无法序列化为 HTML,并且不要求浏览器在这种情况下做出合理的反应。<em>买者自慎!</em></p>
<h2 id="访问其他文档">访问其他文档</h2>
<p>脚本可能会获得指向另一个脚本上下文根层次结构的对象句柄。例如,默认情况下,每个上下文都可以轻松地引用 <em>parent</em>、<em>top</em>、<em>opener</em> 和 <em>frames[]</em>,这些都在顶层对象中提供给它。调用 <em>window.open(...)</em> 函数创建新窗口也会返回一个引用,同样,尝试使用此语法查找现有命名窗口也会返回引用:</p>
<pre><code>var window_handle = window.open("", "`window_name`");
</code></pre>
<p>一旦程序持有指向另一个脚本上下文的句柄,它可能会尝试与该上下文交互,但需遵守第九章中讨论的安全检查。第九章。一个简单的交互示例可能如下:</p>
<pre><code>top.location.path = "/new_path.html";
</code></pre>
<p>或者</p>
<pre><code>frames[2].document.getElementById("output").innerHTML = "Hi mom!";
</code></pre>
<p>在没有有效句柄的情况下,与无关文档的 JavaScript 级别交互不应可能。特别是,无法查找在完全不同的导航流程中打开的无名窗口,至少直到其中一个访问的页面明确设置了它们的名称(<em>window.name</em> 属性允许这样做)。</p>
<hr>
<p>^([33]) Chrome 中最近添加了 <em>window.crypto.getRandomValues(...)</em> API,而 Firefox 中目前不可用的 <em>window.crypto.random(...)</em> API。</p>
<h1 id="脚本字符编码">脚本字符编码</h1>
<p>JavaScript 引擎支持几种熟悉的基于反斜杠的字符串编码方法,可以用来转义引号字符、HTML 标记和其他嵌入文本中的问题部分。这些方法如下:</p>
<ul>
<li>
<p>某些控制字符的 C 风格简写表示法:<em>\b</em> 表示退格,<em>\t</em> 表示水平制表符,<em>\v</em> 表示垂直制表符,<em>\f</em> 表示换页,<em>\r</em> 表示回车,<em>\n</em> 表示换行。这个精确的转义码集合被 ECMAScript 和 JSON RFC 都识别。</p>
</li>
<li>
<p>无前缀的三位、零填充的 8 位八进制字符码(例如,“\145”而不是“e”)。这种受 C 语言启发的语法不是 ECMAScript 的一部分,但在实践中所有脚本引擎都支持它,无论是在普通代码中还是在 <em>JSON.parse(...)</em> 中。</p>
</li>
<li>
<p>前缀为“x”的两位、零填充的 8 位十六进制字符码(“e”变为“\x65”)。同样,这个方案既不被 ECMAScript 也不被 RFC 4627 所支持,但它起源于 C 语言,在实践中被广泛支持。</p>
</li>
<li>
<p>前缀为“u”的两位、零填充的 16 位十六进制 Unicode 值(“e”变为“\u0065”)。这种格式被 ECMAScript 和 RFC 4627 所认可,并且所有现代浏览器都支持。</p>
</li>
<li>
<p>后跟非八进制数字的任何字符;用于其他预定义转义序列的“b”、“t”、“v”、“f”、“r”或“n”字符;以及“x”或“u”。在这个方案中,后续字符将被视为字面量。ECMAScript 允许使用此方案来转义引号和反斜杠字符本身,但在实践中,任何其他值也被接受。</p>
<p>这种方法有一定的错误倾向,并且与 CSS 的情况类似,不应用来转义尖括号和其他 HTML 语法分隔符。这是因为 JavaScript 解析发生在 HTML 解析之后,反斜杠前缀本身不会被 HTML 解析器以任何特殊方式处理。</p>
</li>
</ul>
<h3 id="注意-31">注意</h3>
<p>令人费解的是,Internet Explorer 不识别垂直制表符(“\v”)简写,从而创造了一种更方便(但非常淘气!)的测试该特定浏览器的方法:</p>
<pre><code>if ("\v" == "v") alert("Looks like Internet Explorer!");
</code></pre>
<p>意外地,基于 Unicode 的转义方法(但不包括其他方法)也在字符串之外被识别。尽管这个想法似乎很随意,但与 CSS 相比,行为要合理一些:转义码只能在标识符中使用,并且不能作为任何语法敏感符号的替代。因此,以下是可以的:</p>
<pre><code>\u0061lert("This displays a message!");
</code></pre>
<p>另一方面,任何以类似方式替换括号或引号的行为都会失败。</p>
<p>与某些 C 或 C++ 实现 不同,JavaScript 引擎不容忍多余的多行字符串字面量。话虽如此,尽管 ECMAScript 规范中有明确的禁止,但有一个例外:行尾的单个反斜杠可以用来无缝连接多行字面量。以下是一个示例:</p>
<pre><code>var text = 'This syntax
is invalid.';
var text = 'This syntax, on the other hand, \
is OK in all browsers.';
</code></pre>
<h1 id="代码包含模式及嵌套风险">代码包含模式及嵌套风险</h1>
<p>如本章前面的讨论所示,有几种方法可以在当前页面的上下文中执行脚本。可能有用的是列举一些最常见的方法:</p>
<ul>
<li>
<p>内联 <em><script></em> 块</p>
</li>
<li>
<p>使用 <em><script src=...></em> 加载的远程脚本^([34])</p>
</li>
<li>
<p><em>javascript:</em> 在各种 HTML 参数和 CSS 中的 URL</p>
</li>
<li>
<p>某些浏览器的 CSS <em>expression(...)</em> 语法和 XBL 绑定</p>
</li>
<li>
<p>事件处理器 (<em>onload</em>, <em>onerror</em>, <em>onclick</em>, 等)</p>
</li>
<li>
<p>定时器 (<em>setTimeout</em>, <em>setInterval)</em></p>
</li>
<li>
<p><em>eval(...)</em> 调用</p>
</li>
</ul>
<p>将这些方法结合起来通常看起来很自然,但这样做可能会创建非常意外且危险的分析链。例如,考虑需要对服务器插入的值应用于此代码中 <em>user_string</em> 的转换:</p>
<pre><code><div onclick="setTimeout('do_stuff(\`'user_string`\')', 1000)">
</code></pre>
<p>很难注意到值将经过不少于三轮的解析!首先,HTML 解析器将提取 <em>onclick</em> 参数并将其放入 DOM;接下来,当按钮被点击时,第一轮 JavaScript 解析将提取 <em>setTimeout(...)</em> 语法;最后,在初始点击后一秒钟,实际的 <em>do_stuff(...)</em> 序列将被解析并执行。</p>
<p>因此,在上面的例子中,为了在过程中幸存下来,<em>user_string</em> 需要使用 JavaScript 反斜杠序列进行双重编码,然后使用 HTML 实体再次编码,顺序必须完全一致。任何不同的方法都可能引发代码注入。</p>
<p>下面是一个展示复杂转义情况的示例:</p>
<pre><code><script>
var some_value = `"user_string";`
...
setTimeout("do_stuff('" + some_value + "')", 1000);
</script>
</code></pre>
<p>即使 <em>some_value</em> 的初始赋值只需要对 <em>user_string</em> 进行一次转义,但在 <em>setTimeout(...)</em> 参数中构建的二级脚本在事先没有进行额外转义的情况下会引入漏洞。</p>
<p>这样的编码模式在 JavaScript 程序中经常出现,而且很容易被忽略。一致地禁止它们比审计产生的代码要好得多。</p>
<hr>
<p>^([34]) 在两种类型的 <em><script></em> 块中,Microsoft 支持一种称为 <em>JScript.Encode</em> 的伪方言。可以通过在 <em><script></em> 标签上指定 <em>language</em> 参数来选择此模式,它简单地允许使用简单的字母替换密码对实际脚本进行编码,使其对普通用户不可读。从安全角度来看,这种机制完全无价值,因为“加密”可以很容易地恢复。</p>
<h1 id="活着的死者visual-basic">活着的死者:Visual Basic</h1>
<p>在涵盖了与 JavaScript 相关的绝大部分内容后,现在是时候对那个长期被遗忘的脚本王位竞争者表示敬意了。尽管在几乎完全被遗忘的状态中徘徊了 15 年,但浏览器端的 VBScript 在 Internet Explorer 中仍然得到支持。在大多数方面,Microsoft 的语言应该与 JavaScript 功能上等效,并且它能够访问与 JavaScript 完全相同的 Document Object Model API 和其他内置函数。但是,正如人们所预期的,一些调整和扩展是存在的——例如,用几个 VB 特定的函数替换 JavaScript 的内置函数。</p>
<p>几乎没有研究 VBScript 的安全属性、解析器的健壮性或其与现代 DOM 的潜在不兼容性。轶事证据表明,该语言在 Microsoft 端也没有得到一致的审查。例如,内置的<em>MsgBox</em>^([153])可以用来显示具有一定程度灵活性的模态、始终在顶部的提示,这在 JavaScript 世界中是完全闻所未闻的,让<em>alert(...)</em>相形见绌。</p>
<p>预测 VBScript 在这个浏览器中还将支持多久以及它对用户和 Web 应用安全可能产生的意外后果是困难的。只有时间才能告诉我们。</p>
<p>安全工程速查表</p>
<p>当加载远程脚本时</p>
<p>与 CSS 一样,你将你站点的安全性链接到脚本的原始域名。如有疑问,请制作数据的本地副本。在 HTTPS 站点上,要求所有脚本通过 HTTPS 提供服务。</p>
<p>当解析从服务器接收到的 JSON 时</p>
<p>在支持的地方依赖<em>JSON.parse(...)</em>。不要使用<em>eval(...)</em>或 RFC 4627 中提供的基于<em>eval</em>的实现。两者都不安全,尤其是在处理第三方数据时。RFC 4627 的作者的一个后续实现,<em>json2.js</em>^([154])可能没问题。</p>
<p>当在 JavaScript 块中放置用户提供的数据时</p>
<ul>
<li>
<p><strong><script>块中的独立字符串:</strong>使用数字代码转义所有控制字符(0x00 - 0x1F)、“\”, “<”, “>”和引号。还最好转义高位字符。</p>
<p>不要依赖于用户提供的字符串来构建动态 HTML。始终使用安全的 DOM 功能,如<em>innerText</em>或<em>createTextNode(...)</em>,而不是使用用户提供的字符串来构建二阶脚本;避免使用<em>eval(...)</em>、<em>setTimeout(...)</em>等。</p>
</li>
<li>
<p><strong>独立于单独提供的脚本中的字符串:</strong>遵循与<em><script></em>块相同的规则。如果您的脚本包含任何敏感的、用户特定的信息,请确保考虑到跨站脚本包含的风险;在文件开头附近使用可靠的解析器破坏前缀,例如“)}]' \n”,或者在最低限度内,使用没有填充或其他调整的正确 JSON 序列化。此外,请参阅第十三章以获取有关如何防止非 HTML 内容中的跨站脚本的建议。</p>
</li>
<li>
<p><strong>内联事件处理器中的字符串</strong>、javascript: <strong>URLs</strong>和如此等等:涉及多级转义。不要尝试这样做,因为它容易出错。如果不可避免,首先应用上述 JS 转义规则,然后根据适用情况对结果字符串应用 HTML 或 URL 参数编码。永远不要与<em>eval(...)</em>、<em>setTimeout(...)</em>、<em>innerHTML</em>等一起使用。</p>
</li>
<li>
<p><strong>非字符串内容:</strong>仅允许白名单中的字母数字关键字和经过仔细验证的数值。不要尝试拒绝已知的坏模式。</p>
</li>
</ul>
<p>当与客户端上的浏览器对象交互时</p>
<ul>
<li>
<p><strong>在客户端生成 HTML 内容:</strong>不要求助于<em>innerHTML</em>、<em>document.write(...)</em>和类似的工具,因为它们容易引入跨站脚本漏洞,通常以意想不到的方式。使用安全的方法,如<em>createElement(...)</em>和<em>appendChild(...)</em>以及<em>innerText</em>或<em>textContent</em>属性来构建文档。</p>
</li>
<li>
<p><strong>依赖于用户控制的数据:</strong>对从浏览器读取的任何值应用的转义规则不要做任何假设,特别是对<em>location</em>属性和其他外部 URL 来源,这些属性不一致,并且因实现而异。始终自行转义。</p>
</li>
</ul>
<p>如果您想在您的页面上允许用户控制的脚本</p>
<p>实际上几乎不可能安全地做到这一点。像 Caja([<code>code.google.com/p/google-caja/</code>](<a href="http://code.google.com/p/google-caja/%EF%BC%89%EF%BC%89%E8%BF%99%E6%A0%B7%E7%9A%84%E5%AE%9E%E9%AA%8C%E6%80%A7" target="_blank">http://code.google.com/p/google-caja/))这样的实验性</a> JavaScript 重写框架是唯一的便携式选择。有关沙箱框架的信息,请参阅第十六章,这是在网页上嵌入不受信任的小工具的即将推出的替代方案。</p>
<h1 id="第七章非-html-文档类型">第七章。非 HTML 文档类型</h1>
<p>除了 HTML 文档之外,现代网络浏览器的渲染引擎还识别并显示大约十几种其他文件格式;这个列表可能会随着时间的推移而增长。</p>
<p>由于这些格式中一些格式强大的脚本功能,以及浏览器内容处理的怪异行为,原生支持的非 HTML 输入集值得在此进行更仔细的检查,即使对它们一些不太明显的安全后果的详细讨论——例如<em>内容嗅探</em>——将不得不等到本书的第二部分。</p>
<h1 id="纯文本文件">纯文本文件</h1>
<p>每个浏览器都能识别的最常见的非 HTML 文档类型是纯文本文件。在这种渲染模式下,输入内容会原样显示,通常使用非等宽字体,除非进行可选的字符集转换,否则数据不会以任何方式改变。</p>
<p>所有浏览器都识别 HTTP 头中带有<code>Content-Type: text/plain</code>的纯文本文件。在所有实现中,除了 Internet Explorer 之外,纯文本也是无头 HTTP/0.9 响应和缺少<code>Content-Type</code>的 HTTP/1.<em>x</em>数据的回退显示方法;在这两种情况下,当所有其他内容检测启发式方法失败时,都会使用纯文本。 (Internet Explorer 无条件地回退到 HTML 渲染,这符合蒂姆·伯纳斯-李原始协议草案的字面意思。)</p>
<p>为了方便开发者,大多数浏览器还自动将几个其他 MIME 类型映射到纯文本,包括<code>application/javascript</code>及其相关类型^([35])或<code>text/css</code>。有趣的是,RFC 4627 中规定的 JSON 响应的值<code>application/json</code>并不在列表中(可能是因为在实践中很少使用)。</p>
<p>纯文本渲染没有特定的安全后果。尽管如此,由于其他浏览器组件和第三方代码中存在一系列设计不当的决定,即使是看似无害的非 HTML 格式也有被错误识别为 HTML 的风险。攻击者控制的纯文本文档尤其值得关注,因为它们的布局通常相当不受限制,因此特别容易发生误识别。第十三章分析了这些威胁,并提供了减轻风险的建议。</p>
<hr>
<p>^([35]) 根据 RFC 4329,JavaScript 的官方 MIME 类型是<code>application/javascript</code>,但过去曾使用过大约十种其他值(例如,<code>text/javascript, application/x-javascript, application/ecmascript</code>)。</p>
<h1 id="位图图像">位图图像</h1>
<p>浏览器渲染引擎在通过<code><img></code>标签加载 HTML 文档时,会识别直接导航到一组通常支持的位图图像格式,包括 JPEG、PNG、GIF、BMP 以及一些其他格式。当用户直接导航到这样的资源时,解码后的位图会在文档窗口中显示,用户只能进行滚动、缩放和将文件保存到磁盘等有限操作。</p>
<p>在没有<code>Content-Type</code>信息的情况下,图像的检测基于文件头检查。当存在<code>Content-Type</code>值时,它会与大约十种预定义的图像类型进行比较,并根据结果将用户路由到相应的类型。但如果解码图像失败,则会使用文件头进行第二次猜测。因此,尽管如第十三章中探讨的原因,这样做通常是不明智的,但可以将 GIF 文件作为<code>image/jpeg</code>提供服务。</p>
<p>与文本文件一样,位图图像是一种被动资源,不携带任何异常的安全风险.^([36]) 然而,每当提供用户提交的图像时,请记住攻击者将对数据进行一定程度的控制,即使格式经过仔细验证并进行了缩放或重新压缩。因此,关于这种文档格式可能被浏览器或插件错误解释的担忧仍然存在。</p>
<hr>
<p>^([36]) 自然,处理复杂数据格式的所有程序中偶尔都会发生可利用的编码错误,图像解析器也不例外。</p>
<h1 id="音频和视频">音频和视频</h1>
<p>很长一段时间内,浏览器没有内置支持播放音频和视频内容的功能,除了在 Internet Explorer 中一个晦涩且常被嘲讽的 <em><bgsound></em> 标签,它至今仍可用于播放简单的 MID 或 WAV 文件。在没有真正的跨浏览器多媒体播放功能的情况下,音频和视频几乎完全由浏览器插件掌控,无论是专门构建的(如 Windows Media Player 或 Apple QuickTime)还是通用的(如 Adobe Flash、Microsoft Silverlight 等)。</p>
<p>HTML5 的工作正在寻求通过支持 <em><audio></em> 和 <em><video></em> 标签来改变这一点:方便的、可脚本化的方法来与内置媒体解码器接口。不幸的是,在支持哪些视频格式以及这一决定可能带来的专利后果方面,存在大量的供应商级别分歧。例如,尽管许多浏览器已经支持 Ogg Theora(一个免费、开源但有些小众的编解码器),但围绕支持非常受欢迎但受专利和版税限制的 H.264 格式以及新的、谷歌支持的 WebM 替代方案的激烈争论可能会在可预见的未来继续下去。</p>
<p>与其他被动媒体格式(以及一些插件渲染的内容不同!)一样,只要适当缓解了内容误识别的可能性,<em><bgsound></em> 和 HTML5 多媒体都不太可能对 Web 应用程序安全产生任何异常影响.^([37])</p>
<hr>
<p>^([37]) 但各种技术之间的一些牵强附会的交互是明确可能的。例如,如果 <em><audio></em> 标签支持原始、未压缩的音频,并且指向一个敏感的非音频文档,然后另一个网站使用提出的 HTML5 麦克风 API 来捕获生成的波形并重建文件内容呢?</p>
<h1 id="基于-xml-的文档">基于 XML 的文档</h1>
<p>对于那些认为迄今为止讨论的格式处理过于理智的读者来说,他们将得到一份应得的享受。浏览器支持的最大的、无疑也是最有意思的非 HTML 文档类型家族依赖于常见的 XML 语法,并提供了超过公平份额的有趣惊喜。</p>
<p>属于这一类别的几种格式被转发到专门的、单一用途的 XML 分析器,通常基于接收到的<em>Content-Type</em>值或其他简单的启发式方法。但更常见的是,有效载荷被路由到依赖于渲染 XHTML 文档的相同解析器,然后使用此通用管道显示。</p>
<p>在后一种情况下,文档的实际含义由标记本身中存在的类似 URL 的<em>xmlns</em>命名空间指令决定,命名空间参数可能与最初在<em>Content-Type</em>中提供的值无关。简单来说,没有任何机制可以阻止一个作为<em>application/mathml+xml</em>提供的文档只包含 XHTML 标记并以<em><html ></em>开始。</p>
<p>在最常见的情况下,整个 XML 文件的命名空间只定义一次,并附加到顶级标签上。然而,原则上,单个文件中可以出现任意数量的不同<em>xmlns</em>指令,为文档的每个部分赋予不同的含义。例如:</p>
<pre><code><html >
<u>Hello world!</u>
<svg >
<line x1="0" y1="0" x2="100" y2="100" style="stroke: red" />
</svg>
</html>
</code></pre>
<p>面对这样的输入,通用渲染器通常会尽力理解所有被识别的命名空间,并将标记组装成一个具有正常文档对象模型表示的单个、一致的文档。而且,如果任何被识别的命名空间恰好支持脚本,任何嵌入的脚本也将执行。</p>
<p>由于<em>xmlns</em>处理行为的某种反直觉性,<em>Content-Type</em>不是控制特定 XML 文档如何解析的合适方式;特定顶级<em>xmlns</em>指令的存在也不能保证以后不会尊重其他数据格式。因此,任何由攻击者控制的基于 XML 的格式都必须谨慎处理并彻底清理。</p>
<h2 id="通用-xml-视图">通用 XML 视图</h2>
<p>在大多数浏览器中,一个有效的 XML 文档,其标记中任何地方都没有解析器识别的命名空间,将显示为文档树的交互式、格式化的表示,如图图 7-1 所示。这种模式对最终用户来说并不特别有用,但它可以帮助调试。</p>
<p>话虽如此,当文档中的任何命名空间为浏览器所知(即使顶级命名空间根本不被识别!)时,文档的渲染方式将不同:所有被识别的标记将按预期工作,所有不受支持的标记将简单地没有效果,并且任何位于它们之间的文本将按原样显示。</p>
<p>为了说明这种渲染策略,考虑以下输入:</p>
<pre><code><foo >
<u>Hello</u>
<html >
<u>world!</u>
</html>
</foo>
</code></pre>
<p>上述示例将被渲染为“Hello world!”第一个没有与它关联语义定义命名空间的<em><u></em>标签将没有可见效果。第二个将被理解为触发下划线的 XHTML 标签。</p>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950019.png.jpg" alt="Firefox 显示没有识别命名空间的 XML 文档" loading="lazy"></p>
<p>图 7-1. Firefox 显示没有识别命名空间的 XML 文档</p>
<p>这种容错方法对未知 XML 文档和未识别命名空间的渲染后果微妙但相当重要。例如,即使这种格式通常被路由到专门的渲染器,不受到 XSS 风险的影响,也不应该代理未清理的 RSS 源。任何没有内置 RSS 阅读器的浏览器都可能回退到通用渲染,然后发现 HTML 深深地隐藏在源中。</p>
<h2 id="可缩放矢量图形">可缩放矢量图形</h2>
<p>可缩放矢量图形 (SVG)^([155]) 是一种快速发展的、基于 XML 的矢量图形格式。它由 W3C 于 2001 年首次发布,以其集成的动画功能和直接的 JavaScript 脚本功能而著称。以下矢量图像示例绘制了一个圆,并在点击该圆时显示一条消息:</p>
<pre><code><svg >
<script><![CDATA[
function clicked() { alert("Hi mom!"); }
]]></script>
<circle onclick="clicked()" cx="50" cy="50"
r="50" fill="pink" />
</svg>
</code></pre>
<p>SVG 文件格式被所有现代浏览器所识别,除了 9 版本之前的 Internet Explorer,它由通用 XML 渲染器处理。SVG 图像可以通过适当的 <em>xmlns</em> 指令嵌入到 XHTML 中,或者使用预定义的 <em><svg></em> 标签内联在非 XML HTML5 文档中。</p>
<p>有趣的是,在几个浏览器中,该格式也可以放置在独立的 XML 文档中,然后直接查看,或者可以通过 <em><img></em> 标记在第三方页面上加载。虽然通过 <em><img></em> 加载 SVG 图像是安全的(在这种情况下应禁用脚本),但托管用户提供的 SVG 数据相当危险,因为在直接导航的情况下,所有嵌入的脚本都将执行在托管域的上下文中。这个意外的问题意味着,提供任何外部来源的 SVG 图像都需要非常仔细的语法清理,以从 XML 容器中消除非 SVG <em>xmlns</em> 内容,并允许在文档的其余部分中仅使用某些类型的标记。</p>
<h3 id="注意-32">注意</h3>
<p>相关 HTTP 响应的 <em>Content-Disposition</em> 标头是一个潜在的解决方案,允许通过 <em><img></em> 包含 SVG,但不能直接访问。这种方法并不完美,但它限制了风险。使用一次性域名托管此类图像是另一种可能性。</p>
<h2 id="数学标记语言">数学标记语言</h2>
<p>数学标记语言(MathML)^([156]) 是一种相当直接的方法,用于促进数学方程式的语义表示,尽管有些冗长。该标准最初由 W3C 在 1998 年提出,并且多年来得到了大幅改进。由于其相对狭窄的应用范围,MathML 需要超过十年时间才在 Opera 和 Firefox 浏览器中获得部分支持,但如今它正在逐渐被接受。在支持该语言的浏览器中,它可以被放置在独立的文件中,或者在 XHTML 和 HTML5 文档中内联。</p>
<p>与 SVG 不同,MathML 没有超出通用处理 XML 之外的其他安全考虑。</p>
<h2 id="xml-用户界面语言">XML 用户界面语言</h2>
<p>XML 用户界面语言(XUL)^([157]) 是 Mozilla 专门为构建基于浏览器的应用程序而不是文档而创建的表示标记语言。XUL 之所以存在,是因为尽管现代 HTML 通常足够强大以构建基本的图形用户界面,但它并不特别方便用于桌面应用程序擅长的某些特定任务,例如实现常见的对话框窗口或系统菜单。</p>
<p>XUL 目前除了 Firefox 以外的任何浏览器都不支持,并且似乎在最近的 Firefox 6 版本中被禁用。在 Firefox 中,它由基于适当<em>xmlns</em>命名空间的通用渲染器处理。Firefox 使用 XUL 来处理其大部分内部用户界面,但除此之外,这种语言在互联网上很少遇到。</p>
<p>从网络应用安全的角度来看,源自互联网的 XUL 文档可以大致等同于 HTML 文档。本质上,这种语言具有 JavaScript 脚本功能,并允许对渲染页面的外观进行广泛控制。除了这个特性外,它没有其他不寻常的怪癖。</p>
<h2 id="无线标记语言">无线标记语言</h2>
<p>无线标记语言(WML)^([158]) 是一种在 20 世纪 90 年代由一群移动手机制造商和蜂窝网络运营商联合开发的“优化”HTML 语法,它已经很大程度上过时了。这种基于 XML 的语言是无线应用协议套件(WAP)的一部分,为带宽和 CPU 资源有限的预智能手机设备提供了简化的类似网页的浏览体验。^([38]) 一个简单的 WML 页面可能看起来像这样:</p>
<pre><code><wml>
<card title="Hello world!">
<a href="elsewhere.wml">Click here!</a>
</card>
</wml>
</code></pre>
<p>由于 WAP 服务需要独立于正常的 HTML 内容进行设计,并且必须处理封闭和不明确的客户端架构以及其他运营商强加的限制,因此 WML 从未像其支持者所希望的那样流行。在几乎所有发达市场,WML 已经被快速、互联网连接的智能手机和功能齐全的 HTML 浏览器所取代。尽管如此,该语言的遗产仍然存在,并且它仍然在 Opera 和 Internet Explorer Mobile 中的专用渲染器中被路由。</p>
<p>在支持该格式的浏览器中,通常可以使用基于 WML 的脚本。有两种方法可以实现这一点。标准的方法是使用 WMLScript(WMLS),这是一个基于 JavaScript 的执行环境,依赖于独立的脚本文件,并且对片段 ID 进行了极端的不当使用,以实现可能由攻击者控制的<em>eval(...)</em>语句:</p>
<pre><code><a href="scriptfile.wmls#some_function()">Click here!</a>
</code></pre>
<p>在功能更强大的浏览器中,执行脚本的另一种方法是简单地嵌入正常的<em>javascript:</em> URL 或将<em><script></em>块插入到 WML 文件中。</p>
<h2 id="rss-和-atom-源">RSS 和 Atom 源</h2>
<p><em>源</em>是客户端定期轮询用户感兴趣站点(如他们最喜欢的博客)以获取机器可读更新的一种标准化方式。真正简单的聚合(RSS)^([159])和 Atom^([160])是两种表面上相似但激烈竞争的基于 XML 的源格式。第一个(RSS)很受欢迎;第二个(Atom)据说很好。</p>
<p>Firefox、Safari 和 Opera 都提供了内置的专门 RSS 和 Atom 渲染器。将 XML 文档路由到这些模块的决定基于简单的、浏览器特定的启发式方法,例如顶级标签被命名为<strong><rss></strong>或<strong><feed></strong>(并且没有任何冲突的<strong>xmlns</strong>指令)。在 Firefox 中,即使<strong>Content-Type</strong>是<strong>image/svg+xml</strong>或<strong>text/html</strong>,RSS 解析也可能启动。Safari 会高兴地识别更多不相关的 MIME 类型。</p>
<p>这两种源格式的一个有趣特点是它们允许 HTML 的一个子集,包括 CSS,以一种相当奇特、间接的方式嵌入到文档中:作为实体转义文本。以下是一个这种语法的示例:</p>
<pre><code><rss>
...
<description type="html">
&lt;u&gt; Underlined text! &lt;/u&gt;
</description>
...
</rss>
</code></pre>
<p>RSS 和 Atom 源中允许的 HTML 子集定义得并不好,一些源渲染器以前允许直接脚本或导航到可能危险的伪 URL。然而,更重要的是,任何没有内置源预览功能的浏览器可能会使用通用的 XML 解析方法来渲染文件;如果这些源没有经过仔细的清理,就会导致脚本执行。</p>
<hr>
<p>^([38]) 精明的读者会注意到 XML 并不是特别好的节省带宽或 CPU 资源的方式。为了这个目的,WAP 套件提供了一个 XML 的二进制仅序列化版本,称为 WBXML。</p>
<h1 id="关于不可渲染文件类型的一个说明">关于不可渲染文件类型的一个说明</h1>
<p>为了完整性,应该指出,所有现代浏览器都支持一些专门的文件格式,这些格式对渲染器或 Web 应用程序层来说是完全透明的,但仍然被浏览器中的各种子系统所识别。</p>
<p>对这些格式的详细调查超出了本书的范围,但一些值得注意的例子包括插件和扩展安装清单、自动 HTTP 代理自动配置文件(PAC)、可安装的视觉皮肤、证书吊销列表(CRL)、反恶意软件网站黑名单以及可下载的 TrueType 和 OpenType 字体。</p>
<p>在决定允许任何这些格式被提供给用户之前,应该单独研究这些机制的安全性属性。除了在第十三章中概述的通用内容托管考虑因素外,它们不太可能直接损害托管 Web 应用程序,但它们可能会给用户带来问题。</p>
<p>安全工程速查表</p>
<p>当托管基于 XML 的文档格式时</p>
<p>假设负载可能被解释为 XHTML 或其他启用了脚本功能的文档类型,无论<code>Content-Type</code>和顶级<code>xmlns</code>指令如何。不要允许在文件内部任何地方使用不受约束的攻击者控制的标记。如果数据不是直接查看的,请使用<code>Content-Disposition: attachment</code>;<code><img></code>和 feed 仍然可以工作。</p>
<p>在所有非 HTML 文档类型上</p>
<p>使用正确的、浏览器认可的<code>Content-Type</code>和<code>charset</code>值。尽可能指定<code>Content-Disposition: attachment</code>。验证和约束输出语法。参考第十三章中的 cheat sheet,以避免与内容嗅探漏洞相关的安全问题。</p>
<h1 id="第八章-使用浏览器插件进行内容渲染">第八章. 使用浏览器插件进行内容渲染</h1>
<p>浏览器插件形式多样,但最常见的类型是能够在浏览器中显示新的文件格式,就像它们是 HTML 一样。浏览器只是将检索到的文件交给插件,在文档窗口中为辅助应用程序提供一个矩形绘图表面,并基本上从场景中退去。这种内容渲染插件与浏览器扩展明显不同,后者是一大批数量众多的插件,通常依赖于 JavaScript 代码来调整已支持的、在浏览器中呈现给用户的内容。</p>
<p>浏览器插件有着漫长且复杂的安全漏洞历史。实际上,根据一些分析人士的说法,2010 年最常见的 15 个客户端漏洞中有 12 个可以归因于插件软件的质量。^([161)] 许多这些问题是因为底层解析器最初并未设计为优雅地处理恶意输入,并且没有从整个网络所经历的严格审查中受益。其他问题则源于插件开发者设计的非传统安全模型,以及这些权限、传统浏览器设计以及应用程序开发者常识性期望之间的干扰。</p>
<p>在本书的下一章中,我们将回顾一些流行插件使用的安全机制。在深入探讨之前,了解插件如何与其他在线内容集成以及它们提供的常见功能是有意义的。</p>
<h1 id="调用插件">调用插件</h1>
<p>内容渲染插件可以通过几种方式激活。最流行的一种显式方法是使用“宿主”HTML 文档中的<code><embed src=...></code>或<code><object data=...></code>标记,其中<code>src</code>或<code>data</code>参数指向要检索的实际插件识别文档的 URL。可以通过 CSS(或使用传统的 HTML 参数)来控制分配给插件的绘制区域的尺寸和位置。</p>
<p>在这种情况下,每个 <code><embed></code> 或 <code><object></code> 标签都应该伴随一个额外的 <code>type</code> 参数。那里指定的 MIME 类型将与所有活动插件注册的 MIME 类型列表进行比较,检索到的文件将被路由到适当的处理程序。如果没有找到匹配项,理论上应该显示一个警告,提示用户下载插件,尽管大多数浏览器在求助于这种不可想象的可能性之前会查看其他信号;检查 <code>Content-Type</code> 或在 URL 中发现的明显文件扩展名是两种常见的选择。</p>
<h3 id="注意-33">注意</h3>
<p>一个过时的 <code><applet></code> 标签,用于加载 Java 程序(大致相当于 <code><object type="application/x-java-applet"></code>),以类似的方式工作,但无条件地忽略这些辅助信号。</p>
<p>向插件提供的额外输入通常使用嵌套在 <code><object></code> 块内的 <code><param></code> 标签或通过附加到 <code><embed></code> 标记的非标准额外参数来传递。前者,更现代的方法可能看起来像这样:</p>
<pre><code><object data="app.swf" type="application/x-shockwave-flash">
<param name="some_param1" value="some_value1">
<param name="some_param2" value="some_value2">
...
</object>
</code></pre>
<p>在这种内容包含模式下,当检索子资源时,服务器返回的 <code>Content-Type</code> 头通常会被忽略,除非浏览器不知道 <code>type</code> 参数。这是一个不幸的设计,原因将在稍后解释。</p>
<p>显示插件内容的另一种方法是通过直接导航到合适的文件。在这种情况下,以及当 <code><embed></code> 或 <code><object></code> 缺少 <code>type</code> 参数时,将从服务器获得的 <code>Content-Type</code> 值将被尊重,并将其与插件识别的 MIME 类型列表进行比较。如果找到匹配项,内容将被路由到适当的组件。如果 <code>Content-Type</code> 查找失败或缺少头信息,一些浏览器将检查响应体中的已知内容签名;其他浏览器则放弃。</p>
<h3 id="注意-34">注意</h3>
<p>除了上述内容聚焦的方法之外,一些类型的插件可以直接从 JavaScript 或 VBScript 程序中加载,而无需显式创建任何 HTML 标记或检索任何外部数据。ActiveX 就是这种情况,这是一个臭名昭著的脚本到系统集成的桥梁,可在 Internet Explorer 中使用。(我们将在本章稍后讨论 ActiveX,但首先应该先了解一些基本概念。)</p>
<h2 id="插件内容类型处理的危险">插件内容类型处理的危险</h2>
<p>如前所述,在某些场景中,检索的插件处理的文件上的 <code>Content-Type</code> 参数会被忽略,而嵌入页面上的相应标记中的 <code>type</code> 参数将被使用。虽然这个决定与其他类型特定的内容包含标签(例如,<code><img></code>)的行为有些相似,正如在 类型特定的内容包含 中所讨论的,但在插件世界中,它有一些独特且最终灾难性的后果。</p>
<p>大问题是,几种类型的插件本质上是完全的代码执行环境,并赋予执行的应用程序(<em>applets</em>)一系列特殊权限与原始域名交互。例如,从<a href="http://fuzzybunnies.com" target="_blank">fuzzybunnies.com</a>检索到的 Flash 文件,在嵌入到明显可疑的<a href="http://bunnyoutlet.com" target="_blank">bunnyoutlet.com</a>时,将获得对其原始域的访问权限(包括用户的 cookies)。</p>
<p>在这种情况下,对于<a href="http://fuzzybunnies.com" target="_blank">fuzzybunnies.com</a>来说,能够清楚地传达特定类型的文档确实是为了由插件来解释的——并且因此,某些文档并不适合以这种方式使用——这似乎很重要。不幸的是,这种情况根本无法实现:检索到的文件的处理完全由嵌入网站控制(在我们的例子中,由拥有<a href="http://bunnyoutlet.com" target="_blank">bunnyoutlet.com</a>的刻薄恶霸控制)。因此,如果原始域名托管任何类型的用户可控内容,即使是在名义上无害的格式(如<em>text/plain</em>或<em>image/jpeg</em>),<a href="http://bunnyoutlet.com" target="_blank">bunnyoutlet.com</a>的拥有者可以指示浏览器忽略现有的元数据,并将该文档路由到他们选择的插件。为了实现这个险恶的目标,可能需要一个简单的标记来做到这一点。</p>
<pre><code><object data="http://fuzzybunnies.com/avatars/user11630.jpg"
type="application/x-shockwave-flash">
</code></pre>
<p>如果这种事件的发展看起来是错误的,那是因为它确实是。安全研究人员反复证明,构建同时是有效图像和有效插件识别的可执行文档是非常容易的。2008 年由 Billy Rios 发现的著名的“GIFAR”漏洞,就利用了这种技巧:它将 Java applet 隐藏在一个完全合法的 GIF 图像中。作为回应,Sun Microsystems 据报道加强了 Java JAR 文件解析器的控制以减轻风险,但此类错误的普遍威胁仍然非常真实,并且可能会再次露出其丑陋的真相。</p>
<p>有趣的是,一些开发者决定在未识别<em>type</em>参数时依赖于<em>Content-Type</em>和其他信号,这个决定几乎和前者一样糟糕。这个决定使得<a href="http://fuzzybunnies.com" target="_blank">well-intentioned fuzzybunnies.com</a>无法通过简单地指定<em>type="video/x-ms-wmv"</em>来安全地嵌入来自<a href="http://bunnyoutlet.com" target="_blank">bunnyoutlet.com</a>的无害视频,因为如果访问者中任何一个人没有该特定媒体类型的插件,<a href="http://bunnyoutlet.com" target="_blank">bunnyoutlet.com</a>将突然有权决定在嵌入网站上加载哪种类型的插件。一些浏览器,如 Internet Explorer、Chrome 或 Opera,也可能求助于在 URL 中寻找明显的文件扩展名,这可能导致一个有趣的情况,即嵌入方和托管方都没有真正控制文档的显示方式——而且通常只有攻击者才是真正掌权的人。</p>
<p>一个更安全的设计将需要嵌入器控制的<strong>类型</strong>参数和主机控制的<strong>Content-Type</strong>头来匹配(至少表面上)。不幸的是,目前还没有办法实现这一点。一些单独的插件试图表现得很好(例如,在 2008 年全面改版后,Adobe Flash 拒绝使用<strong>Content-Disposition: attachment</strong>提供的 applet,Chrome 内置的 PDF 阅读器也是如此),但这些改进是少之又少。</p>
<h1 id="文档渲染助手">文档渲染助手</h1>
<p>插件领域的一个显著部分属于允许某些非常传统、非 Web 文档格式直接在浏览器中显示的程序。其中一些程序确实很有用:Windows Media Player、RealNetworks RealPlayer 和 Apple QuickTime 一直是大约十年在线多媒体播放的支柱,至少直到 Adobe Flash 取代它们。然而,其他一些程序的优点则更加可疑。例如,Adobe Reader 和 Microsoft Office 都安装了浏览器文档查看器,大大增加了用户的攻击面,尽管不清楚这些查看器是否比在单独的应用程序中打开同一文档多点击一次真正有益。</p>
<p>当然,在一个完美的世界里,托管或嵌入 PDF 或 Word 文档应该不会对参与网站的安全性产生直接影响。然而,不出所料,现实情况却大相径庭。2009 年,一位研究人员指出,基于 PDF 的表单提交到<em>javascript:</em> URL 可能会导致嵌入站点上的客户端代码执行.^([163]) 根据那位研究人员的描述,Adobe 最初以以下注释驳回了这份报告:“我们的立场是,就像一个 HTML 页面一样,PDF 文件是活动内容。”</p>
<p>很遗憾,托管方无法完全控制何时检测和执行这种活动内容,而且其他合理的网站管理员可能会将 PDF 或 Word 文档仅仅视为一种展示文本的华丽方式。实际上,尽管它们看起来无害,但为了看起来酷,许多这样的文档格式都配备了他们自己的超链接功能或甚至脚本语言。例如,JavaScript 代码可以嵌入到 PDF 文档中,在 Microsoft Office 文件中也可以使用 Visual Basic 宏。当一个包含脚本的文档在 HTML 页面上显示时,通常需要某种形式的插件到浏览器的桥梁,允许与嵌入站点进行一定程度的交互,而这些桥梁的设计可以从模糊可疑到明显荒谬不等。</p>
<p>在 2007 年的一起案例中,Petko D. Petkov 注意到,任何托管 PDF 文档的网站都可以通过在片段标识符中提供完全任意的 JavaScript 代码来被攻击。这个字符串将通过插件桥梁在托管页面上执行:^([164])</p>
<pre><code>http://example.com/random_document.pdf#foo=`javascript:alert(1)`
</code></pre>
<p>这里概述的两个漏洞现在已经修复,但教训是,在敏感域中托管或嵌入任何用户提供的文档时,应格外小心。这样做可能带来的后果没有很好地记录,并且难以预测。</p>
<h1 id="基于插件的-应用程序框架">基于插件的 应用程序框架</h1>
<p>将文档渲染的枯燥工作作为浏览器插件的一个既定角色已经是很常见的了,但一些雄心勃勃的供应商已经远远超出了这个模式。一些插件的目标仅仅是通过提供替代的、功能更强大的平台来构建交互式网络应用,从而取代 HTML 和 JavaScript。这种推理并非完全没有道理:浏览器长期以来在性能、图形能力和多媒体编解码器方面存在不足,这限制了 Web 的一些潜在用途。依赖插件是短期内改变现状的合理方式。然而,当专有、受专利和版权保护的插件被宣传为构建在线生态系统的终极方式,而没有意图去改进浏览器本身时,Web 的开放性不可避免地会受到影响。一些批评者,尤其是史蒂夫·乔布斯,认为创建一个严格控制的环境正是几个插件供应商,尤其是 Adobe 所追求的。^([[165])]</p>
<p>作为对这种被认为是对 Web 进行敌意接管威胁的回应,许多导致替代应用程序框架泛滥的不足之处现在正匆忙地在 HTML5 这个模糊定义的大旗下得到解决;<em><video></em>标签和 WebGL^([[39])]是这一工作的主要例子。尽管如此,一些插件中可用的功能可能在未来的一段时间内不会被纳入任何浏览器标准。例如,目前没有计划添加 Java 支持的危险提升权限程序或隐蔽性内容保护方案(委婉地称为数字版权管理,或 DRM)。</p>
<p>因此,尽管未来几年情况将发生巨大变化,我们可以预期,以某种形式,专有的网络应用程序框架将在这里继续存在。</p>
<h2 id="adobe-flash">Adobe Flash</h2>
<p>Adobe Flash 是一个在 1996 年首次浏览器大战期间推出的网络应用程序框架。在 2005 年被 Adobe 收购之前,Flash 平台被称为 Macromedia Flash 或 Shockwave Flash(因此 Flash 文件使用的<em>.swf</em>文件扩展名),有时仍然这样称呼。</p>
<p>Flash 是一个相当接地气的平台,建立在基于 JavaScript 的语言 ActionScript 之上。^([[166])] 它包括一个 2D 矢量图形和位图图形渲染引擎,以及内置对多种图像、视频和音频格式的支持,例如流行的、高效的 H.264 编解码器(它被用于今天大部分在线多媒体)。</p>
<p>根据大多数估计,Flash 大约安装在所有桌面系统的 95%到 99%之间.^([167]), ^([168]) 这个用户基础比任何其他媒体播放器插件都要高得多。尽管采取了积极的捆绑策略,Windows Media Player 和 QuickTime 插件的支持仅限于大约 60%的 PC,而越来越不受欢迎的 RealPlayer 仍然占据 25%。这种市场地位促成了该产品最显著且出乎意料的使用:取代了之前用于在网络上流式传输视频的所有多媒体播放插件。尽管该插件也被用于各种其他任务(包括实现在线游戏、交互式广告等),但简单的多媒体占据了相当大的市场份额。</p>
<h3 id="注意-35">注意</h3>
<p>令人困惑的是,还有一个名为 Adobe Shockwave Player(不包含“Flash”一词)的独立插件也可供使用,它可以用来播放使用 Adobe Director 创建的内容。这个插件有时会被错误地安装,取代或与 Adobe Flash 一起安装,导致大约 20%的安装基础,^([169])但它几乎总是不必要的。这个插件的安全特性并没有得到特别深入的研究。</p>
<h3 id="actionscript-属性">ActionScript 属性</h3>
<p>SWF 文件中 ActionScript 的功能通常与嵌入 HTML 页面中的 JavaScript 代码的功能类似,有一些细微但有趣的差异。例如,Flash 程序可以自由地列出系统上安装的所有字体,并收集其他有用的系统指纹信号,这些信号对于普通脚本不可用。Flash 程序还可以使用全屏渲染,便于 UI 欺骗攻击,并且可以请求访问输入设备,如摄像头或麦克风(这需要用户的同意)。Flash 还倾向于忽略浏览器的安全性和隐私设置,并使用自己的配置来处理机制,如插件内持久数据存储(尽管在 2011 年 5 月宣布了该领域的某些改进)。</p>
<p>剩余的功能并不令人惊讶。我们将在下一章更详细地讨论 Flash 应用程序的网络和 DOM 访问权限,但简而言之,默认情况下,每个 Flash 小程序都可以使用浏览器 HTTP 堆栈(以及其中管理的任何环境凭证)与其原始服务器进行通信,从其他站点请求有限范围的子资源,并导航当前浏览器窗口或打开一个新窗口。ActionScript 程序还可以协商对其他当前运行的 Flash 应用程序的浏览器级别访问,并在某些情况下访问嵌入页面的 DOM。这种最后的功能是通过向目标 JavaScript 上下文中注入类似<em>eval(...)</em>的语句来实现的。</p>
<p>ActionScript 为 Web 应用程序漏洞提供了肥沃的土壤。例如,用于导航浏览器或打开新窗口的<em>getURL(...)</em>和<em>navigateToURL(...)</em>函数有时会使用攻击者控制的输入来调用。这种用法是危险的。尽管<em>javascript:</em> URL 对 Flash 没有特殊含义,但该函数会将此类字符串传递给浏览器,在某些情况下导致嵌入网站上的脚本注入。</p>
<p>直到最近,其他 URL 处理 API,如<em>loadMovie(...)</em>,也存在相关的问题。尽管该函数不依赖于浏览器来加载文档,但它会识别内部<em>asfunction:</em>方案,该方案与<em>eval(...)</em>类似,可以轻易地被利用来执行对<em>getURL(...)</em>的调用:</p>
<pre><code>asfunction:getURL,javascript:alert('Hi mom!')
</code></pre>
<p>在第六章中讨论的从不受信任的源加载脚本的问题,在插件中也有一个等效问题。在 Flash 中,使用攻击者控制的 URL 调用影响 ActionScript 执行环境状态的某些函数(如<em>LoadVars.load(</em>))是非常不安全的,即使加载资源的方案是<em>http:</em>或<em>https:</em>。</p>
<p>另一个常被忽视的攻击面是 Flash 插件提供的内部简化 HTML 解析器:基本 HTML 标记可以分配给诸如<em>TextField.htmlText</em>和<em>TextArea.htmlText</em>这样的属性。很容易忘记在这个设置中用户提供的内 容必须正确转义。未能这样做可能会允许攻击者修改应用程序 UI 的外观或注入可能存在问题的以脚本为导向的链接。</p>
<p>另一类与 Flash 相关的安全漏洞可能源于插件本身的设计或实现问题。例如,考虑<em>ExternalInterface.call(...)</em> API。它的目的是允许 ActionScript 调用嵌入页面上现有的 JavaScript 函数,并接受两个参数:要调用的 JavaScript 函数的名称和一个可选的字符串,该字符串将被传递给此例程。虽然人们理解第一个参数不应该由攻击者控制,但似乎将用户数据放在第二个参数中是安全的。实际上,文档提供了以下代码片段来概述此特定用例^([170]).</p>
<pre><code>ExternalInterface.call("sendToJavaScript", input.text);
</code></pre>
<p>此调用将在嵌入页面上注入以下<em>eval(...)</em>语句:</p>
<pre><code>try {
__flash__toXML(sendToJavaScript, `"value of input.text"`));
} catch (e) {
"<undefined/>";
}
</code></pre>
<p>在编写此调用背后的代码时,插件的作者记得在输出第二个参数时使用反斜杠转义:<em>hello"world</em>变为<em>hello"world</em>。不幸的是,他们忽略了需要转义任何多余的转义字符。因此,如果<em>input.text</em>的值设置为以下字符串,嵌入的脚本将意外执行:</p>
<pre><code>Hello world!\"+`alert(1)`); } catch(e) {} //
</code></pre>
<p>我于 2010 年 3 月联系了 Adobe 关于这个特定问题。一年多后,它的回应是这样的:“出于向后兼容性的原因,我们没有对此行为做出任何更改。”</p>
<p>这似乎很不幸。</p>
<h2 id="微软-silverlight">微软 Silverlight</h2>
<p>微软 Silverlight 是一个基于 Windows Presentation Foundation(一个微软.NET 堆栈中的 GUI 框架)的多功能开发平台。它于 2007 年首次亮相,结合了可扩展应用程序标记语言(XAML)^([171])(微软对 Mozilla 的 XUL 的替代品)以及用几种托管.NET 语言之一编写的代码,例如 C#或 Visual Basic。</p>
<p>尽管在设计上存在显著差异,并且架构更加雄心勃勃(但也更加复杂),这个插件的主要目的是与 Adobe Flash 竞争。许多 Silverlight 应用程序的功能与其竞争对手实现的功能相似,包括几乎相同的网络安全模型和基于<em>eval(...)</em>的嵌入页面桥接。然而,值得微软称道的是,Silverlight 没有提供<em>asfunction:</em>方案,也没有内置的 HTML 渲染器。</p>
<p>微软对 Silverlight 进行了相当积极的推广,并将其捆绑在一些版本的 Internet Explorer 中。因此,根据来源不同,它被认为大约有 60%到 75%的桌面渗透率.^([172]) 尽管其普及,但 Silverlight 在开发实际 Web 应用程序时使用得相当少,可能是因为它通常没有提供比其更成熟的替代品更有吸引力的优势,或者因为其架构被视为更牵强和特定于平台。(Netflix,一个流行的视频流媒体和租赁服务,是极少数真正依赖 Silverlight 在某些设备上进行播放的高知名度网站之一。)</p>
<h2 id="sun-java">Sun Java</h2>
<p>Java 是一种与平台无关的、托管代码执行平台相结合的编程语言。由詹姆斯·高斯林在 20 世纪 90 年代初为 Sun Microsystems 开发,Java 在服务器端编程语言中有着稳固的地位,并在许多其他领域,包括移动设备中有着非常强大的存在。然而,从一开始,Sun 就希望 Java 也能在浏览器端占据一个突出的位置。</p>
<p>浏览器中的 Java 在 Flash 和大多数类似插件之前,现在已废弃的<em><applet></em>标签证明了在当时这个添加是多么的重要、独特和新颖。尽管如此,Java 语言作为浏览器开发平台几乎已经灭绝,即使在它的鼎盛时期,它也从未真正享有真正的突出地位。它保持了惊人的 80%的安装基数,但这个高比例主要归因于 Java 插件与 Java 运行时环境(JRE)捆绑在一起,这是一个更实用且通常预先安装的组件,它使得在系统上运行正常的桌面 Java 应用程序成为可能,而无需浏览器端的任何参与。</p>
<p>Java 作为浏览器技术的失败原因难以确定。可能是因为插件的启动性能不佳,或者是因为笨拙的 UI 库使得开发快速且用户友好的 Web 应用程序变得困难,亦或是由于 Sun 和 Microsoft 之间长期的恶意诉讼,这对微软操作系统上语言的未来造成了长期的影响.^([41]) 无论原因是什么,Java 的高安装基数加上其边缘化的使用意味着它带来的风险远远超过了对用户潜在的好处。(该插件在 2010 年几乎有 80 个安全漏洞,^([173]) 而供应商通常因修补这些漏洞缓慢而受到批评。)</p>
<p>Java 的安全策略与其他插件有些相似,但在某些方面,例如对同源策略的理解或限制对嵌入页面的访问的能力,它并不占优势。(下一章将概述这一点。)值得注意的是,与 Flash 或 Silverlight 不同,某些类型的加密签名小程序可能请求访问潜在的操作系统功能,如不受限制的网络访问或文件访问,而只有用户的容易诱导的同意才能阻止。</p>
<h2 id="xml-浏览器应用程序xbap">XML 浏览器应用程序(XBAP)</h2>
<p>XML 浏览器应用程序(XBAP)^([174]) 是微软在 Web 应用程序框架领域的强势介入,这一尝试发生在 Java 之战开始变得糟糕的年份,以及公司在发布 Silverlight 之前。</p>
<p>XBAP 与 Silverlight 相似,因为它利用了相同的 Windows Presentation Foundation 和.NET 架构。然而,它并非是一个独立且快速的浏览器插件,而是依赖于庞大且难以驾驭的.NET 运行时,这与 Java 插件的 JRE 依赖方式类似。它在一个名为<em>PresentationHost.exe</em>的独立进程中执行托管代码,通常在初始化时加载大量的依赖项。根据微软自己的说法,一个中等大小的先前未缓存的程序加载时间可能轻易达到 10 秒或更多。当这项技术在 2002 年首次推出时,大多数用户已经期待互联网应用程序的响应速度要远高于此。</p>
<p>XBAP 应用程序的安全模型文档不完善,迄今为止尚未进行研究,可能是因为 XBAP 在现实世界中的使用微乎其微,并且其架构复杂、层次繁多。人们合理地预期,XBAP 的安全特性最终将与 Silverlight 所采用的模型相平行,但将拥有对某些.NET 库和 UI 小部件的更广泛访问权限。显然,由于从 Sun 那里借鉴,XBAP 程序在从本地文件系统加载或使用加密证书签名时也可以获得提升的权限。</p>
<p>微软将 XBAP 插件捆绑到其.NET 框架中,以至于悄无声息地安装了不可删除的 Windows Presentation Foundation 插件——不仅限于 Internet Explorer,还包括竞争的 Firefox 和 Chrome。这一举动引起了一些应得的争议,尤其是当第一个漏洞报告开始涌现时。(Mozilla 甚至通过自动更新暂时禁用了插件以保护其用户。)尽管如此,尽管有这些大胆且可疑的举措来推广它,实际上没有人想要编写 XBAP 小程序,并且这项技术一步一步地跟随 Java 进入了历史的垃圾堆。</p>
<p>最终,微软似乎承认了这一失败,并选择专注于 Silverlight。从 Internet Explorer 9 开始,XBAP 默认禁用对互联网来源内容的支持,可疑的 Firefox 和 Chrome 插件也不再自动推送给用户。尽管如此,合理地假设至少有 10%的所有互联网用户可能仍在使用安装在他们的机器上的复杂、部分废弃且很大程度上不必要的插件进行浏览,并且在未来几年内将继续这样做。</p>
<hr>
<p>^([39]) WebGL 是相对较近的一次尝试,旨在将基于 OpenGL 的 3D 图形引入 JavaScript 应用程序。该标准的第一个规范于 2011 年 3 月出现,预计将得到广泛的浏览器级支持。</p>
<p>^([40]) 管理代码不是直接由 CPU 执行的(这本质上是不可取的,因为 CPU 并未设计用来强制执行网络安全规则)。相反,它被编译成中间二进制形式,然后在运行时由一个专门的虚拟机进行解释。这种方法比在运行时解释脚本要快,并且允许在程序执行过程中实施自定义的安全策略。</p>
<p>^([41]) 法律斗争始于 1997 年,当时微软决定推出自己的(在某些方面,更优越的)Java 虚拟机版本。太阳微系统公司提起诉讼,希望赢得一项禁令,迫使微软捆绑其版本。两家公司最初在 2001 年达成和解,但不久之后,他们又回到了法庭。在 2004 年的最终和解中,太阳微系统公司获得了 16 亿美元现金,但 Windows 用户根本无法获得任何 Java 运行时。</p>
<h1 id="activex-控件">ActiveX 控件</h1>
<p>在其核心,ActiveX 是对象链接和嵌入 (OLE) 的继承者,OLE 是一种 1990 年的技术,它使得程序能够以标准化的、语言无关的方式重用其他应用程序的组件。ActiveX 的一个简单用例是一个电子表格应用程序希望嵌入来自图形编辑程序的可编辑矢量图像,或者一个简单的游戏希望嵌入视频播放器。</p>
<p>这个想法并不具有争议性,但到了 1990 年代中期,微软已经决定 ActiveX 在浏览器中也是有意义的。毕竟,网站不会希望从桌面应用程序可以依赖的相同 Windows 组件中受益吗?这种方法违反了培育一个开放、操作系统无关的网络的观念,但就以下 JavaScript 示例所示,它仍然令人印象深刻,该示例随意创建、编辑和保存了一个 Excel 电子表格:</p>
<pre><code>var sheet = new ActiveXObject("Excel.Sheet");
sheet.ActiveSheet.Cells(42,42).Value = "Hi mom!";
sheet.SaveAs("c:\\spreadsheet.xls");
sheet.Application.Quit();
</code></pre>
<p>除了标准兼容性之外,微软转向 ActiveX 的举措从安全角度来看是灾难性的。许多暴露的 ActiveX 组件在与不受信任的环境交互时完全未准备好正确行为,在接下来的 15 年里,研究人员发现了数百个重要的安全漏洞,这些漏洞存在于可从网络访问的 ActiveX 控件中。事实上,Firefox 不支持这项技术的事实在第二次浏览器大战初期就帮助提升了其安全形象。</p>
<p>尽管发生了这次灾难,微软仍然坚定地支持 ActiveX,逐渐限制可以从互联网访问的控件数量,并修复了那些它认为至关重要的控件的漏洞。直到 Internet Explorer 9,微软才最终决定放手:默认情况下,Internet Explorer 9 禁用了所有 ActiveX 访问,需要点击几次才能在需要时使用它。</p>
<h3 id="注意-36">注意</h3>
<p>将选择权委托给用户的智慧尚不明确,特别是考虑到授予网站的权限不仅限于该网站上的合法内容,而且还包括由于应用程序漏洞(如 XSS)注入的任何有效载荷。尽管如此,Internet Explorer 9 还是有一些改进。</p>
<h1 id="与其他插件共存">与其他插件共存</h1>
<p>到目前为止,我们已经涵盖了今天使用的几乎所有通用浏览器插件。尽管存在大量专用或实验性插件,但它们的使用相对不显著,而且在评估整个在线生态系统的健康状况时,我们不需要考虑它们。</p>
<p>嗯,有一个例外。可以预期,一定比例的在线用户可能拥有各种暴露在网页上的浏览器插件或 ActiveX 控件,他们可能从未有意安装,或者尽管他们可能永远不会从引入的功能中受益,但他们被迫安装。</p>
<p>这种不可原谅的做法有时会被其他信誉良好且受信任的公司所接受。例如,Adobe 强制希望下载 Adobe Flash 的用户也安装 GetRight,这是一个完全不必要的第三方下载工具。微软在其面向开发者的网站上对阿卡迈下载管理器做了同样的事情,并附有令人捧腹的辩解(强调如下):^([175])</p>
<blockquote>
<p>阿卡迈下载管理器是什么,为什么我 <em>必须</em> 使用它?</p>
<p>为了帮助您以减少中断的机会下载大文件,一些下载需要使用阿卡迈下载管理器。</p>
</blockquote>
<p>以这种方式安装并直接暴露于来自互联网任何地方的恶意输入的软件的主要担忧是,除非它被极端小心地设计,否则它很可能存在漏洞(确实,GetRight 和阿卡迈下载管理器都有一些)。因此,使用一次或两次就只服务于特定目的的完全不必要的插件所带来的风险,远远超过了其声称的(通常是不受欢迎的)好处。</p>
<p>安全工程备忘单</p>
<p>在提供插件处理的文件时</p>
<ul>
<li>
<p><strong>来自可信来源的数据:</strong> 来自可信来源的数据通常可以安全托管,但请记住,Flash、Java 或 Silverlight 小程序或 Adobe Reader JavaScript 引擎中的安全漏洞可能会影响您域的安全性。避免处理用户提供的 URL,并在插件执行的 applet 中生成或修改用户控制 HTML。在使用 JavaScript 桥接时保持谨慎。</p>
</li>
<li>
<p><strong>用户控制的简单多媒体:</strong> 用户控制的多媒体相对安全,但务必验证和限制格式,使用正确的 <em>Content-Type</em>,并参考第十三章中的备忘单,以避免由内容嗅探漏洞引起的安全问题。</p>
</li>
<li>
<p><strong>用户控制的文档格式</strong>:这些格式本身并不具有固有的不安全性,但由于插件设计缺陷,它们可能导致更高的安全风险。在可能的情况下,考虑从专用域名托管。如果你需要验证对隔离域的请求,请使用一次性请求令牌而不是依赖 cookie。</p>
</li>
<li>
<p><strong>用户控制的活动应用程序</strong>:这些在敏感域中托管是不安全的。</p>
</li>
</ul>
<p>当嵌入由插件处理的文件时</p>
<p>总是要确保 HTTPS 站点上的插件内容也通过 HTTPS 加载,^([42]) 并且始终在<code><object></code>或<code><embed></code>上显式指定<code>*type*</code>参数。请注意,由于对<code>*type*</code>参数的非权威处理,在从不受信任的来源嵌入插件内容时,必须谨慎行事,尤其是在高度敏感的网站上。</p>
<ul>
<li>
<p><strong>简单多媒体</strong>:通常可以从第三方来源安全地加载简单多媒体,前提是上述警告得到遵守。</p>
</li>
<li>
<p><strong>文档格式</strong>:这些通常比较安全,但与简单的多媒体相比,它们在插件和浏览器内容处理方面具有更大的潜在问题。请谨慎行事。</p>
</li>
<li>
<p><strong>Flash 和 Silverlight</strong>:原则上,如果标记中存在适当的安全标志,Flash 和 Silverlight 应用程序可以从外部来源安全地嵌入。如果标志未正确指定,你可能会将你站点的安全性绑定到内容提供者的安全性。参见第九章中的 cheat sheet 以获取建议。</p>
</li>
<li>
<p><strong>Java</strong>:Java 总是将你的服务安全性与内容提供者的安全性绑定在一起,因为无法可靠地限制对嵌入页面的 DOM 访问。参见第九章。不要从不受信任的站点加载 Java 应用程序。</p>
</li>
</ul>
<p>如果你想要编写一个新的浏览器插件或 ActiveX 组件</p>
<p>除非你正在解决一个重要且广泛使用的用例,这将使互联网的很大一部分受益,否则请重新考虑。如果你正在解决一个重要的需求,考虑以同行评审和标准化的方式作为 HTML5 的一部分来实现。</p>
<hr>
<p>^([42]) 如果在 HTTPS 页面上加载 HTTP 分发的 applet 是绝对不可避免的,将其放置在中间 HTTP 框架内而不是直接放置在 HTTPS 文档中会更安全,因为这可以防止利用 applet 到 JavaScript 的桥梁进行攻击。</p>
<h1 id="第二部分-浏览器安全特性">第二部分. 浏览器安全特性</h1>
<p>在回顾了 Web 的基本构建块之后,我们现在可以舒适地检查所有保持恶意 Web 应用远离的安全特性。本书的第二部分探讨了从众所周知但常常被误解的同源策略到 IE 的神秘且专有的区域设置等所有内容。它解释了这些机制可以为您做什么——以及它们何时容易崩溃。</p>
<h1 id="第九章-内容隔离逻辑">第九章. 内容隔离逻辑</h1>
<p>网络浏览器提供的绝大多数安全保证都是为了根据其来源隔离文档。前提很简单:来自不同来源的两个页面不应该被允许相互干扰。然而,实际的实践可能更为复杂,因为关于单个文档的开始和结束在哪里,或者什么构成一个单一来源,并没有达成普遍的共识。结果是,有时不可预测的、相互矛盾的政策拼凑,它们并不完全协同工作,但如果不进行大幅调整,就会严重影响当前所有合法的 Web 使用。</p>
<p>除了这些问题之外,关于最初应该对哪些操作进行安全检查也缺乏清晰的了解。显然,一些交互,如跟随链接,应该在没有特殊限制的情况下允许,因为它们对于整个生态系统的健康至关重要,而其他一些操作,如修改在单独窗口中加载的页面内容,则应该需要进行安全检查。但在这些极端之间存在着一个很大的灰色区域,而这个中间地带往往感觉更像是由掷骰子决定的,而不是任何统一的计划。在这些浑浊的水域中,跨站请求伪造(见第四章 是 Netscape 在 1995 年与 JavaScript 和文档对象模型(DOM)一起引入的概念,就在 HTTP cookies 创建后的第二年。这一策略背后的基本规则很简单:对于任何两个独立的 JavaScript 执行上下文,一个应该能够访问另一个的 DOM,前提是与其宿主文档相关联的协议、DNS 名称和端口号完全匹配。所有其他跨文档的 JavaScript DOM 访问都应该失败。</p>
<p>该算法引入的协议-主机-端口号三元组通常被称为<em>源</em>。作为安全策略的基础,这相当稳健:SOP 在所有现代浏览器中得到了良好的实施,并且一致性很高,只有偶尔会出现错误.^([44]) 实际上,只有 Internet Explorer 与众不同,因为它在源检查中忽略了端口号。这种做法的安全性略低,尤其是在远程主机上运行 HTTP/0.9 网络服务器时,存在非 HTTP 服务的风险(参见第三章). 但通常这不会造成明显的差异。</p>
<p>表 9-1 展示了在多种情况下 SOP 检查的结果。</p>
<p>表 9-1. SOP 检查结果</p>
<table>
<thead>
<tr>
<th>原始文档</th>
<th>访问文档</th>
<th>非 IE 浏览器</th>
<th>Internet Explorer</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="http://example.com/a/" target="_blank"><code>example.com/a/</code></a></td>
<td><a href="http://example.com/b/" target="_blank"><code>example.com/b/</code></a></td>
<td>访问正常</td>
<td>访问正常</td>
</tr>
<tr>
<td><a href="http://example.com/" target="_blank"><code>example.com/</code></a></td>
<td><a href="http://www.example.com/" target="_blank"><code>www.example.com/</code></a></td>
<td>主机不匹配</td>
<td>主机不匹配</td>
</tr>
<tr>
<td><a href="http://example.com/" target="_blank"><code>example.com/</code></a></td>
<td><a href="https://example.com/" target="_blank"><code>example.com/</code></a></td>
<td>协议不匹配</td>
<td>协议不匹配</td>
</tr>
<tr>
<td><a href="http://example.com:81/" target="_blank"><code>example.com:81/</code></a></td>
<td><a href="http://example.com/" target="_blank"><code>example.com/</code></a></td>
<td>端口不匹配</td>
<td>访问正常</td>
</tr>
</tbody>
</table>
<h3 id="注意-37">注意</h3>
<p>这个同源策略最初是为了仅控制对 DOM 的访问;也就是说,与实际显示文档内容相关的特性和方法。该策略已经逐渐扩展到保护根 JavaScript 对象的其他显然敏感区域,但并非涵盖所有。例如,非同源脚本通常仍然可以在任意窗口或框架上调用 <em>location.assign()</em> 或 <em>location.replace(...)</em>。这些特权的范围和后果是第十一章的主题。</p>
<p>SOP 的简单性既是祝福也是诅咒。该机制相对容易理解,并且正确实施并不太难,但它的僵化可能会给网络开发者带来负担。在某些情况下,该策略过于宽泛,使得无法(例如)隔离属于不同用户的首页(除非为每个用户分配一个单独的域名)。在其他情况下,情况正好相反:该策略使得合法合作的网站(例如,<em>login.example.com</em> 和 <em>payments.example.com</em>)难以无缝交换数据。</p>
<p>尝试解决第一个问题——缩小源的概念——通常会因为与浏览器中其他显式和隐式安全控制的交互而注定失败。尝试扩展源或促进跨域交互更为常见。实现这些目标广泛支持的两个方法如下:<em>document.domain</em> 和 <em>postMessage(...)</em>,如下所述。</p>
<h2 id="documentdomain">document.domain</h2>
<p>这个 JavaScript 属性允许任何两个共享公共顶级域名(例如 <em>example.com</em>,甚至仅仅是 <em>.com</em>)的协作网站同意,为了未来源策略检查的目的,它们希望被视为等效。例如,<em>login.example.com</em> 和 <em>payments.example.com</em> 可能执行以下赋值:</p>
<pre><code>document.domain = "example.com"
</code></pre>
<p>设置此属性将覆盖在源策略检查期间通常的域名匹配逻辑。尽管如此,协议和端口号仍然必须匹配;如果不匹配,调整 <em>document.domain</em> 也不会产生预期的效果。</p>
<p>双方必须明确选择此功能。仅仅因为 <em>login.example.com</em> 将其 <em>document.domain</em> 设置为 <em>example.com</em> 并不意味着它将被允许访问来自<a href="http://example.com/" target="_blank"><code>example.com/</code></a>网站的原始内容。该网站也需要执行这样的赋值,即使常识可能表明这是一个无操作。这种效果是对称的。正如设置 <em>document.domain</em> 的页面将无法访问未设置该属性的页面一样,设置属性的行为也将调用者大部分(但不是全部!)^([45]) 排除在之前被认为是同源的普通文档之外。表 9-2 显示了 <em>document.domain</em> 的各种值的效应。</p>
<p>尽管 <em>document.domain</em> 显示出一定程度的复杂性,暗示着某种特殊类型的巧妙,但它并不特别安全。它最显著的弱点是它邀请不受欢迎的访客。当双方相互将此属性设置为 <em>example.com</em> 后,不仅仅是 <em>login.example.com</em> 和 <em>payments.example.com</em> 将能够通信;<em>funny-cat-videos.example.com</em> 也能加入这个行列。而且由于页面之间允许的访问程度,任何参与 JavaScript 上下文的完整性都无法保证到任何实际的程度上。换句话说,触摸 <em>document.domain</em> 必然意味着将你页面的安全性绑定到整个域中最薄弱环节的安全性。将值设置为 <strong>.com</strong> 的极端情况本质上等同于协助自杀。</p>
<p>表 9-2. <em>document.domain</em> 检查的结果</p>
<table>
<thead>
<tr>
<th>原始文档</th>
<th>访问文档</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr>
<td>URL</td>
<td>document.domain</td>
<td>URL</td>
</tr>
<tr>
<td>---</td>
<td>---</td>
<td>---</td>
</tr>
<tr>
<td><a href="http://www.example.com/" target="_blank"><code>www.example.com/</code></a></td>
<td><em>example.com</em></td>
<td><a href="http://payments.example.com/" target="_blank"><code>payments.example.com/</code></a></td>
</tr>
<tr>
<td><a href="http://www.example.com/" target="_blank"><code>www.example.com/</code></a></td>
<td><em>example.com</em></td>
<td><a href="https://payments.example.com/" target="_blank"><code>payments.example.com/</code></a></td>
</tr>
<tr>
<td><a href="http://payments.example.com/" target="_blank"><code>payments.example.com/</code></a></td>
<td><em>example.com</em></td>
<td><a href="http://example.com/" target="_blank"><code>example.com/</code></a></td>
</tr>
<tr>
<td><a href="http://www.example.com/" target="_blank"><code>www.example.com/</code></a></td>
<td><strong>(未设置)</strong></td>
<td><a href="http://www.example.com/" target="_blank"><code>www.example.com/</code></a></td>
</tr>
</tbody>
</table>
<h2 id="postmessage">postMessage(...)</h2>
<p><em>postMessage(...)</em> API 是一个 HTML5 扩展,允许在非同源站点之间进行稍微不那么方便但非常安全的通信,而不会自动放弃任何一方的完整性。今天,它被所有最新的浏览器支持,尽管由于它相对较新,它不在 Internet Explorer 6 或 7 中。</p>
<p>该机制允许发送任何长度的文本消息到任何发送者持有有效 JavaScript 句柄的窗口(参见第六章)。尽管同源策略存在一些漏洞,允许通过其他方式实现类似功能,^([46]) 但实际上这种方法是安全的。它允许发送者指定哪些源被允许首先接收消息(如果目标窗口的 URL 已更改),并且它向接收者提供了发送者的身份,以便可以轻松地确定通道的完整性。相比之下,依赖于 SOP 漏洞的旧方法通常不提供此类保证;如果某个特定操作在没有稳健的安全检查的情况下被允许,那么它通常也可以被恶意第三方触发,而不仅仅是预期参与者。</p>
<p>为了说明 <em>postMessage(...)</em> 的正确使用方法,考虑一个位于 <em>payments.example.com</em> 的顶级文档需要获取用户登录信息以供显示的情况。为此,它加载了一个指向 <em>login.example.com</em> 的框架。这个框架可以简单地发出以下命令:</p>
<pre><code>parent.postMessage("user=bob", "https://payments.example.com");
</code></pre>
<p>浏览器只有在嵌入的网站确实匹配指定的、受信任的源时才会传递消息。为了安全地处理此响应,顶级文档需要使用以下代码:</p>
<pre><code>// Register the intent to process incoming messages:
addEventListener("message", user_info, false);
// Handle actual data when it arrives:
function user_info(msg) {
if (msg.origin == "https://login.example.com") {
// Use msg.data as planned
}
}
</code></pre>
<p><em>PostMessage(...)</em> 是一个非常健壮的机制,与 <em>document.domain</em> 以及几乎所有早于它的游击战方法相比,提供了显著的好处;因此,它应该尽可能多地使用。尽管如此,它仍然可能被滥用。考虑以下检查,它在域名中查找子字符串:</p>
<pre><code>if (msg.origin.indexOf(".example.com") != −1) { ... }
</code></pre>
<p>如应明显,这个比较不仅会匹配<em>example.com</em>内的网站,而且也会愉快地接受来自<a href="http://www.example.com.bunnyoutlet.com" target="_blank">www.example.com.bunnyoutlet.com</a>的消息。在所有可能的情况下,你可能会不止一次地在你旅途中遇到这样的代码。这就是生活!</p>
<h3 id="注意-38">注意</h3>
<p>最近对 HTML5 的调整扩展了<em>postMessage(...)</em> API,以包含某些过度设计的“端口”和“通道”,这些端口和通道旨在促进网站之间的流式通信。浏览器对这些特性的支持目前非常有限,它们的实际效用尚不清楚,但从安全角度来看,它们似乎并不引起任何特殊关注。</p>
<h2 id="与浏览器凭证的交互">与浏览器凭证的交互</h2>
<p>在我们结束基于 DOM 的同源策略概述时,重要的是要注意,它与环境凭证、SSL 状态、网络上下文或许多其他浏览器跟踪的潜在安全相关参数没有任何同步。在浏览器中打开的两个窗口或框架将保持同源关系,即使用户从一个账户注销并登录到另一个账户,如果页面从使用好的 HTTPS 证书切换到坏的证书,等等。</p>
<p>这种不同步可能导致其他安全漏洞的可利用性。例如,一些网站没有保护他们的登录表单免受跨站请求伪造攻击,允许任何第三方网站简单地提交用户名和密码,并将用户登录到攻击者控制的账户。起初这看起来可能无害,但考虑到在此操作前后加载到浏览器中的内容被认为是同源的,通常被忽视的“自造成的”跨站脚本漏洞(即,特定账户的所有者只能针对自己)的影响突然比之前看起来要大得多。在最基本的情况下,攻击者可能首先打开并保持一个指向目标网站上敏感页面的框架(例如,<a href="http://www.fuzzybunnies.com/address_book.php" target="_blank"><code>www.fuzzybunnies.com/address_book.php</code></a>)并然后登录受害者到攻击者控制的账户以在<a href="http://fuzzybunnies.com" target="_blank">fuzzybunnies.com</a>的一个无关组件中执行自-XSS。尽管 HTTP 凭证发生了变化,但在那最后一步中注入的代码将不受限制地访问之前加载的框架,允许数据窃取。</p>
<hr>
<p>^([43]) 这以及大多数其他浏览器安全机制都是基于 DNS 标签,而不是基于检查底层 IP 地址。这有一个奇特的结果:如果某个主机的 IP 地址发生变化,攻击者可能能够通过用户的浏览器与新的目的地进行通信,可能在进行滥用行为的同时隐藏攻击的真实来源(不幸,但并不很有趣)或者与受害者的内部网络进行交互,这在通常情况下由于防火墙的存在而无法访问(一个更成问题的情况)。出于此目的有意更改 IP 地址的行为被称为<em>DNS 重绑定</em>。浏览器试图通过例如缓存 DNS 查找结果一段时间(<em>DNS 固定</em>)等方式在一定程度上减轻 DNS 重绑定的风险,但这些防御措施并不完美。</p>
<p>^([44]) 同源策略漏洞的一个显著来源是浏览器代码中有多个独立的 URL 解析程序。如果在 HTTP 堆栈中使用的解析方法与用于确定 JavaScript 来源的方法不同,可能会出现问题。特别是 Safari,它对抗了由病态 URL 引起的许多 SOP 绕过漏洞,包括在第二章中讨论的许多输入。</p>
<p>^([45]) 例如,在 Internet Explorer 中,仍然有可能让一个页面导航到任何名义上是同源的但后来在设置<em>document.domain</em>后变为“隔离”的文档,到<em>javascript:</em> URL。这样做允许任何 JavaScript 在伪隔离域的上下文中执行。在此基础上,显然没有任何阻止原始页面简单地将其自己的<em>document.domain</em>设置为与目标相同的值以消除边界的。换句话说,不应依赖于通过<em>document.domain</em>将文档与其他页面非同源的能力来进行任何甚至稍微重要或与安全相关的操作。</p>
<p>^([46]) 更多关于这方面的内容请参阅第十一章,但最显著的例子是将数据编码在 URL 片段标识符中。这是可能的,因为大多数情况下,在框架中导航到新的 URL 不受安全限制,并且仅更改片段标识符的 URL 导航实际上不会触发页面刷新。框架化的 JavaScript 可以简单地轮询<em>location.hash</em>并以此方式检测传入的消息。</p>
<h1 id="xmlhttprequest-的同源策略">XMLHttpRequest 的同源策略</h1>
<p>本书在前面多次提到的 <em>XMLHttpRequest</em> API,赋予了 JavaScript 程序向主机文档来源的服务器发出几乎不受约束的 HTTP 请求的能力,并读取响应头部和文档主体。如果这种机制没有利用现有的浏览器 HTTP 栈及其便利性,包括环境凭据、缓存机制、保持连接会话等,那么这种能力就不会特别重要。</p>
<p>同步 <em>XMLHttpRequest</em> 的一个简单且相当直观的使用方法可以是以下这样:</p>
<pre><code>var x = new XMLHttpRequest();
x.open("POST", "/some_script.cgi", false);
x.setRequestHeader("X-Random-Header", "Hi mom!");
x.send("...POST payload here...");
alert(x.responseText);
</code></pre>
<p>异步请求与同步请求非常相似,但它们不会阻塞 JavaScript 引擎或浏览器。请求在后台发出,并在完成后调用事件处理器。</p>
<p>如最初设想的那样,通过此 API 发出 HTTP 请求并读取数据的能力,受几乎完全相同的同源策略的约束,只有两个微小的、看似随机的调整。首先,<em>document.domain</em> 设置对此机制没有影响,并且为 <em>XMLHttpRequest.open(...)</em> 指定的目标 URL 必须始终与文档的真实来源相匹配。其次,在此上下文中,在 Internet Explorer 9 之前的版本中考虑了端口号,尽管此浏览器在其他地方忽略了它。</p>
<p>事实上,<em>XMLHttpRequest</em> 允许用户对请求中的 HTTP 头部进行前所未有的控制,这实际上可能对安全性有益。例如,插入一个自定义的 HTTP 头部,如 <em>X-Coming-From: same-origin</em>,是一个非常简单的方式来验证特定的请求不是来自第三方域名,因为没有任何其他网站应该能够向浏览器发出的请求中插入自定义头部。这种保证并不非常强,因为没有规范说明对跨域头部的隐式限制不能改变;^([47]) 然而,当涉及到网络安全时,这种假设通常是你必须学会接受的。</p>
<p>对 HTTP 请求结构的控制也可能成为一种负担,因为插入某些类型的头部可能会改变对目标服务器或代理的意义,而浏览器并没有意识到这一点。例如,指定一个错误的 <em>Content-Length</em> 值可能允许攻击者将第二个请求偷偷地放入浏览器维护的保持连接的 HTTP 会话中,如下所示。</p>
<pre><code>var x = new XMLHttpRequest();
x.open("POST", "http://www.example.com/", false);
// This overrides the browser-computed Content-Length header:
x.setRequestHeader(`"Content-Length", "7"`);
// The server will assume that this payload ends after the first
// seven characters, and that the remaining part is a separate
// HTTP request.
x.send(
"Gotcha!\n" +
`"GET /evil_response.html HTTP/1.1\n" +`
`"Host: www.bunnyoutlet.com\n\n"`
);
</code></pre>
<p>如果发生这种情况,浏览器可能会错误地解释第二个注入请求的响应,可能污染缓存或将内容注入到另一个网站。如果使用 HTTP 代理并且所有 HTTP 请求都通过共享通道发送,这个问题尤其明显。</p>
<p>由于这种风险,经过大量的尝试和错误,现代浏览器黑名单了一些 HTTP 头部和请求方法。这做得相对不太一致:虽然 <em>Referer</em>、<em>Content-Length</em> 和 <em>Host</em> 被普遍禁止,但诸如 <em>User-Agent</em>、<em>Cookie</em>、<em>Origin</em> 或 <em>If-Modified-Since</em> 的头部处理因浏览器而异。同样,由于对 <em>httponly</em> cookie 的未预料风险,TRACE 方法在所有地方都被阻止——但 CONNECT 方法在 Firefox 中被允许,尽管它带有可能破坏 HTTP 代理的模糊风险。</p>
<p>自然地,实现这些黑名单本身就是一个有趣的练习。仅为了娱乐,考虑以下在三年前一些浏览器中有效的情况:^([176])</p>
<pre><code>XMLHttpRequest.setRequestHeader("X-Harmless", "1\n`Owned: Gotcha`");
</code></pre>
<p>或</p>
<pre><code>XMLHttpRequest.setRequestHeader("`Content-Length: 123` ", "");
</code></pre>
<p>或简单地</p>
<pre><code>XMLHttpRequest.open("`GET\thttp://evil.com\tHTTP/1.0\n\n`", "/", false);
</code></pre>
<h3 id="注意-39">注意</h3>
<p><em>跨源资源共享</em>^([177]) <em>(CORS)</em> 是对 <em>XMLHttpRequest</em> 的一个提议性扩展,允许跨域发起 HTTP 请求,并在返回的数据中出现特定响应头时读取。该机制通过允许某些“纯”跨域请求(旨在与常规导航无差异)通过 <em>XMLHttpRequest.open(...)</em> 发起,而不需要额外的检查;更复杂的请求需要先进行基于 OPTIONS 的预检请求。CORS 已在一些浏览器中可用,但遭到了微软工程师的反对,他们在 Internet Explorer 8 和 9 中追求了竞争性的 <em>XDomainRequest</em> 方法。由于这一冲突的结果尚不明确,对 CORS 的详细讨论留给了 第十六章,该章提供了一个更系统的对即将推出和实验性机制的概述。</p>
<hr>
<p>^([47]) 事实上,许多插件在过去在这个领域存在问题。最值得注意的是,Adobe Flash 允许任意跨域 HTTP 头部信息,直到 2008 年,那时它的安全模型经历了重大改革。直到 2011 年,该插件还遭受了一个长期存在的实现错误,导致它在攻击者提供的 HTTP 307 重定向代码之后向一个无关的服务器重新发送任何自定义头部。这两个问题现在都已修复,但发现到修复的时间证明是麻烦的。</p>
<h1 id="web-storage-的同源策略">Web Storage 的同源策略</h1>
<p>Web storage 是一个简单的数据库解决方案,最初由 Mozilla 工程师在 Firefox 1.5 中实现,最终被 HTML5 规范所采纳。它在所有当前浏览器中可用,但在 Internet Explorer 6 或 7 中不可用。</p>
<p>经过几次可疑的迭代后,当前的设计依赖于两个简单的 JavaScript 对象:<em>localStorage</em> 和 <em>sessionStorage</em>。这两个对象提供了一个相同且简单的 API,用于在浏览器管理的数据库中创建、检索和删除键值对。例如:</p>
<pre><code>localStorage.setItem("message", "Hi mom!");
alert(localStorage.getItem("message"));
localstorage.removeItem("message");
</code></pre>
<p><em>localStorage</em> 对象实现了一种持久、特定源存储,它能在浏览器关闭后继续存在,而 <em>sessionStorage</em> 则预期与当前浏览器窗口绑定,提供一个在浏览会话结束时被销毁的临时缓存机制。虽然规范说明 <em>localStorage</em> 和 <em>sessionStorage</em> 都应与类似 SOP 的源(协议-主机-端口号三元组)相关联,但某些浏览器的实现并未遵循这一建议,引入了潜在的安全漏洞。最值得注意的是,在 Internet Explorer 8 中,计算源时不考虑协议,导致 HTTP 和 HTTPS 页面处于同一个上下文中。这种设计使得 HTTPS 站点通过此 API 存储或读取敏感数据非常不安全。(此问题在 Internet Explorer 9 中得到修正,但似乎没有计划回滚此修复。)</p>
<p>另一方面,在 Firefox 中,<em>localStorage</em> 的行为是正确的,但 <em>sessionStorage</em> 接口则不正确。HTTP 和 HTTPS 使用共享存储上下文,尽管实现了检查以防止 HTTP 内容读取由 HTTPS 脚本创建的键,但存在一个严重的漏洞:任何首先通过 HTTP 创建,然后通过 HTTPS 更新的键,将始终对非加密页面可见。这个最初在 2009 年报告的漏洞^([179])最终将被解决,但具体时间尚不清楚。</p>
<h1 id="钩子安全策略">钩子安全策略</h1>
<p>我们在第三章中讨论了 HTTP cookies 的语义,但那次讨论遗漏了一个重要细节:必须实施的安全规则,以保护一个站点的 cookies 不被无关页面篡改。这个话题特别有趣,因为这里采取的方法早于同源策略,并以多种意想不到的方式与之交互。</p>
<p>Cookies 旨在限定于域,并且不能轻易限制为仅单个主机名值。与 cookie 一起提供的 <em>domain</em> 参数可能仅匹配当前主机名(例如 <em>foo.example.com</em>),但这不会阻止 cookie 被发送到任何子域,例如 <em>bar.foo.example.com</em>。可以通过指定主机名的合格右端片段(例如 <em>example.com</em>)来请求更广泛的范围。</p>
<p>有趣的是,原始的 RFCs 暗示 Netscape 工程师想要允许精确的主机域 cookies,但他们没有遵循自己的建议。为此目的设计的语法没有被 Netscape Navigator 的后代(或任何其他实现)所认可。在一定程度上,通过完全省略 <em>domain</em> 参数,在某些浏览器中可以设置主机域 cookies,但在 Internet Explorer 中这种方法将没有任何效果。</p>
<p>表 9-3 展示了在某些特定情况下 cookie 设置行为。</p>
<p>表 9-3. Cookie 设置行为的示例</p>
<table>
<thead>
<tr>
<th>在<em>foo.example.com</em>设置的 cookie,<em>domain</em>参数是:</th>
<th>结果 cookie 的作用域</th>
</tr>
</thead>
<tbody>
<tr>
<td>非 IE 浏览器</td>
<td>Internet Explorer</td>
</tr>
<tr>
<td>---</td>
<td>---</td>
</tr>
<tr>
<td>(value omitted)</td>
<td><em>foo.example.com</em> (exact)</td>
</tr>
<tr>
<td><em>bar.foo.example.com</em></td>
<td>Cookie not set: domain more specific than origin</td>
</tr>
<tr>
<td><em>foo.example.com</em></td>
<td>*<em>.foo.example.com</em></td>
</tr>
<tr>
<td><em>baz.example.com</em></td>
<td>Cookie not set: domain mismatch</td>
</tr>
<tr>
<td><em>example.com</em></td>
<td>*<em>.example.com</em></td>
</tr>
<tr>
<td><em>ample.com</em></td>
<td>Cookie not set: domain mismatch</td>
</tr>
<tr>
<td><em>.com</em></td>
<td>Cookie not set: domain too broad, security risk</td>
</tr>
</tbody>
</table>
<p>唯一的另一个真正的 cookie 作用域参数是路径前缀:任何 cookie 都可以设置一个指定的<em>path</em>值。这指示浏览器仅在请求匹配的目录时发送 cookie;一个作用域为<em>example.com</em>的<em>domain</em>和<em>path</em>为<em>/some/path/</em>的 cookie 将包含在请求到</p>
<pre><code>http://foo`.example.com/some/path/`subdirectory/hello_world.txt
</code></pre>
<p>这种机制可能会产生误导。在检查同源策略时,URL 路径不被考虑,因此不能形成一个有用的安全边界。无论 cookie 如何工作,JavaScript 代码都可以随意跳转到单个主机上的任何 URL,并将恶意负载注入到这些目标中,滥用任何受路径边界 cookie 保护的功能。(至今仍有几本安全书籍和白皮书推荐将路径作用域作为安全措施。在大多数情况下,这种建议是错误的。)</p>
<p>除了真正的作用域功能(这些功能与 cookie 名称一起构成一个元组,唯一地标识了每个 cookie)之外,Web 服务器还可以输出带有两个特殊、独立操作的标志的 cookie:<em>httponly</em>和<em>secure</em>。第一个,<em>httponly</em>,通过防止通过<em>document.cookie</em> API 访问 cookie,希望使其更难在成功注入恶意脚本后简单地复制用户的凭据。第二个,<em>secure</em>,阻止 cookie 在未加密的协议上的请求中提交,这使得构建能够抵御主动攻击的 HTTPS 服务成为可能。^([[48])</p>
<p>这些机制的陷阱在于它们只保护数据免受读取,而不保护免受覆盖。例如,通过 HTTP 传输的 JavaScript 代码仍然可以简单地溢出每个域的 cookie 容器,然后设置一个新的 cookie 而不使用 <em>secure</em> 标志。⁴⁹ 由于浏览器发送的 <em>Cookie</em> 标头没有提供有关特定 cookie 的来源或其作用域的元数据,这种技巧很难检测。这种行为的一个显著后果是,常见的“无状态”方式防止跨站请求伪造漏洞,即同时在一个客户端 cookie 和一个隐藏表单字段中存储一个秘密令牌,然后比较这两个令牌,对于 HTTPS 网站来说并不特别安全。看看你是否能弄清楚为什么!</p>
<h3 id="注意-40">注意</h3>
<p>说到破坏性干扰,直到 2010 年,<em>httponly</em> cookies 也与 <em>XMLHttpRequest</em> 发生冲突。该 API 的作者根本没有考虑过 <em>XMLHttpRequest.getResponseHeader(...)</em> 函数是否应该能够检查标记为 <em>httponly</em> 的服务器提供的 <em>Set-Cookie</em> 值——结果是可预见的。</p>
<h2 id="cookies-对同源策略的影响">Cookies 对同源策略的影响</h2>
<p>同源策略对 cookies 的安全性(特别是路径范围机制)有一些不良影响,但相反的交互更为常见且更成问题。困难在于 HTTP cookies 经常充当凭证,在这种情况下,获取它们的能力大致等同于找到绕过 SOP 的方法。简单地说,有了正确的一组 cookies,攻击者可以使用自己的浏览器代表受害者与目标网站交互;同源策略就被排除在外,所有的赌注都取消了。</p>
<p>由于这个特性,两种安全机制之间的任何差异都可能导致对限制更严格的那一方造成麻烦。例如,HTTP cookies 使用的相对宽松的域范围规则意味着无法完全隔离托管在 <em>webmail.example.com</em> 上的敏感内容与托管在 <em>blog.example.com</em> 上的不太可信的 HTML。即使 webmail 应用的所有者将 cookies 的范围限制得很紧(通常是以复杂化登录过程为代价),任何在博客网站上发现脚本注入漏洞的攻击者都可以简单地溢出每个域的 cookie 容器,丢弃当前的凭证,并设置自己的 <strong>.example.com</strong> cookies。这些注入的 cookies 将在所有后续请求中发送到 <em>webmail.example.com</em>,并且与真实的 cookies 几乎无法区分。</p>
<p>这个技巧看起来可能无害,直到你意识到这样的行为可能会有效地将受害者登录到一个虚假账户,并且因此,某些操作(如发送电子邮件)可能会在该账户中无意中记录下来,并在任何恶行被发现之前泄露给攻击者。如果你觉得 webmail 太陌生,考虑在 Amazon 或 Netflix 上做同样的事情:你的产品搜索可能会在你注意到网站有任何异常之前被攻击者发现。(此外,许多网站根本未准备好处理注入 cookie 中的恶意负载,意外的输入可能导致 XSS 或类似的漏洞。)</p>
<p>HTTP cookie 的恶作剧也使得在网络级别攻击者面前保护加密流量变得非常困难。由<a href="https://webmail.example.com/" target="_blank"><code>webmail.example.com/</code></a>设置的<em>安全</em>cookie 仍然可能被篡改并被<a href="http://webmail.example.com/" target="_blank"><code>webmail.example.com/</code></a>上的伪造页面设置的虚构值所替代,即使目标主机上没有实际监听 80 端口的网络服务。</p>
<h2 id="域限制问题">域限制问题</h2>
<p>允许域级 cookie 的错误观念也给浏览器供应商带来了问题,并且是持续痛苦的来源。关键问题是如何可靠地防止<em>example.com</em>为<strong>.com</strong>设置 cookie,并避免这个 cookie 意外地发送到互联网上的每个其他目的地。</p>
<p>虽然有几个简单的解决方案,但当你必须考虑国家级 TLD 时,它们就会失效:<em>example.com.pl</em>也必须防止设置<strong>.com.pl</strong>的 cookie。意识到这一点,原始的 Netscape cookie 规范提供了以下建议:</p>
<blockquote>
<p>只有指定域内的主机才能为该域设置 cookie,并且域必须至少包含两个(2)或三个(3)点,以防止形式为“ .com”,“ .edu”,“ va.us”的域。</p>
<p>在以下七个特殊顶级域之一中失败的任何域只需要两个点。其他域至少需要三个点。这七个特殊顶级域是:“COM”,“EDU”,“NET”,“ORG”,“GOV”,“MIL”,“INT”。</p>
</blockquote>
<p>然而,三段式规则只适用于像<em>example.co.uk</em>这样的顶级域镜像的国家级注册商,而不适用于同样人口众多的直接注册接受国家群体(如<em>example.fr</em>)。实际上,有些地方两种方法都是允许的;例如,<em>example.jp</em>和<em>example.co.jp</em>都是完全可行的。</p>
<p>由于这些建议与实际脱节,大多数浏览器都忽视了它们,并实施了一系列条件表达式,这反而导致了更多的问题。(在一种情况下,超过十年,你实际上可以为<strong>.com.pl</strong>设置 cookie。)在过去四年中,所有现代浏览器都发布了针对国家代码顶级域处理的综合修复方案,但截至本文撰写时,它们还没有被回滚到 Internet Explorer 6 和 7,而且可能永远不会。</p>
<h3 id="注意-41">注意</h3>
<p>更令人愤慨的是,互联网数字分配机构近年来添加了相当数量的顶级域名(例如,<em>.int</em>和<em>.biz</em>),并且正在考虑一个允许任意通用顶级域名注册的提案。如果真的发展到这一步,cookie 可能不得不从头开始重新设计。</p>
<h2 id="localhost的异常危险">“localhost”的异常危险</h2>
<p>域级作用域的 cookie 存在的一个明显后果是,将敏感域内的任何主机名委托给任何不可信(或简单来说易受攻击)的第三方都是相当不安全的;这样做可能会影响任何存储在 cookie 中的凭证的机密性,并且不可避免地会影响其完整性——进而影响目标应用程序处理的其他任何信息。</p>
<p>很明显,但在 2008 年,Tavis Ormandy 发现了一些远不如直观且更加滑稽的事情:180] 由于 HTTP cookie 的无端口感知行为,一个额外的危险在于相当流行且方便的行政实践,即在域中添加一个“localhost”条目,并将其指向 127.0.0.1。50] 当 Ormandy 首次发布他的警告时,他断言这种做法很普遍——这不是一个有争议的声明——并包括以下解析器工具输出以说明他的观点:</p>
<pre><code>localhost.microsoft.com has address 127.0.0.1
localhost.ebay.com has address 127.0.0.1
localhost.yahoo.com has address 127.0.0.1
localhost.fbi.gov has address 127.0.0.1
localhost.citibank.com has address 127.0.0.1
localhost.cisco.com has address 127.0.0.1
</code></pre>
<p>为什么这会是一个安全风险?简单地说,它将用户机器上的 HTTP 服务与网站的其他部分放在同一个域中,更重要的是,它将所有看似 HTTP 的服务放在同一个桶中。这些服务通常不会暴露在互联网上,因此没有感知到需要精心设计它们或保持它们更新的需求。Tavis 的案例是一个由 CUPS(通用 UNIX 打印系统)提供的打印机管理服务,如果以以下方式调用,它将在<em>example.com</em>的上下文中执行攻击者提供的 JavaScript:</p>
<pre><code>http://localhost.example.com:631/jobs/?[...]
&job_printer_uri=`javascript:alert("Hi mom!")`
</code></pre>
<p>CUPS 中的漏洞可以修复,但所有操作系统上可能还有许多其他可疑的本地服务——从磁盘管理工具到防病毒状态仪表板应有尽有。引入指向 127.0.0.1 或其他你无法控制的任何目的地的条目,将你域内 cookie 的安全性绑定到随机第三方软件的安全性。这是应该避免的好事。</p>
<h2 id="cookie-和合法dns-劫持">Cookie 和“合法”DNS 劫持</h2>
<p>对于 cookie 的域作用域策略的危险并不局限于 <em>localhost</em>。另一个意想不到的交互与一些 ISP 和其他 DNS 服务提供商的常见、广泛批评的做法有关,即劫持对不存在(通常是拼写错误)的主机的域名查找。在这个方案中,提供商不是从上游名称服务器返回标准要求的 NXDOMAIN 响应(这会随后在浏览器或其他网络应用程序中触发错误消息),而是伪造一个记录来暗示该名称解析到其网站。其网站反过来会检查浏览器提供的 <em>Host</em> 头部,并向用户提供看似与她浏览兴趣相关的不请自来的、付费的上下文广告。这种做法通常提供的正当理由是提供更用户友好的浏览体验;当然,真正的动机是赚取更多利润。</p>
<p>依赖这种做法的互联网服务提供商包括 Cablevision、Charter、Earthlink、Time Warner、Verizon 以及许多更多。不幸的是,他们的方法不仅道德上有疑问,而且也创造了重大的安全风险。如果广告网站包含任何脚本注入漏洞,攻击者可以通过访问诸如 <em>nonexistent.example.com</em> 这样的地址来利用这些漏洞,在任意其他域的上下文中。当与 HTTP cookie 的设计相结合时,这种做法破坏了互联网上任何任意目标服务的安全性。</p>
<p>预计之下,这种匆忙设计的广告陷阱中可以轻易地发现脚本注入漏洞。例如,在 2008 年,丹·卡明斯基发现了并公开了一个由 Earthlink 运营的页面上的跨站脚本漏洞.^([181])</p>
<p>好吧,好吧:是时候停止对 cookie 的痴迷,继续前进。</p>
<hr>
<p>^([48]) 即使提供的网址 <em><a href="https://webmail.example.com/" target="_blank">https://webmail.example.com/</a></em> 只能通过 HTTPS 访问,如果它使用了一个未锁定到加密协议的 cookie,攻击者只需等待受害者导航到 <a href="http://www.fuzzybunnies.com/" target="_blank"><code>www.fuzzybunnies.com/</code></a>,就可以在页面上静默地注入一个指向 <a href="http://webmail.example.com/" target="_blank"><code>webmail.example.com/</code></a> 的框架,然后拦截产生的 TCP 握手。此时,浏览器将发送所有 <em>webmail.example.com</em> 的 cookie 通过未加密的通道,游戏基本上就结束了。</p>
<p>^([49]) 即使通过将 <em>httponly</em> 和普通 cookie 的 jar 分开来防止这种可能性,也必须允许存在多个同名但作用域不同的 cookie,它们将在任何匹配的请求中一起发送。它们不会伴随任何有用的元数据,并且它们的顺序将是未定义的,并且是浏览器特定的。</p>
<p>^([50]) 这个 IP 地址是为回环接口保留的;任何尝试连接到它的尝试都会将您路由回您自己机器上运行的服务。</p>
<h1 id="插件安全规则">插件安全规则</h1>
<p>浏览器并没有为插件开发者提供一个统一和可扩展的 API 来强制执行安全策略;相反,每个插件都决定应该应用于执行内容哪些规则以及如何实施它们。因此,尽管插件安全模型在某种程度上受到同源策略的启发,但在许多方面都与它不同。</p>
<p>这种脱节可能很危险。在第六章中,我们讨论了插件倾向于检查 JavaScript <em>location</em>对象以确定其宿主页面的来源的趋势。这种错误的做法迫使浏览器开发者限制 JavaScript 程序对其运行时环境的某些部分的篡改能力,以挽救局面。另一个相关的、常见的兼容性问题来源是对 URL 的解释。例如,在 2010 年中旬,一位研究人员发现 Adobe Flash 在以下 URL 上存在问题:^([182])</p>
<pre><code>http://example.com:80@bunnyoutlet.com/
</code></pre>
<p>插件决定通过此 URL 检索的任何代码的来源应设置为<em>example.com</em>,但浏览器在遇到此类 URL 时,自然会从<a href="http://bunnyoutlet.com" target="_blank">bunnyoutlet.com</a>检索数据,然后将它交给困惑的插件执行。</p>
<p>虽然这个特定的漏洞现在已经修复,但未来可能还会出现这种类型的其他漏洞。复制第二章和第三章中讨论的一些 URL 解析怪癖可能是一项徒劳的任务,理想情况下,根本不应该尝试。</p>
<p>在这样的悲观基调下结束这一章是不礼貌的!除了系统性问题之外,让我们看看一些最受欢迎的插件是如何处理安全策略强制执行的。</p>
<h2 id="adobe-flash-1">Adobe Flash</h2>
<p>Flash 安全模型在 2008 年经历了重大改革^([183]),自那以后,它已经相当稳健。现在每个加载的 Flash 小程序都被分配了一个类似于 SOP 的来源,该来源来自其原始 URL^([51]),并且被授予与 JavaScript 相当的名义来源相关权限。特别是,每个小程序都可以从其原始站点加载经过 cookie 认证的内容,从其他来源加载一些受限制的数据类型,并且可以通过<em>URLRequest</em> API 进行类似<em>XMLHttpRequest</em>的 HTTP 调用。这个最后 API 允许的方法和请求头集合被相当合理地管理,并且截至本文撰写时,比大多数浏览器级别的<em>XMLHttpRequest</em>黑名单更为严格.^([184])</p>
<p>在这个合理的基线之上,三种灵活但容易被误用的机制允许在一定程度上修改这种行为,如以下所述。</p>
<h3 id="标记级别安全控制">标记级别安全控制</h3>
<p>嵌入页面可以通过<em><embed></em>或<em><object></em>标签提供的三个特殊参数来指定,以控制小程序如何与其宿主页面和浏览器本身交互:</p>
<ul>
<li>
<p>允许脚本访问<strong>参数</strong>此设置控制小程序使用 JavaScript <em>ExternalInterface.call(...)</em>桥接(见第八章*。然而,截至本文写作时,所有脚本 URL 都应该在此场景中被列入黑名单。)</p>
</li>
<li>
<p>允许全屏<strong>参数</strong>此参数控制小程序是否应被允许进入全屏渲染模式。可能的值是<em>true</em>和<em>false</em>,其中<em>false</em>是默认值。如第八章</p>
</li>
</ul>
<p><em>Security.allowDomain(...)</em>方法允许 Flash 小程序将其变量和函数授予来自不同源的所有 JavaScript 代码或其他小程序。购买者请注意:一旦授予此类访问权限,就再也没有可靠的方法来维护原始 Flash 执行上下文的完整性。授予此类权限的决定不应轻率行事,并且通常应严厉惩罚调用<em>allowDomain("</em>")*的做法。</p>
<p>注意,还有一个名为<em>allowInsecureDomain(...)</em>的奇怪方法也是可用的。这个方法的存在并不表明<em>allowDomain(...)</em>特别安全;相反,提供“不安全”变体是为了与古老的、2003 年之前的语义兼容,这些语义完全忽略了 HTTP/HTTPS 的划分。</p>
<h3 id="跨域策略文件">跨域策略文件</h3>
<p>通过使用 <em>loadPolicyFile(...)</em>,任何 Flash 小程序都可以指示其运行时环境从几乎任意的 URL 检索安全策略文件。这个基于 XML 的文档,通常命名为 <em>crossdomain.xml</em>,将被解释为对跨域、服务器级访问由检查策略 URL 确定的源的表达同意。^([[186])] 策略文件的语法相当直观,可能看起来像这样:</p>
<pre><code><cross-domain-policy>
<allow-access-from domain="foo.example.com"/>
<allow-http-request-headers-from domain="*.example.com"
headers="X-Some-Header" />
</cross-domain-policy>
</code></pre>
<p>政策可能允许通过浏览器 HTTP 堆栈执行诸如加载跨源资源或使用白名单头发出任意 <em>URLRequest</em> 调用等操作。Flash 开发者确实尝试执行一定程度上的路径分离:从特定子目录加载的策略原则上只允许访问该路径内的文件。然而,在实践中,与 SOP(安全策略)以及现代浏览器和 Web 应用程序框架的各种路径映射语义的交互使得依赖这种边界是不明智的。</p>
<h3 id="注意-42">注意</h3>
<p>通过 <em>XMLSocket</em> 建立原始 TCP 连接也是可能的,并且由 XML 策略控制。但是,在 Flash 于 2008 年进行重大改版之后,<em>XMLSocket</em> 要求在目标服务器的 TCP 端口 843 上交付一个单独的策略文件。这是相当安全的,因为在这个端口上没有运行其他常见服务,并且在许多操作系统上,只有特权用户可以在任何低于 1024 的端口上启动服务。由于与某些防火墙级机制(如 FTP 协议助手)的交互,这种设计可能仍然会导致一些网络级干扰,^([[187])] 但这个主题远远超出了本书的范围。</p>
<p>如预期的那样,配置不当的 <em>crossdomain.xml</em> 策略是一个明显的安全风险。特别是,指定指向任何你对其没有完全信心的域的 <em>allow-access-from</em> 规则是一个非常糟糕的想法。此外,将“<code>*</code>”指定为该参数的值大致等同于执行 <em>document.domain = “com”</em>。也就是说,这是自寻死路。</p>
<h3 id="策略文件欺骗风险">策略文件欺骗风险</h3>
<p>除了配置错误的可能性之外,Adobe 基于策略的安全模型中另一个安全风险是,随机用户控制的文档可能被解释为跨域策略,这与网站所有者的意图相反。</p>
<p>在 2008 年之前,Flash 使用了一个臭名昭著的宽松策略解析器,在处理 <em>loadPolicyFile(...)</em> 文件时会跳过任意前导垃圾以寻找开头的 <em><cross-domain-policy></em> 标签。它还会简单地忽略服务器在下载资源时返回的 MIME 类型。因此,仅仅托管一个有效的、用户提供的 JPEG 图像就可能成为一个严重的安全风险。插件还会跳过任何 HTTP 重定向,因此向一个你无法控制的地点(一个在其他方面无害的行为)发出 HTTP 重定向是危险的。</p>
<p>在对<em>loadPolicyFile</em>行为进行必要的改进之后,许多重大错误已经得到纠正,但默认设置仍然不完美。一方面,重定向现在工作得直观,文件必须是一个格式良好的 XML 文档。另一方面,允许的 MIME 类型包括<em>text/**, <em>application/xml</em>, 和 <em>application/xhtml+xml</em>,这感觉有点太宽泛。</em>text/plain<em>或</em>text/csv*可能会被误解释为策略文件,而这不应该发生。</p>
<p>幸运的是,为了减轻这个问题,Adobe 工程师决定推出<em>meta-policies</em>,这些策略托管在预定义的顶级位置(<em>/crossdomain.xml</em>),攻击者无法覆盖。元策略可以指定从攻击者提供的 URL 加载的所有剩余策略的网站范围限制。其中最重要的限制是<em><site-control permitted-cross-domain-policies="..."></em>。当此参数设置为<em>master-only</em>时,简单地向插件指示完全忽略子策略。另一个不那么激进的值,<em>by-content-type</em>,允许加载额外的策略,但要求它们必须有一个非歧义的<em>Content-Type</em>头,设置为<em>text/x-cross-domain-policy</em>。</p>
<p>不言而喻,强烈建议使用指定这两个指令之一的元策略。</p>
<h2 id="微软-silverlight-1">微软 Silverlight</h2>
<p>如果从 Flash 过渡到 Silverlight 看起来很突然,那是因为两者很容易混淆。Silverlight 插件以惊人的热情借鉴了 Flash;事实上,可以说它们之间的安全模型差异大部分仅仅是因为命名。微软的平台使用相同的源确定方法,用<em>enableHtmlAccess</em>替换了<em>allowScriptAccess</em>,用稍微不同的<em>clientaccesspolicy.xml</em>语法替换了<em>crossdomain.xml</em>,提供了<em>System.Net.Sockets</em> API 而不是<em>XMLSocket</em>,用<em>HttpWebRequest</em>代替了<em>URLRequest</em>,重新排列了花,还更换了客厅的窗帘。</p>
<p>它们之间的相似之处非常明显,甚至包括<em>HttpWebRequest</em> API 阻止的请求头列表,甚至包括 Adobe 规范中的<em>X-Flash-Version</em>。188] 这种一致性并不是问题:事实上,与完全新的安全模型相比,这是更可取的。此外,值得称赞的是,微软确实做了一些受欢迎的改进,包括放弃不安全的<em>allowDomain</em>逻辑,转而采用<em>RegisterScriptableObject</em>方法,这种方法只允许显式指定的回调暴露给第三方域。</p>
<h2 id="java">Java</h2>
<p>Sun 的 Java(现在正式属于 Oracle)是一个非常有趣的情况。Java 是一个已经不再使用的插件,其安全架构在过去十年左右的时间里并没有得到太多的审查。然而,由于其庞大的安装基础,很难简单地忽略它并继续前进。</p>
<p>不幸的是,越深入地观察,越明显的是,Java 所采纳的理念往往与现代网络不兼容。例如,一个名为 <em>java.net.HttpURLConnection</em>^([189]) 的类允许对小程序的源网站发起带有凭证的 HTTP 请求,但“源网站”被理解为在特定 IP 地址上托管的所有网站,这是由 <em>java.net.URL.equals(...)</em> 检查所认可的。这种模型本质上取消了 HTTP/1.1 虚拟主机之间的任何隔离——这种隔离是由同源策略、HTTP 饼干和今天几乎所有其他浏览器安全机制严格实施的。</p>
<p>沿着这些思路进一步,<em>java.net.URLConnection</em> 类^([190]) 允许小程序设置任意请求头,包括 <em>Host</em>,并且另一个类,<em>Socket</em>^([191]) 允许在源服务器上的任意端口建立不受限制的 TCP 连接。所有这些行为在浏览器和任何其他当代插件中都是不被推荐的。</p>
<p>小程序通过 <em>JSObject</em> 机制提供对嵌入页面的无原点访问,并期望通过在 <em><applet></em>, <em><embed></em>, 或 <em><object></em> 标签中指定的 <em>mayscript</em> 属性来由嵌入方控制.^([192]) 文档建议这是一个安全特性:</p>
<blockquote>
<p>由于安全原因,Java 插件默认不启用 JSObject 支持。要启用 Java 插件中的 JSObject 支持,需要在 EMBED/OBJECT 标签中存在一个名为 MAYSCRIPT 的新属性。</p>
</blockquote>
<p>不幸的是,文档没有提到另一个与之密切相关的机制,<em>DOMService</em>^([193]) 忽略了这个设置,并给予小程序对嵌入页面的几乎不受限制的访问。虽然 <em>DOMService</em> 在 Firefox 和 Opera 中不受支持,但它在其他浏览器中可用,这使得加载第三方 Java 内容的任何尝试都等同于授予嵌入站点的完全访问权限。</p>
<p>哎呀。</p>
<h3 id="注意-43">注意</h3>
<p>有趣的事实:Java 的最新版本试图复制 Flash 中的 <em>crossdomain.xml</em> 支持。</p>
<hr>
<p>^([51]) 在某些情况下,Flash 可能隐式允许从 HTTPS 原始地访问 HTTP,但不是反过来。这通常是无害的,因此在本节的其余部分没有给予特殊关注。</p>
<p>^([52]) 不应假设这种设置可以阻止任何恶意小程序可用的敏感数据被转发给第三方。任何 Flash 小程序都可以利用许多旁路通道,在不直接发出网络请求的情况下,将信息泄露给合作方。在最简单和最普遍的情况下,可以通过操纵 CPU 负载来向任何同时加载的小程序发送单个信息位,这些小程序会持续采样其运行时环境的响应性。</p>
<h1 id="应对模糊或意外的来源">应对模糊或意外的来源</h1>
<p>这就结束了我们对基本安全策略和同意隔离机制的概述。如果要说一个观察结果,那就是这些机制中的大多数都依赖于一个良好形成的、规范的主机名,以便从中推导出所有后续操作的环境上下文。但假如这个信息不可用或者不是以预期的形式呈现呢?</p>
<p>好吧,事情变得有趣了。让我们看看一些常见的边缘情况,即使只是为了短暂的娱乐。</p>
<h2 id="ip-地址">IP 地址</h2>
<p>由于在设计 HTTP cookie 和同源策略时未能考虑到 IP 地址,几乎所有浏览器都历史性地允许从,比如说,<em><a href="http://1.2.3.4/" target="_blank">http://1.2.3.4/</a></em>加载的文档为名为<strong>.3.4</strong>的“域名”设置 cookie。以类似的方式调整<em>document.domain</em>也会有效。事实上,这些行为中的一些仍然存在于较老版本的 Internet Explorer 中。</p>
<p>这种行为不太可能对主流 Web 应用产生影响,因为这些应用并不是通过基于 IP 的 URL 访问的,并且通常会简单地无法正常工作。但有一些系统,主要用于技术人员,是通过它们的 IP 地址访问的;这些系统可能根本就没有配置 DNS 记录。在这些情况下,<em><a href="http://1.2.3.4/" target="_blank">http://1.2.3.4/</a></em>向<em><a href="http://123.234.3.4/" target="_blank">http://123.234.3.4/</a></em>注入 cookie 的能力可能成为一个问题。家庭路由器的 IP 可达管理接口也引起了一些兴趣。</p>
<h2 id="带有额外句点的主机名">带有额外句点的主机名</h2>
<p>在本质上,设置 cookie 的算法仍然依赖于计算 URL 中句点的数量,以确定特定的<em>域名</em>参数是否可接受。为了做出这个判断,通常会将这个计数与供应商维护的数百个条目的公共后缀列表(<a href="http://publicsuffix.org/" target="_blank"><code>publicsuffix.org/</code></a>)相关联。</p>
<p>不幸的是,对于这个算法来说,通常可以在主机名中添加额外的点,同时仍然正确解析。带有额外点的非规范主机名表示通常被操作系统级别的解析器所尊重,如果被尊重,将会使浏览器困惑。尽管如此,浏览器不会自动将带有额外尾点的域名<a href="http://www.example.com.pl" target="_blank">www.example.com.pl</a>视为与真实的<a href="http://www.example.com.pl" target="_blank">www.example.com.pl</a>相同。(带有额外的尾点)URL 中的微妙且看似无害的差异可能会逃过甚至最警觉的用户。</p>
<p>在这种情况下,与带有尾点的 URL 交互可能是不安全的,因为共享<strong>.com.pl</strong>域的其他文档可能能够相对容易地注入跨域 cookies。</p>
<p>这种点计数问题首次在 1998 年左右被发现.^([194]) 十年后,许多浏览器供应商决定通过在相关代码中添加另一个特殊情况来实施基本缓解措施;截至本文撰写时,Opera 仍然容易受到这种技巧的影响。</p>
<h2 id="非完全合格的主机名">非完全合格的主机名</h2>
<p>许多用户在不知道的情况下,使用配置了将本地后缀附加到所有找到的主机名的 DNS 解析器浏览网络。这种设置通常由 ISP 或雇主通过自动网络配置数据(动态主机配置协议,DHCP)授权。</p>
<p>对于使用这种设置的任何用户,DNS 标签的解析是模糊的。例如,如果 DNS 搜索路径包括<em>coredump.cx</em>,那么<a href="http://www.example.com" target="_blank">www.example.com</a>可能解析到真实的<a href="http://www.example.com" target="_blank">www.example.com</a>网站,或者如果存在这样的记录,解析到<a href="http://www.example.com.coredump.cx" target="_blank">www.example.com.coredump.cx</a>。结果部分受配置设置控制,并在一定程度上可能受到攻击者的影响。</p>
<p>对于浏览器来说,这两个位置看起来是相同的,这可能会产生一些有趣的副作用。考虑一个特别扭曲的情况:<em><a href="http://com" target="_blank">http://com</a></em>,实际上解析到<a href="http://com.coredump.cx/" target="_blank"><code>com.coredump.cx/</code></a>,是否能够通过简单地省略<em>domain</em>参数来设置<strong>.com</strong> cookies?</p>
<h2 id="本地文件">本地文件</h2>
<p>由于通过<em>file:</em>协议加载的本地资源没有与它们关联的显式主机名,浏览器无法计算正常的源。在很长一段时间里,供应商简单地决定在这种情况下最好的做法就是简单地放弃同源策略。因此,任何保存到磁盘的 HTML 文档都会自动获得通过<em>XMLHttpRequest</em>或 DOM 访问任何其他本地文件的权限,甚至更令人费解的是,能够以相同的方式访问任何来自互联网的内容。</p>
<p>这证明是一个糟糕的设计决策。没有人预料到仅仅下载一个 HTML 文档的行为就会将用户的本地文件和在线凭证置于危险之中。毕竟,通过网络访问相同的文档是完全安全的。</p>
<p>许多浏览器近年来试图以不同程度的成功关闭这一漏洞:</p>
<p><strong>Chrome(以及由此扩展的其他 WebKit 浏览器)</strong></p>
<p>Chrome 浏览器完全禁止从 <em>file:</em> 原始来源进行任何跨文档 DOM 或 <em>XMLHttpRequest</em> 访问,并且在此设置中忽略 <em>document.cookie</em> 调用或 <em><meta http-equiv="Set-Cookie" ...></em> 指令。允许访问所有 <em>file:</em> 文档共享的 <em>localStorage</em> 容器,但这种情况可能很快会改变。</p>
<p><strong>Firefox</strong></p>
<p>Mozilla 的浏览器只允许访问原始文档目录内的文件以及附近的子目录。这项政策相当不错,但仍然对存储或以前下载到该位置的文档存在一定风险。通过 <em>document.cookie</em> 或 <em><meta http-equiv="Set-Cookie" ...></em> 访问 cookie 是可能的,并且所有 <em>file:</em> cookie 都对任何其他本地 JavaScript 代码可见.^([53]) 对于访问存储机制也是如此。</p>
<p><strong>Internet Explorer 7 及以上版本</strong></p>
<p>允许从 <em>file:</em> 原始来源无限制地访问本地和互联网内容,但需要用户点击一个非特定的警告来首先执行 JavaScript。这一行动的后果没有明确说明(帮助系统神秘地表示,“Internet Explorer 限制此内容,因为这些程序偶尔可能会出错或给你不想要的内容”),许多用户可能会被误导而点击提示。</p>
<p>Internet Explorer 的 cookie 语义与 Firefox 类似。在此原始来源中不支持 Web 存储。</p>
<p><strong>Opera</strong> 和 <strong>Internet Explorer 6</strong></p>
<p>这两种浏览器都允许无限制的 DOM 或 <em>XMLHttpRequest</em> 访问,而不进行进一步检查。也允许非分区的 <em>file:</em> cookie。</p>
<h3 id="注意-44">注意</h3>
<p>插件在 <em>file:</em> 领域按照自己的规则运行:Flash 使用 <em>local-with-filesystem</em> 沙盒模型,^([195]) 这为本地文件系统提供了大量不受限制的访问权限,无论浏览器本身执行的政策如何,而从本地文件系统执行 Java 或 Windows Presentation Framework 小程序在某些情况下可能大致等同于运行不可信的二进制文件。</p>
<h2 id="伪-url">伪 URL</h2>
<p>类似于 <em>about:</em>、<em>data:</em> 或 <em>javascript:</em> 这样的伪 URL 的行为最初构成了同源策略实现中的一个重大漏洞。所有这样的 URL 都会被视为同源,并允许不受限制的跨域访问来自加载了相同方案的任何其他资源。当前的行为与之前大相径庭,将是本书下一章的主题;简而言之,现状反映了多次匆忙实施的改进,是浏览器特定特殊情况与源继承规则复杂混合的结果。</p>
<h2 id="浏览器扩展和-ui">浏览器扩展和 UI</h2>
<p>几种浏览器允许基于 JavaScript 的 UI 元素或某些用户安装的浏览器扩展以提升权限运行。这些权限可能包括绕过特定的 SOP 检查或调用通常不可用的 API 来写入文件、修改配置设置等。</p>
<p>特权 JavaScript 是 Firefox 的一个显著特性,在那里它与 XUL 一起用于构建浏览器用户界面的大部分。Chrome 也依赖于特权 JavaScript,程度较小但仍很显著。</p>
<p>同源策略并没有以任何特定方式支持特权上下文。实际授予额外权限的机制可能涉及通过特殊且通常无法到达的 URL 方案(如 <em>chrome:</em> 或 <em>res:</em>)加载文档,然后在浏览器代码的其他部分为该方案添加特殊案例。另一种选择是简单地为一个 JavaScript 上下文切换一个二进制标志,无论其实际源是什么,然后在之后检查该标志。在所有情况下,标准 API(如 <em>localStorage</em>、<em>document.domain</em> 或 <em>document.cookie</em>)的行为可能难以预测,不应依赖:一些浏览器试图在不同扩展的上下文之间保持隔离,但大多数并不这样做。</p>
<h3 id="注意-45">注意</h3>
<p>在编写浏览器扩展时,与非特权上下文的任何交互都必须极端谨慎地进行。检查不受信任的上下文可能很困难,使用诸如 <em>eval(...)</em> 或 <em>innerHTML</em> 这样的机制可能会打开权限提升路径。</p>
<hr>
<p>^([53]) 由于 <em>file:</em> 钩子之间没有隔离,因此依赖它们进行合法目的是不安全的。一些本地安装的 HTML 应用程序忽略了这一建议,因此它们的 cookie 可以很容易地被用户查看的任何下载的、可能恶意的内容篡改。</p>
<h1 id="源的其他用途">源的其他用途</h1>
<p>好吧,关于浏览器级别的内容隔离逻辑就说到这里。也许值得指出的是,原点和基于主机或域的安全机制的概念并不仅限于那个特定任务,并在浏览器世界中出现了许多其他情况。其他基于准原点的隐私或安全特性包括与每个站点相关的首选项和缓存信息、弹出窗口阻止、地理位置共享、密码管理、摄像头和麦克风访问(在 Flash 中),以及更多。这些特性至少在一定程度上与本章中描述的安全特性相互作用;我们将在不久的将来更详细地探讨这个主题。</p>
<p>安全工程备忘单</p>
<p>所有网站的优秀安全策略</p>
<p>为了保护您的用户,包括一个顶层 <em>crossdomain.xml</em> 文件,并将 <em>permitted-cross-domain-policies</em> 参数设置为 <em>master-only</em> 或 <em>by-content-type</em>,即使您在网站上任何地方都不使用 Flash。这样做将防止无关的攻击者控制的内容被错误地解释为次要的 <em>crossdomain.xml</em> 文件,从而有效地破坏了 Flash 启用浏览器中同源策略的保证。</p>
<p>当依赖 HTTP Cookies 进行身份验证时</p>
<ul>
<li>
<p>使用 <em>httponly</em> 标志;设计应用程序,使其不需要 JavaScript 直接访问身份验证 cookie。敏感 cookie 应尽可能紧密地限定作用域,最好根本不指定 <em>domain</em>。</p>
</li>
<li>
<p>如果应用程序仅打算使用 HTTPS,则必须将 cookie 标记为 <em>secure</em>,并且你必须准备好优雅地处理 cookie 注入。(HTTP 上下文可能会覆盖 <em>secure</em> cookie,尽管它们无法读取它们。)加密 cookie 签名可能有助于防止不受限制的修改,但它不能防御用另一组合法获得的凭据替换受害者的 cookie。</p>
</li>
</ul>
<p>当在 JavaScript 中安排跨域通信时</p>
<ul>
<li>
<p>不要使用 <em>document.domain</em>。尽可能依赖 <em>postMessage(...)</em>,并确保正确指定目标源;然后在另一端接收数据时验证发送者的源。警惕对域名进行天真子串匹配:<em>msg.origin.indexOf(".example.com")</em> 非常不安全。</p>
</li>
<li>
<p>注意,各种预-<em>postMessage</em> SOP 绕过技巧,如依赖 <em>window.name</em>,不是防篡改的,不应用于交换敏感数据。</p>
</li>
</ul>
<p>当嵌入第三方处理的插件式活动内容时</p>
<p>首先查阅第八章中的备忘单以获取一般建议。</p>
<ul>
<li>
<p><strong>Flash:</strong> 除非你完全信任原始域的所有者和其网站的安全性,否则不要指定 <em>allowScriptAccess=always</em>。在将 HTTP 小程序嵌入 HTTPS 页面时不要使用此设置。此外,根据需要考虑限制 <em>allowFullScreen</em> 和 <em>allowNetworking</em>。</p>
</li>
<li>
<p><strong>Silverlight:</strong> 除非你信任原始域名,如上所述,否则不要指定 <em>enableHtmlAccess=true</em>。</p>
</li>
<li>
<p><strong>Java:</strong> 来自不受信任来源的 Java 小程序不能安全嵌入。省略 <em>mayscript</em> 并不能完全防止访问嵌入页面,因此不要尝试这样做。</p>
</li>
</ul>
<p>当托管你自己的插件执行内容时</p>
<ul>
<li>注意,浏览器插件提供的许多跨域通信机制可能具有意想不到的后果。特别是,避免使用 <em>crossdomain.xml</em>、<em>clientaccesspolicy.xml</em> 或指向你不完全信任的域的 <em>allowDomain(...)</em> 规则。</li>
</ul>
<p>当编写浏览器扩展时</p>
<ul>
<li>
<p>避免依赖 <em>innerHTML</em>、<em>document.write(...)</em>、<em>eval(...)</em> 和其他容易出错的编码模式,这些模式可能导致第三方页面或特权 JavaScript 上下文中的代码注入。</p>
</li>
<li>
<p>不要通过检查不受信任的 JavaScript 安全上下文来做出安全关键的决定,因为它们的行为可能是欺骗性的。</p>
</li>
</ul>
<h1 id="第十章-源继承">第十章. 源继承</h1>
<p>一些 Web 应用程序依赖于伪 URL,如 <em>about:</em>、<em>javascript:</em> 或 <em>data:</em>,来创建不包含任何服务器提供内容的 HTML 文档,而是用完全在客户端构建的数据填充。这种方法消除了与服务器通常的 HTTP 请求相关的延迟,并导致用户界面响应更快。</p>
<p>不幸的是,同源策略的原始愿景没有考虑到这种使用案例。具体来说,对第九章中讨论的协议、主机和端口匹配规则的直接应用会导致客户端创建的每个 <em>about:blank</em> 文档与它的父页面有不同的源,从而阻止它被有意义地操作。此外,由完全不相关的网站创建的所有 <em>about:blank</em> 窗口将属于同一个源,在适当的条件下,它们将能够完全不受监督地相互干扰。</p>
<p>为了解决客户端文档与同源策略的不兼容性,浏览器逐渐发展出了一些不兼容且有时令人费解的方法来计算伪 URL 的合成源和访问权限。理解这些规则本身就很重要,并且它将为讨论第十一章中某些其他 SOP 异常打下基础。</p>
<h1 id="aboutblank-的源继承">about:blank 的源继承</h1>
<p><em>about:</em> 方案在现代浏览器中用于多种目的,其中大多数对普通网页来说并不直接可见。然而,<em>about:blank</em> 文档是一个有趣的特殊情况:这个 URL 可以用来创建一个最小的 DOM 层次结构(本质上是一个有效但空的文档),之后父文档可以写入任意数据。</p>
<p>这里是这个方案典型使用的一个例子:</p>
<pre><code><iframe src="`about:blank`" name="test"></iframe>
<script>
...
frames["test"].document.body.innerHTML = "`<h1>Hi mom!</h1>`";
...
</script>
</code></pre>
<h3 id="注意-46">注意</h3>
<p>在本例中提供的 HTML 标记中,以及创建新窗口或框架时,通常可以省略 <em>about:blank</em>。如果没有指定其他 URL,则默认为此值。</p>
<p>在每个浏览器中,大多数导航到 <em>about:blank</em> 的操作都会创建一个新的文档,该文档从发起导航的页面继承其 SOP 源。继承的源反映在新 JavaScript 执行上下文的 <em>document.domain</em> 属性中,并且不允许从任何其他源访问 DOM。</p>
<p>这个简单的公式适用于导航操作,例如点击链接、提交表单、从脚本中创建新框架或窗口,或以编程方式导航现有文档。尽管如此,存在例外情况,其中最值得注意的是几个特殊、由用户控制的导航方法。这些包括在地址栏手动输入 <em>about:blank</em>、跟随书签,或执行用于在新窗口或标签页中打开链接的手势.^([54]) 这些操作将导致一个占据独特合成源且无法被任何其他页面访问的文档。</p>
<p>另一个特殊情况是加载一个正常的由服务器提供的文档,随后使用 <em>Location</em> 或 <em>Refresh</em> 重定向到 <em>about:blank</em>。在 Firefox 和基于 WebKit 的浏览器中,这种重定向会导致一个独特且不可访问的源,类似于前一段中概述的情况。另一方面,在 Internet Explorer 中,如果重定向发生在 <em><iframe></em> 内,则生成的文档可以被父页面访问,但如果它发生在单独的窗口中,则不可访问。Opera 的行为最难理解:<em>Refresh</em> 导致一个可以被父页面访问的文档,但 <em>Location</em> 重定向将给生成的页面赋予执行重定向的网站的源。</p>
<p>此外,父文档可以将现有文档框架导航到 <em>about:blank</em> URL,即使在该容器中显示的现有文档与调用者具有不同的源.^([55]) 在所有浏览器中,除了 Internet Explorer 之外,新创建的空白文档将从调用者那里继承源。在 Internet Explorer 的情况下,这种导航将成功,但将导致一个不可访问的文档。(这种行为可能不是故意的。)</p>
<p>如果这个描述让你感到困惑,<em>about:blank</em> 文档的处理方法总结在 表 10-1 中。</p>
<p>表 10-1. <em>about:blank</em> URLs 的源继承</p>
<table>
<thead>
<tr>
<th></th>
<th>导航类型</th>
</tr>
</thead>
<tbody>
<tr>
<td>新页面</td>
<td>已存在的非同源页面</td>
</tr>
<tr>
<td>---</td>
<td>---</td>
</tr>
<tr>
<td><strong>Internet Explorer</strong></td>
<td>从调用者继承</td>
</tr>
<tr>
<td><strong>窗口</strong>:唯一来源</td>
<td></td>
</tr>
<tr>
<td><strong>Firefox</strong></td>
<td>从调用者继承</td>
</tr>
<tr>
<td><strong>所有 WebKit</strong></td>
<td>从调用者继承</td>
</tr>
<tr>
<td><strong>Opera</strong></td>
<td>从调用者继承</td>
</tr>
</tbody>
</table>
<hr>
<p>^([54]) 这通常是通过在点击链接时按住 ctrl 或 shift 键,或者通过右键单击鼠标以访问上下文菜单,然后选择适当的选项来实现的。</p>
<p>^([55]) 使这成为可能的确切情况将是 第十一章 的重点。现在,只需说,这可以在许多浏览器特定的设置中实现。例如,在 Firefox 中,你调用 <em>window.open(..., 'target')</em>,而在 Internet Explorer 中,调用 <em>target.location.assign(...)</em> 是正确的方法。</p>
<h1 id="data-url-的继承">data: URL 的继承</h1>
<p><em>data:</em> 方案,^([196]) 首次在 第二章 中概述,旨在允许小文档,如图标,方便地编码并在 HTML 文档中直接内联,从而节省 HTTP 往返时间。例如:</p>
<pre><code><img src="data:image/jpeg;base64,`/9j/4AAQSkZJRgABAQEBLAEsAAD`...">
</code></pre>
<p>当<em>data:</em> 方案与类型特定子资源一起使用时,唯一不寻常的安全考虑是它对插件构成了挑战,这些插件希望从其原始 URL 中获取小程序的权限。来源不能仅通过查看 URL 来计算,并且行为有些不可预测且高度依赖于插件(例如,Adobe Flash 目前拒绝使用<em>data:</em> 文档的任何尝试)。</p>
<p>比类型特定内容的情况更重要的是,<em>data:</em> 作为窗口和框架的目标使用。在所有浏览器中除了 Internet Explorer 外,该方案可以用作<em>about:blank</em>的改进版本,如下例所示:</p>
<pre><code><iframe src="data:text/html;charset=utf-8,`<h1>Hi mom!</h1>`">
</iframe>
</code></pre>
<p>在这种情况下,没有充分的理由让<em>data:</em> URL 的行为与<em>about:blank</em>不同。然而,实际上,在某些浏览器中,它的行为会有所不同,因此必须谨慎使用。</p>
<ul>
<li>
<p><strong>WebKit 浏览器</strong> 在 Chrome 和 Safari 中,所有<em>data:</em> 文档都被赋予一个唯一且不可访问的来源,并且完全不继承自父级。</p>
</li>
<li>
<p><strong>Firefox</strong> 在 Firefox 中,<em>data:</em> 文档的来源是从导航上下文继承的,类似于<em>about:blank</em>。然而,与<em>about:blank</em>不同,手动输入<em>data:</em> URL 或打开书签会导致新文档从发生导航的页面继承来源。</p>
</li>
<li>
<p><strong>Opera</strong> 到目前为止,所有 <em>data:</em> URL 都使用共享的“空”来源,该来源可以通过父文档访问。这种方法是不安全的,因为它可能允许跨域访问由无关页面创建的框架,如图 10-1 所示。图 10-1。 (我已经向 Opera 报告了这种行为,它可能很快就会得到修正。)</p>
</li>
<li>
<p><strong>Internet Explorer</strong> 中的 <em>data:</em> URL 在 8.0 之前的版本中不受支持。该方案仅在 Internet Explorer 8 和 9 中支持某些类型的子资源,并且不能用于导航。</p>
</li>
</ul>
<p>表 10-2 总结了 <em>data:</em> URL 的当前行为。</p>
<p>表 10-2. <em>data:</em> URL 的来源继承</p>
<table>
<thead>
<tr>
<th></th>
<th>导航类型</th>
</tr>
</thead>
<tbody>
<tr>
<td>新页面</td>
<td>已存在的非同源页面</td>
</tr>
<tr>
<td>---</td>
<td>---</td>
</tr>
<tr>
<td><strong>Internet Explorer 6/7</strong></td>
<td>(不支持)</td>
</tr>
<tr>
<td><strong>Internet Explorer 8/9</strong></td>
<td>(不支持导航)</td>
</tr>
<tr>
<td><strong>Firefox</strong></td>
<td>从调用者继承</td>
</tr>
<tr>
<td><strong>所有 WebKit</strong></td>
<td>唯一来源</td>
</tr>
<tr>
<td><strong>Opera</strong></td>
<td>共享来源(这是一个错误!)</td>
</tr>
</tbody>
</table>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950021.png.jpg" alt="Opera 中 *data:* URL 之间的访问" loading="lazy"></p>
<p>图 10-1. Opera 中 <em>data:</em> URL 之间的访问</p>
<h1 id="javascript-和-vbscript-的继承url">JavaScript 和 VBScript 的继承:URL</h1>
<p>与脚本相关的伪 URL,例如 <em>javscript:</em>,是一个非常奇特的机制。使用它们来加载某些类型的子资源将导致在尝试加载此类操作的文档上下文中执行代码(受一些不一致的限制,如第四章所述第四章)。这种情况的一个例子可能是</p>
<pre><code><iframe src="javascript:`alert('Hi mom!')`"></iframe>
</code></pre>
<p>与创建新的子资源相比,更有趣(并且远不那么明显)的是,将现有窗口或框架导航到 <em>javascript:</em> URL 将导致内联 JavaScript 代码在导航页面的上下文中执行(而不是导航文档!)——即使 URL 是手动输入或从书签加载的。</p>
<p>考虑到这种行为,显然允许一个文档将任何非同源上下文导航到 <em>javascript:</em> URL 是非常不安全的,因为它将能够绕过所有其他内容隔离机制:只需在框架中加载 <a href="http://fuzzybunnies.com" target="_blank">fuzzybunnies.com</a>,然后导航该框架到 <em>javascript:do_evil_stuff()</em> 并结束。因此,除了 Firefox 之外的所有浏览器都禁止此类导航。Firefox 似乎出于某种原因允许这样做,但它以巧妙的方式改变了语义。当调用者和导航目标的来源不匹配时,它会在一个特殊的空来源中执行 <em>javascript:</em> 有效负载,该来源没有自己的 DOM 或任何浏览器提供的已注册的 I/O 函数(因此只允许执行纯算法操作)。</p>
<p>跨源情况是危险的,但它的同源等效物并不危险:在单个来源内,任何内容都可以自由地导航自身或其同辈到其自身的 <em>javascript:</em> URL。在这种情况下,当跟随链接、提交表单、调用 <em>location.assign(...)</em> 等时,会尊重 <em>javascript:</em> 方案。在 WebKit 和 Opera 中,<em>Refresh</em> 重定向到 <em>javascript:</em> 也会正常工作;其他浏览器由于模糊且可能位置不当的脚本注入担忧而拒绝此类导航。</p>
<p>脚本 URL 的处理在 表 10-3 中概述。</p>
<p>表 10-3. 脚本 URL 的来源继承</p>
<table>
<thead>
<tr>
<th></th>
<th>导航类型</th>
</tr>
</thead>
<tbody>
<tr>
<td>---</td>
<td></td>
</tr>
<tr>
<td>新页面</td>
<td>存在的同源页面</td>
</tr>
<tr>
<td>---</td>
<td>---</td>
</tr>
<tr>
<td><strong>Internet Explorer</strong></td>
<td>从调用者继承</td>
</tr>
<tr>
<td><strong>Firefox</strong></td>
<td>无上下文</td>
</tr>
<tr>
<td><strong>所有 WebKit</strong></td>
<td>(拒绝)</td>
</tr>
<tr>
<td><strong>歌剧</strong></td>
<td>(拒绝)</td>
</tr>
</tbody>
</table>
<p>在这些迷人的语义之上,还有另一个独特的转折,这是 <em>javascript:</em> 方案的独有特点:在某些情况下,处理包含脚本的 URL 涉及一个额外的步骤。具体来说,如果提供的代码正确评估,并且最后一个语句的值非空且可以转换为字符串,则该字符串将被解释为 HTML 文档,并将替换导航的页面(从调用者继承来源)。支配这种奇特行为的逻辑与影响 <em>data:</em> URL 行为的逻辑非常相似。这样一个文档替换表达式的例子如下:</p>
<pre><code>javascript:"<b>2 + 2 = " + (2+2) + "</b>"
</code></pre>
<h1 id="关于受限伪-url-的说明">关于受限伪 URL 的说明</h1>
<p>上述三种 URL 类别的有些古怪的行为——<em>about:blank</em>、<em>javascript:</em> 和 <em>data:</em>——是大多数网站需要关注的。尽管如此,浏览器使用了一系列其他文档,这些文档没有固有的、明确定义的源(例如,Firefox 中的 <em>about:config</em>,这是一个特权 JavaScript 页面,可以用来调整浏览器的各种底层设置,或者 Chrome 中的 <em>chrome://downloads</em>,它列出了最近下载的文档,并提供链接以打开其中任何一个)。这些文档是持续的安全问题来源,即使它们不能直接从互联网访问。</p>
<p>由于这些 URL 与同源策略控制的边界不兼容,因此必须特别小心,确保在浏览器中由于用户操作或其他间接浏览器级过程加载这些 URL 时,它们与其他内容足够隔离。一个说明风险的有趣案例是 2010 年 Firefox 处理<em>about:neterror</em>的方式中的错误。^([197]) 当 Firefox 无法从远程服务器正确检索文档(这种情况通常可以通过精心制作的链接轻松触发)时,它会将目标 URL 放入地址栏,但用<em>about:neterror</em>代替文档主体。不幸的是,由于一个小的疏忽,这个特殊的错误页面会与任何由互联网源打开的<em>about:blank</em>文档具有相同的源,从而允许攻击者向<em>about:neterror</em>窗口注入任意内容,同时保留显示的目标 URL。</p>
<p>这个故事的意义是什么?避免使用同源策略进行赌博的冲动;相反,与之合作。请注意,将 <em>about:neterror</em> 设置为分层 URL,而不是试图跟踪合成源,本可以防止这个错误。</p>
<p>安全工程速查表</p>
<p>由于它们与同源策略的不兼容性,<em>data:</em>、<em>javascript:</em> 和隐式或显式的<em>about:blank</em> URL 应谨慎使用。当性能不是关键时,最好通过将新框架和窗口指向由服务器提供的具有明确源的空白文档来初始化它们。</p>
<p>请记住,<em>data:</em> 和 <em>javascript:</em> URL 不是<em>about:blank</em>的直接替代品,并且只有在绝对必要时才应使用它们。特别是,目前假设<em>data:</em>窗口不能跨域访问是不安全的。</p>
<h1 id="第十一章同源规则之外的生活">第十一章。同源规则之外的生活</h1>
<p>同源策略是我们用来阻止敌对 Web 应用程序的最重要机制,但它并不完美。尽管它的目的是在任意两个不同且可明确识别的内容源之间提供强大的隔离度,但它往往在这个任务上失败。</p>
<p>要理解这种脱节,请记住,与常识可能暗示的相反,同源策略从未旨在包罗万象。它的初始重点是 DOM 层次结构(即仅暴露给 JavaScript 代码的<em>document</em>对象),这使许多外围 JavaScript 功能完全暴露于跨域操作,需要临时解决方案。例如,在 SOP(Same-Origin Policy)实施几年后,供应商意识到允许第三方文档调整与无关窗口的<em>location.host</em>属性是一个糟糕的想法,并且这种操作可能会将其他 URL 段中可能敏感的数据发送到攻击者指定的网站。该策略随后被扩展到至少部分保护这一点和其他几个敏感对象,但在一些不太明确的情况下,仍然存在尴尬的漏洞。</p>
<p>另一个问题在于,许多跨域交互完全发生在 JavaScript 及其对象层次结构之外。例如加载第三方图像或样式表这样的操作深深植根于 HTML 的设计中,并且不依赖于任何有意义的脚本。原则上,可以通过基于源的安全控制来改造它们,但这样做会干扰现有的网站。此外,有些人认为这样的决定会违反使 Web 成为今天的 Web 的设计原则;他们认为自由交叉引用内容的能力不应受到侵犯。)</p>
<p>鉴于此,探索同源策略的边界并了解网络应用在其范围之外可以拥有的丰富生活似乎是谨慎的。我们从文档导航开始——这个机制一开始看起来非常简单,但实际上却远非如此。</p>
<h1 id="窗口和框架交互">窗口和框架交互</h1>
<p>在互联网上,从一个网站导航到另一个网站的能力被视为理所当然。本书第一部分 Part I 中讨论了实现这种导航的一些常见方法;其中最显著的是 HTML 链接、表单和框架;HTTP 重定向;以及 JavaScript 的<em>window.open(...)</em>和<em>location.</em>调用。</p>
<p>指向新打开的窗口的域外 URL 或指定框架的<em>src</em>参数这样的操作直观且无需进一步审查。但当我们看到一页导航到另一页、现有文档的能力时——嗯,直觉的统治突然结束。</p>
<h2 id="改变现有文档的位置">改变现有文档的位置</h2>
<p>在 HTML 框架出现之前的简单日子里,只有一个文档可以占据一个特定的浏览器窗口,并且只有那个单一的窗口处于文档的控制之下。然而,框架改变了这种模式,允许几个不同且完全独立的文档被拼接成一个单一的逻辑视图,共存于屏幕的公共区域。引入这种机制也 necessitated 另一个步骤:为了合理地实现某些基于框架的网站,窗口中显示的任何组件文档都需要能够导航其相邻的框架或甚至顶级文档本身。(例如,想象一个有两个框架的页面,左侧是目录,右侧是实际的章节。在左侧面板中点击章节名称应该导航右侧面板中的章节,而不会导航其他任何内容。)</p>
<p>为此目的设计的机制相当简单:可以在<em><a href=...></em>链接或表单上指定<em>target</em>参数,或者将窗口的名称提供给名为<em>window.open(...)</em>的 JavaScript 方法,以导航任何其他先前命名的文档视图。在 20 世纪 90 年代中期,当这种功能首次推出时,似乎没有必要将任何特定的安全检查纳入这种逻辑;任何页面都可以随意将任何其他命名的窗口或浏览器显示的框架导航到新位置。</p>
<p>要理解这种设计的后果,重要的是暂停一下,并检查特定文档最初可能获得名字的情况。对于框架来说,故事很简单:为了在嵌入页面上轻松引用框架,几乎所有框架都有一个<em>name</em>属性(并且一些浏览器,如 Chrome,还会查看<em>id</em>)。另一方面,浏览器窗口通常是匿名的(即,它们的<em>window.name</em>属性是一个空字符串),除非是程序创建的;在后一种情况下,名字由创建视图的人指定。然而,匿名窗口并不一定保持匿名。如果恶意应用程序在这样一个窗口中甚至只是短暂地显示,它也可能将<em>window.name</em>属性设置为任何值,并且这种效果将持续存在。</p>
<p>通过名称定位窗口和框架的能力并不是导航它们的唯一方式;持有指向其他文档的窗口句柄的 JavaScript 程序可以直接调用某些 DOM 方法,而无需知道它们目标的名字。攻击者提供的代码通常不会持有与完全无关的窗口的句柄,但它可以遍历诸如<em>opener</em>、<em>top</em>、<em>parent</em>或<em>frames[]</em>等属性,以便在相同的导航流程中定位到远亲。这种远距离查找(以及随后的导航)的一个例子是</p>
<pre><code>opener.opener.frames[2].location.assign("http://www.bunnyoutlet.com/");
</code></pre>
<p>这两种查找技术并不相互排斥:JavaScript 程序可以通过<em>window.open(...)</em>首先获取一个无关但命名的窗口的句柄,然后通过该上下文的<em>opener</em>或<em>frames[]</em>属性遍历,以便到达其附近的有趣相关元素。</p>
<p>一旦以任何方式查找到合适的句柄,原始上下文就可以利用几个 DOM 方法和属性来更改在该视图中显示的文档地址。在每一个现代浏览器中,调用<em><handle>.location.replace(...)</em>方法,或者将值赋给<em><handle>.location</em>或<em><handle>.location.href</em>属性,应该可以达到目的。有趣的是,由于随机实现的怪癖,其他理论上等效的方法(例如调用<em><handle>.location.assign(...)</em>或<em><handle>.window.open(..., "_self")</em>)可能会时灵时不灵。</p>
<p>好吧,所以可能可以将无关的文档导航到新的位置——但让我们看看可能会出什么问题。</p>
<h3 id="框架劫持风险">框架劫持风险</h3>
<p>一个域能够导航由其他网站创建的窗口,或者那些不再与创建者同源的窗口,通常并不是一个严重的问题。这种轻松的设计可能令人烦恼,并可能带来一些轻微的、推测性的钓鱼风险^([56]),但在大局中,这既不是一个非常突出的问题,也不是一个特别独特的问题。这可能是相关 API 的原始作者没有过多考虑整个机制的原因。</p>
<p>然而,HTML 框架的概念极大地改变了这一局面:任何依赖于框架来构建可信用户界面的应用程序,如果允许无关的网站劫持这样的 UI 元素而不在地址栏留下任何攻击痕迹,那么它就处于明显的风险之中!图 11-1 展示了这样一个可能的攻击场景。</p>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950023.png.jpg" alt="历史上允许的、危险的框架导航场景:右侧窗口与银行网站同时打开,并积极篡改它。" loading="lazy"></p>
<p>图 11-1. 历史上允许的、危险的框架导航场景:右侧窗口与银行网站同时打开,并积极篡改它。</p>
<p>乔治·古尼斯基(Georgi Guninski),一位先驱浏览器安全研究人员,早在 1999 年就意识到,通过允许无限制的框架导航,我们正走向一些严重的麻烦。在他的报告之后,供应商试图在 2000 年中期推出框架导航限制。198]他们的实现将所有跨框架导航限制在单个窗口的范围内,防止恶意网页干扰任何其他同时打开的浏览器会话。</p>
<p>令人惊讶的是,即使这项简单的政策也难以正确实施。直到 2008 年,Firefox 才消除了这类问题,199]而微软直到 2006 年才基本忽略这个问题。尽管如此,抛开这些挫折,我们应该没问题——对吧?</p>
<h3 id="框架后代策略和跨域通信">框架后代策略和跨域通信</h3>
<p>在上一节中讨论的简单安全限制实际上并不足够。原因是出现了一类新的 Web 应用程序,有时被称为<em>mashups</em>,它们结合了来自各种来源的数据,使用户能够个性化他们的工作环境并以创新的方式处理数据。不幸的是,对于浏览器供应商来说,这类 Web 应用程序通常依赖于通过<em><iframe></em>标签加载的第三方小工具,而它们的开发者无法合理地期望从恶意源加载单个框架会危及页面上所有其他框架。然而,简单而优雅的窗口级导航策略恰恰允许这样做。</p>
<p>大约在 2006 年,微软同意当前的方法不可持续,并在 Internet Explorer 7 中开发了一种更安全的<em>后代策略</em>用于框架导航。根据这项政策,只有在请求导航的方与目标视图的祖先之一共享源时,才允许非同源框架的导航。图 11-2 展示了这项新政策允许的导航场景。</p>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950025.png.jpg" alt="在非同源框架之间进行复杂但允许的导航。这次尝试之所以成功,仅仅是因为发起框架与目标文档的一个祖先具有相同的源——在这里,就是顶级页面本身。" loading="lazy"></p>
<p>图 11-2. <a href="http://www.robotroom.com/DebouncedCounter/Figure713.gif" target="_blank">在非同源框架之间进行复杂但允许的导航。这次尝试之所以成功,仅仅是因为发起框架与目标文档的一个祖先具有相同的源——在这里,就是顶级页面本身。</a></p>
<p><a href="http://www.fuzzybunnies.com/" target="_blank"></a>[与其他许多安全改进一样,微软从未将此策略回滚到仍然流行的 Internet Explorer 6,也从未有说服力地敦促用户放弃其浏览器中越来越不安全(但表面上仍得到支持)的旧版本。更积极的一面是,到 2009 年,三位安全研究人员(Adam Barth、Collin Jackson 和 John C. Mitchell)说服 Mozilla、Opera 和 WebKit 在其浏览器中实施类似的政策,<sup class="footnote-ref"><a href="#fn4" id="fnref4">[4]</a></sup>(<a href="http://www.bunnyoutlet.com/" target="_blank">http://www.bunnyoutlet.com/</a>)200]最终为大多数互联网用户关闭了 mashup 漏洞。</p>
<p>嗯,<em>几乎</em>关闭了。即使是新的、稳健的政策也有一个细微的缺陷。注意在图 11-2 中,恶意网站<a href="http://bunnyoutlet.com/" target="_blank"><code>bunnyoutlet.com/</code></a>可以干扰<a href="http://fuzzybunnies.com/" target="_blank"><code>fuzzybunnies.com/</code></a>为其自身使用而创建的私有框架。乍一看,这里没有伤害:攻击者的域名显示在地址栏中,所以理论上受害者不应该被欺骗与<a href="http://fuzzybunnies.com/" target="_blank"><code>fuzzybunnies.com/</code></a>的篡改 UI 进行任何有意义的交互。遗憾的是,有一个陷阱:一些 Web 应用已经学会了使用框架不是为了创建用户界面,而是为了在源之间传递程序性消息。对于需要支持 Internet Explorer 6 和 7 的应用程序,其中<em>postMessage(...)</em>不可用,类似图 11-3 中展示的方法是常见的。</p>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950027.png.jpg" alt="一个潜在的跨域通信方案,其中顶级页面在设备框架的片段标识符中编码发送给嵌入式小工具的消息,而小工具通过导航与顶级文档同源的子框架来响应。如果这个应用在恶意网站上被框架化,攻击者控制的顶级文档将能够通过自由导航 send_to_parent 和 send_to_child 在双方之间注入消息。" loading="lazy"></p>
<p>图 11-3. 一种潜在的跨域通信方案,其中顶级页面在嵌入的设备帧的片段标识符中编码发送给嵌入设备的消息,设备通过导航与顶级文档同源的子帧进行响应。如果此应用程序在恶意网站上被嵌入,攻击者控制的顶级文档将能够通过自由导航 <em>send_to_parent</em> 和 <em>send_to_child</em> 在双方之间注入消息。</p>
<p>如果一个依赖于类似黑客手段的应用程序被恶意网站嵌入,通信帧的完整性可能会受到损害,攻击者将能够向流中注入消息。甚至使用 <em>postMessage(...)</em> 也可能存在风险:如果发送消息的一方没有指定目标源或接收方没有检查原始位置,劫持一个帧将以相同的方式使攻击者受益。</p>
<h2 id="未经请求的帧嵌入">未经请求的帧嵌入</h2>
<p>之前关于跨帧导航的讨论突出了浏览器安全模型中一个更有趣的弱点,以及 HTML 设计目标与同源策略目标之间的脱节。但不仅如此:跨域帧的概念本身就有相当大的风险。为什么?好吧,任何恶意页面都可能在不让用户知情甚至未经同意的情况下嵌入第三方应用程序。此外,它还可能通过在帧上叠加其他视觉元素来掩盖这一事实,只露出原始网站的一小部分,例如执行状态更改操作的按钮。在这种设置下,任何使用环境凭证登录目标应用程序的用户都可能很容易被诱骗与伪装的 UI 控件交互,并执行不希望或不打算执行的操作,例如更改社交网络配置文件的分享设置或删除数据。</p>
<p>通过恶意网站利用 CSS2 中的 <em>opacity</em> 属性,可以使目标帧完全不可见,而不会影响其实际行为,从而改进这种攻击。在这种透明的帧占据的区域中的任何点击都将传递到其中包含的 UI 控件(见 图 11-4</p>
<p>图 11-4. 使用 CSS 透明度隐藏用户实际交互的文档的 UI 拼接攻击简化示例</p>
<p>研究人员自 2000 年代初以来在一定程度上认识到了这种欺骗的可能性,但直到 2008 年,罗伯特·汉森和杰里米·格罗斯曼才广泛公开了这个问题,这才证明了有足够说服力的攻击。^[[201]) 因此,“点击劫持”这个术语应运而生。</p>
<p>汉森和格罗斯曼的报告备受关注,以及他们有趣的证明概念示例激起了供应商的兴趣。然而,这种兴趣很快就被证明是短暂的,而且似乎没有简单的方法可以解决这个问题而不承担重大风险。唯一可能稍微减轻影响的方法是添加渲染器级别的启发式规则,禁止将事件传递给部分遮挡或未显示足够长时间的跨域框架。但这个解决方案复杂且棘手,可能不受欢迎。^[[202]) 相反,这个问题只是被贴上了临时补丁。一个新的 HTTP 头信息,<em>X-Frame-Options</em>,允许相关网站选择完全退出被框架嵌入(<em>X-Frame-Options: deny</em>)或仅同意在单个源中框架嵌入(<em>X-Frame-Options: same-origin</em>)。^[[203]) 这个头信息在所有现代浏览器中都有支持(在 Internet Explorer 中,从版本 8 开始),^[[57]) 但实际上对解决漏洞帮助甚微。</p>
<p>首先,防御措施的自愿性质意味着大多数网站不会采用它,或者不会很快采用它;事实上,2011 年对互联网上排名前 10,000 个目的地的调查发现,仅有不到 0.5%使用了这个功能。^[[204])</p>
<p>更令人遗憾的是,所提出的机制对于希望嵌入第三方网站但希望保留其 UI 完整性的应用程序来说毫无用处。各种混合应用和设备,那些由社交网站提供的“喜欢”按钮,以及管理的在线讨论界面都面临着风险。</p>
<h3 id="不仅仅是单次点击的威胁">不仅仅是单次点击的威胁</h3>
<p>正如其名所示,Grossman 和 Hansen 概述的点击劫持攻击针对的是简单的单次点击用户界面操作。然而,实际上,欺骗性框架的问题比早期报道的要复杂得多。一个更复杂的交互示例是选择、拖动和放置一段文本的行为。2010 年,Paul Stone 提出了一些方法,可以将此类行为伪装成与攻击者网站的合理交互,其中最值得注意的是拖放与谦逊的文档级滚动条的使用相似性。相同的点击-拖动-释放动作可以用来与合法的用户界面控件交互,或者无意中将预选文本的一部分从敏感文档中拖出并放入攻击者控制的框架中。(在 WebKit 中不再允许跨域拖放,但截至本文撰写时,其他浏览器供应商仍在争论如何正确应对这一风险。)</p>
<p>一个更具挑战性的问题是按键重定向。大约在 2010 年,我注意到可以通过使用 JavaScript 中的<em>onkeydown</em>事件检查按下的键的代码,有选择地在域之间重定向按键。如果按下的键与恶意网站想要输入的目标应用中的内容匹配,HTML 元素焦点可以暂时切换到一个隐藏的<em><iframe></em>,从而确保实际按键被发送到目标 Web 应用,而不是用户似乎正在与之交互的无害文本字段。使用这种方法,攻击者可以代表用户在另一个域中合成任意复杂的文本——例如,邀请攻击者作为受害者博客的管理员。</p>
<p>浏览器供应商通过禁止在按键过程中更改元素焦点来解决选择性的按键重定向问题,但这样做并没有完全关闭漏洞。毕竟,在某些情况下,攻击者可以预测下一个将被按下的键以及大致的时间,从而允许进行先发制人的、盲目执行的重点切换。最明显的两个案例是基于网络的动作游戏或打字速度测试,因为两者通常都涉及攻击者影响的键的快速按下。</p>
<p>事实上,情况甚至更糟:即使一个恶意应用仅依赖于自由形式的文本输入——例如,通过向用户提供一个评论提交表单——通常也可以仅根据前几个按键来猜测下一个将被按下的字符。英语文本(以及大多数其他人类语言的文本)高度冗余,在许多情况下,可以提前预测相当数量的输入:你可以打赌<em>a-a-r-d-v</em>后面将跟随<em>a-r-k</em>,而且几乎总是正确的。</p>
<hr>
<p>^([56]) 一种潜在的攻击方式是:在一个新窗口中打开一个合法网站(比如,<a href="http://trusted-bank.com/" target="_blank"><code>trusted-bank.com/</code></a>),等待用户检查地址栏,然后迅速将位置更改为一个攻击者控制的但名称相似的网站(例如,<a href="http://trustea-bank.com/" target="_blank"><code>trustea-bank.com/</code></a>)。成功钓鱼受害者的可能性可能高于用户直接导航到恶意 URL 的情况。</p>
<p>^([57]) 在 Internet Explorer 的早期版本中,Web 应用程序开发人员有时会求助于 JavaScript,试图确定 <em>window</em> 对象是否与 <em>parent</em> 相同,如果不存在更高层的框架,则应满足这一条件。遗憾的是,由于 JavaScript DOM 的灵活性,此类检查以及许多可能的纠正措施都臭名昭著地不可靠。</p>
<h1 id="跨域内容包含">跨域内容包含</h1>
<p>框架和导航是一个独特的麻烦来源,但抛开这些机制,HTML 支持许多其他与不同源数据交互的方式。这些功能的常规设计模式简单且看似安全:检索并解析影响文档外观的约束数据格式,而无需直接展示给引用它的源。遵循此规则的机制示例包括如 <em><script src=...></em>、<em><link rel=stylesheet href=...></em>、<em><img src=...></em> 以及本书 第一部分 中讨论的几个相关案例。</p>
<p>可惜,魔鬼藏在细节中。当这些机制最初被提出时,没有人提出几个极其紧迫的问题:</p>
<ul>
<li>
<p>这些子资源是否应该使用与它们的源相关联的环境凭据进行请求?如果是这样,那么响应中可能包含请求方不希望看到的敏感数据。可能更好的做法是要求某种明确的认证形式,或者通知服务器请求页面的来源。</p>
</li>
<li>
<p>相关解析器是否应该设计成最小化误将一种文档类型误认为另一种文档类型的风险?服务器是否应该控制其响应的解析方式(例如通过 <em>Content-Type</em> 标头)?如果不是,将用户的私人 JPEG 图像解释为脚本会有什么后果?</p>
</li>
<li>
<p>请求页面是否应该没有推断检索到的有效负载内容的方式?如果是的话,那么在设计所有相关 API 时,需要极其小心地考虑这一目标。(如果这种分离不是目标,那么前述问题的意义就更加明显。)</p>
</li>
</ul>
<p>开发者在这些主题上存在相互冲突的假设,或者可能根本没有考虑过这些问题,导致了一系列深刻的网络安全风险。例如,在大多数浏览器中,过去可以通过在跨域 <em><script></em> 加载上注册 <em>onerror</em> 处理器来读取任意、通过 Cookie 认证的文本:浏览器生成的冗长的“语法错误”消息将包括检索到的文件的一部分。然而,在这个类别中,没有比克里斯·埃文斯在 2009 年发现的故障更有趣的了.^([207]) 他注意到 CSS 解析器的标志性容错能力(如您所知,通过尝试在最近的括号处重新同步来从语法错误中恢复)也是一个致命的安全漏洞。</p>
<p>为了理解这个问题,考虑以下简单的 HTML 文档。该文档包含两个攻击者控制的字符串,并且——在两者之间——是一个敏感的、特定于用户的值(在这种情况下,是用户的名字):</p>
<pre><code><head>
<title>Page not found: ');`} gotcha { background-image: url('/`</title>
</head>
<body>
...
<span class="header">You are logged in as: John Doe</span>
...
<div class="error_message">
Page not found: ');`} gotcha { background-image: url('/`
</div>
...
</body>
</code></pre>
<p>假设攻击者诱骗受害者访问其自己的页面,并在该页面上使用 <em><link rel=stylesheet></em> 来加载上述跨域 HTML 文档,而不是样式表。受害者的浏览器会欣然同意:它会使用受害者的 Cookie 请求该文档,会忽略后续响应中的 <em>Content-Type</em>,并将检索到的内容交给 CSS 解析器。解析器会愉快地忽略所有导致看似名为 <em>gotcha</em> 的 CSS 规则之前的语法错误。然后,它将处理 <em>url('...</em> 伪函数,消耗所有后续的 HTML(包括秘密的用户名!),直到遇到匹配的引号和闭合括号。当这个假样式表后来应用于攻击者网站上的 <em>class=gotcha</em> 元素时,浏览器将尝试加载生成的 URL,并在过程中向攻击者的服务器泄露秘密值。</p>
<p>聪明的读者可能会注意到 CSS 标准不支持多行字符串字面量,因此这个技巧不会按指定的方式工作。这部分是正确的:在大多数浏览器中,只有当页面的关键部分不包含多余的换行符时,尝试才会成功。一些 Web 应用程序被优化以避免不必要的空白,因此它们会受到影响,但大多数 Web 开发者会自由地使用换行符,从而挫败攻击。遗憾的是,正如在第五章第五章。层叠样式表中所述,有一个浏览器表现不同:Internet Explorer 接受样式表中的多行字符串和许多其他严重的语法违规,意外地放大了这个漏洞的影响。</p>
<h3 id="注意-47">注意</h3>
<p>自从发现这个问题以来,Chris Evans 一直在主流浏览器中推动修复,截至本文撰写时,大多数实现都拒绝接受那些不以有效 CSS 规则开始或带有不兼容的<em>Content-Type</em>头部的跨域样式表(同源样式表受到较少的限制)。唯一抵制这一做法的是微软,它在演示了对 Twitter 成功的概念验证攻击之后才改变主意。^([[208]) 随着这一揭示,微软不仅同意在 Internet Explorer 8 中解决这个问题,而且——不寻常的是——还将这个特定的修复回滚到 Internet Explorer 6 和 7 中。</p>
<p>多亏了 Chris 的努力,样式表问题已经得到解决,但类似的问题在其他类型的跨域子资源中很可能会再次出现。在这种情况下,不能将所有违规行为都归咎于过去的错误。例如,当浏览器厂商推出<em><canvas></em>,一个简单的 HTML5 机制,允许 JavaScript 创建矢量图形和位图图形时^([[209])],许多实现并没有对将跨域图像加载到画布上并逐像素读取施加任何限制。截至本文撰写时,这个问题也得到了解决:一旦被跨域图像接触过的画布就会变成“污染的”,只能写入而不能读取。但当我们需要单独修复每个这样的案例时,问题就非常严重了。</p>
<h2 id="关于跨域子资源的注意事项">关于跨域子资源的注意事项</h2>
<p>到目前为止,我们一直关注恶意网站导航或包含属于受信任方的内容的危险。话虽如此,从其他来源加载某些类型的子资源的能力具有重大影响,即使没有第三方网站积极篡改。</p>
<p>在本书的第一部分中,我们暗示从另一个来源加载脚本或样式表实际上等同于将执行加载的文档的安全性等同于加载的子资源来源的安全性;特别是,在 HTTPS 页面上加载 HTTP 脚本会取消加密的大部分好处。同样,从基础设施容易受到攻击的提供商那里加载脚本可能几乎与没有正确维护自己的服务器一样有问题。</p>
<p>除了脚本和样式表之外,其他可能导致严重问题的内容类型包括远程字体(CSS 中的最新添加)和可以访问嵌入页面的插件(例如,<em>allowScriptAccess=always</em>用于 Flash)。从不受信任的来源加载图像、图标、光标或 HTML 框架也是相当危险的,尽管这样做的影响在一定程度上得到了限制,并且将具体使用。</p>
<p>当代浏览器试图检测 HTTPS 文档加载 HTTP 资源的情况——这种情况被称为 <em>混合内容</em>。然而,它们这样做并不一致:只有 Internet Explorer 默认情况下阻止大多数类型的混合内容(预计 Chrome 也将效仿),但 Internet Explorer、Firefox 和 Opera 并不始终在 <em><embed></em>, <em><object></em>, 或 <em><applet></em> 标签上检测混合内容。在其他浏览器中,默认操作是一个微妙的警告(例如,在锁形图标旁边的感叹号)或一个神秘的对话框,这对保护用户所起的作用非常有限,但可能会提醒一个足够警觉的 Web 开发者。</p>
<p>至于混合内容的另一种形式——在不同信任级别上跨域加载子资源——浏览器没有方法来检测这种情况。从可疑来源包含内容的决定通常过于草率,并且这种错误可能直到太晚才被发现。</p>
<h3 id="注意-48">注意</h3>
<p>跨域子资源的一个有趣问题是它们可能从浏览器请求某些额外的权限或凭证。相关的浏览器安全提示通常没有考虑到这样的场景,并且它们并不总是足够清楚地说明哪个源正在请求权限以及基于与顶级站点的何种关系。我们在第三章中讨论了这样一个问题:对 HTTP 代码 401 响应显示的认证提示。其他几个相关案例将在第十五章中出现。</p>
<h1 id="隐私相关副作用通道">隐私相关副作用通道</h1>
<p>同源策略中的漏洞的另一个不幸且值得注意的后果是能够收集有关用户与无关网站交互的信息。其中一些最基本示例,大多数已为人所知超过十年,^([210]) 包括以下内容:</p>
<ul>
<li>
<p>使用 <em>onload</em> 处理器来测量加载某些文档所需的时间,这是浏览器是否已之前访问并缓存它们的指示.^([211])</p>
</li>
<li>
<p>使用 <em><img></em> 标签上的 <em>onload</em> 和 <em>onerror</em> 来查看第三方站点上的认证要求图像是否可以加载,从而披露用户是否已登录该站点。(加分项:有时,提供给 <em>onerror</em> 处理器的错误消息还会包括目标页面的片段。)</p>
</li>
<li>
<p>在隐藏框架中加载一个无关的 Web 应用程序,并检查该页面上创建的子框架的数量和名称(通过 <em><handle>.frames[]</em> 数组获取)或全局变量集(有时通过 <em>delete</em> 操作符的语义泄露)以检测相同情况。自然地,用户访问或登录的网站集合可能相当敏感。</p>
</li>
</ul>
<p>除了这些技巧之外,一类特别令人恐惧的隐私问题与几年前创建的两个 API 有关,这两个 API 旨在帮助网站理解任何文档元素应用的样式(浏览器特定默认值、CSS 规则以及浏览器自动或通过 JavaScript 执行的任何运行时调整的总和)。这两个 API 分别是 CSS Level 2 规定的<em>getComputedStyle</em>^([212])和仅属于 Internet Explorer 的<em>currentStyle</em>^([213])。它们的函数性与为已访问链接分配独特样式的能力(使用<code>:visited</code>伪类)相结合,意味着任何恶意 JavaScript 都可以快速显示和检查数千个 URL,以查看哪些 URL 被不同地着色(由于存在于用户的浏览历史中),从而以前所未有的效率和可靠性构建一个可靠、广泛且可能具有指控性的用户在线习惯概览。</p>
<p>这个问题至少从 2002 年开始就已经为人所知,当时 Andrew Clover 在流行的 BUGTRAQ 邮件列表上发布了一篇关于它的简短笔记.^([214]) 在接下来的几年里,这个问题并没有受到太多的关注,直到 2006 年左右,一系列针对普通用户的演示和随后的公众舆论爆发。几年后,Firefox 和 WebKit 浏览器推出了安全改进,以限制<code>:visited</code>选择器中可能的样式范围,并限制检查结果组合 CSS 数据的能力。</p>
<p>话虽如此,这样的修复永远不会完美。尽管它们使得自动数据收集变得不可能,但在用户的帮助下,仍然可以获取到少量数据。以 Collin Jackson 和几位其他研究人员提出的一个简单方案为例:该方案涉及展示一个由七段、类似 LCD 的数字组成的假 CAPTCHA^([58])。这个假 CAPTCHA 并不是一个实际的工作挑战,用户看到的数字取决于应用于叠加链接的基于<code>:visited</code>的样式(参见图 11-5</p>
<p>图 11-5. 当尝试解决 CAPTCHA 时,将显示的数字输入浏览器中,可以使用一个假的七段显示器来读取回链样式。用户将看到 5、6、9 或 8,这取决于之前的浏览历史。</p>
<hr>
<p>^([58]) CAPTCHA(有时扩展为完全自动化的公众图灵测试,用于区分计算机和人类)是一个指代安全挑战的术语,这种挑战被认为使用计算机算法难以解决,但对于人类来说应该很容易。它通常通过显示几个随机选择的、严重扭曲的字符的图像,并要求用户将其重新输入来实现。CAPTCHA 可以用来阻止某些任务的自动化,例如开设新账户或发送大量电子邮件。(不用说,由于计算机图像处理技术的进步,健壮的 CAPTCHA 也越来越难以被人类解决。)</p>
<h1 id="其他-sop-漏洞及其用途">其他 SOP 漏洞及其用途</h1>
<p>虽然本章重点介绍了同一源策略限制对在线浏览安全或隐私产生明确负面影响的领域,但在该方案中存在几个意外漏洞,在大多数情况下似乎没有特殊影响。例如,在许多版本的 Internet Explorer 中,可以操纵无关窗口的 <em>window.opener</em> 或 <em>window.name</em> 的值。与此同时,在 Firefox 中,目前对设置跨域的 <em>location.hash</em> 没有任何限制,尽管所有其他部分位置属性都受到限制。</p>
<p>这些机制的主要意义在于,它们通常被重新用于在不支持 <em>postMessage(...)</em> API 的浏览器中建立跨域通信通道。这些机制通常建立在不稳定的基础上:SOP 执行力的缺乏通常是统一的,这意味着任何网站,而不仅仅是“授权”方,都将能够干扰数据。正如在框架劫持风险中讨论的那样,恶意方导航嵌套框架的能力进一步复杂了这一情况。</p>
<p>安全工程速查表</p>
<p>所有网站的优秀安全卫生</p>
<ul>
<li>
<p>使用 <em>X-Frame-Options: sameorigin</em> 为您网站上的所有内容提供服务。仅对特定、明确理解的、需要跨域嵌入的位置进行逐案例外。尽量不依赖 JavaScript“framebusting”代码来防止框架,因为这非常难以正确实现。</p>
</li>
<li>
<p>返回用户特定的敏感数据,这些数据不应跨域加载,应使用严格约束的格式,这些格式不太可能被误解释为独立的脚本、样式表等。始终使用正确的 <em>内容类型</em>。</p>
</li>
</ul>
<p>在包含跨域资源时</p>
<ul>
<li>在许多场景中(尤其是在处理脚本、样式表、字体和某些类型的插件处理内容时),你正在将你站点的安全性链接到子资源的源域。如果有疑问,请本地复制数据。在 HTTPS 站点上,要求所有子资源都通过 HTTPS 提供服务。</li>
</ul>
<p>当在 JavaScript 中安排跨域通信时</p>
<ul>
<li>
<p>请参考第九章中的速查表。除非你准备好处理注入的内容,否则不要使用基于 <em>location.hash</em>、<em>window.name</em>、<em>frameElements</em> 和类似临时黑客技巧的跨框架通信方案。</p>
</li>
<li>
<p>不要期望你的页面上的子框架会静止不动,尤其是如果你没有使用 <em>X-Frame-Options</em> 来限制其他网站框架你的应用程序的能力。在某些情况下,攻击者可能能够在不知情或未经你同意的情况下将此类框架导航到不同的位置。</p>
</li>
</ul>
<h1 id="第十二章其他安全边界">第十二章。其他安全边界</h1>
<p>所有之前描述的基于源的内容隔离策略,以及伴随的上下文继承和文档导航逻辑,共同构成了浏览器安全模型的大部分。这个模型既难以渗透又脆弱,但它也是不完整的:一些有趣的边缘情况完全逃出了任何基于源的框架。</p>
<p>这些边缘情况相关的安全风险不能仅仅通过调整本书前面讨论的机制来解决。相反,需要从头开始创建额外的、有时几乎是无法弥补的安全边界。例如,这些新边界可能会进一步限制恶意网页导航到某些 URL 的能力。</p>
<p>本章简要介绍了基于源模型的一些最显著的漏洞示例以及供应商如何处理这些问题。</p>
<h1 id="导航到敏感方案">导航到敏感方案</h1>
<p>在过去,浏览器供应商认为,允许任何互联网页面使用 <em>file:</em> 协议导航到用户硬盘上存储的文档或打开指向特权资源的新的窗口(例如 Firefox 中的 <em>about:config</em> 页面)并没有什么危害。毕竟,他们认为,源文档和目标不会是同源的,因此任何对敏感数据的直接访问都将被阻止。</p>
<p>多年来,基于这一理由,浏览器允许这种导航发生。遗憾的是,这一决定不仅极其令人困惑^([59]),而且危险。危险在于许多程序,包括浏览器,倾向于在文件系统中存储各种类型的互联网来源内容;临时文件和缓存文档是常见例子。在许多情况下,攻击者可能对这类文件的创建和内容有所控制,如果资源在可预测的位置创建,随后导航到正确的 <em>file:</em> URL 可能允许攻击者在备受青睐的源中执行自己的有效载荷,并访问磁盘上的任何其他文件,也许还能访问互联网上的任何其他网站。</p>
<p>与各种特权、内部处理的 URL 相比,已经观察到具有灾难性的后果。直接导航到诸如 <em>about:config</em>(Firefox)之类的位置的权限不仅使得利用特权脚本中的潜在漏洞(浏览器供应商对此类违规行为并不免疫)成为可能,而且如果通过直接应用同源策略,浏览器天真地认为 <em>about:config</em> 和 <em>about:blank</em> 来自同一源,这可能导致系统被破坏。</p>
<p>从历史上一系列痛苦的事故中吸取教训,现代浏览器通常根据三个级别的 URL 方案来监控导航:</p>
<ul>
<li>
<p><strong>不受限制</strong> 这一类别包括几乎所有真正的网络协议,如 HTTP、HTTPS、FTP;大多数封装的伪协议,如 <em>mhtml:</em> 或 <em>jar:</em>;以及注册到插件和外部应用程序的所有方案。对这些 URL 的导航不受任何特定方式的约束。</p>
</li>
<li>
<p><strong>部分受限</strong> 这一类别包括几个安全敏感的方案,如 <em>file:</em> 和特殊的伪 URL,如 <em>javascript:</em> 或 <em>vbscript:</em>。对这些方案的导航并未完全禁止,但需要额外的、针对特定方案的 安全检查。例如,对 <em>file:</em> 的访问通常仅限于其他 <em>file:</em> 文档,要求第一个文档手动打开。(关于导航到 <em>javascript:</em> URL 的规则已在 第十章 中讨论。)</p>
</li>
<li>
<p><strong>完全受限</strong> 这一类别包括 <em>about:</em>、<em>res:</em>、<em>chrome:</em> 和类似浏览器特定命名空间中的特权页面。在任何情况下,都不允许正常的、非特权的 HTML 文档导航到这些页面。</p>
</li>
</ul>
<hr>
<p>^([59]) 例如,在 Windows 系统上,一个常见的恶作剧是使用无缝嵌入的 <em><iframe></em> 指向 <em>file:///c:/</em> 来显示受害者的硬盘内容,导致一些用户认为执行此操作的页面以某种方式获得了访问他们文件的权限。</p>
<h1 id="内部网络访问">内部网络访问</h1>
<p>访问敏感协议的问题仅仅是同源策略创造者可能没有预料到的更严重问题的序曲。问题是 DNS 衍生的源可能与实际的网络级别边界无关——或者与这些边界随时间变化的方式无关。恶意脚本可能被授予对受害者本地网络上内部网站的同源访问权限,即使防火墙阻止攻击者直接与这些目标交互。</p>
<p>这种攻击至少有三种不同的方式。</p>
<p><strong>源渗透</strong></p>
<p>当用户访问一个恶意网络——例如机场或咖啡馆中的开放无线网络——该网络上的攻击者可能诱骗受害者的浏览器打开一个如 <a href="http://us-payroll/" target="_blank"><code>us-payroll/</code></a> 的 URL。当这种情况发生时,攻击者可能为该网站提供自己的、伪造的内容。令人恐惧的是,如果用户随后带着同一个浏览器进入企业网络,之前注入的内容将具有对 <a href="http://us-payroll/" target="_blank"><code>us-payroll/</code></a> 的真实版本的同源访问权限,包括用户的环境凭据。</p>
<p>注入内容的持久性可以通过几种方式实现。最基本的方法是攻击者简单地在一个隐藏的 <a href="http://us-payroll/" target="_blank"><code>us-payroll/</code></a> 框架中注入到每个访问的页面,希望用户在浏览器仍然运行的情况下暂停携带的计算机,并将其带到另一个网络。另一种技术是 <em>缓存中毒</em>:创建长期存在的、缓存的对象,浏览器将使用这些对象而不是从目标网站检索新鲜副本。还存在几种其他更不为人知的方案。</p>
<p><strong>DNS 重绑定</strong></p>
<p>这个可能不那么严重但更容易利用的问题在 第九章 的脚注 1 中被提及。简而言之,由于同源策略只关注主机的 DNS 名称,而不是 IP 地址,因此拥有 <a href="http://bunnyoutlet.com" target="_blank">bunnyoutlet.com</a> 的攻击者可以自由地最初对具有公共 IP 地址(如 <em>213.134.128.25</em>)的用户进行 DNS 查询,然后切换到为私有网络保留的地址,如 <em>10.0.0.1</em>。从这两个来源加载的文档将被视为同源,这给了攻击者与受害者内部网络交互的能力。</p>
<p>缓解因素是这种交互不会涉及受害者通常为针对的网站拥有的适当环境凭据:就浏览器而言,它仍在与 <a href="http://bunnyoutlet.com" target="_blank">bunnyoutlet.com</a> 而不是上述 <em>us-payroll</em> 网站进行通信。尽管如此,攻击者检查内部网络并可能尝试暴力破解适当的凭据或识别漏洞的前景仍然令人不安。</p>
<p><strong>简单利用 XSS 或 XSRF 漏洞</strong></p>
<p>即使在相同源策略之外,导航到内网 URL 的可能性意味着攻击者可能会尝试(盲目地)针对本地运行的软件中已知或疑似存在的漏洞。由于内部应用程序被认为受到恶意用户的保护,它们通常不会被设计或维护到与面向外部的代码相同的标准。</p>
<p>这个问题的一个引人注目的例子是,在 Linksys(Cisco)、Netgear、D-Link、Motorola 和 Siemens 等公司制造的家用网络路由器的内部仅限 Web 管理界面中发现的数十个漏洞。这些应用程序中的跨站请求伪造漏洞在极端情况下可能允许攻击者访问设备并拦截或修改所有进入或通过该设备的网络流量。</p>
<p>到目前为止,浏览器安全机制和网络分段之间的脱节仍然是浏览器工程中的一个未解决的问题。几个浏览器试图通过缓存 DNS 响应一段时间来限制 DNS 重绑定的影响——这种做法被称为<em>DNS 固定</em>——但防御并不完美,剩余的攻击向量仍然存在。</p>
<h3 id="注意-49">注意</h3>
<p>不寻常的是,Internet Explorer 在这方面领先,提供了一个可选的方式来减轻风险。如果 Microsoft 的用户在本地内网配置选项中将一个神秘的区域设置“在受限制的 Web 内容区域中的网站可以导航到该区域”切换到“禁用”,他们将在一定程度上受到保护。不幸的是,Internet Explorer 中的区域模型带来了一些意外的陷阱,我们将在第十五章中讨论。</p>
<h1 id="禁止端口">禁止端口</h1>
<p>安全研究人员警告说,浏览器提交大量不受约束的跨源请求体的能力,例如使用<code><form method="POST" enctype="text/plain"></code>,可能会干扰某些其他容错但非 HTTP 的网络服务。例如,考虑 SMTP,占主导地位的邮件传输协议:当与一个毫无戒心的浏览器交互时,大多数使用 SMTP 的服务器会耐心地忽略与 HTTP 头关联的几行难以理解的内容,然后尊重请求体中出现的任何 SMTP 命令。实际上,浏览器可以被用作中继垃圾邮件的代理。</p>
<p>在第三章中讨论的一个相关但不太被深入探讨的担忧是,攻击者与运行在目标 Web 应用同一域中的非 HTTP 服务进行交流,并诱使浏览器错误地解释返回的、可能部分受攻击者控制的数据,将其视为通过 HTTP/0.9 发送的 HTML。这种行为可能会暴露与目标站点关联的 cookie 或其他凭证。</p>
<p>HTTP 的设计使得无法以特别稳健的方式解决这些问题。相反,浏览器厂商以一种相当令人怀疑的方式做出了回应:通过提供一份禁止发送请求的 TCP 端口号列表。对于 Internet Explorer 6 和 7 版本,该列表包括以下端口号:</p>
<table>
<thead>
<tr>
<th>19</th>
<th>chargen</th>
</tr>
</thead>
<tbody>
<tr>
<td>21</td>
<td>ftp</td>
</tr>
<tr>
<td>25</td>
<td>smtp</td>
</tr>
<tr>
<td>110</td>
<td>pop3</td>
</tr>
<tr>
<td>119</td>
<td>nntp</td>
</tr>
<tr>
<td>143</td>
<td>imap2</td>
</tr>
</tbody>
</table>
<p>Internet Explorer 的 8 和 9 版本进一步禁止了 220(imap3)和 993(ssl imap3)端口。</p>
<p>本书讨论的所有其他浏览器使用不同的、共同的列表:</p>
<table>
<thead>
<tr>
<th>1</th>
<th>tcpmux</th>
<th>115</th>
<th>sftp</th>
</tr>
</thead>
<tbody>
<tr>
<td>7</td>
<td>echo</td>
<td>117</td>
<td>uccp-path</td>
</tr>
<tr>
<td>9</td>
<td>discard</td>
<td>119</td>
<td>nntp</td>
</tr>
<tr>
<td>11</td>
<td>systat</td>
<td>123</td>
<td>ntp</td>
</tr>
<tr>
<td>13</td>
<td>daytime</td>
<td>135</td>
<td>loc-srv</td>
</tr>
<tr>
<td>15</td>
<td>netstat</td>
<td>139</td>
<td>netbios</td>
</tr>
<tr>
<td>17</td>
<td>qotd</td>
<td>143</td>
<td>imap2</td>
</tr>
<tr>
<td>19</td>
<td>chargen</td>
<td>179</td>
<td>bgp</td>
</tr>
<tr>
<td>20</td>
<td>ftp-data</td>
<td>389</td>
<td>ldap</td>
</tr>
<tr>
<td>21</td>
<td>ftp</td>
<td>465</td>
<td>ssl smtp</td>
</tr>
<tr>
<td>22</td>
<td>ssh</td>
<td>512</td>
<td>exec</td>
</tr>
<tr>
<td>23</td>
<td>telnet</td>
<td>513</td>
<td>login</td>
</tr>
<tr>
<td>25</td>
<td>smtp</td>
<td>514</td>
<td>shell</td>
</tr>
<tr>
<td>37</td>
<td>time</td>
<td>515</td>
<td>printer</td>
</tr>
<tr>
<td>42</td>
<td>name</td>
<td>526</td>
<td>tempo</td>
</tr>
<tr>
<td>43</td>
<td>nicname</td>
<td>530</td>
<td>courier</td>
</tr>
<tr>
<td>53</td>
<td>domain</td>
<td>531</td>
<td>chat</td>
</tr>
<tr>
<td>77</td>
<td>priv-rjs</td>
<td>532</td>
<td>netnews</td>
</tr>
<tr>
<td>79</td>
<td>finger</td>
<td>540</td>
<td>uucp</td>
</tr>
<tr>
<td>87</td>
<td>ttylink</td>
<td>556</td>
<td>remotefs</td>
</tr>
<tr>
<td>95</td>
<td>supdup</td>
<td>563</td>
<td>ssl nntp</td>
</tr>
<tr>
<td>101</td>
<td>hostriame</td>
<td>587</td>
<td>smtp submission</td>
</tr>
<tr>
<td>102</td>
<td>iso-tsap</td>
<td>601</td>
<td>syslog</td>
</tr>
<tr>
<td>103</td>
<td>gppitnp</td>
<td>636</td>
<td>ssl ldap</td>
</tr>
<tr>
<td>104</td>
<td>acr-nema</td>
<td>993</td>
<td>ssl imap</td>
</tr>
<tr>
<td>109</td>
<td>pop2</td>
<td>995</td>
<td>ssl pop3</td>
</tr>
<tr>
<td>110</td>
<td>pop3</td>
<td>2049</td>
<td>nfs</td>
</tr>
<tr>
<td>111</td>
<td>sunrpc</td>
<td>4045</td>
<td>lockd</td>
</tr>
<tr>
<td>113</td>
<td>auth</td>
<td>6000</td>
<td>X11</td>
</tr>
</tbody>
</table>
<p>当然,这些规则存在各种特定协议的例外。例如,<em>ftp:</em> URL 显然允许访问与该协议通常关联的 21 号端口。</p>
<p>当前的解决方案存在几个缺陷,其中最重要的可能是两个列表都有许多明显的遗漏,并且考虑到迄今为止设计的网络协议数量,它们根本不可能完全详尽。例如,没有任何规则可以阻止浏览器与使用容错、基于文本协议的 Internet 中继聊天(IRC)服务器通信,这种协议与 SMTP 并不完全不同。</p>
<p>这些列表也没有定期更新以反映几乎灭绝的网络协议的消亡或新协议的引入。最后,它们可能会不公平和意外地惩罚系统管理员,因为他们选择了某些他们希望隐藏在公众视野之外的服务的不标准端口:这样做意味着选择退出这种浏览器级别的保护机制。</p>
<h1 id="第三方-cookie-的限制">第三方 Cookie 的限制</h1>
<p>自从它们的诞生以来,HTTP cookies 一直被误解为允许在线广告商以前所未有和以前无法达到的程度侵犯用户隐私的工具。这种观点在随后的几年里得到了主流媒体的呼应。例如,2001 年,《纽约时报》发表了一篇关于 HTTP cookies 据称独特风险的详细调查报告,并引用了著名法律专家和政治活动家劳伦斯·莱斯格的话:^([216])</p>
<blockquote>
<p>在 cookie 出现之前,网络本质上是一个私密的领域。而在 cookie 出现之后,网络变成了一个可以进行非凡监控的空间。</p>
</blockquote>
<p>对单个 HTTP 头部的公开攻击持续了十年之久,逐渐将重点转向第三方 cookie。第三方 cookie 是由顶级文档域之外的域设置的 cookie,它们通常与从第三方网站加载图像、框架或小程序的过程相关联。它们之所以引起关注,是因为广告网络的运营商将此类 cookie 视为标记在<a href="http://fuzzybunnies.com" target="_blank">fuzzybunnies.com</a>上看到其广告的用户的一种便捷方式,然后通过在<a href="http://playboy.com" target="_blank">playboy.com</a>上提供的类似嵌入式广告来识别该用户。</p>
<p>由于执行此类跨域跟踪的明显不希望发生的可能性被错误地与第三方 cookie 的存在混为一谈,浏览器供应商所承受的压力持续增加。在其中一个例子中,《华尔街日报》直言不讳地指责微软公司因为在其产品中未消除第三方 cookie 而与广告商有染。^[[217]</p>
<p>自然地,这本书的读者会认识到,对 HTTP cookie 的过度关注是极其错误的。毫无疑问,某些方面使用该机制是为了模糊的恶意目的,但没有任何东西使其特别适合这项任务;还有许多其他等效的方法可以在访客的计算机上存储唯一的标识符(例如,在第三章中讨论过的基于缓存的标签)。此外,简单地阻止合作网站使用每个浏览器现有的唯一指纹(通过 JavaScript 对象模型或如 Flash 之类的插件暴露)随意关联和挖掘跨域浏览模式是不可能的。那些为了盈利而嵌入广告的网站非常愿意与支付他们账单的各方合作。</p>
<p>事实上,对 HTTP cookie 的普遍依赖为用户提供了一种独特的优势:与许多容易接受的替代方案不同,这种机制是专门设计的,并且与合理设计且粒度细的隐私控制相结合。破坏 cookie 不会妨碍跟踪,但会从最终用户那里消除任何透明度的假象。另一位知名的隐私和安全活动家 Ed Felten 曾说过:“如果你要跟踪我,请使用 cookie。”^[[218]</p>
<p>无良的在线跟踪是一个重大的社会问题,可能需要新的技术机制,以便用户可以向行为良好的网站(如 Firefox 4 最近添加的<em>DNT</em>请求头部^([219]))传达他们的隐私偏好。为了处理行为不良的网站,可能还需要一个监管框架。在没有这样的框架的情况下,在 Internet Explorer 9 中,微软正在尝试使用已知跟踪 cookie 不良来源的管理黑名单进行实验——但这种情况可能不足以阻止卑鄙的商业行为。</p>
<p>在任何情况下,尽管这些第三方 cookie 的争议微乎其微或根本不存在,但持续的公众反对最终导致了几家浏览器厂商推出了半成品且容易被规避的解决方案,使他们能够声称已经“做了些什么”。</p>
<ul>
<li>
<p>在 Internet Explorer 中,默认阻止设置和读取第三方 cookie,除非是与令人满意的 P3P 头部一起的会话 cookie。<em>P3P</em> (<em>平台隐私偏好</em>)^([220]) 是一种构建机器可读、具有法律约束力的网站隐私政策的摘要的方法,无论是作为 XML 文件还是作为 HTTP 头部中的<em>紧凑策略</em>。例如,HTTP 头部中的关键词 TEL 表示该网站使用收集到的信息进行电话营销。(没有任何技术措施可以阻止网站在 P3P 头部中撒谎,但潜在的合法后果旨在阻止这种行为。)</p>
<h3 id="注意-50">注意</h3>
<p>非凡雄心勃勃的、111 页的 P3P 规范导致解决方案在其自身重量下崩溃。大型企业通常非常犹豫是否将 P3P 作为解决技术问题的方案,因为规范的法律影响,而小型企业和个人网站所有者则很少或没有理解就复制了 P3P 头部配方。</p>
</li>
<li>
<p>在 Safari 中,默认阻止设置第三方 cookie 的任务,但可以自由读取之前发行的 cookie。然而,如果用户首先与设置 cookie 的文档交互,则可以覆盖这种行为。这种交互可能是故意的,但也可能不是:第十一章(第十一章。同源规则之外的生活)中概述的与 clickjacking 相关的技巧也适用于这种情况。</p>
</li>
<li>
<p>在其他浏览器中,第三方 cookie 默认允许,但提供了一个配置选项来更改行为。启用此选项会限制设置第三方 cookie 的能力,但读取现有的 cookie 没有任何限制。</p>
</li>
</ul>
<p>为了进行这些检查,如果一个 Cookies 是从一个完全无关的域加载的,那么它被认为是来自第三方。例如,在<a href="http://fuzzybunnies.com" target="_blank">fuzzybunnies.com</a>上加载的指向<a href="http://bunnyoutlet.com" target="_blank">bunnyoutlet.com</a>的框架符合这一标准,但<a href="http://www1.fuzzybunnies.com" target="_blank">www1.fuzzybunnies.com</a>和<a href="http://www2.fuzzybunnies.com" target="_blank">www2.fuzzybunnies.com</a>被认为是第一方关系。用于做出这种决定的逻辑是脆弱的,并且它遭受了与 Cookies <em>域</em>作用域相同的问题。例如,在 Internet Explorer 6 和 7 中,某些国家级域的比较执行不正确。</p>
<h3 id="注意-51">注意</h3>
<p>对第三方 Cookies 的十字军东征可能被视为无害的练习,但它也有负面影响。拒绝第三方 Cookies 的浏览器使得构建基于 Cookies 的认证对于可嵌入的小工具和其他类型的混合应用变得非常困难,并且它们使得使用“沙箱”域来隔离不受信任但私有的内容以限制脚本注入漏洞的影响变得困难。</p>
<p>安全工程速查表</p>
<p>在内部网络上构建 Web 应用程序时</p>
<ul>
<li>假设决心攻击者将能够通过受害者的浏览器与那些应用程序交互,无论任何网络级别的安全控制。确保满足适当的工程标准,并要求所有敏感应用程序使用 HTTPS 和<em>安全</em>的 Cookies,以最大限度地降低来源渗透攻击的风险。</li>
</ul>
<p>在启动非 HTTP 服务,尤其是在非标准端口上时</p>
<ul>
<li>评估浏览器无意中向服务发出 HTTP 请求的影响,以及将响应解释为 HTTP/0.9 的影响。对于易受攻击的协议,如果接收到的数据以“GET”或“POST”开头,立即断开连接可能是一个可能的预防措施。</li>
</ul>
<p>在使用第三方 Cookies 用于小工具或沙箱内容时</p>
<ul>
<li>如果需要支持 Internet Explorer,请准备使用 P3P 策略(并评估其法律意义)。如果需要支持 Safari,可能不得不求助于替代的凭证存储机制(例如 HTML5 <em>localStorage</em>)。</li>
</ul>
<h1 id="第十三章-内容识别机制">第十三章. 内容识别机制</h1>
<p>到目前为止,我们已经查看了一些有良好意图的浏览器功能,随着技术的成熟,这些功能证明是短视的,甚至是危险的。但现在,准备好迎接一些特别的东西:在 Web 的历史上,没有什么比<em>内容嗅探</em>更被误导的了。</p>
<p>内容嗅探背后的原始前提很简单:浏览器供应商假设在某些情况下,忽略从服务器接收的正常权威元数据,例如<em>Content-Type</em>头,可能是适当的——甚至是希望的。而不是尊重开发者的声明意图,支持内容嗅探的实现可能会尝试通过应用专有启发式方法来猜测适当的行动方案,以弥补可能的错误。(回想一下第一章,在第一次浏览器大战期间,供应商将容错兼容性变成了一个不切实际的竞争优势。)</p>
<p>内容嗅探功能很快成为浏览器整体安全景观中的一个重大且有害的方面。令他们惊恐和难以置信的是,Web 开发人员很快发现,他们无法安全地为用户托管某些表面上无害的文档类型,如<em>text/plain</em>或<em>text/csv</em>;任何尝试这样做都会不可避免地产生风险,即这种内容可能会被误解释为 HTML。</p>
<p>或许部分是为了回应这些担忧,1999 年,在 HTTP/1.1 中明确禁止了未经请求的内容嗅探实践:</p>
<blockquote>
<p>只有当媒体类型不是由<em>Content-Type</em>字段提供时,接收者才可以通过检查其内容以及/或用于标识资源的 URI 的名称扩展来尝试猜测媒体类型。</p>
</blockquote>
<p>可惜,这个不同寻常的明确要求来得有点太晚了。大多数浏览器已经在某种程度上违反了这项规则,而且由于没有方便的方法来衡量潜在后果,它们的作者犹豫不决,不愿简单地丢弃有问题的代码。尽管在过去十年中,一些最严重的错误被谨慎地回滚,但微软和苹果两家公司主要抵制了这一努力。他们决定,与损坏的 Web 应用程序的互操作性应该优先于明显的安全问题。为了安抚任何批评者,他们实施了几种不完美、次要的安全机制,旨在减轻风险。</p>
<p>今天,内容处理政策和随后部署的限制在在线世界中投下了长长的阴影,使得在没有求助于人为和有时昂贵的技巧的情况下几乎不可能构建某些类型的 Web 服务。为了理解这些限制,让我们首先概述几个场景,在这些场景中,一个表面上被动的文档可能会被误识别为 HTML 或类似的东西。</p>
<h1 id="文档类型检测逻辑">文档类型检测逻辑</h1>
<p>最简单且最具争议性的文档检测启发式方法是处理 <em>Content-Type</em> 标头缺失的逻辑,这是所有现代浏览器所实现的。这种情况非常罕见,可能是由于开发者不小心省略或误输了标头名称,或者文档是通过非 HTTP 传输机制(如 <em>ftp:</em> 或 <em>file:</em>)加载的。</p>
<p>对于 HTTP 而言,原始的 RFC 明确允许浏览器在 <em>Content-Type</em> 值不可用时检查有效负载以获取线索。对于其他协议,通常遵循相同的方法,这通常是底层代码设计的一个自然结果。</p>
<p>用于确定文档类型的启发式方法通常涉及检查与几十种已知文件格式(如图像和常见的插件处理的文件)相关的静态签名。响应也会被扫描以检测无签名的格式,如 HTML(在这种情况下,浏览器将寻找熟悉的标签——<em><body></em>, <em><font></em>, 等)。在许多浏览器中,还会考虑非内容信号,例如 URL 路径段中尾随的 <em>.html</em> 或 <em>.swf</em> 字符串。</p>
<p>内容嗅探逻辑的具体细节在不同浏览器之间差异很大,且没有很好地记录或标准化。为了说明这一点,考虑一下没有 <em>Content-Type</em> 的 Adobe Flash (SWF) 文件的处理:在 Opera 中,它们基于内容签名检查无条件地被识别;在 Firefox 和 Safari 中,URL 中需要显式的 <em>.swf</em> 后缀;而 Internet Explorer 和 Chrome 完全不会自动识别 SWF。</p>
<p>请放心,SWF 文件格式并不是一个特例。例如,当处理 HTML 文件时,Chrome 和 Firefox 只有在文件开头出现几个预定义的 HTML 标签之一时,才会自动检测文档;而 Firefox 会根据 URL 中存在 <em>.html</em> 扩展名来“检测” HTML,即使没有看到可识别的标记。另一方面,Internet Explorer 在没有 <em>Content-Type</em> 的情况下,将始终默认为 HTML,而 Opera 将在返回的有效负载的前 1000 个字节内扫描已知的 HTML 标签。</p>
<p>所有这些混乱背后的假设是,<em>Content-Type</em> 的缺失是页面发布者有意表达的一种愿望——但这种假设并不总是准确的,并导致了相当数量的安全漏洞。话虽如此,大多数 Web 服务器都会积极强制执行 <em>Content-Type</em> 标头的存在,并在服务器端脚本未明确生成时插入一个默认值。所以也许没有必要担心?不幸的是,内容嗅探的故事并没有在这里结束。</p>
<h2 id="格式错误的-mime-类型">格式错误的 MIME 类型</h2>
<p>HTTP RFC 允许在没有 <em>Content-Type</em> 数据的情况下进行内容嗅探;如果头部以任何形式存在,浏览器被明确禁止猜测网站管理员的意图。然而,在实践中,这一建议并未被认真对待。从悬崖上迈出的下一步是决定,如果服务器返回的 MIME 类型被认为以任何方式无效,则采用启发式方法。</p>
<p>根据 RFC,<em>Content-Type</em> 头部应由两个由斜杠分隔的字母数字标记 (<em>type/subtype</em>) 组成,可能后跟其他由分号分隔的参数。这些标记可以包含任何非空白、七位 ASCII 字符,除了少数几个特殊的“分隔符”(一个包括诸如“@”、“?”和斜杠本身的通用集合)。大多数浏览器试图强制执行这种语法,但执行并不一致;几乎普遍认为,缺少斜杠是一种邀请进行内容嗅探的信号,同样,在标识符的第一部分(<em>type</em> 标记)中包含空白和某些(但不是所有)控制字符也是如此。另一方面,技术上非法使用高位字符或分隔符只会影响 Opera 中该字段的合法性。</p>
<p>这种设计的理由难以理解,但公平地说,其安全影响仍然相当有限。对于网络应用开发者来说,必须小心不要在 <em>Content-Type</em> 值中犯拼写错误,并且不允许用户指定任意、受用户控制的 MIME 类型(仅通过已知不良选项的黑名单进行验证)。这些要求可能令人意外,但通常它们并不很重要。那么,我们最终想要达到的目的是什么呢?</p>
<h2 id="特殊的-content-type-值">特殊的 Content-Type 值</h2>
<p>内容嗅探变得真正危险的第一明确信号是对一个看似不起眼的 MIME 类型 <em>application/octet-stream</em> 的处理。这个特定的值在 HTTP 规范中根本未提及,但在 RFC 2046 的深处被赋予了特殊(尽管模糊)的角色:^([221])</p>
<blockquote>
<p>对于接收 <em>application/octet-stream</em> 实体的实现,建议简单地提供将数据放入文件的服务,取消任何 <em>Content-Transfer-Encoding</em>,或者可能将其用作用户指定的过程的输入。</p>
</blockquote>
<p>这个 MIME 类型的原始意图仅从引用的段落中可能并不十分清晰,但它通常被解释为一种让 Web 服务器表明返回的文件对服务器没有特殊意义,并且对客户端也不应有特殊意义的方式。因此,大多数 Web 服务器在没有找到更好的 <em>Content-Type</em> 匹配的情况下,默认将所有类型的非 Web 文件(如可下载的可执行文件或存档)设置为 <em>application/octet-stream</em>。然而,在管理员错误(例如,由于删除 Apache 配置文件中的基本 <em>AddType</em> 指令)的罕见情况下,Web 服务器也可能回退到这种 MIME 类型来处理旨在浏览器内消费的文档。当然,这种配置错误很容易检测和修复,但微软、Opera 和苹果公司仍然选择对此进行补偿。当看到 <em>application/octet-stream</em> 时,这些供应商的浏览器会积极地进行内容嗅探。^([60)]</p>
<p>这个特定的设计决策突然使得 Web 应用程序代表用户托管二进制文件变得更加困难。例如,任何代码托管平台在返回可执行文件或源存档为 <em>application/octet-stream</em> 时都必须谨慎,因为这些文件可能会被错误地解释为 HTML 并内联显示。这对任何软件托管或网络邮件系统以及许多其他类型的 Web 应用程序都是一个重大问题。(它们使用任何其他听起来通用的 MIME 类型(如 <em>application/binary</em>)稍微安全一些,因为在浏览器代码中没有为其设置特殊案例。)</p>
<p>除了对 <em>application/octet-stream</em> 的特殊处理外,还存在一个针对 <em>text/plain</em> 的第二个、破坏性更大的例外。这个决定是独一无二的,仅限于 Internet Explorer 和 Safari。它追溯到 RFC 2046。在该文件中,<em>text/plain</em> 被赋予了双重功能:首先,用于传输纯文本文档(即那些“不提供或允许格式化命令、字体属性规范、处理指令、解释指令或内容标记”的文档);其次,为任何发送者未识别的基于文本的文档提供回退值。</p>
<p><em>application/octet-stream</em> 和 <em>text/plain</em> 回退之间的区别对于电子邮件消息来说是有意义的,这是该 RFC 最初处理的主题,但对于 Web 来说则显得不那么相关。尽管如此,一些 Web 服务器还是采用了 <em>text/plain</em> 作为某些类型响应的回退值(最显著的是 CGI 脚本的输出)。</p>
<p>随后在 Internet Explorer 和 Safari 中实施的<em>text/plain</em>逻辑,以检测此类情况中的 HTML,实际上是个坏消息:它剥夺了 Web 开发者使用此 MIME 类型生成特定用户纯文本文档的能力,并且没有提供替代方案。这导致了大量 Web 应用程序漏洞,但时至今日,Internet Explorer 的开发者似乎没有遗憾,并且没有改变他们代码的默认行为。</p>
<p>另一方面,Safari 的开发者认识到了风险,并试图在保持功能的同时减轻风险——但他们没有充分理解 Web 的复杂性。他们在浏览器中实施的解决方案是依赖除文档主体中看似合理的 HTML 标记之外的次要信号。URL 路径末尾存在扩展名,如<em>.html</em>或<em>.xml</em>,被他们的实现解释为内容嗅探可以安全进行的信号。毕竟,网站的拥有者不会以这种方式命名文件,对吧?</p>
<p>可惜,他们所采用的信号几乎毫无价值。事实证明,几乎所有的 Web 框架都至少支持几种方法来在 URL 路径段中编码参数,而不是在更传统上使用的查询部分。例如,在 Apache 中,这种机制被称为 PATH_INFO,并且默认情况下是启用的。通过利用这种参数传递方案,攻击者通常可以向路径中添加非功能性的垃圾数据,从而混淆浏览器,但不会影响服务器对提交请求本身的响应。</p>
<p>为了说明,以下两个 URL 对于运行在 Apache 或 IIS 上的网站可能产生相同的效果:</p>
<pre><code>http://www.fuzzybunnies.com/get_file.php?id=1234
</code></pre>
<p>和</p>
<pre><code>http://www.fuzzybunnies.com/get_file.php`/evil.html`?id=1234
</code></pre>
<p>在一些不太常见的 Web 框架中,以下方法也可能有效:</p>
<pre><code>http://www.fuzzybunnies.com/get_file.php`;evil.html`?id=1234
</code></pre>
<h2 id="识别不到的内容类型">识别不到的内容类型</h2>
<p>尽管对<em>text/plain</em>存在明显的麻烦,但正在开发 Internet Explorer 的工程师们决定将他们的浏览器启发式方法进一步扩展。Internet Explorer 不仅对少量通用 MIME 类型,而且对任何浏览器无法立即识别的文档类型都应用内容嗅探和扩展匹配^([61])。这个广泛的类别可能包括从 JSON (<em>application/json</em>) 到多媒体格式,如 Ogg Vorbis (<em>audio/ogg</em>)。</p>
<p>这样的设计自然是存在问题的,当在浏览器内部注册的少量通用 MIME 类型之外,或者当路由到少量常见安装的外部应用程序时,托管任何用户控制的文档格式都会引起严重问题。</p>
<p>而 Internet Explorer 的内容嗅探习惯并没有就此结束:当处理浏览器内部识别的文档格式时,如果由于任何原因无法干净地解析,浏览器也会求助于负载检查。在 8.0 之前的 Internet Explorer 版本中,提供用户提供的未经验证的文件,声称是 JPEG 图像,可能会导致响应被当作 HTML 处理。更有趣的是:即使是微小的错误,比如用<em>Content-Type: image/jpeg</em>提供有效的 GIF 文件,也会触发相同的代码路径。天哪,几年前,Internet Explorer 甚至能在任何有效、正确提供的 PNG 文件上检测到 HTML。幸运的是,这个逻辑已经被禁用——但剩下的怪癖仍然是一个雷区。</p>
<h3 id="注意-52">注意</h3>
<p>为了充分理解在有效图像上进行内容嗅探的风险,请注意,构建验证正确但携带攻击者选择的 ASCII 字符串(如 HTML 标记)的图像并不特别困难。实际上,相对容易构建这样的图像,当使用已知、确定性的算法进行清洗、重新缩放和重新压缩时,几乎任意的字符串会突然出现在生成的二进制流中。</p>
<p>值得称赞的是,在 Internet Explorer 8 及更高版本中,微软决定禁止在已知的 MIME 类型中大多数类型的免费内容嗅探,特别是在<em>image/**类别中。它还禁止在浏览器不识别的图像格式上检测 HTML(但不检测 XML),例如</em>image/jp2*(JPEG2000)。</p>
<p>除了这个单一调整之外,微软证明并不愿意对其内容嗅探逻辑进行有意义的更改,其工程师公开捍卫了维护与损坏网站兼容性的必要性.^([222]) 微软可能想要避免大型机构客户的愤怒,许多客户依赖古老且设计不佳的内部应用程序,并依赖于客户端基于 Internet Explorer 的单文化怪癖。</p>
<p>无论如何,由于 Internet Explorer 在处理<em>text/plain</em>逻辑上遭遇的反弹,新版本提供了一种部分解决方案:一个可选的 HTTP 头,<em>X-Content-Type-Options: nosniff</em>,允许网站所有者选择退出大多数有争议的内容启发式方法。使用此头文件强烈推荐;不幸的是,它尚未回滚到浏览器的 6.0 和 7.0 版本,在其他浏览器中支持也有限。换句话说,不能将其作为对抗内容嗅探的唯一防御手段。</p>
<h3 id="注意-53">注意</h3>
<p>思考食物:根据 SHODAN 和 Chris John Riley 在 2011 年进行的一项调查收集的数据,^([223]) 在互联网上最受欢迎的 10,000 个网站中,只有大约 0.6%在网站范围内使用了这个标题。</p>
<h2 id="内容处置的防御性用途">内容处置的防御性用途</h2>
<p>在本书的第一部分中多次提到的 <em>Content-Disposition</em> 头部,在某些用例中可能被视为防止内容嗅探的一种防御手段。HTTP/1.1 规范中对这个头部功能的解释并不令人满意。相反,它仅在 RFC 2183 中进行了文档记录,^([224]) 其中仅将其角色解释为与邮件应用相关:</p>
<blockquote>
<p>可以将 Bodyparts 标记为“附件”,以表明它们与邮件主体的主要部分是分开的,并且它们的显示不应是自动的,而应取决于用户的进一步操作。MUA^([62]) 可能会向位图终端用户展示附件的图标表示,或者在字符终端上,展示一个用户可以选择查看或存储的附件列表。</p>
</blockquote>
<p>HTTP RFC 承认在 Web 域中使用 <em>Content-Disposition: attachment</em>,但没有详细说明其预期功能。在实践中,在正常文档加载期间看到这个头部时,大多数浏览器会显示一个文件下载对话框,通常有三个按钮:“打开”、“保存”和“取消”。除非选择“打开”选项或将文档保存到磁盘并手动打开,否则浏览器不会尝试进一步解释文档。对于“保存”选项,头部中包含的可选 <em>filename</em> 参数用于建议下载的文件名。如果此字段不存在,则文件名将来自众所周知的不可靠的 URL 路径数据。</p>
<p>由于该头部阻止了大多数浏览器立即解释和显示返回的有效载荷,因此它特别适合安全托管不透明的、可下载的文件,例如上述存档或可执行文件的案例。此外,因为它在特定类型子资源加载(如 <em><img></em> 或 <em><script></em>)中被忽略,因此它还可以用于保护用户控制的 JSON 响应、图像等免受内容嗅探风险。 (为什么所有实现都忽略这些类型导航的 <em>Content-Disposition</em> 并不是特别清楚,但考虑到其好处,现在最好不质疑这种逻辑。)</p>
<p>一个合理稳健地使用 <em>Content-Disposition</em> 和其他 HTTP 头部来阻止 JSON 响应内容嗅探的例子可能</p>
<pre><code>Content-Type: application/json; charset=utf-8
X-Content-Type-Options: nosniff
Content-Disposition: attachment; filename="json_response.txt"
{ "search_term": "<html><script>alert('Hi mom!')</script>", ... }
</code></pre>
<p>在可能的情况下,强烈推荐使用 <em>Content-Disposition</em> 的防御性用途,但重要的是要认识到,该机制并非对所有用户代理都有强制要求,也没有得到很好的文档记录。在不太受欢迎的浏览器中,如 Safari Mobile,该头部可能没有任何效果;在主流浏览器中,如 Internet Explorer 6、Opera 和 Safari,一系列 <em>Content-Disposition</em> 缺陷在某个时刻或另一个时刻使头部在攻击者控制的案例中失效。</p>
<p>依赖 <em>Content-Disposition</em> 的另一个问题是,用户仍然可能倾向于点击“打开”。不能期望普通用户因为下载提示而警惕查看 Flash 小程序或 HTML 文档。在大多数浏览器中,选择“打开”会将文档置于 <em>file:</em> 原因,这本身可能就存在问题(Chrome 最近的改进确实有所帮助),而在 Opera 中,文档将在原始域的上下文中显示。可以说,Internet Explorer 做出了最佳选择:HTML 文档使用 <em>mark-of-the-web</em> 机制(在第十五章中更详细地概述)放置在特殊沙盒中,但即使在那个浏览器中,Java 或 Flash 小程序也不会从这一特性中受益。</p>
<h2 id="子资源的内容指令">子资源的内容指令</h2>
<p>大多数与内容相关的 HTTP 头,如 <em>Content-Type</em>、<em>Content-Disposition</em> 和 <em>X-Content-Type-Options</em>,对特定类型的子资源加载(如 <em><img></em>, <em><script></em>, 或 <em><embed></em>)几乎没有影响。在这些情况下,嵌入方几乎完全控制浏览器如何解释响应。</p>
<p>在处理从插件执行代码中发起的请求时,<em>Content-Type</em> 和 <em>Content-Disposition</em> 也可能不会引起太多关注。例如,回想一下第九章,任何 <em>text/plain</em> 或 <em>text/csv</em> 文档都可能被 Adobe Flash 解释为安全敏感的 <em>crossdomain.xml</em> 策略,除非目标服务器根目录中存在适当的全局元策略。无论你是否称之为“内容嗅探”还是仅仅“内容类型盲”,这个问题仍然非常真实。</p>
<p>因此,即使严格遵守之前讨论的所有 HTTP 头,始终考虑第三方页面可能欺骗浏览器将其解释为几种问题文档类型之一的可能性也很重要;小程序和小程序相关的内容、PDF、样式表和脚本通常是特别关注的对象。为了最大限度地减少意外事件的风险,您应该仔细限制任何提供的数据负载的结构和字符集,或者使用“沙盒”域来隔离那些无法很好地限制的文档类型。</p>
<h2 id="下载的文件和其他非-http-内容">下载的文件和其他非 HTTP 内容</h2>
<p>HTTP 头如 <em>Content-Type</em>、<em>Content-Disposition</em> 和 <em>X-Content-Type-Options</em> 的行为可能很复杂,并且充满例外,但至少它们加起来是一个相当一致的总体。然而,很容易忘记在许多实际情况下,这些头中包含的元数据根本不可用——在这种情况下,所有的赌注都取消了。例如,通过 <em>ftp:</em> 获取的文档的处理,或者保存到磁盘并通过 <em>file:</em> 协议打开的文档,高度依赖于浏览器和协议,并且经常让经验最丰富的安全专家都感到惊讶。</p>
<p>当打开本地文件时,浏览器通常会优先考虑文件扩展名数据,如果扩展名是浏览器已知的一些硬编码值之一,例如 <em>.txt</em> 或 <em>.html</em>,大多数浏览器都会直接接受这些信息。Chrome 是一个例外;它会尝试自动检测某些“被动”文档类型,例如 JPEG,即使在 <em>.txt</em> 文档内部也是如此。(然而,HTML 是严格禁止的。)</p>
<p>当涉及到注册到外部程序的其他扩展名时,行为会稍微难以预测。Internet Explorer 通常会调用外部应用程序,但大多数其他浏览器会求助于内容嗅探,表现得好像他们通过 HTTP 加载了没有 <em>Content-Type</em> 设置的文档。所有浏览器在扩展名未知(例如,<em>.foo</em>)的情况下也会回退到内容嗅探。</p>
<p>对文件扩展名数据和内容嗅探的过度依赖与对来自互联网的资源正常处理形成了有趣的对比。在网络上,<em>Content-Type</em> 通常是文档类型的权威描述符。大多数时候,文件扩展名信息会被忽略,在诸如 <a href="http://fuzzybunnies.com/gotcha.txt" target="_blank"><code>fuzzybunnies.com/gotcha.txt</code></a> 这样的位置托管一个功能性的 JPEG 文件是完全合法的。但是,当这个文档下载到磁盘上时会发生什么呢?嗯,在这种情况下,资源的有效含义会意外地改变:当通过 <em>file:</em> 协议访问它时,浏览器可能会坚持将其渲染为文本文件,严格基于扩展名数据。</p>
<p>上述示例相对无害,但其他内容推广向量,如一个图像变成可执行文件,可能会更加令人担忧。为此,Opera 和 Internet Explorer 将尝试修改扩展名以匹配一些已知的 <em>Content-Type</em> 值。然而,其他浏览器并不提供这种程度的保护,甚至可能会被他们所处的情境彻底搞混。图 13-1 捕获了 Firefox 在这样一个尴尬的时刻。</p>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950033.png.jpg" alt="Firefox 在保存带有 Content-Disposition: attachment 的 Content-Type: image/jpeg 文档时显示的提示。浏览器从攻击者附加在 URL 末尾的非功能 PATH_INFO 后缀中推导出“hello.exe”文件名。提示错误地声称.exe 文件是“JPEG 图像”。实际上,当保存到磁盘时,它将是一个可执行文件。" loading="lazy"></p>
<p>图 13-1. Firefox 在保存带有 Content-Disposition: attachment 的 Content-Type: image/jpeg 文档时显示的提示。浏览器从攻击者附加在 URL 末尾的非功能 PATH_INFO 后缀中提取“hello.exe”文件名。提示错误地声称 .exe 文件是“JPEG 图像”。实际上,当保存到磁盘时,它将是一个可执行文件。</p>
<p>这个问题强调了在使用 <em>Content-Disposition</em> 附件时返回一个明确的、无害的 <em>filename</em> 值的重要性,以防止受害者被诱骗下载网站所有者从未打算托管文档格式的文件。</p>
<p>由于用于 <em>file:</em> URL 的复杂逻辑,<em>ftp:</em> 处理的简单性可能会让人感到惊讶。当通过 FTP 访问文档时,大多数浏览器不会对文件扩展名给予特殊关注,而会简单地纵容内容嗅探的泛滥。一个例外是 Opera,在那里扩展数据仍然占据优先地位。从工程角度来看,FTP 的主流方法似乎是有逻辑的:该协议可以大致等同于 HTTP/0.9。然而,这种设计也违反了最小惊讶原则。服务器所有者不会期望通过允许用户将 <em>.txt</em> 文档上传到 FTP 站点,他们就会自动同意在其域名内托管活动 HTML 内容。</p>
<hr>
<p>^([60]) 在 Internet Explorer 中,这种实现的逻辑与没有 <em>Content-Type</em> 的情况略有不同。浏览器不会总是假设 HTML,而是会扫描前 256 字节以查找流行的 HTML 标签和其他预定义的内容签名。然而,从安全角度来看,这并不是一个很大的差异。</p>
<p>^([61]) 自然地,基于路径的扩展名匹配对于前述原因来说基本上是毫无价值的;但在 Internet Explorer 6 的情况下,问题变得更糟。在这个浏览器中,扩展名可以出现在 URL 的查询部分。攻击者可以简单地向请求的 URL 中附加 <em>?foo=bar.html</em>,从而有效地确保这个检查总是满足条件。</p>
<p>^([62]) MUA 代表“邮件用户代理”,即用于检索、显示和编写邮件消息的客户端应用程序。</p>
<h1 id="字符集处理">字符集处理</h1>
<p>文档类型检测是内容处理谜题中更为重要的部分之一,但绝对不是唯一的部分。对于所有在浏览器中渲染的基于文本的文件,还需要做出一个额外的判断:必须确定并应用到输入流中的适当字符集转换。浏览器所寻求的输出编码通常是 UTF-8 或 UTF-16;而输入,另一方面,取决于页面的作者。</p>
<p>在最简单的场景中,适当的编码方法将由服务器在<em>Content-Type</em>头部的<em>charset</em>参数中提供。在 HTML 文档的情况下,相同的信息也可能在一定程度上通过<em><meta></em>指令传达。(浏览器将尝试在实际上解析文档之前推测性地提取和解释此指令。)</p>
<p>不幸的是,某些字符编码的危险特性,以及当<em>charset</em>参数不存在或未被识别时浏览器采取的行动,再次使得生活比上述简单规则所暗示的有趣得多。为了了解可能出错的情况,我们首先需要识别三种可能改变 HTML 或 XML 文档语义的特殊字符集类别:</p>
<ul>
<li>
<p><strong>允许标准 7 位 ASCII 码的非规范表示的字符集</strong>。这样的非规范序列可以巧妙地编码 HTML 语法元素,如尖括号或引号,以在简单的服务器端检查中存活。例如,臭名昭著的 UTF-7 编码允许“<”字符被编码为“+ADw-”这个五字符序列,大多数服务器端过滤器都会乐意允许它原样通过。在类似的情况下,UTF-8 规范正式禁止,但技术上允许“<”通过不必要的冗长 2 到 5 字节序列来表示,从 0xC0 0xBC 到 0xFC 0x80 0x80 0x80 0x80 0xBC.^([63])</p>
</li>
<li>
<p><strong>具有特殊前缀并赋予其后一个或多个字节特殊意义的可变长度编码</strong>。这种逻辑可能导致合法的 HTML 语法元素被“消耗”作为无意中多字节字面量的一部分。例如,Shift JIS 前缀码 0xE0 可能导致后续的尖括号或引号在 Internet Explorer、Firefox 和 Opera 中被消耗(但在 Chrome 中不会),这可能会严重改变内联标记的含义。</p>
<p>反过来也可能出现问题:服务器可能确信它正在输出多字节字面量,但这个字面量可能被浏览器拒绝,并解释为几个单独的字符。在 EUC-KR 中,0x8E 前缀只有在后续字符的 ASCII 码为 0x41 或更高时才会得到尊重。如果更低,它将不会产生预期的效果,但并非所有的服务器端实现都会注意到。</p>
</li>
<li>
<p><strong>与 8 位 ASCII 完全不兼容的编码</strong>。这些情况将导致客户端和服务器之间对文档结构的看法非常不同。常见例子包括 UTF-16 或 UTF-32。</p>
</li>
</ul>
<p>重要的是,除非服务器对其生成的字符集有完美的控制,并且除非它确信客户端不会对有效载荷应用意外的转换,否则可能会出现严重的复杂性。例如,考虑一个 Web 应用程序,它会从以下 HTML 片段中删除高亮显示的用户可控字符串中的尖括号:</p>
<pre><code>You are currently viewing:
<span class="blog_title">
`+ADw-script+AD4-alert("Hi mom!")+ADw-/script+AD4-`
</span>
</code></pre>
<p>如果接收方将该文档解释为 UTF-7,实际解析的标记将如下所示:</p>
<pre><code>You are currently viewing:
<span class="blog_title">
`<script>alert("Hi mom!")</script>`
</span>
</code></pre>
<p>与此类似的问题,这次与 Shift JIS 编码的字节消耗有关,如下所示。允许多字节前缀消耗一个闭合引号,因此相关的 HTML 标签没有按预期终止,这使得攻击者能够向标记中注入额外的 <em>onerror</em> 处理程序:</p>
<pre><code><img src="http://fuzzybunnies.com/`[0xE0]`">
`...this is still a part of the markup...`
`...but the server doesn't know...`
`" onerror="alert('This will execute!')"`
<div>
...page content continues...
</div>
</code></pre>
<p>对于包含任何类型用户可控数据的所有基于文本的文档,防止字符集自动检测是至关重要的。大多数浏览器如果未在 <em>Content-Type</em> 报头或 <em><meta></em> 标签中找到 <em>charset</em> 参数,将会进行字符集检测。实现之间存在一些明显的差异(例如,只有 Internet Explorer 倾向于检测 UTF-7),但您永远不应假设字符集嗅探的结果是安全的。</p>
<p>如果字符集未被识别或输入错误,也会尝试进行字符集自动检测;由于字符集命名可能具有歧义,以及网络浏览器在处理常见名称变化时的不一致性,这个问题变得更加复杂。作为一个数据点,考虑以下事实:Internet Explorer 识别 ISO-8859-2 和 ISO8859-2(ISO 部分后没有连字符)作为 <em>Content-Type</em> 报头中的有效字符集标识符,但无法识别 UTF8 作为 UTF-8 的别名。错误的选择可能会造成一些严重的问题。</p>
<h3 id="注意-54">注意</h3>
<p>有趣的事实:<em>X-Content-Type-Options</em> 报头对字符嗅探逻辑没有影响。</p>
<h2 id="字节顺序标记">字节顺序标记</h2>
<p>我们还没有完成字符集检测!需要特别指出的是,Internet Explorer 还有一个明显错误的处理内容实践:倾向于优先考虑所谓的 <em>字节顺序标记 (BOM)</em>,这是一个可以放在文件开头的字节序列,用于标识其编码,而不是显式提供的 <em>charset</em> 数据。当在输入文件中检测到这样的标记时,声明的字符集将被忽略。</p>
<p>表 13-1") 展示了几个常见的标记。在这些标记中,可打印的 UTF-7 BOM 特别狡猾。</p>
<p>表 13-1. 常见字节顺序标记 (BOMs)</p>
<table>
<thead>
<tr>
<th>编码名称</th>
<th>字节顺序标记序列</th>
</tr>
</thead>
<tbody>
<tr>
<td>UTF-7</td>
<td>“+/v” 后跟 “8”,“9”,“+”,或 “/”</td>
</tr>
<tr>
<td>UTF-8</td>
<td>0xEF 0xBB 0xBF</td>
</tr>
<tr>
<td>UTF-16 小端序</td>
<td>0xFF 0xFE</td>
</tr>
<tr>
<td>UTF-16 大端序</td>
<td>0xFE 0xFF</td>
</tr>
<tr>
<td>UTF-32 小端序</td>
<td>0xFF 0xFE 0x00 0x00</td>
</tr>
<tr>
<td>UTF-32 大端序</td>
<td>0x00 0x00 0xFE 0xFF</td>
</tr>
<tr>
<td>GB −18030</td>
<td>0x84 0x31 0x95 0x33</td>
</tr>
</tbody>
</table>
<h3 id="注意-55">注意</h3>
<p>微软工程师承认这种设计的问题,并且截至本文撰写时,表示逻辑可能会根据兼容性测试的结果进行修订。如果问题在本书上架时得到解决,那么他们值得赞扬。在此之前,允许攻击者控制一个未由 <em>Content-Disposition</em> 保护的 HTTP 响应的前几个字节可能是一个坏主意——除了填充响应之外,没有其他方法可以解决这个问题。</p>
<h2 id="字符集继承和覆盖">字符集继承和覆盖</h2>
<p>在评估当代网络浏览器中字符集处理策略的潜在影响时,应考虑两个额外的、鲜为人知的机制。这两个特性都可能允许攻击者强制对另一个页面使用不希望的字符编码,而无需依赖字符嗅探。</p>
<p>有关的问题的第一个设备,除了 Internet Explorer 之外都得到支持,被称为 <em>字符集继承</em>。根据这项政策,为顶层框架定义的任何编码可以自动应用于任何没有设置自己的、有效的 <em>charset</em> 值的框架文档。最初,这种继承扩展到所有框架场景,甚至跨越完全无关的网站。然而,当 Stefan Esser、Abhishek Arya 和其他一些研究人员展示了一些利用此功能对不知情的目标强制 UTF-7 解析的合理攻击时,Firefox 和 WebKit 开发人员决定将这种行为限制在相同源框架。Opera 仍然允许跨域继承。尽管它不支持 UTF-7,但其他有问题的编码,如 Shift JIS,也是公平的游戏。</p>
<p>值得一提的另一个机制是手动覆盖当前使用的字符集。这个功能在大多数浏览器中的 <em>查看 > 编码</em> 菜单或类似菜单中可用。使用此菜单更改字符集会导致页面及其所有子框架(包括跨域的)使用所选编码重新解析,而不管之前遇到的内容的任何 <em>charset</em> 指令。</p>
<p>由于用户可能很容易被欺骗选择一个攻击者控制的页面的替代编码(只是为了正确查看),这种设计可能会让你感到有些不舒服。普通用户无法期望他们意识到他们的选择也将适用于隐藏的 <em><iframe></em> 标签,并且这种看似无害的行为可能会使针对无关网站的网络脚本攻击成为可能。事实上,让我们现实一点:他们中的大多数甚至不知道——也不应该知道——<em><iframe></em> 是什么。</p>
<h2 id="子资源上的标记控制字符集">子资源上的标记控制字符集</h2>
<p>我们即将结束这段穿越内容处理怪癖网络的史诗之旅,但我们还没有完全结束。敏锐的读者可能会记得,在 特定类型内容包含 中,我在 框架 一章中提到,对于某些类型的子资源(即样式表和脚本),嵌入页面可以指定自己的 <em>charset</em> 值,以便对检索到的文档应用特定的转换,例如,</p>
<pre><code><script src="http://fuzzybunnies.com/get_js_data.php" charset="EUC-JP">
</code></pre>
<p>除了 Opera 外,所有浏览器都支持此参数。在它被支持的地方,它通常不会在 <em>Content-Type</em> 中的 <em>charset</em> 之前优先,除非第二个参数缺失或无法识别。但每条规则都有例外,而这个例外的名字往往是 Internet Explorer 6。在这个仍然流行的浏览器中,标记指定的编码会覆盖 HTTP 数据。</p>
<p>这种行为在实践中是否重要?为了完全理解后果,让我们也快速回到 第六章,在那里我们讨论了如何保护服务器生成的、针对特定用户的、类似 JSON 的代码免受跨域包含。需要这种防御的应用程序的一个例子是网络邮件应用程序中的可搜索地址簿:搜索词包含在 URL 中,匹配的联系人的 JavaScript 序列化返回到浏览器,但必须防止其在无关网站上被包含。</p>
<p>现在,让我们假设开发者想出了一个简单的技巧来防止第三方网页通过 <em><script src=...></em> 加载这些数据:使用单个“//”前缀将整个响应转换为注释。使用 <em>XMLHttpRequest</em> API 的同源调用者可以简单地检查响应,去除前缀,然后将数据传递给 <em>eval(...)</em>——但尝试利用 <em><script src=...></em> 语法的外部调用者将不会成功。</p>
<p>在这个设计中,对 <em>/contact_search.php?q=smith</em> 的请求可能会产生以下响应:</p>
<pre><code>// var result = { "q": "`smith`", "r": [ "j.smith@example.com" ] };
</code></pre>
<p>只要搜索词被正确转义或过滤,这个方案看起来是安全的。但当我们意识到攻击者可能迫使响应被解释为 UTF-7 时,情况发生了戏剧性的变化。一个看似无害的搜索词,从服务器的角度来看,不包含任何非法字符,仍然可能意外地解码为</p>
<pre><code>// var result = { "q": "`smith[CR][LF]`
`var gotcha` **= { "**", "r": [ "j.smith@example.com" ] };
</code></pre>
<p>当通过受害者的浏览器中的 <em><script src=... charset=utf-7></em> 加载此响应时,攻击者可以访问用户地址簿的一部分。</p>
<p>这不仅仅是一个思维练习:“//” 方法在互联网上相当常见,知名研究员 Masato Kinugawa 发现几个流行的 Web 应用程序受到了这个漏洞的影响。同样,针对其他阻止执行的词法前缀(如 <em>while (1);</em>)的攻击变种也是可能的。最终,跨域 <em>charset</em> 在 <em><script></em> 标签上的问题是我们强烈推荐在 第六章 中使用强大的解析器停止前缀以防止解释器查看任何攻击者控制的位的原因之一。哦——如果考虑到对 E4X 的支持,情况变得更加有趣,^([225]) 但让我们就到这里为止。</p>
<h2 id="非-http-文件的检测">非 HTTP 文件的检测</h2>
<p>总结本章内容,让我们看看最后一个缺失的细节:对于通过非 HTTP 协议传输的文档,进行字符集编码检测。正如预期的那样,保存到磁盘并随后通过 <em>file:</em> 协议打开的文档,或者通过其他方式加载且通常的 <em>内容类型</em> 元数据缺失的文档,通常会受到字符集检测逻辑的影响。然而,与文档确定启发式方法不同,所有可能的传输方法之间没有实质性的差异:在所有情况下,嗅探行为大致相同。</p>
<p>对于所有基于文本的文档,没有干净且可移植的方式来解决这个问题,但对于 HTML 来说,可以通过在文档体中嵌入 <em><meta></em> 指令来减轻字符集嗅探的影响:</p>
<pre><code><meta http-equiv="Content-Type" content="text/html;charset=...">
</code></pre>
<p>你不应该为了这个指标而放弃 <em>内容类型</em>。与 <em><meta></em> 不同,该标头适用于非 HTML 内容,并且在网站范围内更容易执行和审计。话虽如此,那些可能被保存到磁盘且包含攻击者控制信息的文档将受益于一个冗余的 <em><meta></em> 标签。(只是确保这个值实际上与 <em>内容类型</em> 匹配。)</p>
<p>安全工程速查表</p>
<p>所有网站的优秀安全实践</p>
<ul>
<li>
<p>指示 Web 服务器将 <em>X-Content-Options: nosniff</em> 标头添加到所有 HTTP 响应中。</p>
</li>
<li>
<p>咨询 第九章 中的速查表来设置适当的 <em>/crossdomain.xml</em> 元策略。</p>
</li>
<li>
<p>配置服务器在所有其他响应中附加默认的 <em>字符集</em> 和 <em>内容类型</em> 值,否则这些响应将没有这些值。</p>
</li>
<li>
<p>如果你没有使用基于路径的参数传递(例如 PATH_INFO),考虑禁用此功能。</p>
</li>
</ul>
<p>在生成包含部分攻击者控制内容的文档时</p>
<ul>
<li>
<p>总是返回一个明确、有效、众所周知的 <em>内容类型</em> 值。不要使用 <em>text/plain</em> 或 <em>application/octet-stream</em>。</p>
</li>
<li>
<p>对于任何基于文本的文档,在<em>Content-Type</em>头中返回一个明确、有效、众所周知的<em>charset</em>值;UTF-8 比任何其他可变宽度编码更可取。不要假设<em>application/xml+svg</em>、<em>text/csv</em>和其他非 HTML 文档不需要指定字符集。对于 HTML,如果文件可能被用户下载,考虑使用冗余的<em><meta></em>指令。注意拼写错误——UTF8 不是 UTF-8 的有效别名。</p>
</li>
<li>
<p>对于不需要直接查看的响应,包括 JSON 数据,使用<em>Content-Disposition: attachment</em>和适当的、明确的<em>filename</em>值。</p>
</li>
<li>
<p>不要允许用户控制文件的前几个字节。尽可能约束响应。除非绝对必要,否则不要传递 NULs、控制字符或高位值。</p>
</li>
<li>
<p>在执行服务器端编码转换时,确保你的转换器拒绝所有意外或无效的输入(例如,过长的 UTF-8)。</p>
</li>
</ul>
<p>当托管用户生成文件时</p>
<p>如果可能的话,考虑使用沙盒域名。如果你打算托管不受限制或未知文件格式,沙盒域名是必需的。否则,至少要执行以下操作:</p>
<ul>
<li>
<p>使用<em>Content-Disposition: attachment</em>和与<em>Content-Type</em>参数匹配的适当、明确的<em>filename</em>值。</p>
</li>
<li>
<p>仔细验证输入数据,并始终使用适当、公认的 MIME 类型。将 JPEG 作为<em>image/gif</em>提供可能会导致麻烦。避免托管不太可能被流行浏览器支持的 MIME 类型。</p>
</li>
<li>
<p>避免使用<em>Content-Type: application/octet-stream</em>,而应使用<em>application/binary</em>,特别是对于未知文档类型。避免返回<em>Content-Type: text/plain</em>。不允许用户指定的<em>Content-Type</em>头。</p>
</li>
</ul>
<hr>
<p>^([63]) 现在,这个问题被大多数浏览器缓解了:它们的解析器现在有额外的检查,以原则性地拒绝过长的 UTF-8 编码。然而,并非所有可能的 UTF-8 服务器端库都如此。</p>
<h1 id="第十四章处理恶意脚本">第十四章。处理恶意脚本</h1>
<p>在前五章中,我们考察了相当广泛的浏览器安全机制——回顾这些内容,可以说它们几乎都有一个共同的目标:阻止恶意内容不当地干扰浏览器中显示的任何其他合法网页。这是一个重要的追求,但也是一个相当狭窄的追求;颠覆无关网站之间的边界是每个攻击者工具箱的一部分,但绝不是书中唯一的技巧。</p>
<p>所有浏览器都必须面对的其他重大设计级安全挑战是,攻击者可能会滥用良好的脚本功能,以干扰或冒充第三方网站,而无需与目标内容实际交互。例如,如果允许攻击者控制的 JavaScript 代码在屏幕上创建任意未装饰的窗口,攻击者可能会发现,与其寻找将恶意有效载荷注入<a href="http://fuzzybunnies.com" target="_blank">fuzzybunnies.com</a>上提供的内容的方法,不如直接打开一个带有可信地址栏复制品的窗口,从而让用户相信显示的内容来自受信任的网站。</p>
<p>对于受害者来说,不幸的是,在互联网的早期,并没有真正关注到 JavaScript API 对旨在干扰或迷惑用户的攻击的易受攻击性,而且与跨域内容隔离问题不同,这类问题至今仍未受到足够的重视。这种情况不太可能在短期内改变:供应商资源在解决臭名昭著的浏览器代码库中相对更严重的实现级别缺陷和推出新的、光鲜的安全特性之间捉襟见肘,这些特性旨在安抚 Web 应用开发者、用户和主流媒体。</p>
<h1 id="服务拒绝攻击">服务拒绝攻击</h1>
<p>攻击者使浏览器崩溃或使其无法操作的可能性是影响现代 Web 最常见、最明显且最不受重视的问题之一。在设备和小拼贴的时代,它也可能产生意想不到的不愉快后果。</p>
<p>大多数浏览器容易受到<em>服务拒绝(DoS)</em>攻击最突出的原因是缺乏规划:底层文档格式以及通过脚本语言暴露的能力都没有设计成具有合理的、受限制的最坏情况 CPU 或内存占用。换句话说,任何足够复杂的 HTML 文件或无休止的 JavaScript 循环都可能使底层操作系统瘫痪。更糟糕的是,试图强制实施资源限制或为用户提供在访问恶意页面后恢复对失控浏览器控制的方法的努力遭到了抵制。例如,许多最近提出的 HTML5 API 的作者没有提供防止资源耗尽攻击的建议,甚至没有承认这种需求,因为他们认为今天实施的任何限制都可能阻碍 5 年或 10 年后的 Web 5 增长。浏览器开发者反过来拒绝采取任何行动,除非有标准级别的指导。</p>
<p>对于任何提议的拒绝服务(DoS)防御措施,常见的功利主义论点是它们毫无意义——浏览器以多种方式轻易崩溃,那么为什么还要采取特殊措施来应对今天的特定向量呢?虽然很难反驳这种观点,但也很重要的是要注意,它起到了自我实现的预言的作用:拒绝服务向量的数量持续增加,使得在不久的将来全面解决这种情况的可能性越来越小。</p>
<h3 id="注意-56">注意</h3>
<p>公平地说,某些操作的计算复杂性并不是浏览器容易崩溃的唯一原因。供应商还受到在页面渲染和脚本执行步骤中保持显著同步需求的限制(参见第六章)。这种设计消除了网站开发者编写可重入和线程安全代码的需求,并具有大量的代码复杂性和安全优势。不幸的是,这也使得一个文档锁定整个浏览器,或者至少是其中很大一部分变得容易得多。</p>
<p>无论考虑上述所有因素,即使浏览器供应商拒绝承认 DoS 风险作为一个特定的缺陷,此类攻击的影响是难以忽视的。首先,每当浏览器崩溃时,都会存在数据丢失的重大风险(在浏览器本身或任何间接受到攻击影响的应用程序中)。此外,在某些社交网站上,攻击者可能只需与受害者分享一个恶意的小工具,或者甚至是一个精心挑选的图像,就可以使受害者无法再使用该服务。</p>
<p>一些常用的使浏览器失效的技巧包括加载复杂的 XHTML 或 SVG 文档,打开非常多的窗口,运行一个无休止的分配内存的 JavaScript 循环,排队大量<em>postMessage(...)</em>调用,等等。虽然这些例子是特定实现的,但每个浏览器都提供了相当多的方法来实现这一目标。即使在 Chrome 浏览器中,它使用单独的渲染进程来隔离无关的页面,也不是很难使整个浏览器崩溃:顶级进程协调各种脚本可访问的以及有时是内存或 CPU 密集型任务。</p>
<p>基于上述情况,尽管普遍持怀疑态度,但主要浏览器仍然实施了几个 DoS 防御措施。它们并不构成一个连贯的策略,并且它们只针对特定 API 的广泛滥用或减轻非恶意但常见的编程错误而推出。尽管如此,我们还是简要地看看它们。</p>
<h2 id="执行时间和内存使用限制">执行时间和内存使用限制</h2>
<p>由于需要强制执行许多类型 JavaScript 操作的同步性,大多数浏览器厂商都偏向于谨慎,并将脚本与大多数剩余的浏览器代码同步执行。这种设计有一个明显的缺点:当 JavaScript 引擎尝试评估一个无效的 <em>while (1)</em> 循环时,浏览器的大部分功能可能会完全无法响应。在 Opera 和 Chrome 中,顶级用户界面仍然可以保持大部分响应性,尽管可能有些缓慢,但在大多数其他浏览器中,甚至无法使用正常 UI 关闭浏览器窗口。</p>
<p>由于无限循环很容易意外创建,为了帮助开发者,Internet Explorer、Firefox、Chrome 和 Safari 对任何持续或几乎持续执行的脚本实施了一个适度的时间限制。如果脚本使浏览器在几秒钟内无法响应,用户将看到一个对话框,并可以选择终止执行。选择此选项的结果类似于遇到未处理的异常,即放弃当前的执行流程。</p>
<p>很遗憾,这样的限制并不是针对恶意脚本的特别有效的防御措施。例如,无论用户的选择如何,仍然可以通过计时器或事件处理程序恢复执行,并且通过定期将 CPU 短暂返回空闲状态以重置计数器,很容易避免触发提示。此外,正如之前所述,有方法可以占用 CPU 资源而不必使用忙循环:渲染复杂的 XHTML、SVG 或 XSLT 文档可能会同样破坏性,并且不受任何检查。</p>
<p>除了执行时间之外,还尝试控制执行脚本的内存占用。调用栈的大小限制在 500 到 65535 之间的浏览器特定值,尝试更深的递归将导致无条件停止。另一方面,脚本堆的大小通常没有以有意义的方式进行限制;页面可以分配和使用数 GB 的内存。实际上,之前实施的大多数限制(如 Internet Explorer 6 中的 16MB 限制)在最近的版本中已被移除。</p>
<h2 id="连接限制">连接限制</h2>
<p>在许多网络应用中,每个网页不仅包括从地址栏中可见的 URL 检索到的正确 HTML 文档,还包括多达几十个其他单独加载的子资源,例如图片、样式表和脚本。由于通过单独建立的 HTTP 连接请求所有这些元素可能会很慢,读者可能还记得从第三章中了解到,该协议已被扩展以提供持久连接和请求管道化。但即使有了这些改进,仍然存在一个顽固的问题。协议的固有局限性在于服务器必须始终以接收请求的相同顺序发送响应,因此如果任何子资源(无论多么微不足道)生成时间稍长,所有后续资源的加载都将被延迟。</p>
<p>为了解决这个问题,并在无法使用持久连接请求或管道化时优化性能,所有浏览器都允许同时打开到目标服务器的几个 HTTP 连接。这样,浏览器可以并行发出多个请求。</p>
<p>不幸的是,并行连接设计对目标网站来说可能代价高昂,特别是如果服务器依赖于传统的基于<em>fork()</em>的连接处理架构。^([64)]. 因此,为了限制意外或故意发起分布式 DoS 攻击的风险,需要将并行连接的数量限制为每个主机的适度值,通常在 4 到 16 之间。此外,为了防止攻击者过载浏览器本身(或影响附近网络设备的性能),到所有目标的总并发连接数也受到限制,通常为每个主机上限的低倍数。</p>
<h3 id="注意-57">注意</h3>
<p>在许多实现中,每个主机的连接限制是通过查看 DNS 标签而不是 IP 地址来强制执行的。因此,攻击者仍然可以将他自己的域中的几个虚假 DNS 条目指向任何无关的目标 IP,从而绕过第一个限制。尽管如此,全局连接限制仍然有效。</p>
<p>尽管并发 HTTP 会话的数量有限,但没有任何实际限制可以保持一个活跃会话的持续时间(也就是说,只要没有遇到内核级别的 TCP/IP 超时)。这种设计可能使得攻击者可以通过与几个故意缓慢的目标进行通信来简单地耗尽全局连接限制,从而阻止用户在此期间进行任何有用的操作。</p>
<h2 id="弹出窗口过滤">弹出窗口过滤</h2>
<p><em>window.open(...)</em>和<em>window.showModalDialog(...)</em>API 允许网页创建新的浏览器窗口,并将它们指向任何其他允许的 URL。在这两种情况下,浏览器可以指示不要显示新加载文档的某些窗口装饰,或将窗口在屏幕上以特定方式定位。<em>window.open(...)</em>的简单使用可能看起来像这样:</p>
<pre><code>window.open("/hello.html", "_blank", "menubar=no,left=50,top=50");
</code></pre>
<p>除了这两种 JavaScript 方法之外,新窗口也可能通过程序性地与某些 HTML 元素交互而间接打开。例如,可以在 HTML 链接上调用<em>click()</em>方法,或者在表单上调用<em>submit()</em>方法。如果相关的标记包括<em>target</em>参数,则结果导航将在指定名称的新窗口中进行。</p>
<p>如预期的那样,随机网页打开新浏览器窗口的能力很快证明是问题。在 20 世纪 90 年代末,当时年轻的在线广告行业的许多参与者决定他们需要不惜一切代价吸引人们对他们广告的注意,甚至以深深烦恼和疏远他们的受众为代价。仅为了展示一个闪亮的广告而自动打开窗口似乎是一种很好的商业方式,也是结交新朋友的好方法。</p>
<p>弹窗和弹出下文广告迅速成为互联网上最知名和最遭人厌恶的方面之一。原因也很充分:特别是对于弹出下文广告,在两到三小时的随意浏览后,积累一打这样的广告并不罕见。</p>
<p>由于普遍的投诉,浏览器厂商介入并实施了一个简单的限制:非白名单页面创建新窗口的虚假尝试将被默默地忽略。对于在鼠标点击或类似用户操作后立即进行的尝试,则做出了例外。例如,在 JavaScript 的情况下,对响应<em>onclick</em>事件的代码调用<em>window.open(...)</em>的能力将被授予,并在之后不久撤销。在 Internet Explorer 和 WebKit 中,这种权限在事件处理程序退出时立即过期。其他浏览器可能认识到大约一秒钟的短暂宽限期。)</p>
<p>弹窗阻止功能最初限制了弹窗广告,但最终证明效果相当有限:许多网站会简单地等待用户在页面上点击任何位置(为了跟随链接或甚至滚动文档)并相应地打开新窗口。其他网站则转向了更具破坏性的做法,例如插页式广告——你需要点击才能进入你真正想要阅读的内容的整页广告。</p>
<p>除了广告军备竞赛之外,对<em>window.open(...)</em>的战争从拒绝服务(DoS)的角度来看也很有趣。创建数十万个窗口,从而耗尽操作系统对 UI 句柄数量的限制,是导致浏览器崩溃和干扰其他应用程序的可靠方法。任何限制这种能力的机制,至少在理论上,都是一种有价值的防御。不幸的是,没有这样的运气:令人难以置信的是,只有 Internet Explorer 和 Chrome 合理地限制了响应单个点击时<em>window.open(...)</em>可以调用的实际次数。在其他浏览器中,一旦授予打开窗口的临时权限,攻击者就可以完全失控,打开她想要的任意数量的窗口。</p>
<h2 id="对话框使用限制">对话框使用限制</h2>
<p>除了与窗口相关的问题之外,所有源自网页的脚本都可以打开某些由浏览器或操作系统处理的对话框。这些对话框对现代网络应用的有用性很小,但它们仍然是浏览器安全景观中另一个有趣的部分。启动对话框的 API 包括<em>window.alert(...)</em>,用于显示简单的文本消息;<em>window.prompt(...)</em>和<em>window.confirm(...)</em>,用于请求基本的用户输入;以及<em>window.print(...)</em>,它将弹出操作系统级别的打印对话框。一些鲜为人知的供应商扩展,例如 Mozilla 的<em>window.sidebar.addPanel(...)</em>和<em>window.sidebar.addSearchEngine(...)</em>(分别用于创建书签和注册新的搜索提供商),也列在这个列表中。</p>
<p>除了上述 JavaScript 方法之外,还可以间接生成几种类型的对话框。例如,可以在文件上传按钮上调用<em>click()</em>方法或导航到可下载的文件,这通常会导致操作系统提供的文件选择对话框。导航到需要 HTTP 身份验证的 URL 通常也会弹出浏览器级别的提示。</p>
<p>那么,是什么使得对话框如此有趣呢?与这些提示相关的挑战与程序化创建的窗口的挑战相当不同。与主要异步的<em>window.open(...)</em> API 不同,对话框会暂停 JavaScript 的执行并推迟许多其他操作(如导航或事件传递),有效地防止大量创建对话框以耗尽资源并导致应用程序崩溃。但它们的模态行为也是它们的诅咒:它们阻止用户与浏览器的一部分进行任何交互,直到用户关闭对话框本身。</p>
<p>这创造了一个有趣的漏洞。如果在关闭旧对话框后立即打开新对话框,受害者可能会被锁在浏览器 UI 的关键部分之外,甚至可能失去关闭窗口或从受影响的页面导航的能力。恶意软件作者有时会利用这个特性,强迫普通、恐慌的用户执行危险的操作(例如下载并执行不可信的可执行文件),以便允许他们继续工作:在脚本启动的安全提示中做出任何其他选择,只会让相同的对话框反复出现。</p>
<p>可能正是因为这个与恶意软件相关的旁路,浏览器厂商已经开始尝试使用不那么干扰的提示方法。例如,在 Chrome 浏览器中,一些最常见的模态对话框都有一个复选框,允许用户抑制页面未来尝试使用有问题的 API(直到下一次重新加载为止)。在 Opera 浏览器中,可以停止页面上脚本的执行。在 Opera 和 Firefox 的最新版本中,许多常见的对话框仅在文档控制的窗口区域是模态的,仍然允许关闭标签页或在地址栏中输入不同的 URL。然而,这些改进的覆盖范围有限。</p>
<p><img src="http://atomoreilly.com/source/nostarch/images/950035.png.jpg" alt="Firefox 在网页上执行 onbeforeunload 处理程序后生成了一个令人困惑且模糊的提示。处理程序给页面作者一个机会来解释离开页面(例如丢失任何未保存的数据)的后果,并请求用户做出最终决定。出于可用性的原因,不再允许互联网上的随机页面通过除这个特定的 onbeforeunload 对话框之外的其他方式中止挂起的导航。(令人惊讶的是,设计上能够永远将用户困在恶意页面上并取消任何导航尝试的能力并没有得到好评。)在这个屏幕截图中,第一行和最后一行来自浏览器本身;中间两行是(未命名的!)恶意网站提供的“解释”。这个特定对话框的安全影响最小,但它是一个糟糕的 UI 设计的显著例子。遗憾的是,几乎相同的对话框也被 Internet Explorer 使用,而且大多数其他浏览器的对话框也好不到哪里去。" loading="lazy"></p>
<p>图 14-1. Firefox 在网页上执行 onbeforeunload 处理程序后生成了一个令人困惑且模糊的提示。处理程序给页面作者一个机会来解释离开页面(例如丢失任何未保存的数据)的后果,并从用户那里请求最终决定.^([68]) 在这个屏幕截图中,第一行和最后一行来自浏览器本身;中间两行是由一个(未命名的!)恶意网站提供的“解释”。这个特定对话框的安全影响最小,但它是一个糟糕 UI 设计的显著例子。遗憾的是,一个几乎相同的对话框也被 Internet Explorer 使用,而且大多数其他浏览器对话框也好不到哪里去。</p>
<h3 id="注意-58">注意</h3>
<p>许多浏览器级别的对话框在解释提示的来源和其预期目的方面做得不好。在某些情况下,例如图 14-1 中显示的 Firefox 对话框,结果可能是滑稽的——而且这种愚蠢也有更险恶的一面。生成听起来权威的对话框,声称它们来自操作系统本身,是恶意软件作者用来混淆经验不足用户的一种常见技巧。不难想象为什么这会有效。</p>
<hr>
<p>^([64]) 大多数 Unix 服务的传统设计是有一个主“监听”进程,然后为每个接受的连接创建一个新的进程。对于开发者来说,这种模型以其简单性而著称;但这也给操作系统带来了许多显著的隐藏成本,有时操作系统发现同时处理超过几百个并发连接具有挑战性。</p>
<p>^([65]) 少为人知的 <code>showModalDialog(...)</code> 方法有点名不副实。它本质上等同于 <code>window.open(...)</code>,但它的目的是通过阻塞调用上下文中的脚本,直到这样的“对话框”窗口被关闭,来模糊地模拟模态对话框的行为。这个 API 的确切行为在不同浏览器之间随机变化。例如,当调用 <code>showModalDialog(...)</code> 的原始 JS 代码正在执行时,有时其他页面可以导航到基础窗口或执行新的脚本。</p>
<p>^([66]) “弹出下推”是一种弹出窗口,在创建后立即通过<code>opener.window.focus()</code>或<code>window.blur()</code>的帮助将其移动到窗口堆栈的后面。与弹出窗口相比,弹出下推可能稍微不那么分散注意力,因为用户不需要立即采取行动来返回原始文档。然而,它们并不少人讨厌。</p>
<p>^([67]) 例如,调用<code>window.open(...)</code>不会生成异常。然而,在这种情况下,返回值并不标准化,这使得可靠地检测被阻止的弹出窗口变得困难。在 Internet Explorer 和 Firefox 中,该函数将返回<code>null</code>;在 Safari 中,它将返回另一个特殊值<code>undefined</code>;在 Opera 中,将提供一个虚拟窗口句柄;而在 Chrome 中,返回的窗口句柄甚至具有准功能 DOM。</p>
<p>^([68]) 由于可用性原因,不再允许互联网上的随机页面通过除这种特定的<em>onbeforeunload</em>对话框之外的其他方式中止挂起的导航。(令人惊讶的是,设计上能够将用户永远困在恶意页面上并取消任何导航尝试的能力并没有得到好评。)</p>
<h1 id="窗口定位和外观问题">窗口定位和外观问题</h1>
<p>好吧,好吧——让我们超越可能令人提不起兴趣且不受欢迎的 DoS 漏洞话题。与 UI 相关的各种 API 还有很多内容——<code>window.open(...)</code>就是一个特别有趣的情况。回想一下本章前面讨论的内容,这个不起眼的功能不仅允许 Web 应用程序创建新窗口,还可以在屏幕上的特定位置定位它们。其他几种方法,如<code>window.moveTo(...)</code>、<code>window.resizeTo(...)</code>、<code>window.focus()</code>或<code>window.blur()</code>,进一步允许这样的窗口在屏幕上移动、缩放或以特定方式堆叠。最后,<code>window.close()</code>允许在脚本不再需要它时悄悄地将其处理掉。</p>
<p>与大多数其他 UI 操作功能一样,这些 API 很快就被证明是痛苦的来源。在一系列有趣的黑客攻击之后,这些攻击通过将窗口部分或完全放置在屏幕之外或使其变得非常小来创建“隐藏”窗口,现在这些功能要求新创建的窗口必须具有某些最小尺寸,并且完全保持在可见桌面区域内。(尽管如此,仍然可以创建一个不断在屏幕上跳跃并逃避所有鼠标尝试关闭它的窗口,但鉴于你到目前为止所读到的内容,这只能引起深深的叹息。)</p>
<p>然而,对窗口大小的限制并不意味着地址栏的整个内容都必须对用户可见。一个尺寸过小的窗口可以通过精心截断主机名来误导用户关于文档来源,如图图 14-2 所示。浏览器厂商至少从 2010 年我的报告以来就已经意识到这个问题,^([226])但截至本文写作时,只有 Internet Explorer 使用了一种多少有些令人信服但微妙的缓解措施:它在地址栏中省略的主机名末尾附加“...”。</p>
<p>使用脚本控制的窗口定位的另一个有趣问题是,创建几个巧妙对齐、重叠的窗口,以形成一个看似单个文档窗口,其地址栏并不对应显示的文档部分。这种攻击,我将其称为<em>窗口拼接</em>,在图 14-3 中或许得到了最好的说明。</p>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950037.png.jpg" alt="脚本精心调整的窗口大小,使得显示内容的真实来源以一种令人困惑的方式被省略。这个以猫为主题的页面的实际 URL 是,而不是。" loading="lazy"></p>
<p>图 14-2. 脚本精心调整的窗口大小,使得显示内容的真实来源以一种令人困惑的方式被省略。这个以猫为主题的页面的实际 URL 是<a href="http://www.example.com.coredump.cx/" target="_blank"><code>www.example.com.coredump.cx/</code></a>,而不是<a href="http://www.example.com/" target="_blank"><code>www.example.com/</code></a>。</p>
<p>窗口定位提供了一些有趣但有些牵强的攻击场景,但操纵通过编程创建的窗口的内容对浏览器安全也具有一定的相关性。我们已经提到,<em>window.open(...)</em> API 的一个特性是它能够在新打开的窗口中隐藏浏览器界面的某些元素(滚动条、菜单等)。这样的 UI 限制调用示例是</p>
<pre><code>window.open("http://example.com/", "_blank", "location=no,menubar=no");
</code></pre>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950039.png.jpg" alt="Chrome 中的窗口拼接攻击。可能看起来是一个单独的文档,实际上是由两个重叠、对齐的窗口组合而成。用户会被引导相信文件上传按钮来自顶部窗口地址栏中显示的域名,但实际上并非如此。某些视觉线索表明有欺诈行为(例如,窗口边框的一部分颜色略有不同),但这些线索太微妙,不容易被用户察觉。" loading="lazy"></p>
<p>图 14-3. Chrome 中的窗口拼接攻击。可能看起来是一个单独的文档,实际上是由两个重叠、对齐的窗口组合而成。用户会被引导相信文件上传按钮来自顶部窗口地址栏中显示的域名,但实际上并非如此。某些视觉线索表明有欺诈行为(例如,窗口边框的一部分颜色略有不同),但这些线索太微妙,不容易被用户察觉。</p>
<p>其中一个设置,<code>location=no</code>,原本是为了隐藏地址栏。这当然是一个糟糕的想法:它不仅允许攻击者隐藏实际的地址栏,还可以加载一个仅提供地址栏像素完美图像的页面,显示一个完全不相关的 URL。更糟糕的是,通过一些最小限度的努力,这个假地址栏甚至可能完全交互式。</p>
<p>认识到这种设计的危险,大多数浏览器最终开始在任何使用 <code>location=no</code> 打开的窗口中显示一个简约的、只读的地址栏;然而,苹果公司认为允许这种设置按照 20 世纪 90 年代最初设想的方式工作并无害处。遗憾的是:图 14-4 创建的窗口中的真实地址栏的截图。") 展示了对其用户界面的简单攻击。(我在 2010 年左右联系了苹果公司关于这种攻击,但至今未收到回复。)</p>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950041.png.jpg" alt="允许网站在 Safari 中隐藏地址栏是一个糟糕的想法。显示的文档并非来自 . 而是页面简单地显示了一个由 window.open("...", "location=no") 创建的窗口中的真实地址栏的截图。" loading="lazy"></p>
<p>图 14-4. 允许网站在 Safari 中隐藏地址栏是一个糟糕的想法。显示的文档并非来自 <a href="http://www.example.com/" target="_blank"><code>www.example.com/</code></a>。而是页面简单地显示了一个由 <em>window.open("</em><a href="http://coredump.cx/" target="_blank"><code>coredump.cx/</code></a><em>...", "location=no")</em> 创建的窗口中的真实地址栏的截图。</p>
<p>微软的表现也好不到哪里去:尽管他们修补了 <em>window.open(...)</em>,但他们忘记了 <em>window.createPopup(...)</em>,这是一个古老且不为人知的 API,至今仍未受到必要的检查。</p>
<h1 id="用户界面的时间攻击">用户界面的时间攻击</h1>
<p>本章中我们讨论的问题可能难以解决,但至少在原则上,解决方案并非遥不可及。然而,这里有一个荒谬的问题:当前的网页脚本模型是否可能与人类的工作方式在本质上不兼容?通过这个问题,我并不是仅仅指针对粗心大意和容易混淆的人进行的网络社会工程学的危险;而是在问,是否有可能让脚本持续地智胜警觉和知识渊博的受害者,仅仅是因为人类认知的固有局限性?</p>
<p>这个问题足够荒谬,以至于很少被问起,但答案可能是肯定的。考虑一下,在典型的、警觉的人类受试者中,视觉刺激和自愿运动反应之间的通常延迟在 100 到 300 毫秒之间.^([227])人类不会在每次肌肉运动后都停下来评估情况那么长时间;相反,我们潜意识地提前安排了一系列学习到的运动动作,并在稍后处理任何到达的感官反馈。在一瞬间,我们无法取消预先计划好的动作,即使出了可怕的问题。</p>
<p>可惜的是,在今天的个人电脑上,在这么短的时间内可能发生很多事情。特别是,脚本可以打开新的窗口,移动它们,或者关闭任何现有的窗口;它们还可以启动或终止系统级提示。在这种设置下,设计对安全敏感的用户界面远不如看起来那么简单,而且某些类型的攻击可能在没有进行软件设计范式重大转变的情况下根本无法防御。</p>
<p>为了说明这个问题,考虑一个尝试启动危险文件类型的不请自来的下载的页面。下载通常会在浏览器级别启动一个带有三个选项的对话框:“打开”、“保存”和“取消”。理智的用户会选择最后一个选项——但如果攻击者剥夺了他们这样做的机会,情况就不同了。</p>
<p>假设在对话框打开后的几毫秒内,甚至在用户甚至没有意识到它的存在之前,就创建了一个新的窗口,将其隐藏起来。在这个窗口中,攻击者放置了一个精心定位的按钮或链接,用户很可能会点击,例如,关闭一个令人讨厌的插页广告的按钮。当用户试图执行这个完全合理的动作时,恶意页面可能会使用<code>onmousemove</code>事件来监控鼠标指针的位置和速度,并相当准确地预测即将点击的时间。在点击之前几毫秒关闭覆盖窗口,然后在该位置显示“打开”按钮,将不可避免地引导用户在安全提示中选择该选项。用户实际上什么也做不了。(我在 2007 年演示了基于这些原理的 Firefox 的实际攻击。)^([228])</p>
<p>作为对安全对话框攻击的回应,在过去的几年中已经实施了各种安全延迟,要求在对话框聚焦和任何危险按钮启用用户输入之间有 500 毫秒到 5 秒的时间。但这样的延迟并不符合浏览器 UI 设计师的口味:他们讨厌它,觉得产品应该尽可能有响应性,而用不可点击的按钮或倒计时来烦扰用户是一个重大的可用性问题。有些人甚至推动从遗留 UI 中移除现有的超时设置.^([69]) HTML5 地理位置共享提示也受到了这种观点的影响。许多浏览器在以任何重要方式保护这种 UI 免受攻击方面做得不足.^([229])</p>
<p>为了进一步复杂化情况,浏览器级别的用户界面并不是 UI 时间攻击的唯一关注点。许多受信任网站的安全或隐私敏感功能也可能受到攻击,解决这个问题比在几个已知的危险系统级 UI 上添加延迟计时器要困难得多。</p>
<h3 id="注意-59">注意</h3>
<p>除了毫秒级的点击或按键劫持之外,已经反复证明,通过最小化和看似无害的条件反射,健康的专注测试对象可以被可靠地欺骗,忽略甚至非常突出和异常的视觉刺激。臭名昭著的“不可见的大猩猩”实验,^([230]) 如图 14-5。当被要求观看这个视频并数出球员传递篮球的次数时,大多数观众未能注意到一个穿着大猩猩服装的人在中途悠闲地穿过房间。真的!去试试看,并让你的朋友也试试。"), 是这一点的特别著名例子。几乎所有观看研究人员制作的剪辑的观众都未能注意到人群中一个明显可见的大猩猩。由此得出的推论是,即使是老练的用户也可能被训练去忽略诸如地址栏变化或浏览器中 SSL 指示器的变化等提示——这是一个非常令人不安的想法。我们今天没有试图解决这个问题,唯一的原因是很少有利用者作者是行为科学家。但如果你是一个高调的目标,这似乎是一个风险很大的赌注。</p>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950043.png.jpg" alt="来自“不可见的大猩猩”实验的单帧画面,由丹尼尔·西蒙斯提供()。当被要求观看这个视频并数出球员传递篮球的次数时,大多数观众未能注意到一个穿着大猩猩服装的人在中途悠闲地穿过房间。真的!去试试看,并让你的朋友也试试。" loading="lazy"></p>
<p>图 14-5. 无形大猩猩实验的单帧,由 Daniel Simons 提供^([231]) (<a href="http://dansimons.com/" target="_blank"><code>dansimons.com/</code></a>). 当被要求观看这个视频并数出球员传递篮球的次数时,大多数观众未能注意到一个穿着大猩猩服装的人在中途悠闲地穿过房间。真的!去<a href="http://theinvisiblegorilla.com/videos.html" target="_blank"><code>theinvisiblegorilla.com/videos.html</code></a>并让朋友试试。</p>
<p>安全工程速查表</p>
<p>当允许在你的网站上使用用户创建的<iframe>小工具时</p>
<ul>
<li>除非你准备好接受后果,否则不要这样做。你无法可靠地防止恶意设备对你的用户发起 DoS 攻击。任何这样的设备也将能够弹出各种晦涩的对话框,通常情况下,这些对话框不会区分你的顶级页面和设备所在的主域。</li>
</ul>
<p>在构建安全敏感的 UI 时</p>
<ul>
<li>由于存在 UI 竞态条件风险,避免出现单次按键或单次点击即可更改关键设置的情况。至少需要两个操作(例如选择复选框然后点击保存)。如果单次点击操作不可避免,考虑检查其他信号。例如,鼠标指针在当前窗口 500 毫秒前是否在?</li>
</ul>
<hr>
<p>^([69]) 例如,参见 Mozilla 错误报告 561177,其中一位 Firefox UI 工程师提出了从插件安装提示中移除安全延迟的建议。</p>
<h1 id="第十五章外部网站权限">第十五章。外部网站权限</h1>
<p>为了总结所有值得注意的浏览器安全功能的讨论,我们将探讨一些机制,这些机制赋予用户或浏览器作者硬编码的特定网站特殊权限。在这些情况下采取的方法与我们之前讨论的方案截然不同,所有这些方案都依赖于对显示内容内在属性的相当合理的检查。通常,实现方式会要求我们查看文档的源代码、其显示的上下文或文档试图执行的操作的性质,但在排除这些检查的结果之外,浏览器永远不会给予一个原本无足轻重的来源优先权。</p>
<p>每站权限以一种相当残酷的方式违反了这一公平原则,原因从可疑到——更常见的是——实用主义。有充分的可用性理由将某些固有的危险功能引入浏览器世界,但没有好的方法可以程序化地决定哪些 Web 应用程序足够值得信赖,可以赋予它们访问这些功能的权限。将这项任务委托给人类可能是我们能做的最好的事情.^([70])</p>
<p>自然地,创建一个特权应用程序阶层可能会非常成问题,因为任何两个 Web 应用程序之间的边界一开始就不太明确,这使得精确控制权限变得困难。而且,由于这些已经不完美的边界仅适用于某些跨站交互,XSS 或 XSRF 等漏洞可能会进一步加剧问题。最终,网站权限的意图与实际后果之间可能会出现重大脱节。</p>
<h1 id="浏览器和插件管理的网站权限">浏览器和插件管理的网站权限</h1>
<p>在平衡安全、隐私和可用性时,浏览器供应商有时会发现自己处于两难境地。一些提议的功能似乎对网络的持续增长至关重要,但过于危险,不能提供给互联网上的每个网站。这类问题机制包括访问视频摄像头或麦克风数据,^([71]) 允许网站查询用户地理位置数据,^([72]) 安装浏览器扩展或主题,或开启桌面通知。</p>
<p>作为这个问题的解决方案,供应商要求用户批准应用程序的请求,以便允许它访问特权 API。在第一次尝试使用受限功能时,用户通常会收到一个视觉提示(从图标到模态提示),并给出三个选择:忽略请求、允许一次或永久授权请求网站访问 API。在这些选择中,最后一个是最有趣的:如果选择,所有来自匹配主机的未来访问都将自动批准,有时甚至没有任何进一步的视觉指示。</p>
<h3 id="注意-60">注意</h3>
<p>大多数白名单只查看主机名,而不是协议或端口。因此,这些列表上的任何条目都将匹配多个 SOP 源。特别是,授权<a href="https://fuzzybunnies.com/" target="_blank"><code>fuzzybunnies.com/</code></a>访问您的摄像头也可能授权非加密网站<a href="http://fuzzybunnies.com/" target="_blank"><code>fuzzybunnies.com/</code></a>执行相同的操作。</p>
<p>谨慎地授予网站访问隐私或安全敏感功能的权限,因为,如前所述,这样做的影响不仅限于信任白名单应用程序的作者。权限授予给在匹配源中执行的所有内容,无论其负载如何到达,这极大地放大了简单(并且在长远来看是不可避免的)实现错误的影响。在特权源中的脚本注入漏洞不再仅仅暴露应用程序中存储的数据,还可能泄露来自客户端的敏感数据流。</p>
<h2 id="固定域名">固定域名</h2>
<p>除了用户授权的特权域名列表之外,一些浏览器或浏览器插件附带了一个由供应商选择的网站列表或 SOP 来源,这些网站被赋予了重新配置或更新浏览器或操作系统的部分内容的重大特权。这一趋势的最突出例子包括 <a href="http://update.microsoft.com" target="_blank">update.microsoft.com</a>,它被随 Microsoft Windows 一起发布的 ActiveX 控件所识别,并允许安装软件更新;<a href="http://addons.mozilla.org" target="_blank">addons.mozilla.org</a> 和 <a href="http://chrome.google.com" target="_blank">chrome.google.com</a>,分别被它们对应的浏览器所识别,并赋予了安装扩展或主题的特殊权限;或者 <a href="http://www.macromedia.com" target="_blank">www.macromedia.com</a>,它被允许重新配置 Adobe Flash。</p>
<p>这些机制的设计各不相同,通常情况下,它们并没有得到令人满意的文档记录。一些功能需要二级验证,例如加密签名或用户同意,但其他则不需要。从广义上讲,这种特权域的泛滥令人担忧,因为很明显,它们不会免受困扰现代网络的其他常见安全问题。例如:<a href="http://xssed.com/" target="_blank"><code>xssed.com/</code></a> 列出了 <a href="http://addons.mozilla.org" target="_blank">addons.mozilla.org</a> 中的六个公开报告的跨站脚本(XSS)漏洞.^([232])</p>
<hr>
<p>^([70]) 有理由抱怨浏览器并没有做很多来为用户提供有关访问网站可信度的积极信号,尽管许多强大的指标可能以自动化的方式得到。基于黑名单的尝试来阻止已知的恶意网站存在,但考虑到注册新域名(或破坏一个随机现有的域名)的微不足道的成本,这些方法的价值可能较小。</p>
<p>^([71]) 目前这项功能仅由插件支持,例如 Adobe Flash,但预计将成为 HTML5 的一部分。</p>
<p>^([72]) 此 API 从当前 IP 地址、附近无线网络或蜂窝基站列表,或由硬件 GPS 接收器提供的数据等参数中获取用户位置。除了 GPS 数据外,可能需要咨询外部服务提供商将这些输入映射到物理坐标。</p>
<h1 id="基于表单的密码管理器">基于表单的密码管理器</h1>
<p>感到惊讶吗?不要。提到密码管理器可能看起来有些不合适,但将这项技术视为一种间接的站点特权形式非常有用。在我们解释之前,让我们简要回顾一下为什么密码管理最初在现代浏览器中得到实现,以及它是如何实际运作的。</p>
<p>第一个问题的答案相当简单:今天,几乎每个主要网站都要求,或者至少强烈建议所有访客注册一个账户。登录通常是自定义网站外观所必需的,并且是与其他注册用户互动的先决条件。不幸的是,这些特定站点的身份验证系统并没有同步(除了几个有限的“联合登录”实验,如 OpenID),^([233]) 并且实际上迫使普通大众为每个常去的网站创建和记住几十个强大的密码。这种方法难以维持,并导致密码重用泛滥且危险;这就是浏览器厂商决定介入的原因。</p>
<p>基于表单的密码管理器是对处理站点凭证激增问题的一种不优雅但实用的解决方案。它们通过简单的启发式方法来检测看似正常的登录表单的提交(浏览器会寻找一个 <em><input type=password></em> 字段,然后可能检查表单字段的名称,寻找诸如 <em>user</em> 和 <em>pass</em> 这样的字符串)。当检测到合适的表单时,浏览器会提供将相关的登录信息保存到硬盘上的持久存储中的选项,^([73]) 如果用户同意,它将随后自动检索并将这些数据粘贴到后来遇到的匹配表单中。在 Firefox、Chrome 和 Safari 中,检索存储的密码的过程是自动的;在 Internet Explorer 和 Opera 中,可能需要额外的用户操作来确认意图。</p>
<p>密码管理器的设计脆弱,但有一个明显的优点:即使在没有任何网站官方支持(或,更确切地说,知情同意)的情况下,它也能立即工作。不希望有此功能的 Web 应用程序可以通过将一个命名不佳的 <em>autocomplete=off</em> 参数附加到受影响的密码字段上选择退出,^([74]) 但除此之外,整个过程几乎是完全无缝的。</p>
<p>每个浏览器内置密码管理器保护存储数据的主要方式是将凭证与其最初输入的 SOP(Same-Origin Policy,同源策略)源绑定——密切注意主机名、协议和端口。一些浏览器还会考虑次要指标,例如表单字段的排序或命名、表单的 URL 路径或凭证发送到的地址。(正如我们在第九章中了解到的,由于同源策略的操作,这些范围措施从安全角度来看并不特别有用。)</p>
<p>在无需人工交互即可自动完成登录表单的浏览器中,将这种机制视为一种特权 API 的形式是合理的:在适当的源中执行的内容将能够通过构建看起来可信的表单并等待自动填充登录数据来请求浏览器存储的凭据。为了读取这些信息,脚本只需检查与密码字段关联的 DOM 元素的<em>value</em>属性。</p>
<h3 id="注意-61">注意</h3>
<p>移除检查密码字段值的能力可能看起来是改进方案的一种简单方法,但这并不是一个好方法。数据仍然可能被窃取,例如,通过等待密码自动完成,将数据提交方法从 POST 更改为 GET,然后在登录表单上调用<em>submit()</em>。这些步骤会导致导航到一个页面,该页面在<em>location.search</em>字符串中清楚地显示了密码。(此外,许多 Web 应用程序在客户端读取这些字段具有合法用途,例如,提供密码强度建议。)</p>
<p>应该很明显,与密码管理器相关的最大风险是 XSS 漏洞的放大。在使用<em>httponly</em> cookie 的 Web 应用程序中,成功利用 XSS 漏洞可能只会让攻击者暂时访问用户的账户,但如果同样的漏洞可以用来窃取用户的密码,后果将更加严重且持久.^([75]) 还可能出现更不明显的影响。例如,任何允许用户构建基于表单的自定义调查的应用程序都必须仔细限制生成的表单布局,否则可能会成为密码收集工具的双重角色。</p>
<hr>
<p>^([73]) 这些数据可能以纯文本表示、天真地混淆的字符串或使用“主”密码保护的加密值的形式存储在磁盘上,该密码需要事先输入。这三种方法都相对容易受到有权访问本地系统的有决心攻击者的攻击,但纯文本方法有时会受到批评,因为它更容易暴露给好奇但非技术性的用户。</p>
<p>^([74]) 尽管名称如此,但这阻止了浏览器记录密码,而不仅仅是自动完成它。</p>
<p>^([75]) 这样的后果可能超出了受影响的应用程序:即使有密码管理器的存在,密码重用仍然是一个普遍且不幸的趋势。</p>
<h1 id="internet-explorer-的-zone-模型">Internet Explorer 的 Zone 模型</h1>
<p>Internet Explorer 的 zone 模型^([234]) 是一种专有尝试,旨在解决用户(或系统管理员)对不同类型 Web 应用程序可能具有的不同安全需求,例如,银行页面和在线游戏。微软的方法是建立几个预定义的网站类别——称为<em>zones</em>——每个类别都有自己的可配置安全权限集。支持的五个区域如下:</p>
<ul>
<li>
<p><strong>我的电脑(即本地计算机)</strong> 这个隐藏区域用于所有本地 <em>file:</em> 资源(有一个例外——稍后将有更多介绍)。用户不能从这个集合中添加或删除任何元素,也不能通过正常用户界面更改其安全设置。管理员和开发者可以通过修改注册表或使用 <em>urlmon.dll</em> 钩子来覆盖设置。</p>
</li>
<li>
<p><strong>本地内部网络</strong> 这个区域旨在包括用户本地网络上的受信任应用程序。默认情况下,<em>本地内部网络</em> 拥有许多有问题的特权,例如无限制访问系统剪贴板、能够打开没有地址栏的窗口,或者能够绕过通常的框架导航安全检查(第十一章中概述的后代策略)。该集合的成员会自动通过几个可配置的启发式方法检测到,它们可能包括具有非完全限定主机名的目的地、HTTP 代理豁免列表上的地址,或通过 SMB 访问的远程 <em>file:</em> URL。也可以手动将站点包含在这个区域中(除了或代替内置的启发式方法)。</p>
<h3 id="注意-62">注意</h3>
<p><em>本地内部网络</em> 区域在本地网络和受信任环境之间建立了一个隐含的连接。在现代环境中,这种连接往往值得怀疑,尤其是在公共互联网访问普遍通过未加密的 Wi-Fi 的情况下:网络的其他用途并不比全球范围内的随机网站更值得信赖。</p>
</li>
<li>
<p><strong>受信任的站点</strong> 这些区域在安全设置上相当于本地内部网络,但完全由用户管理。自动检测启发式方法不可用,所有条目都必须手动创建。</p>
</li>
<li>
<p><strong>受限站点</strong> 在这些名义上为空区域中,用户可以添加“不受信任”的目的地。这些区域的默认设置从加载的内容中移除了许多基本且通常无害的功能(例如,<em>刷新</em>头将无法工作),同时提供有限的安全优势。</p>
<p>这个区域的实际用途似乎并不明确。由于需要将每个不受信任的站点列入白名单,该区域显然不能作为对具有合理默认设置的先前未见目的地的互联网浏览的替代方案。</p>
</li>
<li>
<p><strong>互联网</strong> 这是未包含在任何其他类别中的站点的默认区域。其默认设置与本书之前讨论的通用浏览器安全模型基准相匹配。</p>
</li>
</ul>
<p>区域的概念,加上它们的一些安全控制,似乎是一个正确的方向。例如,它允许系统管理员在不影响正常浏览的安全或便利性的情况下,对<em>文件:</em>文档的权限进行微调——或者禁止互联网站点导航到本地、企业系统(使用名为“受限制的网站内容区域中的网站可以导航到该区域”的设置)。不幸的是,区域模型的实际实现由于缺乏重点而变得混乱,在实践中,它被误用的频率比真正从中受益的频率要高。</p>
<p>对于任何试图掌握区域机制的人来说,第一个明显的问题是其晦涩的术语以及许多设置几乎令人捧腹的复杂性。每个区域都带有超过 100 个复选框;其中一些会深刻地改变浏览器安全模型,而其他则完全没有安全后果。(上述的<em>刷新</em>设置就是一个安全无操作示例;禁用表单提交的能力是另一个。)这两类设置在没有任何明显的方式中区分,许多设置几乎一眼看去就难以理解。例如,选项“二进制和脚本行为”可以设置为“启用”或“禁用”,但帮助子系统没有提供关于这两个设置实际会做什么的信息。唯一的解释是在微软网站上发布的官方开发者文档中提供的——但即使是这份文档也可能让人困惑.^([235]) 请自己看看:</p>
<blockquote>
<p>Internet Explorer 包含动态二进制行为:封装了特定功能的组件,用于附加到 HTML 元素上。这些二进制行为不受任何 Internet Explorer 安全设置的约束,允许它们在受限制站点区域中的网页上工作。在 Windows Server 2003 Service Pack 1 中,有一个新的 Internet Explorer 安全设置用于二进制行为。这个新设置默认禁用受限制站点区域中的二进制行为。结合本地计算机锁定安全功能,它还要求默认情况下在本地计算机区域中运行二进制行为需要管理员批准。这个新的二进制行为安全设置提供了对 Internet Explorer 二进制行为漏洞的一般缓解措施。</p>
</blockquote>
<p>存在许多类似的设置,需要付出大量努力才能理解。例如,即使是经验最丰富的管理员也不太可能理解名为“跨域访问数据源”或“在不同域之间导航窗口和框架”的设置的含义。所有这些混乱都有一个有趣的后果:受信任的各方无意中提供了可疑的建议。例如,著名的投资银行查尔斯·施瓦布(Charles Schwab)建议客户禁用框架导航后裔模型,^([236]) 这实际上使得 HTML 框架不仅对查尔斯·施瓦布,而且对任何其他网站都不安全。美国国税局维护的一个网站提供了同样的、极其不体贴的建议.^([237])</p>
<p>不论是 Internet Explorer 区域设置的复杂性还是文档的糟糕,区域模型的其他问题是不相关的权限的聚集。<em>本地 intranet</em> 和 <em>受信任站点</em> 容器的设置允许随机收集一些受信任站点可能需要的特性,但没有任何受信任站点可能需要区域所包含的所有权限。正因为这种设计,将站点添加到特权区域可能会在诸如简单的 XSS 漏洞的情况下产生意想不到的广泛影响。</p>
<h2 id="网页标记和区域标识符">网页标记和区域标识符</h2>
<p>为了保持下载文件上区域模型的完整性,Internet Explorer 进一步利用两个重叠机制来跟踪任何外部检索文档的原始区域信息:</p>
<ul>
<li>
<p><strong>网页标记(MotW)</strong> 这是一个简单的伪 HTML 标签,被插入到通过 Internet Explorer 下载的 HTML 文档的开头,以指示它们的初始来源.^([238]) MotW 标签的一个例子可能是 <em><!-- saved from url=(0024)*[`fuzzybunnies.com/`](http://fuzzybunnies.com/) *--></em>. 此标签中记录的 URL 映射到相应的区域;文档随后在该区域的唯一源中打开。最重要的后果是,下载的内容与其他 <em>file:</em> URL 隔离开。</p>
<h3 id="注意-63">注意</h3>
<p>MotW 的内联性质是其缺陷之一。恶意方可以通过非 Internet Explorer 浏览器下载的 HTML 文档、从电子邮件客户端保存的文档或使用非 HTML 扩展名下载的 Internet Explorer(然后进行内容嗅探)预先插入假标签。尽管如此,没有 MotW 标签保存的 <em>file:</em> 文档的权限已经足够大,以至于攻击者相对不感兴趣从 <em>我的电脑</em> 区域跳转到,比如说,<em>本地 intranet</em> 区域。</p>
</li>
<li>
<p><strong>交替数据流(ADS)区域标识符</strong> 这是一段由 Internet Explorer(以及 Chrome)附加到每个下载文件的 NTFS 元数据,指示文件检索来源的区域数值代码.^([239]) 与 MotW 相比,<em>Zone.Identifier</em>机制的可移植性较差,当文件保存到非 NTFS 文件系统时,信息会丢失。然而,它也更加灵活,因为它可以应用于非 HTML 文档。</p>
<p><em>Zone.Identifier</em>元数据被 Internet Explorer 自身、Windows GUI 外壳和某些其他 Microsoft 产品所识别,但第三方软件几乎普遍忽略它。在它被支持的地方,它可能会导致对文档应用更严格的安全策略;更常见的是,它只会弹出一个关于打开源自互联网数据的未指定风险的安全警告。</p>
</li>
</ul>
<p>安全工程速查表</p>
<p>当在 Web 应用程序内请求提升权限时</p>
<p>请记住,请求访问地理位置数据、视频或麦克风流以及其他特权 API 伴随着责任。如果你的网站容易受到 XSS 漏洞的影响,你不仅是在冒险应用程序中存储的数据,还在冒险用户的隐私。相应地规划,并妥善隔离特权功能。永远不要要求你的用户降低他们的 Internet Explorer 安全设置以适应你的应用程序,并且当别人给出这样的建议时,不要盲目跟随——无论他们是谁。</p>
<p>当编写识别特权来源的插件或扩展时</p>
<p>你正在让你的用户面临由于不可避免的 Web 应用程序安全漏洞而提高的风险。设计健壮的 API,并尝试使用二级安全措施,如加密,以进一步保护与服务器通信。不要将未加密的来源列入白名单,因为它们容易在开放的无线网络上被欺骗。</p>
<hr>
<p>^([76]) 在需要代理才能访问受保护内部系统但不需要访问互联网的配置中,这些配置可能会产生意想不到且令人恐惧的效果,将整个网络分类为本地网络。</p>
<h1 id="第三部分未来的展望">第三部分。未来的展望</h1>
<p>经过近十年的停滞,浏览器世界再次成为了一场激烈的战场。这让人不禁联想到 20 世纪 90 年代末的第一场浏览器大战,厂商们通过每月推出新功能来竞争。主要的不同之处在于,现在安全性被视为一个明显的卖点。</p>
<p>当然,客观地衡量任何足够复杂的软件的健壮性是计算机科学中的一个未解问题,如果你的代码库恰好承载了近二十年的膨胀,那就更是如此。因此,大部分的竞争努力都投入到发明并迅速部署新的以安全为主题的新增功能中,往往很少考虑它们实际上解决他们应该解决的问题的能力。</p>
<p>同时,考虑到他们早期的失误,标准机构已经放弃了大部分学术上的严谨性,转而让一群专门的贡献者根据他们的看法调整规范。有传言称,将 HTML5 作为标准的最后一个编号版本,并过渡到一个每天都会变化——通常是根本性变化——的活文档。放宽要求有助于保持 W3C 和 WHATWG 周围的大部分工作持续进行,但也削弱了拥有一个中心组织的一些好处。许多最近的提案都倾向于快速、范围狭窄的修补,甚至不试图形成一个一致且高度集成的框架。当这种情况发生时,没有稳健的反馈机制来允许外部专家在实施工作开始之前合理地审查稳定规范并表达担忧。跟上变化的唯一方法就是沉浸在工作组的日常动态中。</p>
<p>很难说这种新的标准化方法是否是坏事。事实上,它的好处可能轻易地超过任何推测性的风险;首先,我们现在有机会得到一个与浏览器实际操作相当接近的标准。尽管如此,这个疯狂且很大程度上不受监督的过程的结果可能是不可预测的,这要求安全社区保持高度警惕。</p>
<p>在这种精神下,本书的最后一部分将探讨一些更合理和先进的建议,这些建议可能会塑造 Web 的未来……或者也可能在几年后最终被历史垃圾箱所淘汰。</p>
<h1 id="第十六章新和即将推出的安全功能">第十六章。新和即将推出的安全功能</h1>
<p>你很快就会意识到所有这些新浏览器功能之间的结合几乎没有规律和理由,但我们仍然需要以某种方式组织讨论。也许最好的方法就是看看它们的预期目的,并从专门为调整 Web 的安全模型以获得明确收益而创建的所有机制开始。</p>
<p>社区内强烈渴望发明一种全新的浏览器安全模型,但随之而来的是意识到这将需要重建整个网络。因此,大部分实际工作都集中在对现有方法的更谦逊扩展上,这不可避免地增加了浏览器代码库中安全关键部分复杂性。这种复杂性不受欢迎,但它的支持者总是认为这是合理的,无论是为了减轻一类漏洞,还是为了为一些其他难以解决的问题提供一个临时的解决方案,或者简单地为了在未来能够构建新的应用程序类型。所有这些好处通常都超过了模糊的风险。</p>
<h1 id="安全模型扩展框架">安全模型扩展框架</h1>
<p>在过去几年中,一些最成功的安全增强措施归结为向由同源策略及其相关策略强加的原始约束中增加灵活性。例如,一个曾经是实验性的提议现在已经进入主流,那就是用于跨源通信的 <em>postMessage(...)</em> API,这在第九章中有讨论。令人惊讶的是,在某些精心选择的场景中放松 SOP 检查的行为比锁定策略更为直观,也更不容易引起问题。因此,为了轻松开始,我们将首先关注这类框架。</p>
<h2 id="跨域请求">跨域请求</h2>
<p>在同源策略的原始约束下,与一个源关联的脚本没有干净且安全的方式与在任何其他源中执行的客户端脚本通信,也没有从愿意提供数据的第三方服务器中检索潜在有用数据的安全方式。</p>
<p>网络开发者长期以来一直抱怨这些约束,近年来,浏览器供应商开始倾听他们的需求。如您所忆,通过 <em>postMessage(...)</em> 解决了在脚本之间安排客户端通信的紧迫任务。客户端到服务器的场景被认为不那么紧急,但仍需一个标准化的解决方案,但已有一些进展可以报告。</p>
<p>创建从非同源服务器检索文档的方法最成功的尝试始于 2005 年。在 W3C 的赞助下,几位在开发用于构建交互式语音响应(IVR)系统的神秘文档格式 VoiceXML 的开发者,起草了一个关于 <em>跨源资源共享(CORS)</em> 的提议。240]。在 2007 年至 2009 年之间,他们笨拙的基于 XML 的设计逐渐演变成一个更简单、更广泛有用的方案,该方案依赖于 HTTP 头部级别的信号来通过 <em>XMLHttpRequest</em> API 的自然扩展来传达对跨源内容检索的同意。</p>
<h3 id="cors-请求类型">CORS 请求类型</h3>
<p>如今天所指定,CORS 依赖于区分对 <em>XMLHttpRequest</em> API 的两种调用类型。当网站尝试通过 API 加载跨源文档时,浏览器首先需要区分 <em>简单请求</em>,其中生成的 HTTP 流量被认为足够接近通过其他现有导航方法生成的流量,以及 <em>非简单请求</em>,它包括所有其他内容。这两类请求的操作差异很大,正如我们将看到的。</p>
<p>当前规范表示,简单请求必须具有 GET、POST 或 HEAD 方法。此外,如果调用者指定了任何自定义标头,它们必须属于以下集合:</p>
<ul>
<li>
<p><em>Cache-Control</em></p>
</li>
<li>
<p><em>Content-Language</em></p>
</li>
<li>
<p><em>Content-Type</em></p>
</li>
<li>
<p><em>Expires</em></p>
</li>
<li>
<p><em>Last-Modified</em></p>
</li>
<li>
<p><em>Pragma</em></p>
</li>
</ul>
<p>今天,支持 CORS 的浏览器简单地不允许除 GET、POST 和 HEAD 之外的方法。同时,它们忽略了推荐的标头白名单,无条件地将任何具有自定义标头值的请求降级为非简单状态。WebKit 的实现还将任何携带有效载荷的请求视为非简单请求。(不清楚这是有意的设计决策还是错误。)</p>
<h3 id="简单请求的安全检查">简单请求的安全检查</h3>
<p>CORS 规范允许简单请求立即提交给目标服务器,而无需尝试确认目标是否愿意首先进行跨域通信。这个决定基于这样一个事实,攻击者可以通过其他手段(例如,通过自动提交表单)发起相当相似的基于 cookie 认证的流量,因此引入专门用于 CORS 的额外握手是没有意义的。(<sup class="footnote-ref"><a href="#fn5" id="fnref5">[5]</a></sup>)</p>
<p>关键的安全检查仅在从服务器检索到响应后进行:只有当响应包含合适的、格式良好的 <em>Access-Control-Allow-Origin</em> 标头时,数据才会通过 <em>XMLHttpRequest</em> API 向调用者揭示。为了协助服务器,原始请求将包括一个强制性的 <em>Origin</em> 标头,指定与调用脚本关联的源。</p>
<p>为了说明这种行为,考虑以下从 <a href="http://www.bunnyoutlet.com/" target="_blank"><code>www.bunnyoutlet.com/</code></a> 发起的跨域 <em>XMLHttpRequest</em> 调用:</p>
<pre><code>var x = XMLHttpRequest();
x.open('GET', 'http://fuzzybunnies.com/get_message.php?id=42', false);
x.send(null);
</code></pre>
<p>结果将是一个大致如下所示的 HTTP 请求:</p>
<pre><code>GET /get_message.php?id=42 HTTP/1.0
Host: fuzzybunnies.com
Cookie: FUZZYBUNNIES_SESSION_ID=EA7E8167CE8B6AD93D43AC5AA869A920
`Origin: http://www.bunnyoutlet.com`
</code></pre>
<p>为了指示响应应在跨域中可读,服务器需要响应如下:</p>
<pre><code>HTTP/1.0 200 OK
Access-Control-Allow-Origin: `http://www.bunnyoutlet.com`
The secret message is: "It's a cold day for pontooning."
</code></pre>
<h3 id="注意-64">注意</h3>
<p>在 <em>Access-Control-Allow-Origin</em> 中使用通配符(“*”)是可能的,但请谨慎操作。在所有 HTTP 响应中无差别地设置 *Access-Control-Allow-Origin: ** 是不明智的,因为这步在很大程度上消除了 CORS 兼容浏览器中同源策略的任何保证。</p>
<h3 id="非简单请求和预检">非简单请求和预检</h3>
<p>在 CORS 协议的早期草案中,几乎所有请求都旨在在没有首先检查服务器是否实际上愿意接受它们的情况下提交。不幸的是,这种设计破坏了某些网络应用利用的一个有趣特性,以防止跨站请求伪造:在 CORS 之前,攻击者无法将任意 HTTP 头部注入跨域请求中,因此自定义头部的存在通常作为请求来自与目标相同的源并且是通过 <em>XMLHttpRequest</em> 发出的证明。</p>
<p>后续的 CORS 修订通过要求一个更复杂的两步握手来纠正这个问题,该握手适用于不符合 CORS 请求类型 中概述的严格“简单请求”标准的请求,这些标准在 安全模型扩展框架 中。非简单请求的握手旨在确认目标服务器符合 CORS 规范,并且它希望接收来自特定调用者的非标准流量。握手是通过向目标 URL 发送一个包含底层 <em>XMLHttpRequest</em> 调用参数概述的纯 OPTIONS 请求(“预检”)来实现的。最重要的信息通过三个自解释的头部传递给服务器:<em>Origin</em>、<em>Access-Control-Request-Method</em> 和 <em>Access-Control-Request-Headers</em>。</p>
<p>这种握手只有在响应中通过使用 <em>Access-Control-Allow-Origin</em>、<em>Access-Control-Allow-Method</em> 和 <em>Access-Control-Allow-Headers</em> 正确认可这些参数时才被认为是成功的。在正确的握手之后,实际请求才会被发送。出于性能考虑,特定 URL 的预检检查结果可能会被客户端缓存一段时间。</p>
<h3 id="cors-的当前状态">CORS 的当前状态</h3>
<p>到目前为止,CORS 仅在 Firefox 和基于 WebKit 的浏览器中可用,并且在 Opera 或 Internet Explorer 中明显缺失。阻碍其采用的最重要因素可能仅仅是 API 并不像其客户端对应物 <em>postMessage(...)</em> 那样关键,因为它通常可以被服务器端的内容获取代理所替代。但是,该方案也面临着三个主要、尽管是轻微的批评,其中一些直接来自一家供应商。显然,这些批评并没有帮助解决问题。</p>
<p>第一项投诉主要是由微软的开发人员和一些学者提出的,他们认为该方案无谓地滥用环境权限。他们争辩说,在跨域共享数据的情况下,需要根据目标站点的凭证进行定制的情况非常少。批评者认为,意外泄露敏感信息的风险远远超过了任何好处,并且认为只允许进行非认证请求的方案更可取。在他们看来,任何需要某种形式认证的网站都应该依靠明确交换的认证令牌。^([79])</p>
<p>对 CORS 的另一种,更实际的批评是,该方案过于复杂:它扩展了一个已经问题重重且容易出错的 API,而没有清楚地解释一些调整的好处。特别是,预先请求增加的复杂性是否值得能够以非常规方法或随机头信息发出跨域请求的边缘利益并不清楚。</p>
<p>最后一点微弱的投诉基于这样一个事实,即 CORS 容易受到头信息注入的影响。与其他一些最近提出的浏览器功能不同,例如 WebSockets (第十七章),CORS 不需要服务器回显一个不可预测的挑战字符串来完成握手。特别是与预检缓存结合使用时,这可能会加剧服务器端代码中某些头信息分割漏洞的影响。</p>
<h2 id="xdomainrequest">XDomainRequest</h2>
<p>微软对 CORS 的反对似乎源于对使用环境权限的上述担忧,但也隐含着他们对与 W3C 互动的不满。2008 年,微软的程序经理 Sunava Dutta 提出了这个有些神秘的见解:^([241])</p>
<blockquote>
<p>在[Internet Explorer 8] Beta 1 阶段,针对使用跨站 XMLHttpRequest 和访问控制框架进行跨域访问第三方数据提出了许多基于安全性的担忧。自 Beta 1 以来,我们有幸与其他浏览器和 W3C 面对面会议的与会者合作,以改善 W3C 访问控制框架的服务器端体验和安全性。</p>
</blockquote>
<p>微软没有接受 CORS 对<em>XMLHttpRequest</em>的扩展,而是决定实施一个名为<em>XDomainRequest</em>的反提案。这个非常简单的新 API 与其他浏览器中可用的变体不同,其结果是请求始终是匿名的(即没有任何浏览器管理的凭证),并且它不允许使用任何自定义 HTTP 头或方法。</p>
<p>使用微软的 API 在其他方面与<em>XMLHttpRequest</em>非常相似:</p>
<pre><code>var x = new XDomainRequest();
x.open("GET", "http://www.fuzzybunnies.com/get_data.php?id=1234");
x.send();
</code></pre>
<p>借鉴 W3C 的提案,生成的请求将包含一个<em>Origin</em>头,并且只有在响应中存在匹配的<em>Access-Control-Allow-Origin</em>头时,响应数据才会被调用者看到.^([80]) 预检请求和权限缓存不是设计的一部分。</p>
<p>就目的和用途而言,微软的解决方案比 CORS 更合理:它更简单、更安全,并且在所有可能的用途中可能同样有效。然而,它并不受欢迎。它仅在 Internet Explorer 8 及以上版本中受支持,由于 W3C 支持 CORS,其他人没有理由很快接受<em>XDomainRequest</em>。</p>
<p>同时,另一组研究人员在 W3C 的赞助下提出了第三个解决方案。他们的设计被称为统一消息策略(包括相应的<em>UniformRequest</em> API),^([243]) 采用的方法几乎与微软的方案相同。它不被任何现有浏览器支持,但有人谈论将其与 CORS 统一。</p>
<h2 id="origin-头的其他用途">Origin 头的其他用途</h2>
<p><em>Origin</em>头是 CORS、<em>XDomainRequest</em>和 UMP 的重要组成部分,但实际上它是独立于其他用途而演化的。在他们的 2008 年论文中,Adam Barth、Collin Jackson 和 John C. Mitchell^([244]) 倡导引入一个新的 HTTP 头,该头将提供一种比<em>Referer</em>更可靠和更注重隐私的替代方案。它还将通过向服务器提供所需的信息来识别请求的 SOP 级别源,而无需披露可能更敏感的路径或查询数据。</p>
<p>当然,是否在<em>Referer</em>及其建议的继任者之间存在的细微改进实际上会对那些基于隐私原因阻止第一个头的小部分但不可忽视的用户群体产生影响,这一点尚不清楚。因此,该提案最终陷入了一种虚拟的停滞状态,既没有被任何现有浏览器部署,也阻止了其他人追求其他解决方案,如 XSRF 或 XSSI.<sup>([245])(公平地说,这个概念最近以*From-Origin*的新名字被重新启用,可能还没有完全消失。)</sup>([246])</p>
<p>除了原始想法的命运之外,<em>Origin</em>头在 CORS 等特殊案例中的效用相当明显。大约在 2009 年,这导致了 Barth 提交了一份 IETF 草案,指定了头的语法,^([247]) 同时回避了关于何时发送头或它可能解决的具体安全问题的声明:</p>
<blockquote>
<p>用户代理可以在任何 HTTP 请求中包含一个 Origin 头。</p>
<p>[...]</p>
<p>当用户代理从“隐私敏感”的上下文中发起 HTTP 请求时,用户代理必须在 Origin 头中发送值“null”。</p>
<p>注意:本文件未定义隐私敏感上下文的概念。生成 HTTP 请求的应用程序可以将上下文指定为隐私敏感,以对用户代理生成 Origin 标头的生成方式施加限制。</p>
</blockquote>
<p>本规范的底线是,无论决策过程如何,一旦客户端选择提供该头,该值必须准确表示发起请求的 SOP 原因。例如,当特定操作从 <a href="http://www.bunnyoutlet.com:1234/bunny_reports.php" target="_blank"><code>www.bunnyoutlet.com:1234/bunny_reports.php</code></a> 发生时,传输的值应该是</p>
<pre><code>Origin: http://www.bunnyoutlet.com:1234
</code></pre>
<p>对于无法有意义地映射到协议-主机-端口号元组的来源,浏览器必须发送 <em>null</em> 的值。</p>
<p>尽管有所有这些计划,但截至本文撰写时,只有一种浏览器在非 CORS 导航中包含 <em>Origin</em> 头:基于 WebKit 的实现会在提交 HTML 表单时发送它。Firefox 似乎正在考虑不同的方法,但似乎还没有实施任何具体措施。</p>
<hr>
<p>^([77]) 恶意 URL 黑名单,这是该趋势的一个主要例子。黑名单是防病毒软件的一个轻量级、粗略的替代品,而防病毒软件本身又是不更新且设计不佳的软件的一个糟糕替代品。反恶意软件功能并不会使个别攻击更加困难;它们只是旨在阻止低级恶意软件的大规模传播,基于大多数用户不够有趣,不会成为特定目标或被巧妙攻击的假设。</p>
<p>^([78]) 该假设并不完全正确。例如,在引入此方案之前,攻击者无法发起一个与文件上传表单提交完全无法区分的跨域请求,但在 CORS 下,这种伪造是可能的。</p>
<p>^([79]) 同样的说法也可以用于任何其他设置中 HTTP cookies 的使用,看起来同样徒劳。确实,环境凭证比其他一些显式认证形式更频繁地引起问题,但它们的使用也更为方便,并且根本不会消失。</p>
<p>^([80]) 即使响应未经过认证,进行此检查的原因是为了防止将浏览器用作代理(例如,用于爬取内部网络或发送垃圾邮件)。</p>
<h1 id="安全模型限制框架">安全模型限制框架</h1>
<p>扩展同源策略边界的方案相对简单易懂,并且通常安全失败。如果提议的更改没有在可能的代码路径之一中得到考虑,或者在某些特定浏览器中根本不支持,那么之前实施的、更严格的逻辑将启动。与这种方法相比,在现有的浏览器安全模型之上建立新的边界要危险得多。这是因为每个与安全相关的代码路径都必须调整以识别新的方案,并且每个浏览器都必须立即遵守,否则将出现意外问题。</p>
<p>在本节中,我们将快速查看一些更成功的尝试,这些尝试试图走这条危险但可能有益的道路——并探讨它们在哪里分道扬镳。</p>
<h2 id="内容安全策略">内容安全策略</h2>
<p><em>内容安全策略</em>(<em>CSP</em>)是一个不寻常的全面安全框架,最初由 Mozilla 的 Brandon Sterne 于 2008 年提出。^([[248]) 该框架最初设想为一种全面的方法来减轻常见网络漏洞的影响,从 XSRF 到 XSS,以及作为网站所有者执行各种非安全内容管理任务的工具。</p>
<p>在随后的几年里,CSP 迅速发展,在几次重大事件中,其范围发生了重大变化。(例如,作者迅速放弃了处理 XSRF 漏洞的计划,将这项工作委托给了尚未实现的<em>Origin</em>头部扩展。)事实上,截至本文撰写时,Mozilla 的规范正在被重写为 W3C 草案。^([[249]) 这导致了 Firefox 中发布的实现与 Adam Barth 在 WebKit 中实现的有限支持之间存在重大差异。(Internet Explorer 和 Opera 不支持 CSP,并且没有宣布任何具体计划来采用它。)</p>
<h3 id="主要-csp-指令">主要 CSP 指令</h3>
<p>在其核心,Sterne 的设计允许网站所有者为每个文档指定策略,以限制受主体文档执行通常在相同源策略下允许的操作的能力。例如,CSP 可能阻止页面加载任何外部子资源,除了图像,并限制图像源仅限于一组受信任的来源,如下所示:</p>
<pre><code>X-Content-Security-Policy: default-src 'none'; img-src http://*.example.com
</code></pre>
<p>如此例所示,策略可能被编码在 HTTP 头部中。根据 W3C 草案,也可以将它们嵌入到文档本身(使用<em><meta></em>标签)或者在外部 URL 上托管策略并通过<em>policy-uri</em>指向它。</p>
<p>对于每个内容源指令,策略的作者可以指定任意数量的完全限定起源或匹配多个主机、协议或端口的通配符表达式。三个特殊关键词(<em>none</em>、<em>self</em>和<em>data:</em>)分别对应一个空集、与携带策略的页面关联的起源或所有内联<em>data:</em> URL,顺序对应。</p>
<p>到目前为止,以下行为可以通过 CSP 指令进行控制:</p>
<ul>
<li>
<p><strong>脚本执行</strong> 可以使用 <em>script-src</em> 指令来指定允许的 <em><script src=...></em> URL 的协议、主机和端口。通常,CSP 会禁用在文档中内嵌脚本的能力(无论是通过独立的 <em><script></em> 块还是通过事件处理器),以及现有的脚本不小心将字符串传递给函数,如 <em>eval(...)</em>、<em>setTimeout(...)</em>、<em>setInterval(...)</em> 等等。正因为如此,<em>script-src</em> 指令对于限制 XSS 漏洞的影响是有用的:攻击者注入的任何标记都将限制在合法托管在批准来源之一的脚本加载。^([81])</p>
</li>
<li>
<p><strong>插件内容</strong> 这通过 <em>object-src</em> 控制。由于 Java 或 Flash 等插件可能对嵌入页面有不受限制的访问权限,该指令应被视为与 <em>script-src</em> 大致相似,并且两个指令必须以类似的方式限制,以实现任何安全效益。</p>
</li>
<li>
<p><strong>样式表和字体</strong> 这由 <em>style-src</em> 和 <em>font-src</em> 控制。与处理脚本的方式不同,CSP 最初并没有阻止内联 <em><style></em> 块或 <em>style=</em> 参数出现在页面上。因此,任何利用 XSS 漏洞的攻击者都可以大幅改变受影响页面的外观和功能(或者更糟),^([82]) 这两个指令仅服务于非安全目标,可能唯一的例外是限制混合内容漏洞。就在本书出版前的一刻,规范已经被修改,以包括对 CSS 的更稳健的方法。</p>
</li>
<li>
<p><strong>被动多媒体</strong> 指令如 <em>img-src</em> 或 <em>media-src</em> 控制从特定来源嵌入多媒体内容的能力。与 CSS 控制的原设计一样,这不能被认为是一个安全特性。例如,在 XSS 漏洞的情况下,CSP 不会阻止攻击者利用样式表在受影响页面上绘制任意形状,甚至在一定程度上进行动画化。</p>
</li>
<li>
<p><strong>子框架</strong> <em>frame-src</em> 指令指定了在页面上遇到的任何 <em><iframe></em> 标签的可接受目标;框架文档不会继承父页面的策略。为了保持其他 XSS 缓解措施的价值,必须采取措施不允许 <em>data:</em> URL 出现在这里(参见第十章)。</p>
</li>
<li>
<p><strong>默认策略</strong> 在 W3C 草案中被称为 <em>default-src</em>,在 Mozilla 文档中有一个更神秘的名称(<em>allow</em>),该指令指定了任何未由更具体指令覆盖的内容的回退行为。即使在技术上不必要的情况下,该指令也是必需的。</p>
</li>
</ul>
<h3 id="备注-1">备注</h3>
<p>由于 CSP 指令被选用来非常紧密地映射到单个 HTML 标签,而不是按功能相似的行为分组,这可能是令人遗憾的。因此,很难理解 <em>script-src</em>、<em>frame-src</em> 和 <em>object-src</em> 等设置之间的复杂交互。此外,这种方法在未来的安全性方面也并不很高:已经有一些边缘类别的子资源(例如“favicons”)被完全排除在 CSP 之外,而这个列表可能会无意中增长。</p>
<p>与迄今为止概述的子资源驱动模型不同,CSP 还包含一个名为 <em>frame-ancestors</em> 的古怪指令。此参数旨在通过以类似于已建立的 <em>X-Frame-Options</em> 标头(在第十一章第十一章。同源规则之外的生活中概述)的方式指定当前文档的允许祖先来减轻点击劫持的影响。<em>frame-ancestors</em> 逻辑与 <em>default-src</em> 或 CSP 的其他任何部分完全独立;其默认值为 “*”。</p>
<p>在撰写本文时,正在讨论许多其他可能的策略扩展。这包括一个 <em>script-nonce</em> 指令,可以用于更安全地嵌入内联脚本(每个脚本块都必须以策略指定的、不可预测的令牌开始,这通常使得 XSS 利用更加困难),以及一个 <em>sandbox</em> 指令,它提供了一个到另一种安全机制的替代接口,这在 沙盒框架中进行了讨论。</p>
<h3 id="策略违规">策略违规</h3>
<p>根据这些规则指定的策略限制了底层文档的行为。违规通常会导致子资源加载失败、内联脚本执行失败或页面渲染抑制(在特殊情况下为 <em>frame-ancestors</em>)。</p>
<p>由于 CSP 控制了广泛的内容行为,并且默认的失败模式相当残酷,作者认为有必要减轻网站管理员的不安。为了使 CSP 更易于使用,也许还出于一种天真的尝试提供利用检测,CSP 的一个可选功能允许浏览器立即将所有策略违规报告给网站的拥有者。此功能可以通过策略中的 <em>report-uri</em> 关键字启用。为了进一步简化部署,还可能以“软”模式推出任何策略或其部分,其中违规只会导致 HTTP 通知,而不会真正破坏页面。这是通过在名为 <em>X-Content-Security-Policy-Report-Only</em> 的头中指定策略来实现的。^([83])</p>
<h3 id="对-csp-的批评">对 CSP 的批评</h3>
<p>与浏览器世界中大多数一次性安全功能相比,CSP 是一个非常合理和一致的设计。然而,从其诞生之日起,该提案就一直在设计实现方面的持续担忧中困扰。</p>
<p>关于 CSP 的最常见的抱怨可能不是安全问题:为了从框架提供的 XSS 防御中受益,网站管理员必须将页面上的所有内联脚本(通常是数百个单独的代码片段)移动到一个单独请求的文档中;在 CSP 的新草案中,所有样式表也需要这样做。将现有页面改造为与 CSP 兼容的复杂性以及额外 HTTP 请求的性能惩罚往往是不可行的。(可能可以通过最近草案中提出的<em>script-nonce</em>扩展来解决这个问题。)</p>
<p>对于 CSP(内容安全策略)的设计,一个更根本的担忧是,目前设想的规则集的原始粒度可能不足以提供足够的防御来抵御 XSS 攻击。考虑这样一个事实:任何复杂、现实生活中的领域都可能托管数十个相互独立的 Web 应用程序,每个应用程序可能包含数百个可能无关的静态脚本和 JavaScript API。攻击者利用 CSP 保护网站中的 XSS 漏洞,被阻止直接执行恶意脚本,但他们可能通过在错误上下文或错误顺序中加载现有脚本,将应用程序置于不一致且可能危险的状态。非 Web 软件中漏洞的历史表明,这种状态破坏条件比我们想象的更容易被利用。</p>
<p>更令人担忧的是,攻击者可以加载一个并非真正是脚本的子资源,但可能会被误认为是脚本。一个极端的例子是支持 E4X(见第六章}<em>——当通过</em><script src=...><em>加载时,可能会导致代码执行。认识到这个问题,开发者决定要求为在 CSP 下加载的任何脚本指定白名单</em>Content-Type*值,但即使这种方法也往往是不够的。</p>
<p>为了理解可能出错的地方,考虑一个极其常见的做法,即在公共 JSONP API 中托管,客户端可以指定回调函数的名称:</p>
<pre><code>GET /store_locator_api.cgi?zip=90210&callback=`myResultParser` HTTP/1.0
...
HTTP/1.0 200 OK
Content-Type: application/x-javascript
...
`myResultParser`({ "store_name": "Spacely Space Sprockets",
"street": ... });
</code></pre>
<p>在 CSP 允许的源中的任何地方,这样的 API 都可能被攻击者用来调用客户端代码中的任意现有函数,可能还伴随着攻击者控制的参数。如果<em>回调</em>字符串没有被限制为字母数字(为什么应该限制呢?),指定<em>callback=alert(1);//</em>将直接导致代码注入。</p>
<p>除去粒度问题,CSP(内容安全策略)有时因令人困惑且有害的缺乏关注而应受到一些温和的批评。一方面,通过包含诸如 <em>frame-descendants</em> 或 <em>sandbox</em> 这样的指令,它似乎在试探性地构建一个单一、统一的浏览器安全框架——然而却意外地将 XSRF 漏洞排除在其范围之外,而没有提供除对 <em>Origin</em> 的模糊提及之外的任何可行的替代方案。另一方面,该提案往往只希望成为一项“内容策略”,并未特别关注提供足够强大和直观的安全特性。创建危险脚本策略的简便性,加上最初对样式表和图像监管的不力,都是这一趋势的明证。</p>
<h2 id="沙盒框架">沙盒框架</h2>
<p>沙盒框架是正常 <em><iframe></em> 行为的一种扩展。它们允许顶层页面的所有者对嵌入的文档及其任何子框架施加某些额外的限制。目标是使网络应用程序在敏感网站上嵌入可能不受信任的广告、小工具或预格式化的 HTML 文档时更加安全。该设计精炼和 WebKit(目前唯一支持该功能的引擎)中此功能的初始实现是由 Adam Barth 推动的。</p>
<h3 id="注意-65">注意</h3>
<p>奇怪的是,沙盒框架并不是一个全新的想法:微软几乎十年前就提出了一个类似的提案。自第 6 版以来,Internet Explorer 支持一个专有的 <em>security=restricted</em> 参数,该参数强制目标框架在受限区域渲染,从而有效地移除了其执行脚本、导航到其他位置等能力。然而,似乎没有人对使用此功能做任何事情感兴趣,除了绕过某些客户端 JavaScript 安全机制(最著名的是 anticlickjacking 检查)。我们很快就会知道 HTML5 的继任者是否表现得更好。</p>
<p>沙盒框架的设计相当简单:任何嵌入文档的框架都可以通过在适当的 <em><iframe></em> 标签上指定 <em>sandbox</em> 参数来受到限制。默认情况下,受到此限制的文档将无法执行脚本和执行某些类型的导航。可以通过一个或多个空格分隔的关键字来微调权限,这些关键字作为 <em>sandbox</em> 参数本身的值指定:</p>
<ul>
<li>
<p>Allow-scripts 在没有此关键字的情况下,框架内显示的文档将无法执行 JavaScript 代码。此功能的主要功能是防止嵌入的文档执行 DoS 攻击、打开浏览器对话框或使用页面上的任何其他复杂自动化。</p>
</li>
<li>
<p>允许表单(Allow-forms)当这个关键字不存在时,在嵌入文档中遇到的任何 HTML 表单将无法工作。这种机制旨在防止框架内容利用其在受信任网站上的位置来钓鱼敏感信息。(注意:如果启用了 <em>allow-scripts</em>,则 <em>allow-forms</em> 几乎没有意义。脚本可以轻松构建类似表单的控件,并自动将收集到的信息发送到另一个网站,而无需功能性的 <em><form></em> 标签。)</p>
</li>
<li>
<p>允许顶级导航(Allow-top-navigation)这个关键字重新启用嵌入页面导航顶级窗口的能力。这种类型的导航通常作为同源策略(见第十一章来表示仅应在沙盒框架中显示的内容。他们的理由是,浏览器通常不会识别这种 MIME 类型,并且不会将其内联显示,并且可能在<em><iframe></em>处理代码中创建一个特殊情况。当然,正如从第十三章中可以清楚地看到的,这种防御是不充分的,因为一些浏览器和插件会内联渲染<em>text/html-sandboxed</em>响应或以其他令人不安的方式解释返回的数据(例如,作为<em>crossdomain.xml</em>)。</p>
</li>
</ul>
<p>由于典型浏览器中源或域级别安全机制的碎片化,合成源的概念也存在严重问题。例如,与密码管理器的危险交互是可能的,这些交互必须被明确阻止,以防止在沙盒文档中自动完成登录表单。此外,还必须在安全提示中添加特殊逻辑,例如与地理位置 API 关联的提示。</p>
<p>经过一些尝试和错误,目前 WebKit 中可用的实现已经针对这些问题的每一个案例解决了许多问题。然而,未来的实现很可能会反复陷入这个陷阱,特别是 HTML5 规范认为这些特性的行为超出了范围,并且没有以任何方式指定所需的行为。</p>
<h3 id="注意-66">注意</h3>
<p>移除合成源也会带来麻烦:如果用户在沙盒广告中点击了一个同站链接,并且该链接在新窗口中打开,那么浏览器可能应该阻止新窗口中的无限制脚本通过<em>打开者</em>对象执行其父级被禁止执行的操作。</p>
<h2 id="严格传输安全">严格传输安全</h2>
<p>HTTPS 设计中最显著的弱点之一是用户经常通过在地址栏中输入无协议 URL(例如<a href="http://bankofamerica.com" target="_blank">bankofamerica.com</a>而不是<a href="https://www.bankofamerica.com" target="_blank"><code>www.bankofamerica.com</code></a>)开始导航,在这种情况下,浏览器将假定使用 HTTP 并以明文发送初始请求。即使网站立即将流量重定向到 HTTPS,任何活跃的攻击者都可能在受害者的网络上拦截并修改该初始响应,从而阻止用户升级到安全协议。在这种情况下,浏览器 UI 中缺少小锁图标将很容易被忽略。</p>
<p>这个问题以及与混合内容和 cookie 作用域相关的几个外围问题促使 Jeff Hodges 和几位其他研究人员起草了一个关于 HTTP 严格传输安全(HSTS,或简称 STS)的提案。^([[251]) 他们的方法(目前被 WebKit 和 Firefox 支持)允许任何网站在互联网上指示浏览器,所有未来针对特定主机名或域名的请求都应该始终使用 HTTPS,并且任何 HTTP 流量都应该自动升级并仅通过 HTTPS 提交。</p>
<p>HSTS 设计背后的推理是,用户与特定域的第一次交互不太可能发生在被积极篡改的连接上——但随着时间的推移,当用户在开放的无线网络上漫游时,遇到攻击者的可能性会迅速增加。因此,HSTS 是一种不完美的防御,但在实践中通常足够好。</p>
<p>HSTS 的 opt-in 头可能在 HTTPS 响应中出现,看起来可能像这样:</p>
<pre><code>Strict-Transport-Security: max-age=3000000; includeSubDomains
</code></pre>
<h3 id="注意-67">注意</h3>
<p>为了让 HSTS 提供合理的保护,<em>max-age</em>(STS 记录可以存储在浏览器中的秒数)必须设置为一个远高于通常最坏情况下的网站访问时间间隔的值。由于没有简单的方法来禁用或覆盖 HSTS,当 HTTPS 网站出现问题时,网站所有者可能会选择一个足够小的值,以最小化当他们搞砸事情并需要回滚时的干扰。不清楚这种利益冲突是否会导致网络程序员做出最佳选择。</p>
<p>这种设计的安全后果相当不明显:DoS 攻击的风险略有增加,因为攻击者可以将此响应头注入尚未完全启用 HTTPS 的域。还有使用 HSTS 设置的唯一组合为几个诱饵主机名标记特定浏览器实例的可能性,这为基于 cookie 的用户跟踪提供了另一种选择。然而,这些担忧并不特别突出。</p>
<p>不幸的是,正如本书本节讨论的其他添加限制的框架一样,机制在原则上听起来很棒,但很难完全考虑到它可能与其他遗留代码的交互。特别是,除非使用<em>includeSubDomains</em>标志,否则 HSTS 对 HTTP cookie 的保护出人意料地少:未标记为<em>secure</em>的 cookie 仍然可能通过创建一个不存在的子域并拦截发送到该目的地的 HTTP 请求而被拦截。^([[84]) (即使是<em>secure</em>的 cookie 也可能以类似的方式被破坏,只是不能读取回。)</p>
<p>在类似的情况下,对来自插件内容请求的 HSTS 强制执行可能不会很好地工作。</p>
<h2 id="隐私浏览模式">隐私浏览模式</h2>
<p>私密浏览,俗称“色情模式”,是大多数最新浏览器中的一项非标准化功能。它的目的是创建一个非持久的浏览沙盒,与主浏览器会话隔离,一旦最后一个私密浏览窗口关闭,就会完全丢弃。从某种意义上说,这种机制可以被视为在现有浏览器安全范式之上添加的一种内容隔离形式,因此现在简要提及它是合适的。</p>
<p>除了 Chrome 之外,大多数浏览器厂商并没有准确解释与私密浏览相关的安全保证。不幸的是,对这一术语的直观理解与浏览器实际能提供的内容大相径庭。</p>
<p>不可否认,对该功能的直接解释是,私密浏览会话应该是完全匿名的,并且用户的任何活动数据都不会在系统中持久化。这两个假设已经部分被现代操作系统的网络堆栈和内存管理实践所破坏。但即使在浏览器内部,实现合理的匿名性几乎是不可能的。几乎每一种有状态的浏览器机制,从地理位置或弹出权限到严格传输安全到表单自动完成,再到基于插件的持久数据存储,都必须进行修改,以便正确处理两种浏览模式之间的区别,对于每个厂商来说,实现这一目标都是一场艰难的战斗。也许更令人沮丧的是,匿名性还受到脚本通过检查系统的特征(如已安装插件的集合、字体、屏幕分辨率、窗口大小、时钟漂移,甚至非加密安全的 PRNG 的行为)来唯一标识任何给定系统的能力的影响。252]</p>
<p>最后,尽管表面上看起来相反,私密浏览模式仅适用于防止向同一台机器上其他非技术用户泄露数据,而且有时甚至难以实现这一目标。</p>
<hr>
<p>^([81]) 内容安全策略(CSP)在这里提供了几种可能导致自我伤害的方法。一方面,可以通过设置如<em>inline-script</em>(Mozilla 的命名,在 W3C 草案中改为<em>disable-xss-protection</em>)或<em>eval-script</em>来重新启用脚本执行。也许不那么明显的是,还可能犯下允许<em>data:</em>或***作为允许的源或允许 HTTPS 站点上的 HTTP 源的错误。</p>
<p>^([82]) 还记得 CSS3 中的高级选择器吗?通过巧妙地在注入样式中利用它们,可以方便地将页面中出现的字符串信息传递给第三方服务器,而无需使用 JavaScript。</p>
<p>^([83]) 作为旁注,这个特性不仅对短期实验有用,而且可以持续检测非关键问题。例如,网站所有者可以利用它通过为将被任何 HTTP 脚本违反的 HTTPS 页面创建仅报告策略来检测混合内容问题。</p>
<p>^([84]) 从第九章回顾,我们知道在某些浏览器中创建主机作用域的 cookie 相当棘手,而在 Internet Explorer 中则完全不可能拥有。</p>
<h1 id="其他发展">其他发展</h1>
<p>本章之前讨论的安全功能旨在改变 Web 应用程序之间的边界,以及网站之间交互的方式。另一组提出的机制虽然不属于简单的分类,但仍然重要或足够成熟,值得在此简要提及。我们现在将回顾其中的一些。</p>
<h2 id="浏览器内-html-清理器">浏览器内 HTML 清理器</h2>
<p>XSS 漏洞是现代 Web 应用程序中遇到的最常见的安全问题。因此,如此少的提议安全框架旨在全面解决这个问题,这确实令人惊讶。诚然,CSP 是一个强有力的竞争者,但它要求对 Web 应用程序的编写方式进行根本性的改变,并且不能特别逐步或选择性地部署。另一方面,沙盒框架可能过于资源密集且使用不便,不适合显示数百个用户提供的短片段数据的最常见任务。</p>
<p>可能解决许多 XSS 问题的最佳方法之一是让 Web 框架提供解析的、无歧义的、二进制 DOM 树给浏览器。这样的解决方案将消除与模板转义和 HTML 清理相关的大多数问题。一个更实际的选择可能是为 Web 开发者提供一种强大的工具,以标记攻击者提供的字符串的边界,并限制嵌入有效负载的行为或外观,而无需对其进行转义或清理。可以想象这样的语法:</p>
<pre><code><sandbox token="random_value12345" settings="allow_static_html">
...any unsanitized text or HTML...
</sandbox token="random_value12345">
</code></pre>
<p>如果使用这样的工具,攻击者将无法逃出这样的沙盒,除非猜出随机生成的<em>令牌</em>边界的正确值,否则无法移除对脚本的限制。</p>
<p>可惜,这样的提议不太可能成为 HTML5 的一部分,也不会在任何浏览器中发布,因为这种序列化在本质上与 XML 不兼容,而修改 XML 本身以允许 HTML 中的隐蔽用例是一项难以实现的行动。令人沮丧的是,XML 已经提供了一种类似的方法,可以在<em><![CDATA[...]]></em>块内封装任意数据,但如果没有基于令牌的防护,这个沙盒在利用 XSS 时可以轻易逃逸。</p>
<p>相反,限制客户端脚本生成的任何 HTML 的权限要容易得多。从 Internet Explorer 8 开始,微软提供了一个简单但相对不灵活的 <em>toStaticHTML(...)</em> API,^([253]) 它承诺从传递给它的任何完全限定的 HTML 片段中移除 JavaScript。此方法输出的设计是为了安全地分配给现有 DOM 中的 <em>innerHTML</em> 属性.^([85])</p>
<p>微软的提议是可行的,但它回避了最常见且问题最多的任务,即安全地显示服务器提供的文档。而且它的 API 有一个微小但完全不必要的弱点:在调用 <em>toStaticHTML(...)</em> 后但在 <em>innerHTML</em> 赋值之前修剪或连接清理后的输出是出乎意料的危险,许多网络开发者可能会尝试这种做法。一个更合理的做法是在将内容清理仅限于赋值给 <em>innerHTML</em>。实际上,WebKit 工程师曾短暂讨论过这样一个 API 的提议(也称为 <em>innerStaticHTML</em> 或 <em>safeInnerHTML</em>),但这项努力似乎早已无疾而终。</p>
<h2 id="xss-过滤">XSS 过滤</h2>
<p>减少跨站漏洞的发生率是困难的,限制其影响也同样困难。正因为如此,一些研究人员得出结论,检测和阻止此类漏洞的利用可能是一个更好的选择。因此,大约在 2008 年,微软的 David Ross 宣布将在即将发布的 Internet Explorer 8 中包含 XSS 检测逻辑;^([254]) 几个月后,Adam Barth 在 WebKit 中实现了类似的功能。这些实现将当前 URL 的部分与检索到的页面上的任何字符串或传递给诸如 <em>document.write(...)</em> 和 <em>innerHTML</em> 等 API 的字符串进行比较。如果比较发现页面上可能存在的 JavaScript 部分可能源自未正确转义的 URL 参数,则页面的相关部分可能被替换为无害的字符串。</p>
<p>可惜,这个看似优雅的想法已知会导致严重问题。除了偶然的误报(Internet Explorer 8 的用户在访问<em><a href="http://www.google.com/search?q=" target="_blank">http://www.google.com/search?q=</a><script></em>时可能会遇到意外麻烦)之外,过滤器还可能被恶意目的触发,通过在 URL 中附加页面的合法部分作为非功能参数。在一个极端且现已解决的案例中,这种行为被用来创建之前不存在的前端跨站脚本(XSS)攻击向量,仅仅是通过欺骗浏览器随意重新排列标记。^([255]) 但更基本的是,对于任何复杂的 Web 应用来说,选择性地禁用攻击者选择的脚本块都是风险很大的,即使页面的结构在其他方面是正确的,这样的调整也可能轻易地将客户端代码置于不一致或危险的状态。例如,考虑一个在线文档编辑器,它将以下每个功能都实现在一个单独的<em><script></em>块中:</p>
<ol>
<li>
<p>初始化编辑器的内部状态,并使用空起始文档创建用户界面。</p>
</li>
<li>
<p>通过 URL 参数加载用户请求的文档当前版本,并进行错误检查以捕获任何潜在的网络问题。</p>
</li>
<li>
<p>如果没有检测到错误,则进入交互式编辑模式,并自动将文档的当前状态每 30 秒保存一次,保存的 ID 基于 URL。</p>
</li>
</ol>
<p>在这个并非完全不合理的设计中,移除第二步的能力可能会带来灾难性的后果,因为下一步可能会用空白副本覆盖现有的、服务器存储的文档。哎呀。</p>
<p>通过使用更简单的方案可以避免这个问题,即任何疑似 XSS 攻击都会导致浏览器简单地拒绝渲染文档。遗憾的是,相对较高的误报率阻止了作者采取这条路线。经过一番辩论后,微软决定提供基于选择的“严格”阻止模式,可以通过如下响应头切换:</p>
<pre><code>XSS-Protection: 1; mode=block
</code></pre>
<h3 id="注意-68">注意</h3>
<p>除了误报的风险之外,XSS 过滤器还容易产生误报,这种情况可能无法通过多少来改善。按照设计,这些过滤器永远无法检测到更危险的可存储 XSS 漏洞,即错误转义的数据来自除跟随链接之外的其他来源。但即便如此,众多(通常是隐式的)输入转义方案和<em>location.hash</em>或<em>pushState</em>(第十七章:相当安全,但容易误用。避免非简单请求,不允许任意头或方法。如果你控制服务器端应用程序框架,考虑自动从带有非白名单<em>Origin</em>值的传入 CORS 请求中删除<em>Cookie</em>头,以最大限度地减少意外共享用户特定数据的风险。为了最大限度地减少混合内容错误的发病率,考虑在通过纯 HTTP 接收到的任何请求上拒绝 HTTPS <em>Origin</em>值。</p>
<pre><code>谨慎对待*Access-Control-Allow-Origin: **,如果你需要使用它,请确保它只返回你打算共享的位置。
</code></pre>
<ul>
<li>
<p>XDomainRequest:这可以安全使用。与<em>XMLHttpRequest</em>一样,限制从 HTTPS 源访问 HTTP API 可能是消除混合内容错误的好方法。</p>
</li>
<li>
<p><strong>内容安全策略(Content Security Policy):</strong> 这可以作为深度防御的一种安全措施使用。审查与<em>script-src</em>、<em>object-src</em>等之间的交互相关的注意事项,以及允许<em>data:</em>源的危险性。不要意外地允许混合内容:始终在规则集中指定协议,并确保它们与请求页面上提供的协议相匹配。</p>
</li>
<li>
<p><strong>沙盒框架(Sandboxed frames):</strong> 这可以作为从其他源嵌入小工具的安全方式使用,但在不合规的浏览器中,该机制将失败得很严重。你不应该对同源文档进行沙盒化。</p>
</li>
<li>
<p><strong>严格传输安全(Strict Transport Security):</strong> 这可以作为深度防御的一种安全措施使用。请确保将所有相关 cookie 标记为<em>secure</em>,并准备好应对通过伪造、非 STS 位置在您的域中注入 cookie 的可能性。在可行的情况下使用<em>includeSubDomains</em>来减轻这一风险。</p>
</li>
<li>
<p>toStaticHTML(...): 在可用的情况下可以安全使用,但在不合规的浏览器客户端替换它很困难。由于过滤器的设计,绕过漏洞在 API 中有较高的复发概率。</p>
</li>
<li>
<p><strong>私密浏览(Private browsing):</strong> 不要依赖此机制进行安全目的。</p>
</li>
<li>
<p><strong>跨站脚本过滤(XSS filtering):</strong> 不要依赖此机制进行安全目的。始终在 HTTP 响应中显式指定<em>XSS-Protection: 1; mode=block</em>或<em>XSS-Protection: 0</em>。默认设置相当不安全。</p>
</li>
</ul>
<hr>
<p>^([85]) 有趣的是,Internet Explorer 中的 HTML 解析器似乎非常愚钝,以至于甚至<em>toStaticHTML(...)</em>的作者也有些难以理解它。自从其引入以来,该 API 就遭受了相当多的绕过漏洞,其中大多数与处理 CSS 数据有关。</p>
<h1 id="第十七章其他值得注意的浏览器机制">第十七章。其他值得注意的浏览器机制</h1>
<p>为了总结本书的第三部分,我们简要列举了一些最近实施或计划实施的 API,尽管它们并非专为安全目的设计,但可能会在未来的几年内显著改变安全格局。例如,一些 API 改变了 Web 应用程序可以访问的数据类型,或者改变了浏览器与外部世界的通信方式。</p>
<p>以下列表必然是不完整的:每周都会起草新的、合理可信的设计,而旧的方法则可能在实际浏览器发货之前被废弃。尽管如此,本章应该可以作为对未来可能带来什么的有趣快照。</p>
<h1 id="url-和协议级别的提案">URL 和协议级别的提案</h1>
<p>这些功能旨在改变围绕链接行为、地址栏以及通过网络交换数据的过程。</p>
<p><strong>协议注册</strong></p>
<p>Web 应用程序通常假定处理之前为“真实”桌面软件保留的 URL 方案。一个典型的例子可能是<em>mailto:</em>协议,它最初旨在实例化一个独立的邮件应用程序,但如今通常更合理地被路由到网络邮件界面。为此,Mozilla 提出了,WebKit 接受了简单的<em>navigator.registerProtocolHandler(...)</em> API。²⁵⁶] 当调用此 API 时,用户会看到一个简单的安全提示,如果操作得到批准,则将基于 URL 的处理程序与特定的方案关联。截至今天,相关的提示容易受到第十四章中概述的竞争条件的影响,并且它们在其他方面似乎也缺乏,如图 17-1 所示。</p>
<p><img src="https://github.com/OpenDocCN/greenhat-zh/raw/master/docs/tgl-web/img/httpatomoreillycomsourcenostarchimages950045.png.jpg" alt="Firefox 中的一个严重令人困惑的提示。浏览器窗口上方的提示是由浏览器在调用 registerProtocolHandler(...) API 时生成的,协议名称设置为“做真正酷炫的事情”,应用程序名称设置为“Firefox (mozilla.org)”。这个特定的例子是无害的,但更危险的滥用手段就在眼前。" loading="lazy"></p>
<p>图 17-1. Firefox 中的一个严重令人困惑的提示。浏览器窗口上方的提示是由浏览器在调用<em>registerProtocolHandler(...)</em> API 时生成的,协议名称设置为“做真正酷炫的事情”,应用程序名称设置为“Firefox (mozilla.org)”。这个特定的例子是无害的,但更危险的滥用手段就在眼前。</p>
<p><strong>地址栏操作</strong></p>
<p>新引入的 HTML5 <em>history.pushState(...)</em> API,^([257]) 由 Firefox、WebKit 和 Opera 支持,允许当前显示的文档更改地址栏的内容为任何其他同源 URL,而不会实际触发通常与此步骤相关的页面转换。该 API 提供了比广泛滥用的 <em>location.hash</em> 存储应用程序状态的替代方案。有趣的是,尽管它很简单,但它已经导致了相当数量的有趣的安全漏洞。例如,一些实现不仅允许顶级文档更改地址栏中显示的顶级 URL,还允许任何可疑的第三方框架更改地址栏中的顶级 URL,并且它们允许诸如 <em>about:blank</em> 这样的源将大量不受约束的乱码放入 URL 字段。</p>
<p><strong>二进制 HTTP</strong></p>
<p>SPDY^([258]) (“快速”)是一个简单的加密 HTTP 替代品,它保留了协议的关键设计原则(包括大多数头部的布局和功能)。同时,它最小化了与处理并发请求或解析基于文本的请求和响应数据相关的开销。该协议目前仅在 Chrome 中得到支持,除了选定的 Google 服务外,在网络上并不常见。然而,它也可能很快就会出现在 Firefox 中。</p>
<p><strong>无 HTTP 网络</strong></p>
<p>WebSocket^([259]) 是一个仍在发展的 API,旨在协商大量不受约束的双向 TCP 流,用于 TCP 的事务性特性阻碍时(例如,在低延迟聊天应用的情况下)。该协议通过一个带密钥的挑战-响应握手来启动,看起来有点像 HTTP,并且(非常令人惊讶地)仅通过利用目标网站的头部分割漏洞是不可能伪造的。在握手成功后,可以在结果的长连接中双向交换原始数据,每条消息都封装在一个简单的协议帧中。该机制在 WebKit 中得到支持,并且可能很快就会出现在 Firefox 中。</p>
<p><strong>P2P 网络</strong></p>
<p>WebRTC^([260]) 是一组提议的 API 和网络协议,旨在无需集中式服务器基础设施即可促进与其他浏览器的发现和通信。此类协议的主要用例是在 Web 应用中实现 IP 电话和视频会议功能。目前还没有稳定的浏览器支持。</p>
<p><strong>离线应用</strong></p>
<p>缓存清单^([261]) 是一种相对简单的方法,允许 Web 服务器指示浏览器,某些文档的副本应无限期存储并重复使用,只要客户端似乎没有网络连接。结合客户端存储机制,如<em>localStorage</em> (第九章),这允许某些自给自足的 JavaScript 应用程序在离线模式下使用。Firefox、WebKit 浏览器和 Opera 支持离线操作。与<em>localStorage</em>一样,这种机制的持久性可能会加剧访问不受信任网络的长远后果。</p>
<p><strong>更好的 cookies</strong></p>
<p><em>Cake</em>^([262]) 是由 Adam Barth 起草的一个现已失效的提议,旨在创建一个比 HTTP cookies 更轻量级、更安全的替代品:为每个目标网站生成一个与源绑定、浏览器生成的 nonce。一个更现代但尚不完整的提议似乎在正常但基于源的 cookies 作为替代品之间摇摆。这两种方法目前在任何浏览器中都不可用。</p>
<h1 id="内容级功能">内容级功能</h1>
<p>本节概述的提议旨在使新的 Web 应用程序类别能够在 HTML 和 JavaScript 之上构建。</p>
<p><strong>客户端数据库</strong></p>
<p>几年来,已经提出了多个用于创建和操作本地存储数据库的 API,包括臭名昭著的<em>WebSQL</em> API,^([263])它本可以将著名的危险 SQL 语法带到客户端 JavaScript 中。WebSQL 提议被放弃,转而采用更合理的<em>IndexedDB</em>设计,^([264])它提供了一个干净的 API,没有序列化查询,并且具有与<em>localStorage</em>相当的安全模型——但这是在 WebSQL 支持在几个浏览器中发布之后。与此同时,新的 API 已经发布在 Chrome 中,并预计将在 Firefox 中出现。</p>
<p><strong>后台进程</strong></p>
<p>在 Firefox、WebKit 和 Opera 中可用的<em>Worker</em> API,^([265]) 允许创建后台 JavaScript 进程来执行计算密集型任务,而无需担心阻塞浏览器 UI。每个 worker 都在一个隔离的环境中运行,缺少通常的<em>window</em>或<em>document</em> DOM,并且可以通过<em>postMessage(...)</em> API 异步与其创建者通信。<em>专用 worker</em>只能由其创建者直接访问,而<em>共享 worker</em>可以在任何给定时间“附加”到几个不同的网站。(<em>持久 worker</em>,即独立于对其服务的持续需求运行的 worker,最初被提出,但后来被放弃。)Worker 线程的概念引发了一些外围的 DoS 担忧,但否则没有明显的安全风险。</p>
<p><strong>地理位置发现</strong></p>
<p>The <em>navigator.geolocation.getCurrentPosition(...)</em> API^([266]) 允许任何网站在用户(很大程度上可被劫持)同意的情况下请求有关客户端设备物理位置的信息。计算出的地理位置数据可能来自具有合适硬件模块的系统的 GPS 信息,或者根据附近的无线接入点、蜂窝基站等名称进行查询。该 API 在所有主要浏览器中都有支持,除了 Internet Explorer。</p>
<p><strong>设备方向</strong></p>
<p>一个非限制性的事件驱动 <em>DeviceOrientation</em> API^([267]) 允许网站根据加速度计数据读取设备的方向。这个 API 可能针对移动游戏,在配备了适当硬件的系统上的 Firefox、WebKit 和 Opera 中可用。加州大学戴维斯分校的两名研究人员最近演示了一个致命缺陷:在智能手机上,设备的微小移动可能被用来可靠地重建屏幕上的键盘输入,包括在无关网站上输入的密码^([268])。</p>
<p><strong>页面预渲染</strong></p>
<p>Chrome 中的这个实验性功能允许在用户点击特定链接之前预先获取页面,并允许整个 HTML 文档在一个隐藏的标签页中预先渲染^([269]),一旦预测的导航操作发生,就暂时显示出来。如果预先渲染的页面被证明是恶意的话,这个机制会有一些有趣的浏览器安全后果。Chrome 的实现非常小心,直到标签页被揭示才推迟任何破坏性行为,但跨所有浏览器代码库犯错误将非常容易。</p>
<p><strong>导航时间</strong></p>
<p>几个互补的 API,目前仅在 Chrome 中可用,允许某些类型的导航,包括跨域页面加载,能够从客户端 JavaScript 进行非常精确的基准测试^([270])。该接口旨在允许网站所有者识别典型访客所经历的性能瓶颈。API 允许通过分析加载某些第三方内容所需的时间来收集一些与隐私相关的信息,但由于同样的攻击可以通过许多其他方式(例如,在子资源上的 <em>onload</em> 处理器)进行,这可能并不重要。</p>
<h1 id="io-接口">I/O 接口</h1>
<p>下面列出的功能为基于 Web 的脚本提供了新的输入和输出能力。</p>
<p><strong>用户界面通知</strong></p>
<p><em>通知</em> 和 <em>window.notifications</em>^([271]) API 允许在屏幕角落创建仅文本或基于 HTML 的始终显示在顶部的弹出窗口,允许选定的 Web 应用程序温和地通知用户重要事件(如新邮件消息)。用户需要在每个网站上单独同意接收通知,这限制了滥用的风险。尽管如此,仍需注意正确传达微小通知窗口及其随后创建的任何对话框或提示的来源,这一方面花费了一些时间才得以完善。目前该 API 仅在 WebKit 中可用。</p>
<p><strong>全屏模式</strong></p>
<p>已有多个提案流传开来,允许 JavaScript 最大化当前浏览器窗口并隐藏所有浏览器界面。这一功能对于查看演示或观看电影等任务至关重要,但从安全角度来看显然非常危险:一旦控制了整个屏幕,任何恶意页面都可能绘制一个带有虚假地址栏的虚假浏览器窗口。到目前为止,似乎还没有具体的实现可供审查。关于鼠标光标锁定的一个早期提案也在讨论中。</p>
<p><strong>媒体捕获</strong></p>
<p>提出了一套名为 <em>navigator.device.capture</em> 的 API 套件^([272]),旨在让网站能够访问摄像头和麦克风数据。围绕这一机制,尤其是在处理与竞态条件攻击相关的安全提示的弹性方面,引发了明显的安全和隐私担忧。目前该 API 没有稳定的浏览器支持。</p>
<h1 id="第十八章-常见网络漏洞">第十八章. 常见网络漏洞</h1>
<p>到目前为止,我们很少关注常见网络漏洞的分类。深入了解网络应用的基本机制远比记住几千个随机且往往不必要的术语重要;例如,像<em>内存缓冲区操作不当限制</em>(常见弱点枚举)或<em>不安全的直接对象引用</em>(开放式网络应用安全项目)这样的术语在合理的对话中找不到位置——这是正确的。</p>
<p>尽管如此,行业已经提出了一些相当精确的短语,安全研究人员每天都在使用。在详细讨论了浏览器的内部工作原理之后,回顾和强调普通读者可能会看到的术语似乎是有用的。</p>
<h1 id="专门针对-web-应用的漏洞">专门针对 Web 应用的漏洞</h1>
<p>本节中概述的术语仅适用于 Web 上使用的某些技术,并且在“传统”应用安全领域通常没有直接对应物。</p>
<p><strong>跨站请求伪造(XSRF,CSRF</strong>)</p>
<p>由于未能验证服务器端 Web 应用接收到的特定状态改变 HTTP 请求是否由预期的客户端源发起,从而导致的漏洞。这个缺陷允许任何在浏览器中加载的第三方网站代表受害者执行操作。</p>
<ul>
<li><em>参见</em> 第四章 <em>以获取关于 XSRF 的更详细讨论</em>。</li>
</ul>
<p><strong>跨站脚本包含(XSSI</strong>)</p>
<p>由于未能确保敏感的类似 JSON 的响应不会被第三方网站通过<em><script src=...></em>加载,从而导致的缺陷。响应中的用户特定信息可能会泄露给攻击者。</p>
<ul>
<li><em>参见</em> 第六章 <em>以了解该问题(以及潜在解决方案)的概述</em>。</li>
</ul>
<p><strong>跨站脚本(XSS</strong>)</p>
<p>输入验证不足或输出转义不当可能会允许攻击者在易受攻击的网站上植入自己的 HTML 标记或脚本。注入的脚本将能够访问目标 Web 应用的所有内容,在许多情况下,还能访问客户端存储的 HTTP cookies。</p>
<p>限定词<em>反射的</em>指的是注入的字符串仅仅是请求错误回显部分的结果,而<em>存储的</em>或<em>持久的</em>则指的是有效载荷采取更复杂的路径的情况。<em>基于 DOM 的</em>可以用来表示漏洞是由 Web 应用的客户端部分(即 JavaScript)的行为触发的。</p>
<ul>
<li>
<p><em>参见</em> 第四章 <em>以了解 HTML 文档中的常见跨站脚本攻击向量</em>。</p>
</li>
<li>
<p><em>参见</em> 第六章 <em>以了解基于 DOM 的 XSS 风险概述</em>。</p>
</li>
<li>
<p><em>参见</em> 第十三章 <em>以了解与内容嗅探相关的跨站脚本攻击向量</em>。</p>
</li>
<li>
<p><em>查看</em> 第九章 了解 JS 代码的正常安全模型的讨论。</p>
</li>
</ul>
<p><strong>头部注入(响应分割</strong>)</p>
<p>在网络应用服务器端生成的 HTTP 响应中,新行(或等效字符)的转义不足。这种行为通常会导致 XSS、浏览器或代理缓存中毒,以及更多问题。</p>
<ul>
<li><em>查看</em> 第三章 了解缺陷的详细讨论。</li>
</ul>
<p><strong>混合内容</strong></p>
<p>一个总称,用于指在 HTTPS 页面上加载非 HTTPS 子资源。在脚本和小程序的情况下,这种行为使得应用程序对主动攻击者极其脆弱,尤其是在开放的无线网络(如咖啡馆、机场等)上,并且几乎抵消了 HTTPS 的所有好处。与样式表、字体、图像或框架相关的混合内容漏洞的后果通常也相当严重,但更为受限。</p>
<ul>
<li>
<p><em>查看</em> 第四章 和 第八章 了解 HTTPS 站点上的内容特定预防措施。</p>
</li>
<li>
<p><em>查看</em> 第十一章 以了解混合内容处理规则的概述。</p>
</li>
</ul>
<p><strong>开放重定向</strong></p>
<p>一个术语,用于指那些执行基于 HTTP 或脚本请求到用户提供的 URL 的应用程序,而不以任何有意义的方式限制可能的目的地。开放重定向不建议使用,在某些情况下可能可利用,但本身通常并不特别危险。</p>
<ul>
<li><em>查看</em> 第十章 了解不受限制的重定向可能导致 XSS 的案例。</li>
</ul>
<p><strong>Referer</strong> <strong>泄露</strong></p>
<p>通过嵌入外部子资源或提供外部链接意外泄露敏感 URL。父文档 URL 中编码的任何与安全或隐私相关的数据都将泄露在<em>Referer</em>头中,除了片段标识符。</p>
<ul>
<li><em>查看</em> 第三章 了解* Referer *逻辑的详细讨论。</li>
</ul>
<h1 id="网络应用设计时需要注意的问题">网络应用设计时需要注意的问题</h1>
<p>本节中概述的问题是在互联网上开展业务不可避免的情况,在设计或实施新的网络应用时必须妥善考虑。</p>
<p><strong>缓存中毒</strong></p>
<p>长期污染浏览器缓存(或任何中间代理)中目标网络应用的虚构、恶意版本的可能性。由于响应分割漏洞,加密网络应用可能成为目标。对于非加密流量,主动的网络攻击者也可能能够修改请求者收到的响应。</p>
<ul>
<li><em>查看</em> 第三章 了解 HTTP 缓存行为的概述。</li>
</ul>
<p><strong>点击劫持</strong></p>
<p>框架或以其他方式装饰或掩盖另一个 Web 应用程序的一部分,使得受害者在与攻击者的网站交互时,不会意识到单个点击或按键操作被发送到另一个网站,从而导致对用户不利的操作。</p>
<ul>
<li>*参见第十一章以讨论点击劫持和相关 UI 问题。</li>
</ul>
<p><strong>内容和字符集嗅探</strong></p>
<p>描述了浏览器可能会忽略服务器提供的任何权威内容类型或字符集信息,并错误地解释返回的文档的可能性。</p>
<ul>
<li>
<p>*参见第十三章以讨论内容嗅探逻辑。</p>
</li>
<li>
<p><em>参见第四章和第八章以了解</em>Content-Type*数据被忽略的场景。</p>
</li>
</ul>
<p><strong>Cookie 强制(或 Cookie 注入)</strong></p>
<p>由于现代浏览器中机制的设计和实现存在问题,可能导致盲目地将 HTTP Cookie 注入到其他方面坚不可摧的 Web 应用程序的上下文中。Cookie 注入对 HTTPS 应用程序尤其关注。(<em>Cookie 填充</em>是一个较少使用的术语,指的是通过溢出 cookie 容器恶意删除属于另一个应用程序的 cookie。)</p>
<ul>
<li>
<p>*参见第九章以获取有关 cookie 作用域的更多信息。</p>
</li>
<li>
<p>*参见第三章以了解 HTTP Cookie 的一般操作。</p>
</li>
</ul>
<p><strong>拒绝服务(DoS)攻击</strong></p>
<p>一个广泛的术语,表示攻击者有机会使浏览器或服务器崩溃,或者以其他方式使特定目标应用程序的使用变得显著困难。</p>
<ul>
<li>*参见第十四章以了解 JavaScript 的 DoS 考虑因素概述。</li>
</ul>
<p><strong>框架破坏</strong></p>
<p>框架页面无需满足同源检查即可导航到顶级文档的新 URL 的可能性。这种行为可能被用于钓鱼攻击或仅仅是小小的恶作剧。</p>
<ul>
<li>*参见第十一章以了解此及其他框架导航怪癖。</li>
</ul>
<p><strong>HTTP 降级</strong></p>
<p>激活攻击者阻止用户访问特定网站的 HTTPS 版本或将现有的 HTTPS 会话降级为 HTTP 的能力。</p>
<ul>
<li>
<p>*参见第三章以了解 HTTPS 的概述。</p>
</li>
<li>
<p>*参见第十六章以了解严格传输安全,这是针对该问题的提议解决方案。</p>
</li>
</ul>
<p><strong>网络边界</strong></p>
<p>网站利用浏览器与攻击者无法直接访问的目的地进行交互的可能性,例如,与受害者内部网络上的系统。这种攻击可以是盲目的,或者(借助如 DNS 重绑定等攻击)攻击者可能能够看到所有请求的响应。</p>
<ul>
<li>
<p><em>参见第十二章,了解浏览器中的非 SOP 边界概述</em>。</p>
</li>
<li>
<p><em>参见第十五章,其中介绍了针对此风险的潜在方法,即 Internet Explorer 区域模型</em>。</p>
</li>
</ul>
<h3 id="注意-69">注意</h3>
<p>警惕非流行词的漏洞!并非所有漏洞都有吸引人的名称。网络开发者应警惕许多其他实现和设计问题,这些问题超出了本书的范围,但仍然可能造成严重后果。例如,弱伪随机数生成器(特别是用于会话管理目的的);认证和授权检查不足(特别是过度信任浏览器来源的数据);密码学使用不当(发明自己的算法通常是不可取的);等等。关于这些和其他许多失败模式的详细讨论,请参阅 Dowd、McDonald 和 Schuh 合著的《软件安全评估的艺术》(Addison-Wesley,2006 年)。</p>
<h1 id="服务器端代码特有的常见问题">服务器端代码特有的常见问题</h1>
<p>以下问题通常出现在任何 Web 应用程序的服务器托管部分,由于与特定的编程语言或软件组件相关联,因此不太可能出现在客户端。</p>
<p><strong>缓冲区溢出</strong></p>
<p>一种情况,程序允许在特定内存区域存储比容纳传入数据所需空间更多的信息,导致其他重要数据结构的意外覆盖。缓冲区溢出主要发生在底层编程语言中,如 C 或 C++,在这些语言中,它们可以频繁地被利用来执行攻击者提供的代码。</p>
<p><strong>命令注入(SQL、shell、PHP 等)</strong></p>
<p>由于输入过滤不足或输出转义不当,攻击者控制的字符串可能无意中被处理为应用程序使用的解释语言中的语句。(在某种程度上,这与跨站脚本攻击相似。)后果取决于语言的能力,但在大多数情况下,代码执行是最终的结果。</p>
<p><strong>目录遍历</strong></p>
<p>由于输入过滤不足(最常见的是未能正确识别和处理文件名中的“../”段),应用程序可能被欺骗在磁盘上的任意位置读取或写入文件。任何后果都取决于额外的约束,但未受约束的文件写入漏洞通常很容易被利用来运行攻击者提供的代码。</p>
<p><strong>文件包含</strong></p>
<p>如果没有限定词或以 <em>local (LFI)</em> 为前缀使用,该术语在很大程度上等同于与读取相关的目录遍历。另一方面,<em>远程文件包含 (RFI)</em> 是一种通过指定 URL 而不是有效文件路径来利用文件包含漏洞的替代方法。在某些脚本语言中,一个单一的、通用的 API 可以打开本地文件并获取远程 URL。在这些情况下,从攻击者控制的服务器检索文件的能力可能提供实质性的好处,具体取决于数据随后的处理方式。</p>
<p><strong>格式化字符串漏洞</strong></p>
<p>一些常用的库函数接受模板(“格式字符串”),后跟一组函数预期将插入到模板中预定义位置的参数。这种做法在 C 语言中特别常见(如 <em>printf(...)</em>、<em>syslog(...)</em> 等),但并不仅限于该语言。格式化字符串漏洞是由于无意中允许攻击者向这些函数之一提供模板而引起的。根据模板系统的功能和语言的特定情况,此错误可能导致从轻微的数据泄露到代码执行等各种后果。</p>
<p><strong>整数溢出</strong></p>
<p>一种特定于具有有限或无范围检查的语言的漏洞。缺陷是由于开发者未能检测到整数超过了最大可能值并回滚到零、一个非常大的负整数或某些其他硬件特定的意外结果。根据值的用途,这可能会使程序处于不一致的状态,或者更糟,导致在错误的内存位置读取或写入数据(这反过来又可能导致代码执行)。整数 <em>下溢</em> 是相反的效果:超过最小允许值并回滚到一个非常大的正整数。</p>
<p><strong>指针管理漏洞</strong></p>
<p>在鼓励或要求使用原始内存指针的语言(主要是 C 和 C++)中,可以使用未初始化或不再有效的指针(“悬空”),导致诸如 <em>使用后释放</em>、<em>双重释放</em> 以及更多漏洞。这些漏洞将破坏程序的内部状态,通常允许攻击者执行攻击者提供的代码。</p>
<h1 id="附录-a-结语">附录 A. 结语</h1>
<p>好吧,谁能想到。这就是《错综复杂的网络》的结尾!我希望你阅读这本书的乐趣和我过去十年探索浏览器安全世界的乐趣一样。我也希望你在这些页面上的发现能引导你在未来的旅程中,无论它们可能在哪里。</p>
<p>关于这一切应该如何理解:对我来说,现代网络惊人的鲁棒性与其基础的不可解释的不稳定性之间的鲜明对比最初是很难调和的。回顾过去,我认为这为我们自己对确保网络世界的态度提供了一个重要的洞见。</p>
<p>我被一个不舒服的观察所困扰,那就是在现实生活中,现代社会是建立在非常不稳固的基础上的。每天,我们都依赖于数千个随机陌生人的理智、道德标准和自我克制——从出租车司机、食品摊贩到电梯维修技术人员。游戏规则通过一系列威慑机制得到弱化执行,但如果我们相信犯罪统计数据,它们的效力是惊人的低。问题不仅仅在于大多数小偷认为他们可以逃脱他们的罪行,而且他们通常是对的。</p>
<p>在这种意义上,我们的世界几乎只是一个极其复杂的荣誉体系,我们中的大多数人都自愿同意参与其中。这可能没问题:遵守自我强加的规范已被证明是一种聪明的进化策略,这也是我们今天的一部分。一定程度的信任对于以合理的速度推进我们的文明是必不可少的。同样,矛盾的是,尽管短期内存在弱点,但加速的进步使我们所有人从长远来看都变得更强大、更适应。</p>
<p>那么,我们为什么以如此截然不同的方式对待我们的在线存在就难以理解了。例如,为什么我们会对使用密码学不当的开发者感到愤怒,但我们却不在乎我们的门锁可以用安全别针打开?为什么我们会轻视那些无法正确进行输入验证的网页开发者,但我们却不测试我们的早餐是否含有泻药或 LSD?</p>
<p>我能看到的唯一解释是,人类在物理领域的社会互动规则上已经工作了数千年。在这段时间里,整个社会崩溃了,新的社会出现了,在这个过程中,一个越来越复杂的、旨在保护社区的行为规范体系也随之产生。不幸的是,我们很难将这些规则转移到在线生态系统中,而这个世界还非常年轻,还没有机会发展出自己独立的道德规范。</p>
<p>这种现象很容易看到:虽然你的邻居不会试图潜入你的房子,但他可能不会介意使用你的无线网络,因为这样做感觉更像是一种犯罪。他可能反对盗窃,但他可能对非法复制数字内容持矛盾的态度。或者他可能对社区中的粗俗涂鸦皱眉,但对网站被破坏的景象却笑出声来。这些相似之处是存在的,但还不够好。</p>
<p>如果我们追求信息安全世界中的完美,仅仅源于对人类社区如何出现和繁荣的根本误解,那会怎样?像我这样的专家宣扬的是一个基于完全不信任的网络存在模式,但可能错误地如此:随着我们在线互动的复杂性接近现实生活,设计出完美安全软件的可能性正在迅速减少。同时,极端的偏执开始对我们进步的速度造成重大影响。</p>
<p>或许我们正在推销一个失败的配方。如果我们对绝对安全的坚持只会让我们更接近许多其他早期文明悲惨的命运,这些文明在自身缺陷的重压下崩溃并最终消失,那会怎样?我发现这种荒谬的想法很难摒弃。幸运的是,我们知道,从废墟中,新的、更加开明的社会终将有一天会肯定出现。它们的最终性质是任何人都可以猜测的。</p>
<h1 id="注释">注释</h1>
<h3 id="章节第-1">章节第 1</h3>
<p>^([86])</p>
<p>^([87])</p>
<p>^([88])</p>
<p>^([89])</p>
<p>^([90])</p>
<p>^([91])</p>
<p>^([92])</p>
<h3 id="页面第-19">页面第 19</h3>
<p>^([93])</p>
<h3 id="章节第-2">章节第 2</h3>
<p>^([94])</p>
<p>^([95])</p>
<p>^([96])</p>
<p>^([97])</p>
<p>^([98])</p>
<p>^([99])</p>
<p>^([100])</p>
<p>^([101])</p>
<p>^([102])</p>
<p>^([103])</p>
<p>^([104])</p>
<p>^([105])</p>
<p>^([106])</p>
<p>^([107])</p>
<p>^([108])</p>
<p>^([109])</p>
<p>^([110])</p>
<p>^([111])</p>
<h3 id="章节第-3">章节第 3</h3>
<p>^([112])</p>
<p>^([113])</p>
<p>^([114])</p>
<p>^([115])</p>
<p>^([116])</p>
<p>^([117])</p>
<p>^([118])</p>
<p>^([119])</p>
<p>^([120])</p>
<p>^([121])</p>
<p>^([122])</p>
<p>^([123])</p>
<p>^([124])</p>
<p>^([125])</p>
<p>^([126])</p>
<p>^([127])</p>
<p>^([128])</p>
<p>^([129])</p>
<p>^([130])</p>
<p>^([131])</p>
<p>^([132])</p>
<p>^([133])</p>
<p>^([134])</p>
<p>^([135])</p>
<h3 id="章节第-4">章节第 4</h3>
<p>^([136])</p>
<p>^([137])</p>
<p>^([138])</p>
<p>^([139])</p>
<p>^([140])</p>
<p>^([141])</p>
<h3 id="章节第-5">章节第 5</h3>
<p>^([142])</p>
<p>^([143])</p>
<p>^([144])</p>
<p>^([145])</p>
<h3 id="章节第-6">章节第 6</h3>
<p>^([146])</p>
<p>^([147])</p>
<p>^([148])</p>
<p>^([149])</p>
<p>^([150])</p>
<p>^([151])</p>
<p>^([152])</p>
<p>^([153])</p>
<p>^([154])</p>
<h3 id="章节第-7">章节第 7</h3>
<p>^([155])</p>
<p>^([156])</p>
<p>^([157])</p>
<p>^([158])</p>
<p>^([159])</p>
<p>^([160])</p>
<h3 id="章节第-8">章节第 8</h3>
<p>^([161])</p>
<p>^([162])</p>
<p>^([163])</p>
<p>^([164])</p>
<p>^([165])</p>
<p>^([166])</p>
<p>^([167])</p>
<p>^([168])</p>
<p>^([169])</p>
<p>^([170])</p>
<p>^([171])</p>
<p>^([172])</p>
<p>^([173])</p>
<p>^([174])</p>
<p>^([175])</p>
<h3 id="章节第-9">章节第 9</h3>
<p>^([176])</p>
<p>^([177])</p>
<p>^([178])</p>
<p>^([179])</p>
<p>^([180])</p>
<p>^([181])</p>
<p>^([182])</p>
<p>^([183])</p>
<p>^([184])</p>
<p>^([185])</p>
<p>^([186])</p>
<p>^([187])</p>
<p>^([188])</p>
<p>^([189])</p>
<p>^([190])</p>
<p>^([191])</p>
<p>^([192])</p>
<p>^([193])</p>
<p>^([194])</p>
<p>^([195])</p>
<h3 id="第十章">第十章</h3>
<p>^([196])</p>
<p>^([197])</p>
<h3 id="第十一章">第十一章</h3>
<p>^([198])</p>
<p>^([199])</p>
<p>^([200])</p>
<p>^([201])</p>
<p>^([202])</p>
<p>^([203])</p>
<p>^([204])</p>
<p>^([205])</p>
<p>^([206])</p>
<p>^([207])</p>
<p>^([208])</p>
<p>^([209])</p>
<p>^([210])</p>
<p>^([211])</p>
<p>^([212])</p>
<p>^([213])</p>
<p>^([214])</p>
<p>^([215])</p>
<h3 id="第十二章">第十二章</h3>
<p>^([216])</p>
<p>^([217])</p>
<p>^([218])</p>
<p>^([219])</p>
<p>^([220])</p>
<h3 id="第十三章">第十三章</h3>
<p>^([221])</p>
<p>^([222])</p>
<p>^([223])</p>
<p>^([224])</p>
<p>^([225])</p>
<h3 id="第十四章">第十四章</h3>
<p>^([226])</p>
<p>^([227])</p>
<p>^([228])</p>
<p>^([229])</p>
<p>^([230])</p>
<p>^([231])</p>
<h3 id="第十五章">第十五章</h3>
<p>^([232])</p>
<p>^([233])</p>
<p>^([234])</p>
<p>^([235])</p>
<p>^([236])</p>
<p>^([237])</p>
<p>^([238])</p>
<p>^([239])</p>
<h3 id="第十六章">第十六章</h3>
<p>^([240])</p>
<p>^([241])</p>
<p>^([242])</p>
<p>^([243])</p>
<p>^([244])</p>
<p>^([245])</p>
<p>^([246])</p>
<p>^([247])</p>
<p>^([248])</p>
<p>^([249])</p>
<p>^([250])</p>
<p>^([251])</p>
<p>^([252])</p>
<p>^([253])</p>
<p>^([254])</p>
<p>^([255])</p>
<h3 id="第十七章">第十七章</h3>
<p>^([256])</p>
<p>^([257])</p>
<p>^([258])</p>
<p>^([259])</p>
<p>^([260])</p>
<p>^([261])</p>
<p>^([262])</p>
<p>^([263])</p>
<p>^([264])</p>
<p>^([265])</p>
<p>^([266])</p>
<p>^([267])</p>
<p>^([268])</p>
<p>^([269])</p>
<p>^([270])</p>
<p>^([271])</p>
<p>^([272])</p>
<hr>
<p>^([86]) D.E. Bell 和 L.J. La Padula, <em>安全计算机系统:统一阐述与 Multics 解释</em> (ESD-TR-75-306), 贝德福德,马萨诸塞州:美国空军 MITRE 公司(1976 年),<a href="http://csrc.nist.gov/publications/history/bell76.pdf" target="_blank"><code>csrc.nist.gov/publications/history/bell76.pdf</code></a>.</p>
<p>^([87]) C.E. Landwehr, C.L. Heitmeyer, 和 J.D. McLean, “军事消息系统的安全模型:回顾,”在第 17 届计算机安全应用会议上的论文,新奥尔良,LA (2001 年), <a href="http://www.acsa-admin.org/2001/papers/141.pdf" target="_blank"><code>www.acsa-admin.org/2001/papers/141.pdf</code></a>.</p>
<p>^([88]) V. Bush, “我们可能如何思考,” <em>大西洋月刊</em> (1945 年 7 月), <a href="http://www.theatlantic.com/doc/194507/bush/" target="_blank"><code>www.theatlantic.com/doc/194507/bush/</code></a>.</p>
<p>^([89]) R. Dhamija, J.D. Tygar, 和 M. Hearst, “为什么钓鱼攻击有效,”在加拿大蒙特利尔计算机系统人类因素会议上的论文,(2006 年), <a href="http://people.seas.harvard.edu/~rachna/papers/why_phishing_works.pdf" target="_blank"><code>people.seas.harvard.edu/~rachna/papers/why_phishing_works.pdf</code></a>.</p>
<p>^([90]) C. Jackson, D.R. Simon, D.S. Tan, 和 A. Barth, “对扩展验证和图片嵌入式钓鱼攻击的评估,”在特立尼达和多巴哥的 Lowlands 可用性安全会议上的论文,(2007 年), <a href="http://usablesecurity.org/papers/jackson.pdf" target="_blank"><code>usablesecurity.org/papers/jackson.pdf</code></a>.</p>
<p>^([91]) C. Jackson 和 A. Barth, “小心更细粒度的来源,”在加利福尼亚奥克兰 Web 2.0 安全和隐私会议上的论文,(2008 年), <a href="http://seclab.stanford.edu/websec/origins/fgo.pdf" target="_blank"><code>seclab.stanford.edu/websec/origins/fgo.pdf</code></a>;C. Jackson 和 A. Barth, “小心更粗粒度的来源,”在加利福尼亚奥克兰 Web 2.0 安全和隐私会议上的论文,(2008 年), <a href="http://seclab.stanford.edu/websec/origins/scheme/" target="_blank"><code>seclab.stanford.edu/websec/origins/scheme/</code></a>.</p>
<p>^([92]) “使用 Internet Explorer 攻击 Mozilla Firefox 的安全漏洞,” MozillaZine (2007 年 7 月 11 日), <a href="http://www.mozillazine.org/talkback.html?article=22198" target="_blank"><code>www.mozillazine.org/talkback.html?article=22198</code></a>.</p>
<p>^([93]) Net Applications 网站, <a href="http://marketshare.hitslink.com/browser-market-share.aspx?qprid=0" target="_blank"><code>marketshare.hitslink.com/browser-market-share.aspx?qprid=0</code></a>, <a href="http://marketshare.hitslink.com/browser-market-share.aspx?qprid=2" target="_blank"><code>marketshare.hitslink.com/browser-market-share.aspx?qprid=2</code></a> (访问日期:2011 年 6 月 13 日).</p>
<p>^([94]) T. Berners-Lee, R. Fielding, 和 L. Masinter, “统一资源标识符 (URI):通用语法,” IETF 请求评论 3986 (2005 年), <a href="http://www.ietf.org/rfc/rfc3986.txt" target="_blank"><code>www.ietf.org/rfc/rfc3986.txt</code></a>.</p>
<p>^([95]) T. Berners-Lee, L. Masinter, 和 M. McCahill, “统一资源定位符 (URL),” IETF 请求评论 1738 (1994 年), <a href="http://www.ietf.org/rfc/rfc1738.txt" target="_blank"><code>www.ietf.org/rfc/rfc1738.txt</code></a>.</p>
<p>^([96]) R. Fielding, J. Gettys, J. Mogul, H. Frystyk, L. Masinter, P. Leach, 和 T. Berners-Lee, “超文本传输协议—HTTP/1.1,” IETF 请求评论 2616 (1999 年), <a href="http://www.ietf.org/rfc/rfc2616.txt" target="_blank"><code>www.ietf.org/rfc/rfc2616.txt</code></a>.</p>
<p>^([97]) “统一资源标识符 (URI) 方案根据 RFC4395,” 互联网数字分配机构 (2011 年 6 月 6 日), <a href="http://www.iana.org/assignments/uri-schemes.html" target="_blank"><code>www.iana.org/assignments/uri-schemes.html</code></a>.</p>
<p>^([98]) P. Mockapetris, “域名实现与规范,” IETF 请求评论 1035 (1987), <a href="http://www.ietf.org/rfc/rfc1035.txt" target="_blank"><code>www.ietf.org/rfc/rfc1035.txt</code></a>.</p>
<p>^([99]) T. Berners-Lee, “万维网中的通用资源标识符,” IETF 请求评论 1630 (1994), <a href="http://www.w3.org/Addressing/rfc1630.txt" target="_blank"><code>www.w3.org/Addressing/rfc1630.txt</code></a>.</p>
<p>^([100]) P. Hoffman, L. Masinter, 和 J. Zawinski, “mailto URL 方案,” IETF 请求评论 2368 (1998), <a href="http://www.ietf.org/rfc/rfc2368.txt" target="_blank"><code>www.ietf.org/rfc/rfc2368.txt</code></a>.</p>
<p>^([101]) “HTML 4.01 规范:表单,” 万维网联盟 (1999 年), <a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1" target="_blank"><code>www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1</code></a>.</p>
<p>^([102]) P. Faltstrom, P. Hoffman, 和 A. Costello, “应用程序中的域名国际化 (IDNA),” IETF 请求评论 3490 (2003), <a href="http://www.ietf.org/rfc/rfc3490.txt" target="_blank"><code>www.ietf.org/rfc/rfc3490.txt</code></a>.</p>
<p>^([103]) A. Costello, “Punycode:用于应用程序中国际化域名 (IDNA) 的 Unicode 编码,” IETF 请求评论 3492 (2003), <a href="http://www.ietf.org/rfc/rfc3492.txt" target="_blank"><code>www.ietf.org/rfc/rfc3492.txt</code></a>.</p>
<p>^([104]) E. Gabrilovich 和 A. Gontmakher, “同形攻击,” 计算机通信杂志 (2002), <a href="http://www.cs.technion.ac.il/~gabr/papers/homograph_full.pdf" target="_blank"><code>www.cs.technion.ac.il/~gabr/papers/homograph_full.pdf</code></a>.</p>
<p>^([105]) E. Rescorla, “TLS 上的 HTTP,” IETF 请求评论 2818 (2000), <a href="http://www.ietf.org/rfc/rfc2818.txt" target="_blank"><code>www.ietf.org/rfc/rfc2818.txt</code></a>.</p>
<p>^([106]) J. Postel 和 J. Reynolds, “文件传输协议 (FTP),” IETF 请求评论 959 (1985), <a href="http://www.ietf.org/rfc/rfc959.txt" target="_blank"><code>www.ietf.org/rfc/rfc959.txt</code></a>.</p>
<p>^([107]) F. Anklesaria, M. McCahill, P. Lindner, D. Johnson, D. Torrey, 和 B. Alberti, “互联网 Gopher 协议,” IETF 请求评论 1436 (1993), <a href="http://www.ietf.org/rfc/rfc1436.txt" target="_blank"><code>www.ietf.org/rfc/rfc1436.txt</code></a>.</p>
<p>^([108]) E. Rescorla 和 A. Schiffman, “安全超文本传输协议,” IETF 请求评论 2660 (1999), <a href="http://www.ietf.org/rfc/rfc2660.txt" target="_blank"><code>www.ietf.org/rfc/rfc2660.txt</code></a>.</p>
<p>^([109]) L. Masinter, “‘data’ URL 方案,” IETF 请求评论 2397 (1998), <a href="http://www.ietf.org/rfc/rfc2397.txt" target="_blank"><code>www.ietf.org/rfc/rfc2397.txt</code></a>.</p>
<p>^([110]) “什么是 rss: 和 feed: 链接?” <a href="http://www.brindys.com/winrss/feedformat.html" target="_blank"><code>www.brindys.com/winrss/feedformat.html</code></a>.</p>
<p>^([111]) M. Zalewski, “关于 MHTML 漏洞的笔记,” <em>Lcamtuf 的博客</em> (2011 年 3 月 11 日),<a href="http://lcamtuf.blogspot.com/2011/03/note-on-mhtml-vulnerability.html" target="_blank"><code>lcamtuf.blogspot.com/2011/03/note-on-mhtml-vulnerability.html</code></a>.</p>
<p>^([112]) T. Berners-Lee, “1991 年定义的原始 HTTP。” 万维网联盟档案 (1991),<a href="http://www.w3.org/Protocols/HTTP/AsImplemented.html" target="_blank"><code>www.w3.org/Protocols/HTTP/AsImplemented.html</code></a>.</p>
<p>^([113]) T. Berners-Lee, R. Fielding, 和 H. Frystyk, “超文本传输协议—HTTP/1.0,” IETF 请求评论 1945 (1996),<a href="http://www.ietf.org/rfc/rfc1945.txt" target="_blank"><code>www.ietf.org/rfc/rfc1945.txt</code></a>.</p>
<p>^([114]) R. Fielding, J. Gettys, J. Mogul, H. Frystyk, L. Masinter, P. Leach, 和 T. Berners-Lee, “超文本传输协议—HTTP/1.1,” IETF 请求评论 2616 (1999),<a href="http://www.ietf.org/rfc/rfc2616.txt" target="_blank"><code>www.ietf.org/rfc/rfc2616.txt</code></a>.</p>
<p>^([115]) HTTPbis 工作组, “Httpbis 状态页面,” <a href="http://tools.ietf.org/wg/httpbis/" target="_blank"><code>tools.ietf.org/wg/httpbis/</code></a>.</p>
<p>^([116]) A. Luotonen, “通过 Web 代理服务器隧道 TCP 基于的协议,” IETF 草案 (1998),<a href="http://tools.ietf.org/id/draft-luotonen-web-proxy-tunneling-01.txt" target="_blank"><code>tools.ietf.org/id/draft-luotonen-web-proxy-tunneling-01.txt</code></a>.</p>
<p>^([117]) S. Chen, Z. Mao, Y.M. Wang, 和 M. Zhang, “Pretty-Bad-Proxy: 浏览器 HTTPS 部署中被忽视的对手,” 微软研究院 (2009),<a href="http://research.microsoft.com/pubs/79323/pbp-final-with-update.pdf" target="_blank"><code>research.microsoft.com/pubs/79323/pbp-final-with-update.pdf</code></a>.</p>
<p>^([118]) “Mozilla 交叉引用 mozilla1.8.0,” 火狐代码仓库,<a href="http://mxr.mozilla.org/mozilla1.8.0/source/nsprpub/pr/src/misc/prtime.c#1045" target="_blank"><code>mxr.mozilla.org/mozilla1.8.0/source/nsprpub/pr/src/misc/prtime.c#1045</code></a>.</p>
<p>^([119]) K. Moore, “MIME (多用途互联网邮件扩展) 第三部分:非 ASCII 文本的消息头扩展,” IETF 请求评论 2047 (1996),<a href="http://www.ietf.org/rfc/rfc2047.txt" target="_blank"><code>www.ietf.org/rfc/rfc2047.txt</code></a>.</p>
<p>^([120]) N. Freed 和 K. Moore, “MIME 参数值和编码词扩展:字符集、语言和延续,” IETF 请求评论 2231 (1997),<a href="http://www.ietf.org/rfc/rfc2231.txt" target="_blank"><code>www.ietf.org/rfc/rfc2231.txt</code></a>.</p>
<p>^([121]) 火狐漏洞跟踪系统,火狐漏洞编号 #418394,<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=418394" target="_blank"><code>bugzilla.mozilla.org/show_bug.cgi?id=418394</code></a>.</p>
<p>^([122]) T. Berners-Lee, “1992 年定义的基本 HTTP:方法,” 万维网联盟档案 (1992),<a href="http://www.w3.org/Protocols/HTTP/Methods.html" target="_blank"><code>www.w3.org/Protocols/HTTP/Methods.html</code></a>.</p>
<p>^([123]) L. Dusseault, “Web 分布式作者和版本控制(WebDAV)的 HTTP 扩展,” IETF 请求评论 4918 (2007), <a href="http://www.ietf.org/rfc/rfc4918.txt" target="_blank"><code>www.ietf.org/rfc/rfc4918.txt</code></a>.</p>
<p>^([124]) 参见上面第 12 条注释。</p>
<p>^([125]) M. Pool, “Meantime:使用缓存的非同意 HTTP 用户跟踪” (2000), <a href="http://sourcefrog.net/projects/meantime/" target="_blank"><code>sourcefrog.net/projects/meantime/</code></a>.</p>
<p>^([126]) L. Montulli, “持久客户端状态 HTTP Cookies” (1994), <a href="http://curl.haxx.se/rfc/cookie_spec.html" target="_blank"><code>curl.haxx.se/rfc/cookie_spec.html</code></a>.</p>
<p>^([127]) D. Kristol 和 L. Montulli, “HTTP 状态管理机制,” IETF 请求评论 2109 (1997), <a href="http://www.ietf.org/rfc/rfc2109.txt" target="_blank"><code>www.ietf.org/rfc/rfc2109.txt</code></a>.</p>
<p>^([128]) D. Kristol 和 L. Montulli, “HTTP 状态管理机制,” IETF 请求评论 2965 (2000), <a href="http://tools.ietf.org/rfc/rfc2965.txt" target="_blank"><code>tools.ietf.org/rfc/rfc2965.txt</code></a>.</p>
<p>^([129]) A. Barth, “HTTP 状态管理机制,” IETF 请求评论 6265 (2011), <a href="http://www.ietf.org/rfc/rfc6265.txt" target="_blank"><code>www.ietf.org/rfc/rfc6265.txt</code></a>.</p>
<p>^([130]) J. Franks, P. Hallam-Baker, J. Hostetler, S. Lawrence, P. Leach, A. Luotonen, and L. Stewart, “HTTP 认证:基本和摘要访问认证,” IETF 请求评论 2617 (1999), <a href="http://www.ietf.org/rfc/rfc2617.txt" target="_blank"><code>www.ietf.org/rfc/rfc2617.txt</code></a>.</p>
<p>^([131]) R. Tschalär, “HTTP 的 NTLM 认证方案” (2003), <a href="http://www.innovation.ch/personal/ronald/ntlm.html" target="_blank"><code>www.innovation.ch/personal/ronald/ntlm.html</code></a>.</p>
<p>^([132]) E. Rescorla, “HTTP over TLS,” IETF 请求评论 2818 (2000), <a href="http://www.ietf.org/rfc/rfc2818.txt" target="_blank"><code>www.ietf.org/rfc/rfc2818.txt</code></a>.</p>
<p>^([133]) P. Hallam-Baker, “最近的 RA 妥协,” <em>Comodo IT 安全</em> (博客) (2011 年 3 月 23 日), <a href="http://blogs.comodo.com/it-security/data-security/the-recent-ra-compromise/" target="_blank"><code>blogs.comodo.com/it-security/data-security/the-recent-ra-compromise/</code></a>.</p>
<p>^([134]) S. Chen, R. Wang, X. F. Wang, and K. Zhang, “Web 应用程序中的侧信道泄露:今天是现实,明天是挑战,”微软研究院 (2010), <a href="http://research.microsoft.com/pubs/119060/WebAppSideChannel-final.pdf" target="_blank"><code>research.microsoft.com/pubs/119060/WebAppSideChannel-final.pdf</code></a>.</p>
<p>^([135]) C. Evans, “开放重定向器:一些理智的看法,” <em>安全:黑客一切</em> (博客) (2010 年 6 月 25 日), <a href="http://scarybeastsecurity.blogspot.com/2010_06_01_archive.html" target="_blank"><code>scarybeastsecurity.blogspot.com/2010_06_01_archive.html</code></a>.</p>
<p>^([136]) T. Berners-Lee, “HTML 标签,”万维网联盟档案 (1991), <a href="http://www.w3.org/History/19921103-hypertext/hypertext/WWW/MarkUp/Tags.html" target="_blank"><code>www.w3.org/History/19921103-hypertext/hypertext/WWW/MarkUp/Tags.html</code></a>.</p>
<p>^([137]) T. Berners-Lee 和 D. Connolly, “超文本标记语言—2.0,” IETF 请求评论 1866 (1995), <a href="http://www.ietf.org/rfc/rfc1866.txt" target="_blank"><code>www.ietf.org/rfc/rfc1866.txt</code></a>.</p>
<p>^([138]) D. Raggett, “HTML 3.2 参考规范,” World Wide Web Consortium (1997), <a href="http://www.w3.org/TR/REC-html32" target="_blank"><code>www.w3.org/TR/REC-html32</code></a>.</p>
<p>^([139]) D. Raggett, A. Le Hors 和 I. Jacobs, “HTML 4.01 规范,” World Wide Web Consortium (1999), <a href="http://www.w3.org/TR/html401/" target="_blank"><code>www.w3.org/TR/html401/</code></a>.</p>
<p>^([140]) I. Hickson, “HTML5,” World Wide Web Consortium 草案,修订版 1.5019 (2011), <a href="http://dev.w3.org/html5/spec/Overview.html" target="_blank"><code>dev.w3.org/html5/spec/Overview.html</code></a>.</p>
<p>^([141]) G. Coldwind, “Too general charset = detection in meta,” Mozilla 缺陷 640529 (2011), <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=640529" target="_blank"><code>bugzilla.mozilla.org/show_bug.cgi?id=640529</code></a>.</p>
<p>^([142]) H. Wium Lie 和 B. Bos, “Cascading Style Sheets, Level 1,” World Wide Web Consortium, (1996), <a href="http://www.w3.org/TR/CSS1/" target="_blank"><code>www.w3.org/TR/CSS1/</code></a>.</p>
<p>^([143]) T. Çelik, E.J. Etemad, D. Glazman, I. Hickson, P. Linss 和 J. Williams, “选择器级别 3:选择器,” World Wide Web Consortium (2009), <a href="http://www.w3.org/TR/css3-selectors/#selectors" target="_blank"><code>www.w3.org/TR/css3-selectors/#selectors</code></a>.</p>
<p>^([144]) I. Hickson, “XML 绑定语言 (XBL) 2.0,” World Wide Web Consortium (2007), <a href="http://www.w3.org/TR/xbl/" target="_blank"><code>www.w3.org/TR/xbl/</code></a>.</p>
<p>^([145]) G. Heyes, D. Lindsay 和 E.V. Nava, “性感刺客:使用 CSS 的战术利用” (2009), <a href="http://www.scribd.com/doc/54664700/Tactical-Xploit-Css" target="_blank"><code>www.scribd.com/doc/54664700/Tactical-Xploit-Css</code></a>.</p>
<p>^([146]) Netscape 通信公司, “Netscape 和 Sun 宣布 JavaScript,面向企业网络和互联网的开放、跨平台对象脚本语言” (新闻发布) (1995 年 12 月 4 日), <a href="http://web.archive.org/web/20070916144913/http://wp.netscape.com/newsref/pr/newsrelease67.html" target="_blank"><code>web.archive.org/web/20070916144913/http://wp.netscape.com/newsref/pr/newsrelease67.html</code></a>.</p>
<p>^([147]) ECMA 国际, “ECMA-262: ECMAScript 语言规范,” 第 3 版 (1999), <a href="http://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf" target="_blank"><code>www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf</code></a>.</p>
<p>^([148]) ECMA 国际, “ECMA-262: ECMAScript 语言规范,” 第 5 版 (2009), <a href="http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf" target="_blank"><code>www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf</code></a>.</p>
<p>^([149]) D. Crockford, “JavaScript 对象表示法 (JSON) 的应用/JSON 媒体类型,” 互联网工程任务组 (IETF) 请求评论 4627 (2006), <a href="http://www.ietf.org/rfc/rfc4627.txt" target="_blank"><code>www.ietf.org/rfc/rfc4627.txt</code></a>.</p>
<p>^([150]) J. Schneider, R. Yu, and J. Dyer, 编著,“标准 ECMA-357:XML 的 ECMAScript (E4X) 规范,” 第 2 版,ECMA 国际 (2005), <a href="http://www.ecma-international.org/publications/standards/Ecma-357.htm" target="_blank"><code>www.ecma-international.org/publications/standards/Ecma-357.htm</code></a>.</p>
<p>^([151]) P. Le Hégaret, R. Whitmer, and L. Wood, “文档对象模型 (DOM),” 万维网联盟 (2005), <a href="http://www.w3.org/DOM/" target="_blank"><code>www.w3.org/DOM/</code></a>.</p>
<p>^([152]) E. Vela Nava, “Bug 38922—text-area 中的 innerHTML 反编译问题” (WebKit 缺陷跟踪系统帖子) (2010), <a href="https://bugs.webkit.org/show_bug.cgi?id=38922" target="_blank"><code>bugs.webkit.org/show_bug.cgi?id=38922</code></a>.</p>
<p>^([153]) “Windows 脚本 5.8:MsgBox 函数,” 微软开发者网络平台 (2009), <a href="http://msdn.microsoft.com/en-us/library/sfw6660x%28v=vs.85%29.aspx" target="_blank"><code>msdn.microsoft.com/en-us/library/sfw6660x%28v=vs.85%29.aspx</code></a>.</p>
<p>^([154]) D. Crockford, “JavaScript 中的 JSON,” <em>GitHub 社会编码</em> (博客) (2011 年 3 月 5 日), <a href="https://github.com/douglascrockford/JSON-js/blob/master/json2.js" target="_blank"><code>github.com/douglascrockford/JSON-js/blob/master/json2.js</code></a>.</p>
<p>^([155]) J. Ferraiolo, F. Jun, and D. Jackson, “可伸缩矢量图形 (SVG) 1.1 规范,” 万维网联盟 (2003), <a href="http://www.w3.org/TR/2003/REC-SVG11-20030114/" target="_blank"><code>www.w3.org/TR/2003/REC-SVG11-20030114/</code></a>.</p>
<p>^([156]) D. Carlisle, P. Ion, and R. Miner, “数学标记语言 (MathML) 版本 3.0,” 万维网联盟 WC3 建议 21 (2010), <a href="http://www.w3.org/TR/MathML3/" target="_blank"><code>www.w3.org/TR/MathML3/</code></a>.</p>
<p>^([157]) A. Mechelynck, “XUL,” 火狐开发者网络 (2011), <a href="https://developer.mozilla.org/en/xul" target="_blank"><code>developer.mozilla.org/en/xul</code></a>.</p>
<p>^([158]) 无线应用协议论坛, “无线应用协议:无线标记语言规范版本 30” (1998), <a href="http://www.wapforum.org/what/technical/wml-30-apr-98.pdf" target="_blank"><code>www.wapforum.org/what/technical/wml-30-apr-98.pdf</code></a>.</p>
<p>^([159]) RSS 咨询委员会, “RSS 2.0 规范版本 2.0.11” (2009), <a href="http://www.rssboard.org/rss-specification" target="_blank"><code>www.rssboard.org/rss-specification</code></a>.</p>
<p>^([160]) M. Nottingham 和 R. Sayre,主编,“Atom 订阅格式,” 互联网工程任务组 (IETF) 请求评论 4287 (2005), <a href="http://www.ietf.org/rfc/rfc4287.txt" target="_blank"><code>www.ietf.org/rfc/rfc4287.txt</code></a>.</p>
<p>^([161]) E. Mills, “安全实验室报告:2010 年 1 月至 6 月回顾,” M86 安全 (2010 年), <a href="http://www.m86security.com/documents/pdfs/security_labs/m86_security_labs_report_1H2010.pdf" target="_blank"><code>www.m86security.com/documents/pdfs/security_labs/m86_security_labs_report_1H2010.pdf</code></a>.</p>
<p>^([162]) B. Rios, “Sun 修复 GIFARs” (2008 年 12 月 17 日), <a href="http://xs-sniper.com/blog/2008/12/17/sun-fixes-gifars/" target="_blank"><code>xs-sniper.com/blog/2008/12/17/sun-fixes-gifars/</code></a>.</p>
<p>^([163]) A.K. Sood, “PDF 静默 HTTP 表单重定向攻击,” SecNiche 安全实验室 (2009 年), <a href="http://secniche.org/papers/SNS_09_03_PDF_Silent_Form_Re_Purp_Attack.pdf" target="_blank"><code>secniche.org/papers/SNS_09_03_PDF_Silent_Form_Re_Purp_Attack.pdf</code></a>.</p>
<p>^([164]) P.D. Petkov, “通用 PDF XSS 后续活动” (2007 年 1 月 4 日), <a href="http://www.gnucitizen.org/blog/universal-pdf-xss-after-party/" target="_blank"><code>www.gnucitizen.org/blog/universal-pdf-xss-after-party/</code></a>.</p>
<p>^([165]) S. Jobs, “关于 Flash 的思考” (2010 年), <a href="http://www.apple.com/hotnews/thoughts-on-flash/" target="_blank"><code>www.apple.com/hotnews/thoughts-on-flash/</code></a>.</p>
<p>^([166]) “Adobe Shockwave Player,” Adobe Systems Incorporated, <a href="http://www.adobe.com/products/shockwaveplayer/" target="_blank"><code>www.adobe.com/products/shockwaveplayer/</code></a>.</p>
<p>^([167]) “Adobe Flash 平台 ActionScript 3.0 参考,” Adobe Systems Incorporated, <a href="http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/index.html" target="_blank"><code>help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/index.html</code></a>.</p>
<p>^([168]) “在 Flash Player 中播放的内容达到互联网观众的 99%,” Adobe Systems Incorporated (2011 年 3 月), <a href="http://www.adobe.com/products/player_census/flashplayer/" target="_blank"><code>www.adobe.com/products/player_census/flashplayer/</code></a>.</p>
<p>^([169]) “网络浏览器插件市场份额,” StatOwl (2011 年 5 月), <a href="http://www.statowl.com/plugin_overview.php" target="_blank"><code>www.statowl.com/plugin_overview.php</code></a>.</p>
<p>^([170]) “Adobe Flash 平台 ActionScript 3.0 参考:外部接口,” Adobe Systems Incorporated, <a href="http://livedocs.adobe.com/flex/3/langref/flash/external/ExternalInterface.html#includeExamplesSummary" target="_blank"><code>livedocs.adobe.com/flex/3/langref/flash/external/ExternalInterface.html#includeExamplesSummary</code></a>.</p>
<p>^([171]) “XAML 概述(WPF),” Microsoft Corporation, <a href="http://msdn.microsoft.com/en-us/library/ms752059.aspx" target="_blank"><code>msdn.microsoft.com/en-us/library/ms752059.aspx</code></a>.</p>
<p>^([172]) “富互联网应用统计” (2011 年 7 月), <a href="http://www.riastats.com/" target="_blank"><code>www.riastats.com/</code></a>. 参见 StatOwl (第八章, 注 9).</p>
<p>^([173]) “2010 年 Secunia 半年报告,” Secunia (2010), <a href="http://secunia.com/gfx/pdf/Secunia_Half_Year_Report_2010.pdf" target="_blank"><code>secunia.com/gfx/pdf/Secunia_Half_Year_Report_2010.pdf</code></a>.</p>
<p>^([174]) “WPF XAML 浏览器应用程序概述,” Microsoft Corporation, <a href="http://msdn.microsoft.com/en-us/library/aa970060.aspx" target="_blank"><code>msdn.microsoft.com/en-us/library/aa970060.aspx</code></a>.</p>
<p>^([175]) “Akamai 下载管理器帮助,” Microsoft Corporation, <a href="https://msdn.microsoft.com/en-us/subscriptions/manage/bb153537.aspx" target="_blank"><code>msdn.microsoft.com/en-us/subscriptions/manage/bb153537.aspx</code></a>.</p>
<p>^([176]) A. Klein, “IE + 一些流行的代理服务器 = XSS, 网站篡改(浏览器缓存投毒)”(2006 年 5 月 22 日), <a href="http://seclists.org/webappsec/2006/q2/352;" target="_blank"><code>seclists.org/webappsec/2006/q2/352;</code></a> M. Zalewski, “使用 MSIE & XMLHttpRequest 轻松创建 Web 2.0 后门”(2007 年 2 月 3 日), <a href="http://seclists.org/fulldisclosure/2007/Feb/81" target="_blank"><code>seclists.org/fulldisclosure/2007/Feb/81</code></a>.</p>
<p>^([177]) A. van Kesteren, 编, “跨源资源共享,” 工作草案,万维网联盟 (2010 年 7 月 27 日), <a href="http://www.w3.org/TR/cors/" target="_blank"><code>www.w3.org/TR/cors/</code></a>.</p>
<p>^([178]) I. Hickson, “Web 存储,” 编辑草案,万维网联盟 (2011 年 7 月 28 日), <a href="http://dev.w3.org/html5/webstorage/" target="_blank"><code>dev.w3.org/html5/webstorage/</code></a>.</p>
<p>^([179]) J. Stenback, “使用主体而不是字符串域来使用 sessionStorage,” Mozilla 错误报告#495337 (2009 年 5 月 28 日), <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=495337" target="_blank"><code>bugzilla.mozilla.org/show_bug.cgi?id=495337</code></a>.</p>
<p>^([180]) T. Ormandy, “常见的 DNS 配置错误可能导致‘Same Site’脚本” (2008 年 1 月 18 日), <a href="http://seclists.org/bugtraq/2008/Jan/270" target="_blank"><code>seclists.org/bugtraq/2008/Jan/270</code></a>.</p>
<p>^([181]) R. Singel, “互联网服务提供商的错误页面广告让黑客劫持整个网络,研究员揭露,” <em>Wired</em> (2008 年 4 月 19 日), <a href="http://www.wired.com/threatlevel/2008/04/isps-error-page/" target="_blank"><code>www.wired.com/threatlevel/2008/04/isps-error-page/</code></a>.</p>
<p>^([182]) “Adobe Flash Player 可用的 APSB10-14 安全更新,” Adobe Systems Incorporated (2010 年 6 月 10 日), <a href="http://www.adobe.com/support/security/bulletins/apsb10-14.html" target="_blank"><code>www.adobe.com/support/security/bulletins/apsb10-14.html</code></a>.</p>
<p>^([183]) “理解 2008 年 4 月 Flash Player 9:安全更新兼容性,” Adobe Systems Incorporated (2008 年 4 月 8 日), <a href="http://www.adobe.com/devnet/flashplayer/articles/flash_player9_security_update.html" target="_blank"><code>www.adobe.com/devnet/flashplayer/articles/flash_player9_security_update.html</code></a>.</p>
<p>^([184]) “Adobe® Flash® 平台 ActionScript® 3.0 参考:URLRequestHeader,” Adobe Systems Incorporated, <a href="http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/URLRequestHeader.html" target="_blank"><code>help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/URLRequestHeader.html</code></a>.</p>
<p>^([185]) “ActionScript® 3.0 Adobe® Flash® 平台参考:安全”,Adobe Systems Incorporated,<a href="http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/system/Security.html" target="_blank"><code>livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/system/Security.html</code></a>.</p>
<p>^([186]) “Adobe 跨域策略文件规范”,版本 2.0,Adobe Systems Incorporated(2010 年 8 月 2 日),<a href="http://learn.adobe.com/wiki/download/attachments/64389123/CrossDomain_PolicyFile_Specification.pdf?version=1" target="_blank"><code>learn.adobe.com/wiki/download/attachments/64389123/CrossDomain_PolicyFile_Specification.pdf?version=1</code></a>.</p>
<p>^([187]) M. Zalewski,"[RAZOR] Linux 内核 IP 伪装漏洞"(2001 年 7 月 30 日),<a href="http://seclists.org/bugtraq/2001/Jul/733" target="_blank"><code>seclists.org/bugtraq/2001/Jul/733</code></a>.</p>
<p>^([188]) “Silverlight:WebHeaderCollection 类”,Microsoft,<a href="http://msdn.microsoft.com/en-us/library/system.net.webheadercollection%28v=VS.95%29.aspx" target="_blank"><code>msdn.microsoft.com/en-us/library/system.net.webheadercollection%28v=VS.95%29.aspx</code></a>.</p>
<p>^([189]) “类 HttpURLConnection”,Sun Microsystems/Oracle,<a href="http://download.oracle.com/javase/1.4.2/docs/api/java/net/HttpURLConnection.html" target="_blank"><code>download.oracle.com/javase/1.4.2/docs/api/java/net/HttpURLConnection.html</code></a>.</p>
<p>^([190]) “类 URLConnection”,Sun Microsystems/Oracle,<a href="http://download.oracle.com/javase/1.4.2/docs/api/java/net/URLConnection.html" target="_blank"><code>download.oracle.com/javase/1.4.2/docs/api/java/net/URLConnection.html</code></a>.</p>
<p>^([191]) “类 Socket”,Sun Microsystems/Oracle,<a href="http://download.oracle.com/javase/1.4.2/docs/api/java/net/Socket.html" target="_blank"><code>download.oracle.com/javase/1.4.2/docs/api/java/net/Socket.html</code></a>.</p>
<p>^([192]) “Java 到 JavaScript 通信”,Sun Microsystems/Oracle,<a href="http://download.oracle.com/javase/1.4.2/docs/guide/plugin/developer_guide/java_js.html" target="_blank"><code>download.oracle.com/javase/1.4.2/docs/guide/plugin/developer_guide/java_js.html</code></a>.</p>
<p>^([193]) “Java 到 JavaScript 通信:常见 DOM API”,Sun Microsystems/Oracle,<a href="http://download.oracle.com/javase/1.4.2/docs/guide/plugin/developer_guide/java_js.html#common_dom" target="_blank"><code>download.oracle.com/javase/1.4.2/docs/guide/plugin/developer_guide/java_js.html#common_dom</code></a>.</p>
<p>^([194]) B. “Snowhare” Franz,“三点 Cookies”(1998 年),<a href="http://snowhare.com/utilities/triple_dot/" target="_blank"><code>snowhare.com/utilities/triple_dot/</code></a>.</p>
<p>^([195]) “Adobe ActionScript 3.0:安全沙箱”,Adobe Systems Incorporated,<a href="http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7e3f.html" target="_blank"><code>help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7e3f.html</code></a>.</p>
<p>^([196]) L. Masinter, “‘data’ URL 方案,” IETF 请求评论 2397 (1998 年), <a href="http://www.ietf.org/rfc/rfc2397.txt" target="_blank"><code>www.ietf.org/rfc/rfc2397.txt</code></a>.</p>
<p>^([197]) M. Zalewski, “about:neterror, certerror 允许与 about:blank 同源,从而进行 URL 欺骗,” Mozilla 缺陷 #602780 (CVE-2010-3774) (2010 年), <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=602780" target="_blank"><code>bugzilla.mozilla.org/show_bug.cgi?id=602780</code></a>.</p>
<p>^([198]) G. Guninski, “使用加载两个帧进行帧欺骗,” Mozilla 缺陷 #13871 (1999 年), <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=13871" target="_blank"><code>bugzilla.mozilla.org/show_bug.cgi?id=13871</code></a>.</p>
<p>^([199]) R. Zilberman, “在窗口加载的短时间内,存在帧欺骗的可能性,” Mozilla 缺陷 #381300 (CVE-2007-3089) (2008 年), <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=381300" target="_blank"><code>bugzilla.mozilla.org/show_bug.cgi?id=381300</code></a>.</p>
<p>^([200]) A. Barth, C. Jackson, 和 J.C. Mitchell, “在浏览器中保护帧通信,” <em>ACM 通讯 52</em>, 第 6 期 (2009 年): 83-91, <a href="http://www.adambarth.com/papers/2009/barth-jackson-mitchell-cacm.pdf" target="_blank"><code>www.adambarth.com/papers/2009/barth-jackson-mitchell-cacm.pdf</code></a>.</p>
<p>^([201]) R. Hansen 和 J. Grossman, “Clickjacking” (2008 年), <a href="http://www.sectheory.com/clickjacking.htm" target="_blank"><code>www.sectheory.com/clickjacking.htm</code></a>.</p>
<p>^([202]) M. Zalewski, “处理当前网络固有的 UI 重定向漏洞” (发布于 whatwg.org 列表) (2008 年 9 月 25 日), <a href="http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2008-September/thread.html#16292" target="_blank"><code>lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2008-September/thread.html#16292</code></a>.</p>
<p>^([203]) E. Lawrence, “IE8 安全性第七部分:ClickJacking 防御,” <em>IE 博客</em> (2009 年 1 月 27 日), <a href="http://blogs.msdn.com/b/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx" target="_blank"><code>blogs.msdn.com/b/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx</code></a>.</p>
<p>^([204]) SHODAN, “HTTP 头部调查” (2011 年 3 月 14 日), <a href="http://www.shodanhq.com/research/infodisc/report" target="_blank"><code>www.shodanhq.com/research/infodisc/report</code></a>.</p>
<p>^([205]) P. Stone, “下一代 Clickjacking,” Blackhat Europe (2010 年), <a href="http://blog.c22.cc/2010/04/14/blackhat-europe-next-generation-clickjacking-3/" target="_blank"><code>blog.c22.cc/2010/04/14/blackhat-europe-next-generation-clickjacking-3/</code></a>.</p>
<p>^([206]) M. Zalewski, “逆划线欺骗的诅咒,” <em>Icamtuf 的博客</em> (2010 年 6 月 8 日), <a href="http://lcamtuf.blogspot.com/2010/06/curse-of-inverse-strokejacking.html" target="_blank"><code>lcamtuf.blogspot.com/2010/06/curse-of-inverse-strokejacking.html</code></a>.</p>
<p>^([207]) C. Evans, “通用跨浏览器跨域窃取,” <em>安全</em> (博客) (2009 年 12 月 28 日) <a href="http://scarybeastsecurity.blogspot.com/2009/12/generic-cross-browser-cross-domain.html" target="_blank"><code>scarybeastsecurity.blogspot.com/2009/12/generic-cross-browser-cross-domain.html</code></a>.</p>
<p>^([208]) C. Evans, “IE8 基于 CSS 的强制推文,” <em>安全</em> (博客) (2009 年 9 月 29 日), <a href="http://scarybeastsecurity.blogspot.com/2010/09/ie8-css-based-forced-tweeting.html" target="_blank"><code>scarybeastsecurity.blogspot.com/2010/09/ie8-css-based-forced-tweeting.html</code></a>.</p>
<p>^([209]) I. Hickson, “HTML: 4.8.11 线画元素,” WHATWG (2011), <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html" target="_blank"><code>www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html</code></a>.</p>
<p>^([210]) E.W. Felten 和 M.A. Schneider, “对网络隐私的计时攻击,” <em>第 7 届 ACM 计算机和通信安全会议论文集</em> (2000), <a href="http://sip.cs.princeton.edu/pub/webtiming.pdf" target="_blank"><code>sip.cs.princeton.edu/pub/webtiming.pdf</code></a>.</p>
<p>^([211]) C. Evans, “跨域搜索计时,” <em>安全</em> (博客) (2009 年 12 月 11 日), <a href="http://scarybeastsecurity.blogspot.com/2009/12/cross-domain-search-timing.html" target="_blank"><code>scarybeastsecurity.blogspot.com/2009/12/cross-domain-search-timing.html</code></a>.</p>
<p>^([212]) C. Wilson, P. Le Hégaret, 和 V. Apparao, “文档对象模型 CSS: 2.2.1 覆盖和计算样式表,” 万维网联盟 (2000), <a href="http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-OverrideAndComputed" target="_blank"><code>www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-OverrideAndComputed</code></a>.</p>
<p>^([213]) “currentStyle 对象,” 微软公司 MSDN 图书馆, <a href="http://msdn.microsoft.com/en-us/library/ms535231%28v=vs.85%29.aspx" target="_blank"><code>msdn.microsoft.com/en-us/library/ms535231%28v=vs.85%29.aspx</code></a>.</p>
<p>^([214]) A. Clover, “CSS 访问页面披露” (2002 年 2 月 20 日), <a href="http://seclists.org/bugtraq/2002/Feb/271" target="_blank"><code>seclists.org/bugtraq/2002/Feb/271</code></a>.</p>
<p>^([215]) Z. Weinberg, E.Y. Chen, P.R. Jayaraman 和 C. Jackson, “我仍然知道你去年夏天访问了什么” (2011), <a href="http://websec.sv.cmu.edu/visited/visited.pdf" target="_blank"><code>websec.sv.cmu.edu/visited/visited.pdf</code></a>.</p>
<p>^([216]) J. Schwartz, “给予网络记忆成本,损害用户隐私,” <em>纽约时报</em> (2001 年 9 月 4 日), <a href="http://www.nytimes.com/2001/09/04/technology/04COOK.html" target="_blank"><code>www.nytimes.com/2001/09/04/technology/04COOK.html</code></a>.</p>
<p>^([217]) N. Wingfield, “微软压制提高在线隐私的努力,” <em>华尔街日报</em> (2010 年 8 月 2 日), <a href="http://online.wsj.com/article/SB10001424052748703467304575383530439838568.html" target="_blank"><code>online.wsj.com/article/SB10001424052748703467304575383530439838568.html</code></a>.</p>
<p>^([218]) E. Felten, “如果您要跟踪我,请使用 Cookies,” <em>自由创新</em> (博客) (2009 年 7 月 7 日), <a href="http://www.freedom-to-tinker.com/blog/felten/if-youre-going-track-me-please-use-cookies" target="_blank"><code>www.freedom-to-tinker.com/blog/felten/if-youre-going-track-me-please-use-cookies</code></a>.</p>
<p>^([219]) J. Mayer, A. Narayanan 和 S. Stamm, “不要跟踪:一个通用的第三方网络跟踪退出的选择,” IETF 请求评论 (2011 年), <a href="http://datatracker.ietf.org/doc/draft-mayer-do-not-track/?include_text=1" target="_blank"><code>datatracker.ietf.org/doc/draft-mayer-do-not-track/?include_text=1</code></a>.</p>
<p>^([220]) L. Cranor, M. Langheinrich, M. Marchiori, M. Presler-Marshall, and J. Reagle, “平台隐私偏好 1.0 (P3P1.0) 规范,” 万维网联盟 (2002 年), <a href="http://www.w3.org/TR/P3P/" target="_blank"><code>www.w3.org/TR/P3P/</code></a>.</p>
<p>^([221]) N. Freed 和 N. Borenstein, “多用途互联网邮件扩展 (MIME) 第二部分:媒体类型,” IETF 请求评论 2046 (1996 年), <a href="http://www.ietf.org/rfc/rfc2046.txt" target="_blank"><code>www.ietf.org/rfc/rfc2046.txt</code></a>.</p>
<p>^([222]) V. Gupta, “IE 内容类型逻辑,” <em>IE 博客</em> (2005 年 2 月 1 日), <a href="http://blogs.msdn.com/b/ie/archive/2005/02/01/364581.aspx" target="_blank"><code>blogs.msdn.com/b/ie/archive/2005/02/01/364581.aspx</code></a>.</p>
<p>^([223]) SHODAN, “HTTP 头部调查” (2011 年), <a href="http://www.shodanhq.com/research/infodisc/download_latest" target="_blank"><code>www.shodanhq.com/research/infodisc/download_latest</code></a>.</p>
<p>^([224]) R. Troost, S. Dorner 和 K. Moore, “在互联网消息中传达呈现信息:内容处置头字段,” IETF 请求评论 2183 (1997 年), <a href="http://www.ietf.org/rfc/rfc2183.txt" target="_blank"><code>www.ietf.org/rfc/rfc2183.txt</code></a>.</p>
<p>^([225]) G. Heyes, “内联 UTF-7 E4X JavaScript 劫持,” <em>The Spanner</em> (博客) (2009 年 2 月 24 日), <a href="http://www.thespanner.co.uk/2009/02/24/inline-utf-7-e4x-javascript-hijacking/" target="_blank"><code>www.thespanner.co.uk/2009/02/24/inline-utf-7-e4x-javascript-hijacking/</code></a>.</p>
<p>^([226]) M. Zalewski, “通过地址栏省略可能实现 URL 欺骗” (2010 年), <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=581313" target="_blank"><code>bugzilla.mozilla.org/show_bug.cgi?id=581313</code></a>.</p>
<p>^([227]) R. J. Kosinski, “关于反应时间的文献综述,” 克莱姆森大学 (2010 年), <a href="http://biae.clemson.edu/bpc/bp/Lab/110/reaction.htm#Type%20of%20Stimulus" target="_blank"><code>biae.clemson.edu/bpc/bp/Lab/110/reaction.htm#Type%20of%20Stimulus</code></a>.</p>
<p>^([228]) M. Zalewski, “Bug 376473: 文件操作对话框控件易受重新聚焦竞争条件的影响” (2007 年), <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=376473" target="_blank"><code>bugzilla.mozilla.org/show_bug.cgi?id=376473</code></a>.</p>
<p>^([229]) M. Zalewski, “地理位置欺骗及其他用户界面问题,” <em>Bugtraq</em> (邮件列表) (2010 年 8 月 17 日), <a href="http://seclists.org/bugtraq/2010/Aug/201" target="_blank"><code>seclists.org/bugtraq/2010/Aug/201</code></a>.</p>
<p>^([230]) D. Simons and C. Chabris, “Selective Attention Test” (1999), <a href="http://www.youtube.com/watch?v=vJG698U2Mvo&feature=player_embedded" target="_blank"><code>www.youtube.com/watch?v=vJG698U2Mvo&feature=player_embedded</code></a>.</p>
<p>^([231]) D.J. Simmons and C.F. Chabris, “Gorillas in our midst: Sustained inattentional blindness for dynamic events,” <em>Perception</em>, 28, 1059 −1074 (1999), <a href="http://www.cnbc.cmu.edu/~behrmann/dlpapers/Simons_Chabris.pdf" target="_blank"><code>www.cnbc.cmu.edu/~behrmann/dlpapers/Simons_Chabris.pdf</code></a>.</p>
<p>^([232]) <a href="http://www.xssed.com/search?key=addons.mozilla.org" target="_blank"><code>www.xssed.com/search?key=addons.mozilla.org</code></a></p>
<p>^([233]) <a href="http://openid.net/" target="_blank"><code>openid.net/</code></a></p>
<p>^([234]) “Internet Explorer: Security Zones,” Microsoft, <a href="http://technet.microsoft.com/en-us/library/dd361896.aspx" target="_blank"><code>technet.microsoft.com/en-us/library/dd361896.aspx</code></a>.</p>
<p>^([235]) “Internet Explorer Binary Behaviors Security Setting,” Microsoft, <a href="http://technet.microsoft.com/en-us/library/cc776248(WS.10).aspx" target="_blank"><code>technet.microsoft.com/en-us/library/cc776248(WS.10).aspx</code></a>.</p>
<p>^([236]) Charles Schwab, “Technical Support,” <a href="http://www.visualwebcaster.com/charles_schwab/support/" target="_blank"><code>www.visualwebcaster.com/charles_schwab/support/</code></a> (accessed September 9, 2011).</p>
<p>^([237]) Internal Revenue Service, “Streaming Media System Requirements & Troubleshooting Assistance,” <a href="http://www.irsvideos.gov/sbv_1099webinar/player/IRS_Webinar_Technical_Support.pdf" target="_blank"><code>www.irsvideos.gov/sbv_1099webinar/player/IRS_Webinar_Technical_Support.pdf</code></a> (accessed September 9, 2011).</p>
<p>^([238]) “.NET Framework 3.0: Mark of the Web,” Microsoft, <a href="http://msdn.microsoft.com/en-us/library/ms537628%28VS.85%29.aspx" target="_blank"><code>msdn.microsoft.com/en-us/library/ms537628%28VS.85%29.aspx</code></a>.</p>
<p>^([239]) “Persistent Zone Identifier Object,” Microsoft, <a href="http://msdn.microsoft.com/en-us/library/ms537029%28VS.85%29.aspx" target="_blank"><code>msdn.microsoft.com/en-us/library/ms537029%28VS.85%29.aspx</code></a>.</p>
<p>^([240]) A. van Kesteren, “Cross-Origin Resource Sharing,” (working draft) World Wide Web Consortium (July 27, 2010), <a href="http://www.w3.org/TR/cors/" target="_blank"><code>www.w3.org/TR/cors/</code></a>.</p>
<p>^([241]) S. Dutta, “Updates for AJAX in IE8 Beta 2,” <em>IEBlog</em> (2008), <a href="http://blogs.msdn.com/b/ie/archive/2008/10/06/updates-for-ajax-in-ie8-beta-2.aspx" target="_blank"><code>blogs.msdn.com/b/ie/archive/2008/10/06/updates-for-ajax-in-ie8-beta-2.aspx</code></a>.</p>
<p>^([242]) “.NET Framework 3.0: XDomainRequest Object,” Microsoft Developer Network, <a href="http://msdn.microsoft.com/en-us/library/cc288060%28v=vs.85%29.aspx" target="_blank"><code>msdn.microsoft.com/en-us/library/cc288060%28v=vs.85%29.aspx</code></a>.</p>
<p>^([243]) T. Close and M. Miller, “Uniform Messaging Policy, Level One,” (working draft) World Wide Web Consortium (January 26, 2010), <a href="http://www.w3.org/TR/UMP/" target="_blank"><code>www.w3.org/TR/UMP/</code></a>.</p>
<p>^([244]) A. Barth, C. Jackson, and J.C. Mitchell, “Robust Defenses for Cross-Site Request Forgery,” ACM Conference on Computer and Communications Security (2008), <a href="http://seclab.stanford.edu/websec/csrf/csrf.pdf" target="_blank"><code>seclab.stanford.edu/websec/csrf/csrf.pdf</code></a>.</p>
<p>^([245]) B. Sterne, “Origin Header Proposal,” <a href="http://people.mozilla.com/~bsterne/content-security-policy/origin-header-proposal.html" target="_blank"><code>people.mozilla.com/~bsterne/content-security-policy/origin-header-proposal.html</code></a>.</p>
<p>^([246]) A. van Kesteren, “The From-Origin Header,” (working draft) World Wide Web Consortium (July 21, 2011), <a href="http://www.w3.org/TR/2011/WD-from-origin-20110721/" target="_blank"><code>www.w3.org/TR/2011/WD-from-origin-20110721/</code></a>.</p>
<p>^([247]) A. Barth, “The Web Origin Concept (v. 9),” IETF Draft (November 26, 2010), <a href="http://tools.ietf.org/html/draft-abarth-origin-09" target="_blank"><code>tools.ietf.org/html/draft-abarth-origin-09</code></a>.</p>
<p>^([248]) B. Sterne, “Content Security Policy” (2008), <a href="http://people.mozilla.com/~bsterne/content-security-policy/" target="_blank"><code>people.mozilla.com/~bsterne/content-security-policy/</code></a>.</p>
<p>^([249]) B. Sterne, “Content Security Policy,” (draft) World Wide Web Consortium (March 15, 2011), <a href="https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-unofficial-draft-20110315.html" target="_blank"><code>dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-unofficial-draft-20110315.html</code></a>.</p>
<p>^([250]) I. Hickson, “HTML Living Standard,” WHATWG (2011), <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#attr-iframe-sandbox" target="_blank"><code>www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#attr-iframe-sandbox</code></a>.</p>
<p>^([251]) J. Hodges, C. Jackson, and A. Barth, “HTTP Strict Transport Security (HSTS),” (draft) IETF Request for Comments (August 5, 2011), <a href="http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-02" target="_blank"><code>tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-02</code></a>.</p>
<p>^([252]) A. Klein, “Google Chrome 6.0 and Above: Math.random Vulnerability” (2010), <a href="http://www.trusteer.com/sites/default/files/Google_Chrome_6.0_and_7.0_Math.random_vulnerability.pdf" target="_blank"><code>www.trusteer.com/sites/default/files/Google_Chrome_6.0_and_7.0_Math.random_vulnerability.pdf</code></a>.</p>
<p>^([253]) “.NET Framework 3.0: toStaticHTML Method,” Microsoft, <a href="http://msdn.microsoft.com/en-us/library/cc848922%28v=vs.85%29.aspx" target="_blank"><code>msdn.microsoft.com/en-us/library/cc848922%28v=vs.85%29.aspx</code></a>.</p>
<p>^([254]) D. Ross, “IE8 Security Part IV: The XSS Filter,” <em>IEBlog</em> (2008), <a href="http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx" target="_blank"><code>blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx</code></a>.</p>
<p>^([255]) E. Vela Nava and D. Lindsay, “滥用 Internet Explorer 8 的 XSS 过滤器”(2009 年), <a href="http://p42.us/ie8xss/Abusing_IE8s_XSS_Filters.pdf" target="_blank"><code>p42.us/ie8xss/Abusing_IE8s_XSS_Filters.pdf</code></a>.</p>
<p>^([256]) “navigator.registerProtocolHandler,” 火狐开发者网络, <em><a href="https://developer.mozilla.org/en/DOM/window.navigator.registerProtocolHandler" target="_blank">https://developer.mozilla.org/en/DOM/window.navigator.registerProtocolHandler</a></em>.</p>
<p>^([257]) “操作浏览器历史记录,” 火狐开发者网络, <em><a href="https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history/" target="_blank">https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history/</a></em>.</p>
<p>^([258]) A. Langley and M. Belsche, “SPDY:一个更快的 Web 实验协议,” Chromium 项目, <a href="http://www.chromium.org/spdy/spdy-whitepaper/" target="_blank"><code>www.chromium.org/spdy/spdy-whitepaper/</code></a>.</p>
<p>^([259]) I. Fette and A. Melnikov, “WebSocket 协议,” IETF 请求评论草案(2011 年), <a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10" target="_blank"><code>tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10</code></a>/.</p>
<p>^([260]) J. Rosenberg, M. Kaufman, M. Hiie, and F. Audet, “基于浏览器的实时通信架构框架,” IETF 请求评论草案(2011 年), <a href="http://tools.ietf.org/html/draft-rosenberg-rtcweb-framework-00/" target="_blank"><code>tools.ietf.org/html/draft-rosenberg-rtcweb-framework-00/</code></a>.</p>
<p>^([261]) I. Hickson, “HTML5:5.6—离线 Web 应用程序,” 万维网联盟(2011 年), <a href="http://www.w3.org/TR/html5/offline.html" target="_blank"><code>www.w3.org/TR/html5/offline.html</code></a>.</p>
<p>^([262]) A. Barth, “简单的 HTTP 状态管理机制,” IETF 请求评论草案(2010 年), <a href="http://tools.ietf.org/html/draft-abarth-cake-00/" target="_blank"><code>tools.ietf.org/html/draft-abarth-cake-00/</code></a>.</p>
<p>^([263]) I. Hickson, “Web SQL 数据库:W3C 工作组笔记 18,” 万维网联盟(2010 年), <a href="http://www.w3.org/TR/webdatabase/" target="_blank"><code>www.w3.org/TR/webdatabase/</code></a>.</p>
<p>^([264]) N. Mehta, J. Sicking, E. Graff, A. Popescu, and J. Orlow, “索引数据库 API:W3C 工作草案 19,” 万维网联盟(2011 年), <a href="http://www.w3.org/TR/IndexedDB/" target="_blank"><code>www.w3.org/TR/IndexedDB/</code></a>.</p>
<p>^([265]) I. Hickson, “Web 应用程序 1.0:Web Workers,” WHATWG(2011 年), <a href="http://www.whatwg.org/specs/web-apps/current-work/complete/workers.html" target="_blank"><code>www.whatwg.org/specs/web-apps/current-work/complete/workers.html</code></a>.</p>
<p>^([266]) A. Popescu, “地理位置 API 规范:编辑草案,” 万维网联盟(2010 年 2 月 10 日), <a href="http://dev.w3.org/geo/api/spec-source.html" target="_blank"><code>dev.w3.org/geo/api/spec-source.html</code></a>.</p>
<p>^([267]) “检测设备方向,” 火狐开发者网络, <em><a href="https://developer.mozilla.org/en/detecting_device_orientation/" target="_blank">https://developer.mozilla.org/en/detecting_device_orientation/</a></em>.</p>
<p>^([268]) L. Cai 和 H. Chen, “TouchLogger:从智能手机运动推断触摸屏上的按键,” Usenix HOTSEC (2011 年), <a href="http://www.usenix.org/event/hotsec11/tech/final_files/Cai.pdf" target="_blank"><code>www.usenix.org/event/hotsec11/tech/final_files/Cai.pdf</code></a>.</p>
<p>^([269]) “Chrome 中的预渲染指南:Web 开发者指南,” Google 代码实验室,<a href="http://code.google.com/chrome/whitepapers/prerender.html" target="_blank"><code>code.google.com/chrome/whitepapers/prerender.html</code></a>.</p>
<p>^([270]) Z. Wang, “导航时间:编辑草案,” 世界 Wide Web 联盟 (2011 年 7 月 27 日), <em><a href="https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html" target="_blank">https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html</a></em>.</p>
<p>^([271]) J. Gregg, “Web 通知概述:W3C 编辑草案,” 世界 Wide Web 联盟 (2010 年 10 月 12 日), <a href="http://dev.w3.org/2006/webapi/WebNotifications/publish/" target="_blank"><code>dev.w3.org/2006/webapi/WebNotifications/publish/</code></a>.</p>
<p>^([272]) D.D. Tran, I. Oksanen, 和 I. Kliche, “媒体捕获 API:W3C 工作草案,” 世界 Wide Web 联盟 (2010 年 9 月 28 日), <a href="http://www.w3.org/TR/media-capture-api/" target="_blank"><code>www.w3.org/TR/media-capture-api/</code></a>.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>3 <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>87 <a href="#fnref2" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn3" class="footnote-item"><p>[135] <a href="#fnref3" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn4" class="footnote-item"><p> <a href="#fnref4" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn5" class="footnote-item"><p>78 <a href="#fnref5" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>