WEB安全第十五课 内容识别机制 |
到目前为止,我们已经研究了不少浏览器特性,很多特性的本意是好的,但随着技术的成熟,人们日益认识到它们也是短视和非常危险的。现在就让我们再来看点特别的东西吧:在Web的历史上,再没有比内容检测(Content Sniffing)概念更误导人的事情了。
内容检测这一想法的出发点倒也简单:浏览器厂商们认为在某些场合里,忽略来自服务器端发出的信息(如Content-Type响应头)是没问题的,甚至是理所当然的。这些浏览器之所以罔顾网站程序员的设置,是因为想要补偿某些网页错误(请回忆一下,在第一次浏览器大战期间,浏览器开发商们故意把高兼容性吹嘘成一项竞争优势,这其实是错误的),所以浏览器可能会根据返回的数据,各自猜测应如何解析网页内容才算合理。 👩✈️🥼✒🤐👀 但没过多长时间,内容检测就成了浏览器安全领域里的一大危害。网页程序员惊诧地发现这简直叫人难以置信,他们甚至不能安全地存放某些原本无害的用户文档类型,如text/plain或ext/csv;无论怎么做都难以避免风险,这些内容仍有可能被误当成HTML来解析。 也许部分原因是源于对此问题的忧虑,1999年时HTTP/1.1规范里就明确禁止了内容检测的处理: 💎🔭🤔🙌 只有在完全没有Content-Type响应头时,接收方才可能需要根据网页的内容并结合资源定位URI后缀,去主动检测传输的媒体类型。 啊哈,尽管规范里这个要求已经说得非常清晰,但它的出现实在有点儿太晚了。大多数浏览器已经把这条规则破坏到一定程度,对潜在的后果已经没什么其他的办法修复,所以浏览器的作者们对放弃这些违规代码百般不舍。尽管在过去10年间,若干项最离谱的错误已经被谨慎地修正了,但两大巨头微软和苹果仍然相当抵制这条规范。他们认为对解析出错的Web应用进行额外处理,要比其中显而易见的安全问题来得更重要。为平息针对他们的指责,他们也引入了若干不太完美,属于妥协性质的安全机制,指望消除这些风险。 到今天,内容处理策略的各种修修补补,还有其后引入的各种限制,使线上世界始终笼罩在一道长长的阴影之下,在创建某些类型的Web服务时,就得考虑使用某些别扭甚至代价高昂的机巧手段。要理解这些限制,让我们先来看看这几种场景,在这些场景里一些常规无害的文档是如何被错误地解析成HTML或类似格式的。 👍⛄🦀📵🐢 1.文档类型检测的逻辑 所有浏览器都支持最简单也最没有争议的文档类型检测,就是在响应头里完全没有Content-Type头域时的实现逻辑。这种情况实际上很少会碰到,多半是由于程序员无意中忘了写这个头域或写错了头域的名称,或页面是通过非HTTP传输机制加载,如ftp:或file:。 🧑🍳🩳🔭😍👂 只谈HTTP的话,RFC文档里明确规定在缺乏Content-Type值时,浏览器可以主动检查返回的内容。其他的协议通常也是采用同样的处理,因为底层的代码往往是这么设计的。 这时候浏览器往往会检查文件的静态特征,与已知的几十种文件格式相对比(如图片以及通常由插件处理的各种文件),得到文件类型的猜测结果。对那些没有格式特征的文档,如HTML文件,会扫描返回的内容,查找特定的子字符串(在这种情况下,浏览器会去找某些熟悉的标签——如<body>、<font>等等)。许多浏览器除了内容里的信息,还会考虑URL里路径部分的文件后缀,譬如是否为.html或者.swf字符串。 但在不同的浏览器之间,内容检测的逻辑还是差异巨大,也没有详细的文档或标准化的规定。要说明白这个问题,可以拿没有Content-Type响应头的Adobe Flash(SWF)文件来做例子:Opera会无条件地根据内容签名来做判断;在Firefox和Safari里,URL需要具有明确的.swf后缀;而在IE浏览器和Chrome里,则根本不会自动识别SWF。👨🚒👜🏮😭🖕 SWF文件格式并非特例,其他的情况也差不多如此。例如,在和HTML文件打交道时,Chrome和Firefox会自动检测在文件起始部分的位置里有没有发现某几个预定义的HTML标签;而Firefox只要碰到URL里有.html这样的文件扩展名,就会急切地把文档判断为HTML类型,即使没有发现能识别的标签代码。IE浏览器则又不一样,当没有Content-Type响应头时,默认就会把文档简单地归为HTML格式处理,而Opera则会扫描返回数据里的前1000个字节,看是否有能识别的HTML标签。 这些乱七八糟非常想当然的处理,都基于一个假设:页面发布者是故意不设置Content-Type响应头的 但这假设并不一定正确,而且已经由此引发了不少安全漏洞。 👩✈️👗💰😳👍 那么既然大多数Web服务器都会积极地提供Content-Type响应头信息,而且即使程序脚本没有明确设定这个值,服务器也会指定一个默认值呀,那可能我们就无需担心此事了?可惜得很,内容检测问题远非这么简单。 1.1. 格式错误的MIMEType写法 HTTP RFC规定只有在缺乏Content-Type响应头时才应该由浏览器进行内容检测;如果有Content-Type响应头,不管其形式和格式是怎样的,规范里都明确规定浏览器不应该再主动去猜测网站管理者的用意。但实际上,这条建议并未得到严格的遵守。从服务器端返回的MIME类型写法无效时,如果厂商再往悬崖边多迈一小步,那就会主动去猜测页面的类型了。🧑🚀💎🧻😃💪 根据RFC的规定,Content-Type响应头的值应该由斜线符分隔,由前后两段包含字符和数字的标记名称组成(前后两段代表着“类型/子类型”),还有可能另外再跟着一个由分号分隔的参数。标记名称可以由任何非空的7位ASCII字符组成,但不包括7位ASCII字符里某些特殊的“分隔符”(这类字符一般包括“斜线符)。 大多数浏览器都遵从这套语法,但各自的处理又不太统一;基本上只要缺少了斜线符,几乎每种浏览器都会主动去猜测内容的类型,而在标识符的前半段(也就是“类型”这个标记位)如果包含空格和某些(但非全部)的控制符也会启用内容检测。而另一方面,在这个字段里如果非法地使用了高位的字符或分隔符,则只有Opera会认为这是无效的。 👴🪖🗝😛👄 这么设计实在让人费解,当然公平地说,它们对安全的影响还算相当有限。从Web应用开发人员的角度来说,应该注意不要输错Content-Type的值,也不要允许用户任意地设定MIME Type值(譬如仅仅针对黑名单进行无效选项的验证,这是不妥当的)。这些对开发人员的要求看上去似乎有点让人意外,实际上它们往往也并不太重要。那么,我们要抱怨的是什么呢? 1.2. 特殊的Content-Type值 人们是首先从表面上看颇不起眼的application/octet-stream这种MIME Type类型开始认识到,内容检测机制其实也具有危险性^实际上所有和HTTP协议相关的规范里都没有明确提过application/octet-stream这种MIME Type类型,只有在和MIME Type相关的RFC 2046文档深处才能找到对它特殊(但也颇含糊)的角色定义:🧓🩲🎷😚🙏 建议程序在接收到application/octet-stream类型的数据时,应直接把数据以文件形式保存起来,或把它作为用户相关处理的输入,此时无需再关注Content-Transfer-Encoding的值. 单看所引用的这段文字,这种MIME Type类型的原意还是很含糊,通常是在Web服务器认为返回的文件无论对服务器端还是客户端,都没有什么特殊含义时,才会用到这种类型的设置。随后,大多数Web服务器对各种不透明的非Web文件,如供下载的可执行文件或归档文件,假使没有更适用的Content-Type与之相匹配,都会默认地把它们归到application/octet-stream类型里。 🥷🛍💿🤤👂 然而,在某些罕见的情况下如管理员做了错误配置(例如,无意中删除了Apache配置文件里的全部Add Type指令设置),某些原本应该直接由浏览器解析处理的文件,可能都被Web服务器回退到application/octet-stream这种MIMEType类型。这种配置错误原本是非常容易觉察和修复的,但Microsoft、Opera和Apple都选择对这种情况进行补偿。这几家浏览器在碰到application/octet-stream类型文件,都会急切地启用内容检测. 这种特别的设计使得Web应用很难按用户的意愿存放二进制文件。例如,任何代码托管的平台在返回的可执行文件或源代码压缩包时,对加人application/octet-stream设置都要万分谨慎,因为它们有可能会被当做HTML解析,导致文件直接显示在浏览器里的风险。这对任何提供软件下载的站点、Webmail系统和其他各种Web应用来说,都是个大问题(对这种站点来说,使用其他一些更通用的MIMEType会更安全,如application/binary,因为浏览器没有专门针对这些类型的处理)。 除了对application/octet-stream的特殊处理,另一个有危害的例子是text/plain。这种情况只出现在Internet Explorer和Safari里,缘由也要追溯到RFC 2046。这份文档里说,text/plain有两种用途:首先,用于纯文本文件的传输(这类文件“不用于也不允许格式显示,字体属性、处理设定、解析指令或内容标记修饰”),其次,当发送者无法识别文档类型时,作为回退的手段,可以把文档按纯文本格式处理。👩🧢🧹😅✊ 在电子邮件里对application/octet-stream和text/plain不同的回退处理是完全合理的,因为这份RFC的主题原本也是关于电子邮件的,但事实证明这种处理基本不适用于Web。然而,某些Web服务器也会把text/plain作为某些类型响应的默认值(最值得引起注意的,就是CGI脚本的输出默认即为此格式)。 而其后在IE浏览器和Safari对text/plairi类型的文档也会进行HTML检测的处理实在很糟糕:这就抹杀了开发人员使用这种MIME Type为用户提供纯文本文件的权力,而且又未提出其他替代性的选择。这么做已经导致了若干Web应用漏洞,但直到今时今日,IE浏览器的开发人员似乎对他们的代码既无悔意亦不打算纠正这种默认处理方式。 👩👖🔑💀 而Safari的开发人员则又不同,他们倒是对此问题有认识,也尝试降低风险,又维持原功能不变——但他们没有意识到Web的复杂程度。他们的解决之道是,除了判断返回的文件数据体里是否有可疑的HTML标记之外,还会做第二项检查。就是看一下URL路径的结尾,是否为.html或.xml这样的扩展名,作为内容检测的补充特征。毕竟,站点的所有者不会随便这么命名文件的,对不? 唉,他们引入的这种特征检查几乎是聊胜于无。因为大多数的Web框架都至少有一种办法,能把原本在查询字符串里的参数,通过编码转为放到URL的Path路径部分进行传输。例如,Apache有个机制叫PATH_INFO,而它正好是默认启用的。 通过这种参数传递机制,攻击者可以在Path部分添加一些与实际功能无关的垃圾数据,以迷惑浏览器又不影响服务器对提交请求的响应。 ✋🚤🍞‼🐙 要描述清楚这个问题,可以看看这两个URL,它们在Apache或者IIS上有可能具有相同的效果: 以及 👮♂️👚🎷😈🤞 在一些不常见的Web框架里,以下做法甚至也是可行的: 🦴🗼🎂♊🐅 1.3. 无法识别的Content Type类型 尽管Text/plain已明显带来麻烦,但IE的工程师却决定进一步深化浏览器的内容检测功能。IE浏览器不但会针对一系列通用的MIME Type类型同时启用内容检测和后缀匹配机制,对一些它无法识别出来的文件也会这么做。这类文件的范围很广,包括从JSON(application/json)到多媒体格式如Ogg Vorbis(audio/ogg)等各种类型。🧒👒📷😫👂 这种设计自然是有问题的,除了一小部分注册在浏览器内部,浏览器完全支持的MIME Type类型或通常会转给外部应用程序处理的文件类型,那些能由用户控制的其他托管文件格式就会带来严重的问题。 IE浏览器对内容检测的处理并未止步于此:在碰到浏览器内部原本可以识别的文件格式但出于未知的原因该文件不能被正常解析时,这款浏览器还是会继续检查文件返回的数据。在IE8以前的IE浏览器版本,在碰到由用户提供的内容不正确的JPEG图片时,可能会把它当成HTML来解析。 🦴🌡🫖🦮 而且更滑稽的是:即使只是细微的错误,如把正常的GIF文件类型误设为Content-Type:image/jpeg,也会触发同样的代码处理。天晓得,仅在几年前IE浏览器还会把类型设定正确且有效的PNG文件当成HTML。谢天谢地,现在这些处理逻辑已经被禁用了——但其他的陷阱仍然泛滥。 注意:要完全理解有效图片的内容检测风险,就需要记住,想构造一个格式验证不会出错的原始图片文件,而其中包含攻击者精心挑选的ASCII字符串,例如HTML代码,实际上并不困难。事实上,即使这个构造的图像,会经过固定的确定算法,来进行磨砂、放大缩小和压缩等处理,要想在处理后的二进制流里获得任意字符串也仍然是相对容易的。 当然也要表扬一下,从IE8开始,微软决定禁用对Image/*这个MIME类别里绝大多数已知图片文件的内容检测。对于浏览器不认识的图片,如Image/jp2(JPEG2000),也不会进行HTML检测(但还是会检测XML)。🧑🎤🧦🛋😚💪 但除了这项唯一的调整之外,事实上微软不愿意对IE浏览器的内容检测逻辑做任何有意义的改变,微软的工程师也公开表态,认为仍然需要兼容那些有问题的网站。微软可能是为了避免触犯大机构客户,因为他们有很多古老和设计得很差劲的内部应用,完全依赖于IE浏览器独家的特殊处理,才能得以运行。 反正无论出于何种考虑,IE浏览器对text/plairi类型的处理逻辑都是有隐患的,在新版本的IE浏览器里提供了一种局部的解决之道:通过发送可选的HTTP响应头域X-Content-Type-Options:nosniff,这样网站管理员就能禁用绝大多数有争议的内容检测逻辑。非常建议使用这个头域,但遗憾的是目前IE6和7还不支持这一机制,而其他的浏览器也只是有限度地支持。换而言之,不能把这个响应头当成解决内容检测风险的唯一救命稻草。 🧑⚕️👔🏮💀 1.4.防御性使用 Content-Disposition 曾提过几次Content-Disposition响应头域,在某些场景里,可以考虑把这个头域用作内容检测的防御性机制。这个头域的功能在HTTP/1.1规范里解释得不太令人满意。而实际上仅在RFC 2183里才有对它的详细描述,这份文档里谈的是该头域在邮件应用里的用法。 数据体部分可以被设定为“附件”,以和邮件消息里的正文区别开来,不应该自动显示附件的内容,但这也要取决于用户的进一步行动。MUAe可以为用户提供一个位图图标代表这个附件,或者在文本型终端里,给出一个附件列表,供用户从中选择查看或存储附件。 👳🥼📟😡👂 HTTP RFC认可了Content-Disposition:attachment在Web领域里的使用,但又没有详述具体应该怎么用它。实际上,常规加载文档时如果碰到了这个头域,大多数浏览器会显示文件下载对话框,通常有3个按钮:“打开”、“保持”和“取消”。 除非选择了“打开”选项,或者文档被保存到磁盘上再手工地打开,否则浏览器也不会再尝试去解析这个文档了。对于“保存”选项,在Content-Disposition响应头里可以包含一个可选的文件名参数,这个就是建议使用的文件名。如果没有这个值,浏览器会很不靠谱地按照URL的路径提取出一个默认文件名。 👃🏠🍏✔🐶 大多数浏览器碰到这个响应头时,并不会直接解析或显示其返回的数据,所以这个头域特别适合用于那些只供下载用的不透明文件,譬如前面提到的归档文件或可执行文件。 进一步来说,如果碰到和类型相关的子资源加载时(例如<image> 和 <script>),这个头域就会被忽略掉,也就是文件不会被直接下载,但以这个方式,却可以保护用户控制的JSON响应、图片等内容,以避免由此引入内容检测的风险。(为什么所有的浏览器在加载特定类型的子资源文件时,都会忽略Content-Disposition响应头的原因也不是特别清楚,但既然有这种好处,那我们最好也别追究其背后的逻辑了。) 以下例子展示了在JSON响应里,怎样尽可能地通过采用Content-Disposition和其他头域,较为合理可靠地规避内容检测风险: 👂🧳🥩🈳🦬 [mw_shl_code=html,true] 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>",...}[/mw_shl_code] 只要有可能,非常推荐使用Content-Disposition头,但也要意识到,并不是所有的客户端程序都支持这一机制,而且关于这个头域也缺乏完整的文档。在更小众的浏览器里,如Safari Mobile,这个头域就无效了;在Internet Explorer6、Opera和Safari等主流浏览器里,也有若干因为攻击者的控制,使得Content-Disposition头变得无效的缺陷。 另一个和Content-Disposition可靠性有关的问题是,在下载文件时,用户可能仍然倾向于选择“打开”。可不能指望仅仅因为有个下载提示框的阻拦,普通用户就不会轻易地选择直接打幵Flash小程序或者HTML文件。在大多数浏览器里,选择“打开”使得文件处于file:源的状态,这本身可能就有问题(Chrome的改进对此问题的处理会有改善),而在Opera里,这样打开文件会按原页面所在域的执行环境权限来对待。 👁⛪🦀ℹ🐙 也许IE浏览器的选择是最合理的,当然这个也还有待探讨;它的做法是把HTML文件放在一个特殊的沙盒环境里,使用mark-of-the-web机制来执行,但即使在这种浏览器里,这套机制也没有涵盖到Java或者Flash小程序。 1.5. 子资源的内容设置 🧠🚠🍇🚷🐻 大多数内容相关的HTTP头域,如Content-Type、Content-Disposition和X-Content-Type-Options,对于和具体类型相关的子资源加载都是基本无效的,例如<img>、<script>和<embed>。在这些情况下,几乎完全由引用这些子资源的页面决定浏览器要以哪种格式来解析这些子资源。 而如果对这类资源的请求是从插件的可执行代码里发出时,Content-Type和Content-Disposition的设置则几乎可以忽略不计了。例如,任何text/plain或text/csv文档都有可能被Adobe Flash当作安全敏感的crossdomain.xml策略文件来解析,除非在目标服务器根目录上有正确的全站元策略文件设置。无论这种情况是叫“内容检测”或仅仅是“内容类型盲猜”,问题是显而易见的。 结果就是,即使非常正确严谨地使用了我们之前提到的各种HTTP头域,也要谨记第三方页面仍然有可能诱使浏览器把页面当做某种有问题的文档类型来解析;插件小程序和插件相关的内容、PDF、样式表和脚本都是需要特别关注的对象。要降低这种不幸的风险,对任何返回内容的结构和字符集都需要加以限制,或使用“沙盒”域来隔离那些不能被有效限制的文档类型。 👃⛪🦞❓🦠 1.6. 文件下载和其他非HTTP内容 Content-Type、Content-Disposition和X-Content-Type-Options等头域的表现,可能很令人费解并且充满了各种例外的情况,但至少,它们构成了一个相对统一的整体。 👦🩲📀😥👊 然而,在现实世界里,还有一些被遗忘的角落,在这些场景里,就完全没有以上头域所提供的元数据了——这种情况下内容检测的限制也就全然没有保障了。例如,处理通过ftp:协议传输的文件时,或通过file:协议保存或打开的文件,这些情况与具体的浏览器和具体的协议密切相关,即使最有经验的安全专家,有时候也会觉得意外。 当打开本地文件时,浏览器通常都会优先考虑文件的扩展名信息,如果扩展名是浏览器能明确处理的类型,如.txt和.html,大多数浏览器就会根据这个信息取值了。Chrome却是个例外,它会尝试自动检测这些文件是否为某些“消极”的文件类型,如JPEG,即使是对.txt后缀的文件也会检查(而HTML,则完全不属于“消极”文件之列)。 如果是注册给外部程序处理的其他扩展名,情况要更难以预期。IE浏览器通常会启动外部程序来打开这些文件,但大多数其他浏览器会启用内容检测,情况和这些文件是通过HTTP形式加载且没有Content-Type设置时的处理一样。如果是个完全陌生的扩展名(譬如说.foo),所有的浏览器都会回退为启用内容检测。👴🧣📮😉👊 对file:协议的文件处理,完全依赖于文件扩展名的信息和内容检测,这与通过互联网访问资源时的处理形成了有趣的对比。通过Web方式访问的时候,Content-Type是文件类型最权威的描述。文件扩展名在大多数情况下都会被忽略,所以如果一个实际上是JPEG类型的文件,通过这样的地址 👨🎨👓📥🥰🤙 访问,是完全合法的。 但文件被下载到磁盘之后的情况会怎样呢?好吧,在这种情况下,该资源的有效含义就出人意料地改变了:通过file:协议去访问它时,浏览器会坚持把它当文本格式来显示,严格听命于文件的扩展名信息。 上述例子还算相当无害,但在其他内容相关的场景里,譬如把图片保存为可执行文件,就可能带来很大的麻烦。对这种后果,Opera和IE浏览器会根据文件的Content-Type对应的MIME Type值,尝试修改保存下来的文件扩展名。其他浏览器则没有提供这样的保护,甚至可能完全被这种情况搞糊涂了。 🦴🧳🍒♏🐕 这个问题展示了在使用Content-Disposition附件设置时,返回一个明确的无害的文件名是有多重要,否则会导致受害者被诱导而下载的文档格式最终完全背离了网站本意。和file:URL的复杂逻辑相比,ftp:协议处理之简单简直叫人吃惊。当通过FTP访问文件时,大多数浏览器都不太关心文件的扩展名,而沉浸在胡乱的内容检测里。唯一的例外是Opera,它的扩展名优先级依然高于内容检测。从工程的角度来说,现在占主流的FTP处理方式其实还蛮符合逻辑的:因为FTP协议大体相当于HTTP/0.9协议。然而,这个设计也违背了最小惊讶原则(Principle of least astonishmente)。服务器的所有者肯定没想到由于允许用户上传.txt文件到FTP站点,导致在自己的域范围内,实际上自动允许存放活跃的HTML内容了。 2.字符集处理 👨🎨🩴🛏😒🙏 文档类型检测是浏览器内容处理这套拼图里最重要的一块,但显然并不是唯一的一块。对各种显示在浏览器里的文本类型文件,还需要确定一件事:输人的数据流使用的是哪种字符集编码以及浏览器需要以哪种字符集编码来处理这些数据。浏览器的输出通常使用UTF-8或GBK编码;而传人的内容,就要取决于页面的作者了。 在最简单的场景里,由服务器提供的Content-Type头域中的charset参数决定了编码的方式。如果是HTML文件,在某种程度上可能还需要根据文件里的<meta>指令设置来决定它的编码方式(在真正开始页面解析之前,浏览器会猜测性地尝试提取和解析页面里的这个指令)。遗憾的是,当缺乏charset参数或charset参数无法识别时,某些字符编码的危险性和浏览器的处理,都会带来一定的风险,事情远比前面提到的简单规则判断要复杂和有趣。要理解哪里会出问题,让我们先来认识以下三类可能改变HTML或XML文件语义的字符集吧: ●能用非标准方式表示7位ASCII码的字符集。可以使用这类非常规的序列,非常狡猾地对HTML语法元素如尖括号或引号进行编码,以逃避服务器端的常规检查。例如,著名的UTF-7编码方式,就可以把“<”符号编码成这样的一个5字节序列“+ADw-”,大部分服务器端的过滤都不会拦截这个序列。相似的做法在UTF-8里也有,尽管在规范上UTF-8是禁止这么做的,但在技术上却是可行的,譬如“<”符号可以很复杂地以2到6位的字节序列来表示,形式可以从0xC0 0xBC—直到0xFC 0x80 0x80 0x80 0x80 0xBC 🙌⛄🍟☣🦖 ●特殊前缀加上其后1或多个字节能组成有特殊含义的可变长度编码方式。这种逻辑会导致原本合法的HTML语法元素被“吞并”掉,出人意料组成了一个多字节文本。例如,在日文ShiftJIS编码里加上前缀0xE0,会使得在其后的尖括号或者引号在IE浏览器、F"irefox和Opera(但不包括Chrome)被吞掉,有可能大大地改变文档内部代码的含义。 相反的情况也会出现:服务器认为自己输出的是多字节文本,但浏览器可能不认识这种文本,而把它们当做几个独立的字符处理了。在韩语EUC-KR字符集里,0x8E前缀的后面只有跟0x41或更高数值的ASCII码才是有效的韩文字符。低于这个范围则不会产生预期的效果了,但并非所有的服务器端实现都注意到这个问题了。 👩🧢📮😤✍ ●与8位ASCII码完全不兼容的编码方式。这会导致客户端和服务器端看到的文档结构视图完全不同。常见的例子包括UTF-16和UTF-32。除非服务器对它产生的字符集有绝对的控制权,并确定客户端不会对返回的数据做超出预期的变型处理,否则很难保证是否带来严重的安全并发症。例如,这个Web应用会移除HTML片段里粗体部分由用户控制内容里的尖括号,进行有害字符过滤: [mw_shl_code=html,true] 您当前在查看: <span class="blog_title"> +ADw-script+AD4-alert("Hi mom!")+ADw-/script+AD4-👦👒💾😔👌 </span>[/mw_shl_code] 但如果接收方是以UTF-7格式来解析这些内容的,实际获得的代码如下 [mw_shl_code=html,true]您当前正在查看: 👴🧦💰🙂💪 <span class="blog_title"> <script>alert("Hi mom!")</script> </span>[/mw_shl_code] 下面是个类似的问题,但这次和ShiftJIS编码里的字节吞并有关。通过一个多字节前缀,相邻的引号被吞并了,结果导致相关的HTML标签不能按预期的结果闭合,使得攻击者可以在代码里注人额外的onerror处理器: 🦴🪐🍓📳🦊[mw_shl_code=html,true] <img src="http://fuzzybunnies.com/[0xE0]"> ...这里仍然是HTML代码的一部分... ...但服务器不知道... "onerror="alert('这部分会被执行!')" <div> 👨🦱🧢💾😴🦴 ...页面内容继续.. </div>[/mw_shl_code] 对于内容里包含用户控制数据的文本类型文档,就应该强行禁止字符集自动检测。大多数浏览器在Content-Type头域里没有charset参数或页面里缺少<meta>标签时,就会尝试自己来检测字符集。各种浏览器中的具体实现存在一些显著的差异(例如,只有IE浏览器会急切地辨认UTF-7),但绝不应该假设字符集检测的结果是安全的。 👳🎒💊🤡🙌 如果浏览器碰到不能识别的字符集或字符集的名称确实被弄错了,就会启动字符集自动检测;由于字符集的命名可能含糊不清,而浏览器对常见名称的变型能有多大的容错度也不统一,所以才导致了这个问题。其中一个例子就是IE浏览器会把ISO-8859-2和ISO8859-2(在ISO之后没有横线)都视为Content-Type头域里的有效字符集写法,但却无法识别出UTF8其实是UTF-8的别名。这些错误的做法可能会导致一些严重的问题。 注意:有趣的是,X-Content-Type-Options这个头域对字符检测逻辑并没有影响。 2.1. 字节顺序标记 🧠🏠🥚🅾🐖 我们还没有说完字符集检测的事情呢!由于IE浏览器有一个非常误导的内容处理机制,所以需要单独拎出来说一下:IE浏览器对字节顺序标记(Byte Order Marks,BOM)信息的优先级要高于明确的charset参数,BOM是一串放在文档开头的字节序列,这串信息决定了文档的编码方式。如果在输入的文件里检测到了这样的标记符,那就不需要理会其他方式的字符集设定了。 下表显示了几种常见的标记符。在这些类型里,由可打印字符组成的UTF-7BOM是其中特别有问题的一种。 🤝🚈🍼❓🐝 2.2. 字符集继承和覆盖 在评估常见浏览器的字符集处理策略有什么潜在影响时,还需要考虑两个较不为人知的额外机制。攻击者有可能利用这些机制,无需依赖于字符检测,就可以对其他页面强制应用一个不正确的字符编码。 🤝🚂🍏❌🐟 第一个有问题的机制是IE浏览器支持的字符集继承。在这个策略里,如果子页面本身缺乏有效的字符集设置的话,顶级框架里定义的编码就会自动应用于框架内的这些子页面。最开始的时候,所有的框架场景都采用这种继承方式,即使这些子页面属于完全无关的站点。后来Stefan Esser、Abhishek Arya和其他几位研究人员通过强制UTF-7解析,展示了针对毫无防备的目标进行攻击的可能,Firefox和WebKit开发人员决定把字符集编码继承限制在同源的页面之间(Opera依然支持跨域的编码继承。尽管它不支持UTF-7,其他可能出问题的编码也会有同样的效果。) 另一个值得一提的机制是,浏览器可以手工设置页面的字符集编码,来覆盖当前使用的默认编码方式。在大多数浏览器里这个功能可以通过在浏览器菜单里选择“查看”->“编码”或类似的方法实现。使用菜单改变字符集会使这个页面和所有的子框架页面(包括跨域的页面!)都会按照所选的编码方式重新解析,这些内容原先的编码字符集设置已经完全没有意义了。 🧑🚀👠💶🤔🖕 因为用户可能会被轻易诱骗,而对攻击者控制的页面选择了其他的编码(仅是为了正确显示页面内容),这事令人很不安。因为不可能指望一个漫不经意的用户意识到,他的编码选择也会应用于隐藏的<iframe>标签,所以即使表面上看无害的动作,也可能导致无关Web页面上的跨站脚本攻击。但说真的,大多数人根本不会意识到——他们也确实不需要知道 <iframe>到底是什么意思。 2.3. 通过HTML代码设置子资源字符集 我们正接近在Web内容检测这漫漫征途的尾声,但还差一点没说完。聪明的小伙伴大概还能想得起来,在前面曾提到某些类型的子资源(主要是样式表和脚本),包含这些资源的页面可以指定子资源的charset值,使得返回的文件按设定的编码进行特定转换,例如: 👎🌡🍟📶🐟[mw_shl_code=javascript,true] <script src="http://fuzzybiinnies.com/get_js_data.php" charset="EUC-JP"> [/mw_shl_code] 除了Opera,其他所有的浏览器都接受这个参数。如果浏览器支持这一参数,通常它的优先级要低于Content-Type里的字符集设置,除非缺乏Content-Type设置或无法识别该设置。 🧑🚀🎒📀😋✋ 但每个规则都会有例外,而且往往如此,这个例外就是IE6。在这个依然常见的浏览器里,HTML代码里的编码设定优先级要高于HTTP数据里的Content-Type。 这种行为会对现实中的Web应用有影响吗?要完全理解这个问题的后果,让我们来快速回顾一下前面的知识,我们曾讨论过怎样保护由服务器端产生的JSON格式的用户数据,以防止它们被跨域包含。譬如Webmail应用需要在地址本里查找联系人..它通过URL传递要查询的信息,然后以JavaScript序列化数据的方式,把符合条件的联系人信息返回给浏览器,但需要作必要的防护以避免无关的站点包含这些数据。 现在,让我们假设开发人员为了防范第三方网页通过<script src=...>方式来包含这段数据,就在返回的数据之前添加了注释符“//”。同源的调用者使用XMLHttpRequestAH可以很容易通过检查返回的数据,把额外加入的前缀注释符给过滤掉,再把后面的数据传给eval(...)处理——但远程的调用者如果通过<script src=...>语法企图利用这些数据,就会失败了。👩🧦🤐🤛 在这个设计里,类似这样的请求:/contact_search.php?q=smith,响应可能如下: [mw_shl_code=javascript,true] //var result = {"q":"smith","r":["[email protected]" ] }; [/mw_shl_code] ✊🎠🌰❌ 只要搜索的词语被正确地转义或过滤,这个处理看来还挺安全的。但如果我们意识到攻击者可能强制返回的数据按UTF-7格式来解析,情况又会完全两样了。一串从服务器端看起来无害的搜索关键字,似乎并未包含非法字符,却可能出人意表地被解码成这样: [mw_shl_code=javascript,true] //var result = { "q": "smith[CR][LF] var gotcha = {"","r":["[email protected]"] } ;[/mw_shl_code] 🤌🌦🍓🈸🐠 在受害者浏览器端以<script src=...charset=utf-7>方式加载脚本时,攻击者就能获得用户的部分地址本信息了。 这并非一个臆造的场景:使用“//”注释符的做法在Web应用里很常见,著名研究员Masato Kinugawa就发现了好几种流行的Web应用都受到这个缺陷的影响。对于其他阻止脚本执行的前缀字符,如while(l);,也有更处心积虑的攻击手段。 总之,我们一直强烈建议,添加的前缀一定要很可靠地阻止JavaScript引擎去解析返回的数据,以避免攻击者控制的数据能部分获得执行的原因。噢,如果还打算支持E4X的话,那情况就愈加复杂有趣了,但我们还是点到为止吧。👨⚕️👒🖨😃👆 2.4. 非HTTP文件的编码检测 在结束本章之前,让我们来看最后一个没讲到的细节:对非HTTP协议传输文件的字符集编码检测。可以想象得到,文件被保存在磁盘后,通过file:协议或其他方式打开时,就完全没有Content-Type信息了,这时就要依赖于字符集的检测逻辑了。#t255: 🙏💈🥚🚭🐙 然而,和文档类型的检查不同,各种传输方式里的字符集编码检测没什么大差别:在各种情况下,表现的行为都差不多。 没有什么明确的一揽子解决各种文本文档里字符检测的办法,但对HTML文件来说,可以在文档的内部嵌人这样的<meta>指令信息来设置字符集编码: [mw_shl_code=html,true] <meta http-equiv="Content-Type" content="text/html;charset=..."> 👍⛵🦞🉑🐋[/mw_shl_code] 不能因为有了<meta>数据,而放弃Content-Type头域。因为和<meta>不同,Content-Type头域对非HTML内容也适用,而且便于在全局范围内做编码设置和审计。也就是说,对那些有可能会被保持到磁盘,而且其中包含攻击者提交数据的文件,才需要用到这个冗余的<meta>标签(当然要确保这个值与实际使用的Content-Type类型是一致的)。
帖子热度 1.1万 ℃
|
|
大师的话真如“大音希声扫阴翳”,犹如“拨开云雾见青天”,使我等网民看到了希望,看到了未来!晴天霹雳、醍醐灌顶或许不足以形容大师文章的万一;巫山行云、长江流水更难以比拟大师的文才!黄钟大吕,振聋发聩!你烛照天下,明见万里;雨露苍生,泽被万方!透过你深邃的文字,我仿佛看到了你鹰视狼顾、龙行虎步的伟岸英姿;仿佛看到了你手执如椽大笔、写天下文章的智慧神态;仿佛看见了你按剑四顾、指点江山的英武气概!
|