WSGI中的请求走私问题研究

admin 2024年12月28日12:14:45评论9 views字数 4456阅读14分51秒阅读模式

一、WSGI介绍

二、请求走私

三、WSGI server中的走私问题

四、总  结

WSGI介绍

WSGI是一种规范,描述了web server如何与web application通信的规范。

WSGI中的请求走私问题研究

WSGI规范是为python生态定义的,符合WSGI接口的server如下所示:

WSGI中的请求走私问题研究

  • environ:一个包含所有HTTP请求信息的dict对象;

  • start_response:一个发送HTTP响应的函数。WSGI server负责完成http请求解析到environ的映射过程,这样python的Web框架可以专注于业务逻辑,直接使用解析好的http请求对象。

请求走私
01

keep-alive 与 pipeline

为了缓解源站的压力,一般会在用户和后端服务器(源站)之间加设前置服务器,用以缓存、简单校验、负载均衡等,而前置服务器与后端服务器往往是在可靠的网络域中,ip 也是相对固定的,所以可以重用 TCP 连接来减少频繁 TCP 握手带来的开销。这里就用到了 HTTP1.1Keep-Alive和 Pipeline性:

所谓 Keep-Alive,就是在 HTTP 请求中增加一个特殊的请求头 Connection: Keep-Alive,告诉服务器,接收完这次 HTTP 请求后,不要关闭 TCP 链接,后面对相同目标服务器的 HTTP 请求,重用这一个 TCP 链接,这样只需要进行一次 TCP 握手的过程,可以减少服务器的开销,节约资源,还能加快访问速度。这个特性在 HTTP1.1 中是默认开启的。

有了 Keep-Alive 之后,后续就有了 Pipeline,在这里呢,客户端可以像流水线一样发送自己的 HTTP 请求,而不需要等待服务器的响应,服务器那边接收到请求后,需要遵循先入先出机制,将请求和响应严格对应起来,再将响应发送给客户端。现如今,浏览器默认是不启用 Pipeline 的,但是一般的服务器都提供了对 Pipleline 的支持。

http消息处理过程中出现两次http解析就可能出现走私,常见的情景里,容易出现在Content-LengthTransfer-Encoding的处理差异中。而WSGI中进行了一次http请求解析,并且经常置于nginx等中间件后使用,所以也容易出现请求走私问题。

WSGI server中的走私问题
01

waitress中条件竞争导致的走私问题(CVE-2024-49768) 

waitress 是一个流行的纯 Python 实现的 WSGI 服务器,用于在生产环境中部署 Python Web 应用程序。影响版本:>=2.0.0,<3.0.1 当配置文件中channel_request_lookahead被设置为大于0时,waitress中存在http请求走私漏洞,当攻击者构造特定大小的http请求时,因为waitress的异步处理机制可导致请求走私。

利用场景

nginx 使用proxy_pass向后端服务器转发请求,配置如下:

WSGI中的请求走私问题研究

python代码示例如下:

WSGI中的请求走私问题研究

因为nginx只转发/user对应的请求,正常情况下waitress应该只能处理针对/user的请求。但利用下文中提供的Poc,可以观察到日志中将输出Hello, Admin!,代表可以通过nginx访问到/admin路由。如果python部分未进行额外的请求校验,将产生请求绕过问题。

漏洞分析

waitress主要逻辑为:通过socket读取用户请求,然后交给子线程异步处理。处理socket时,主流程如下(wasyncore.py->poll函数):

WSGI中的请求走私问题研究

触发的函数调用顺序为:read->handle_read_event->(HTTPChannel)handle_read->(HTTPChannel)receivedsocket接受的数据最终交给HTTPChannel中的received函数处理。同时可以观察到只要channel.readable为True,就会有新的数据交给received处理,如果发生request.error不为空的情况,write函数中会根据channel中的close_when_flushed关闭HTTPChannel对象。

HTTPChannel中,received函数负责处理接收到的数据(默认是8192大小,在adjustments.py中的recv_bytes = 8192定义) 同时通过self.request.received(data)填充request对象,其返回值代表消耗掉的字节数,当填充出错时,将返回一个比正常值更小的值,同时将request.error设置为错误原因,然后整个请求被add_task加入待处理队列,由另一个线程(ThreadedTaskDispatcher)处理,相关代码如下:

WSGI中的请求走私问题研究

可以看到n代表消费的字节数,只要data中还有数据就会重复解析过程,这与pipeline的行为相符。而self.request.received处理数据时,只要处理完成都会标记request.completedTrue,不过对于解析出错的请求,其request.error属性不为空。

ThreadedTaskDispatcher中将异步调用channelservice方法,当碰到request.error不为空的请求时,会将task.close_on_finish设置为True以执行以下代码:

WSGI中的请求走私问题研究

代码执行时在设置self.close_when_flushed = True之前,如果channel_request_lookahead被设置为大于0,则:

WSGI中的请求走私问题研究

这时的readable函数将返回True, 此时如果handle_read函数读取了下一个包,因为task.close_on_finishTrueself.requests = []将清空已有的请求队列,handle_read中刚读取到的bytes将作为全新的包进行解析(事实上它是上一个包未处理完的部分)。解析后的请求将交由service函数处理,虽然这时self.close_when_flushed = Truehandle_write执行前self.connected仍被设置为True,所以service函数仍将处理该请求。

简单总结:在service执行过程中,self.close_when_flushed = True之前,handler_read接收到一个包,然后self.close_when_flushed = Trueself.requests = [],此时received函数将handler_read收到的内容作为新的http内容处理解析后交给service异步处理。在handle_writeself.connected设置为False前,service的异步处理逻辑仍有机会执行传入的http请求。

PoC构造

WSGI中的请求走私问题研究

首先是一个长度为recv_bytes(之前提到的8192)大小的包,其header中,出现x7f来使其解析时出错(可根据其解析逻辑选择其它报错逻辑),这样下一个包就有机会被handler_read处理。第二个包是用来请求走私的报文,两部分一起在nginx中是合法的一个包。在waitress中,被解析为两个,产生走私问题。因为涉及到条件竞争,使用多线程发包可增大走私成功的几率。

02

非生产环境server的请求走私问题

python官方库中提供了部分简单的server实现,如常用的SimpleHttpServer就有用到。而继承了BaseHTTPRequestHandler并且没有重写do_GET方法,或者继承了SimpleHTTPRequestHandler方法,均会受到请求走私问题的影响。不过官方已经声明不应该在生产环境使用,所以不归类为安全问题。

场景

nginx设置了proxy_set_header Connection ""(否则默认每个请求结束都会close),或使用httpd。这里使用的nginx配置如下:

WSGI中的请求走私问题研究

后端启动的服务为http.server,高版本中增加了-p参数可以指定使用的http协议版本,这里使用的是python3.11,指定为HTTP/1.1是为了启用keep-alivepipeline特性。

WSGI中的请求走私问题研究

因为其代码实现中处理GET请求时没有处理Content-LengthGET请求的body被当做新的请求处理,如果发送以下请求。

WSGI中的请求走私问题研究

可观察到日志如下,收到两个请求:

WSGI中的请求走私问题研究

CGIHTTPRequestHandler也继承了BaseHTTPRequestHandler :测试后端执行以下命令:

WSGI中的请求走私问题研究

发送如下请求:

WSGI中的请求走私问题研究

响应如下,可以看到在cgi模式中,特定的情况下可走私产生RCE效果。

WSGI中的请求走私问题研究

Flask中的修复

部分常用框架如flask也扩展了BaseHTTPRequestHandler用于开发环境使用,可以看到flask中对于以上提到的走私问题做了修复,将Connection固定为close

WSGI中的请求走私问题研究

WSGI中的请求走私问题研究

03

gevent中的请求走私问题

其处理未使用的数据时将调用_discard函数,而如果请求中带有100-continue请求头,则self.socket不为None,未被使用的数据将被作为新的请求包处理,产生走私。

WSGI中的请求走私问题研究

测试服务启动如下,前端为httpd

WSGI中的请求走私问题研究

发送如下请求:

WSGI中的请求走私问题研究

日志输出如下:

WSGI中的请求走私问题研究

可以观察到产生了走私,同时可以看到这里的测试服务使用gunicorn来启动,而gunicorn是应用最广泛的WSGI服务器,这是因为其gevent_wsgi模式直接使用了gevent的对应实现。另外gevent维护者将此问题归类为非安全问题,因为文档中说明了其WSGI模块是为开发与测试设计的,不过笔者已经提交了对应的补丁,后续版本中将修复此问题。

总  结

本文通过几个案例研究了WSGI(Web Server Gateway Interface)中出现的请求走私问题,可以看到传统的Content-LengthTransfer-Encoding已经得到了重视,但许多问题仍然源于先前请求的body未被正确丢弃。除了传统的中间件,WSGI服务本身也会出现请求走私问题。鉴于此,类似Python的WSGI实现,以及Perl、Ruby等其他语言的相关实现也值得进一步探索。

参考链接

1. 浅谈HTTP请求走私

2. Request processing race condition in HTTP pipelining with invalid first request

【版权说明】

本作品著作权归m4yfly所有

未经作者同意,不得转载

WSGI中的请求走私问题研究

m4yfly

天工实验室安全研究员

专注于代码审计、物联网安全。

往期回顾
01
硬件辅助虚拟化及Fuzzing工作研究
02
vCenter漏洞分析:CVE-2024-37079 & CVE-2024-37080
03
系统文件管理行为漏洞导致本地提权
04
CVE-2018-9568 Linux内核漏洞分析和利用
WSGI中的请求走私问题研究
每周三更新一篇技术文章  点击关注我们吧!

原文始发于微信公众号(奇安信天工实验室):WSGI中的请求走私问题研究

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年12月28日12:14:45
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   WSGI中的请求走私问题研究http://cn-sec.com/archives/3551358.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息