为何一个Http Header中的空格会被骇客利用 - HTTP request smuggling

 

导读:本文经过一个Netty的一个issue来学习什么是 "http request smuggling"、它产生的缘由与解决方法,从而对http协议有进一步了解。html

前言

前阵子在Netty的issue里有人提了一个问题 http request smuggling, cause by obfuscating TE header ,描述了一个Netty的http解码器一直以来都存在的问题:没有正确地分割http header field名称,可能致使被骇客利用。java

引发问题的那段code很简单,它的做用是从一个字符串中分割出header field-name:node

 
 
 
 
 
 
 
 
for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
            char ch = sb.charAt(nameEnd);
            if (ch == ':' || Character.isWhitespace(ch)) {
                break;
            }
 }
 

这段有什么问题呢?它不该该把空格也当成header field-name的终止符,这会致使Transfer-Encoding[空格]: chunked 被解析为Transfer-Encoding 而不是Transfer-Encoding[空格]nginx

乍一看可能让不少人迷惑,一个Header Field名称识别错了为何会被骇客攻击呢?大不了就缺了一个Header嘛。但真实世界,却没这么简单。这事,还得从现代的web服务架构提及。git

原由

链式处理

一般现代的Web服务器并不是单体存在的,而是由一系列的程序组成(好比nginx -> tomcat),为了实现均衡负载、请求路由、缓存等等功能。这些系统都会解析HTTP协议来进行一系列处理,处理完后,会将对应的请求分发链路中的下一个(每每也是经过HTTP协议)。github

假设如今一个用户的请求到达逻辑服务器B的过程为: 用户 -> A -> B 。一般会有许多个用户链接到A,而为了减小链接创建的消耗,A到B的链接数会少不少,A与B之间会保持稳定的长链接,这样可以使性能获得提高。因此可能属于不一样客户端的多个请求会共用同一个链接。(以NGINX举例,Nginx从1.1.4开始支持到后端的长链接池,不过在以前是使用的HTTP/1协议与后端通讯,即建立链接处理完以后销毁。)web

 

 

你们的请求都合在一块儿了,如何区分请求与请求之间的边界呢?接下来咱们再来看看Http协议中的Transfer-Encoding: chunkedContent-Lengthwindows

Transfer-Encoding: chunked和Content-Length

Http协议经过Transfer-Encoding: chunked(如下简称TE)或Content-Length(如下简称CL)来肯定entity的长度。后端

TE表示将数据以一系列分块的形式进行发送,在每个分块的开头须要添加当前分块的长度,以十六进制的形式表示,后面紧跟着 '\r\n' ,以后是分块自己,后面也是'\r\n' 。终止块是一个常规的分块,不一样之处在于其长度为0。缓存

而CL经过直接指定entity的字节长度来完成一样的使命。

那么当它们两个同时出如今Http Header里时会发生什么呢?实际上Http协议规范有明肯定义,当这两种肯定长度的Header都存在时,应该优先使用TE,而后忽略CL。

看到这,结合上述那段不规范的header field-name解析代码,或许你想到了一些东西?假如一个包含了TE和CL的Http请求被处理了两次会发生什么?接下来,咱们来看看具体的状况。

发送攻击请求

此时咱们依旧使用 用户 -> A -> B 的例子。 这里咱们假设B是存在问题的Netty,而A是一个正常的只识别CL的程序。一个包含了Transfer-Encoding[空格]: chunkedContent-Length的Http请求来了

POST / HTTP/1.1

Host: vulnerable-website.com

Content-Length: 8

Transfer-Encoding[空格]: chunked

 

0

 

FOR

而后又来了一个正常的用户请求

GET /my-info HTTP/1.1

Host: vulnerable-website.com

A

A识别到了CL为8,并将这两个请求合并到一个链接中一块儿传给B

B

B识别到了Transfer-Encoding[空格],按照规则忽略了CL,此时B会怎么分割这两个请求呢:

请求1

POST / HTTP/1.1

Host: vulnerable-website.com

Content-Length: 8

Transfer-Encoding[空格]: chunked

 

0

请求2

FORGET /my-info HTTP/1.1

Host: vulnerable-website.com

这就糟糕了:服务端报了一个错,正经常使用户得到了一个400的响应。

 

(任何上下游识别请求的分界不一致的状况都会出现这样的问题)

如何避免这样的漏洞

  1. 避免重用链接,这样的方式会形成性能的降低。
  2. 使用HTTP/2做为系统间协议,此协议避免了http header的混淆。
  3. 使用同一种服务器做为上下游服务器。
  4. 你们都按规范来,不出BUG。

其中咱们展开了解一下为何HTTP/2能够避免这种状况

HTTP/2不会有含糊不清的Header

HTTP/2是一个二进制协议,致力于避免没必要要的网络流量以及提升TCP链接的利用率等等。

它对于经常使用的Header使用了一个静态字典来压缩。好比Content-Length使用28来表示;`

Transfer-Encoding`使用57来表示。这样一来,各类实现就不会有歧义了。更多的定义详见HTTP/2关于Header静态表的定义

 

 

文献

  1. Hypertext Transfer Protocol (HTTP/1.1)关于Http Header Field的解析定义
  2. Transfer-Encoding - HTTP | MDN
  3. Content-Length - HTTP | MDN
  4. What is HTTP request smuggling? Tutorial & Examples
  5. HTTP/1.x 的链接管理 - HTTP | MDN
  6. HTTP/2关于Header静态表的定义
  7. HTTP Keepalive Connections and Web Performance
相关文章
相关标签/搜索