HTTP/0.9
0.9的版本虽然看起来和1.0 1.1只差了一点点,但实际上,0.9只能算是HTTP 一个非常原始的版本。完全没有为客户端和服务器提供任何额外的元数据交换空间,其过程非常的简单,只有一行,客户端的请求只有一行,比如以GET 开头,后跟URL 路径和查询字符串,CRLF结束,服务器端接到后立刻返回HTML 数据。
所以,这个版本上,有很明显的缺陷,比如无法根据浏览器用户的首选语言或者文档类型进行交互处理,找不到请求文件或返回内容不是HTML的话,无法通知客户端,比如不能做到在一个服务器用一个IP 支持多个不同主机名的网站。
HTTP/1.0 & HTTP/1.1
下面这两个版本,修正了这些问题和缺陷,其格式在之前的HTTP 详解里,已经介绍过了,请求头里包括了具体版本信息,和许多以键值对形式存在的数据,同时一个单独的空行代表结束。响应头则包含了包含协议版本,响应码,状态信息,和一些其他的键值对形式存在的响应信息,同时以一个空行结束,随后才是要返回的具体资源。
很显然,这一版本就成熟了许多,但是协议里有一个要求,HTTP/1.0 的客户端必须能够理解HTTP/0.9 ,这看起来是一种向下兼容的表现,但实际上会带来许多恶果,如果攻击者想要攻击某个HTTP 客户端,攻击者会试图令客户端HTTP 降级,采用0.9的模式,而这种模式下,十分危险,返回给客户端的文件,然而在0.9中,返回的内容只有请求的文件本身,无法从这些回应的内容里,表明响应方确实可以理解HTTP 协议,以及返回内容是否是真的HTML 文件。
这让我想起了SSL 里的安全问题,也是由于SSL 3.0 需要向下支持SSL 2.0 ,而SSL 2.0 中存在有严重的漏洞,所以攻击者会故意引起降级到SSL 2.0,寻找漏洞。到后来,进步到TLS后,SSL 3.0也不被信任了。
换行处理
这是一个小细节,HTTP/1.1 要求客户端不仅要接受CRLF 和LF 换行模式,还要接受CR,然而不同的浏览器和服务器却又有不一样的支持情况,比如Apache 就不接受RFC 这条建议,而FireFox以外的客户端都能接受。
所以,开发人员在处理HTTP 头域的时候,不仅要考虑LF,CRLF,还要考虑CR 字符。不然就有可能产生漏洞利用,也就是在头域中注入CR ,响应拆分。
另外一个不安全的小细节是,HTTP/1.1 要求支持多行请求头,也就是以一个空格开头的行,认为是接上一行的内容。但是这一规定,有些浏览器和服务器支持,有些却并不支持,比如IE ,Opera 等等都不支持。这种混乱,就会造成漏洞,不过这种攻击可能性比较低一些,我也没想到它能造成什么严重的影响。
经过代理的HTTP 请求
这一块在HTTP 详解那里,只对代理这一部分做了基础的了解。使用代理的原因有很多,比如为了提高性能(缓存),强制某些网络的访问策略(禁止)或需要以代理方式接入某些独立的网络环境等等。
相比于普通语法格式,有一些不同:第一行URL 是一个完整的URL,而在Host 中也要标识出主机名,这看起来是重复的,但实际上两者是两套机制独立发展起来的,当Host 与 URL 不匹配时,代理服务器以URL为准,或用特定的URL-Host 数据对和缓存内容关联
同时,代理服务器还允许浏览器获取非HTTP 类型的资源,比如FTP 文件或者目录。这种情况下,数据返回给用户之前,代理服务器会把HTTP 响应里返回的内容先封装整理,转换成HTML格式。而如果服务器不便于查看数据,就另做处理了。也就是Connect 方式,这样代理服务器就会进行盲转发。当时,盲转发中间存在一个疏忽,不过已经解决,处理代理服务器返回的非加密错误响应信息时,浏览器会认为是目的服务器返回的。
另外有一些代理服务器并不适用HTTP 方式和浏览器打交道,这些代理为了缓存内容或者强制执行某些规则,会需要检查HTTP 过程中的信息交换。比如一个透明代理,它拦截TCP/IP 层流量,但是它能看到连接的目标端IP,和主机头Host信息,但是它并不能确认,要连接的目标端IP 是否真的和Host 设定的服务器名称匹配,除非去做一次查询,确定两者是否相关,否则客户端和服务器串通好久回对代理服务器造成影响。比如,如果不检查,攻击者可以向代理服务器要求连接自己的服务器,但发送的是一个故意误导的代理的Host头,www.google.com ,这样,那些想要访问www.google.com 获得的可能是错误缓存的响应内容。
重复或者有冲突的头域的解析
一句话,不同的浏览器和服务端都有不同的处理,而且在协议中,对此的描述也有很多小的疏漏,比如同时有用HTTP/1.0 和1.1 相同功能但是不同名称的头域,应当如何处理,比如EXPires 和 Cache-Control。
另外,比如第一行的URL 和 Host 冲突时候,应当怎么处理,文档要求是不要理会Host, 但是Host 必须存在。
以分号作分隔符的头域值
有一些HTTP 头域,比如Cache-Control 或者Content-Disposition ,可以使用分号来分隔在同一行里的几对独立的’名称=值’数据组,允许使用这种嵌套语法,可以更加有效率和直观。
同时RFC 里规定了某些使用场景,这种数据组里等号右边的参数可以使用Quoted-String(双引号括起来的任意可打印字符组成的字符串,支持分号和空格)。
但遗憾的是有些浏览器对该语法的支持并不好,比如IE,他不能支持分号,如果””中存在分号,有分号的地方就会被截断,造成安全漏洞。
还有一个问题并有做统一规定,就是多次出现的名称=值,名称如果相同,应该如何处理,不同的浏览器和服务器做了不同的处理。一般以第一次出现的值为准。
头域里的字符集和编码策略
这又是一个令人头疼的地方,因为规则混乱,处理方式混乱,甚至可以说,没有任何一套编码方式能同时为所有的浏览器所支持。书里只是简单的举例说明了这个编码有多混乱,记录如下:
Referer头域的表现
Referer 头域里包含的是,哪个URL 地址触发了对当前网页的访问,Referer头域都纠错处理有一定的帮助,但是这个头域也会泄露一定的信息,还会暴露引用页面的查询字符串参数等。
一般情况下,在HTTP 头域都会出现,但除了一下集中情况:
这一块比较清楚,但是其中包含的各种问题有一些之前没有想到过。在此简单列举:
同上,此处也做过详细解释,RFC 文档里列出了几十种状态码,但是常用的大约15种,而其他的一般是为未来准备的。这些有用的,列举如下:
持续会话应当注意,使用KeepAlive 会话时,响应端要包含一个Content-Length 头域,用于判断还有多少数据,有可能遇上拆分漏洞,也是前边所提到的[CR][CRLF]等等的处理品混乱。
对于分段数据传输,RFC 提供了 Transfer-Encoding:chunked 功能,协议里讲,只要可能,产生的数据立刻作为部分内容先发送出去,单独传输的每部分长度,都以16进制整数标识放在一个单独的行里,但是整个文件的长度是不确定的,知道出现下一个0字节标志整段结束。
RFC 里说,如果没有其他服务器端指令,客户端可以默认装若干的GET请求的HTTP响应码进行缓存,只要请求方法和URL 一致。
当响应被缓存后,客户端都会在重用之前判断是否需要验证和重载内容。通过Data/If-Modified-Since 和 ETag/If-None-Match 判断是否重用。这些是隐式的缓存机制,存在一些问题,比如我们通过以上两组的搭配,在结合Cache-Control: private ,能方便的获得浏览器在一段时间内的访问规律和习惯。甚至,如果讲一个字符串标记嵌入和缓存的JavaScript 文件中,然后在访问该文件时候,如果请求头里包含了缓存条件,一律答复 304 Not Modified 。
为此,服务器更倾向于使用显示的HTTP 缓存指令。但是HTTP/1.0 和 1.1之间的处理方式有不同。
1.0中提供的是Expires响应头,判断Expires He Date 之间的联系,文档没有详细的说明,不同的浏览器处理不同,FireFox,Opera 根据两者的差值,其他浏览器则是Expires和缓存服务器的系统时间进行相比。
同时,1.0中还提供一个Pragma: no-cache ,代理服务器如果收到该请求头,就会重新抓取。
1.1 中,则是在Cache-Control 中承载缓存指令,有四种区域:public 可以被公开缓存的文档。private 代理服务器不得缓存文档。no-cache 可以被缓存,不能被重用(仅能用于后退前进)。no-store, 不缓存。
1.0 和 1.1 处理方式的不同,造成了服务器和客户端在处理上,必须两者都兼容。
HTTP 缓存带来的安全问题
攻击者可以通过拦截对某些URL 的请求,想受害者返回被篡改,并且长期缓存的请求内容,这样收到污染的浏览器缓存如何在受信任的网络上被重用,被注入的内容可能就出人意料的重新浮现。甚至,攻击者可以精心选择一些敏感域,然后在其他上下文环境中再引用这些域的内容。
cookie 是四种全局授权方式之一,其他三种是HTTP 认证,IP检查, 客户端认证。
cookie 是通过Set-Cookie 响应头进行设置的,其基本参数之前列举过,现在简单陈列:
HTTP 认证现在很少有人用了,而且会有401钓鱼的风险。
SSL 部分参见另一篇博文。
值得注意的是,出于方便和降低成本的考虑,某些机构现在只需要一张信用卡,在目标服务器放一个用于完成验证的文件就能够获得证书。这种做法导致了证书中除了cn 和 subjectAltName 两个字段,信息都不再可信了。
处理Content-Disposition 头域中用户提供的文件名,如果不需要非拉丁语系的文件名,通常只保留字母数字和. \ - _ ,其他字符一概移除。为了保证用户免遭有害或者欺骗文件,至少保证文件名的首字符是字母或者数字,并把所有最后一个句号外的其他句号一律用其他字符代替。
如果而需要用到非拉丁语系的文件名,按照RFC 文档昨说,根据浏览器的情况使用百分号编码的URL 形式作为文件名,确保过滤控制字符(0xxx~0x1f) 和对任何分号,反斜杠和引号进行转义。
在处理HTTP cookie 里的用户出入信息,应当对字母和数字以外的所有字符进行百分号编码,或者干脆使用base64, 因为那些引号,控制字符(0x00~0x1f),高位字符(0x80~0xff),逗号,分号,反斜杠都可能导致Cookie注入,或是当前Cookie 含义发生变化。
在发送用户提供的Location 头域,应对提供的URL 进行解析和规范化处理,确保URL 对应协议的允许的白名单上,以及重定向到的指定主机是安全的。同时确保任何控制字符和高位字符得到了恰当的转义。
在发送用户提供的Redirect 头域,遵从与Location 主机头一样的建议,注意在这里,分号也是不安全的,而且没办法的进行可靠的转义,而分号在某些特定的URL 里又有特殊的含义,应当干脆杜绝这样的URL 或者对分号进行百分号编码。
构建其他类别的用户输入请求或者是响应时,一定要进行语法检查,排除头域可能导致的副作用,重点注意控制字符,高位字符,逗号,引号,反斜杠,分号。创建新HTTP客户端,服务器或代理时,一定要注意上述那些问题。
HTTP 这一部分,算是吧基础的知识又一次串了一遍,很明显的,所谓Web 安全问题,都是围绕 HTTP展开的,因为任何web的交互,基本上都需要使用HTTP 协议。本篇提到的,我觉得迷惑点最多的还是混乱的编码模式,由于很难搞清楚各种编码的真谛,何处支持何种编码,拒绝何种编码,才能真正的解决那些通过各种构造形式的攻击,所以,关于编码,后边我还会再做讨论,当然,我感觉这是一个漫长的工作量,因为在任何一个犄角旮旯里,都会藏着对Web 致命威胁的点。