[toc]

漏洞描述

Apache HTTP Server的mod_proxy_ajp模块与tomcat ajp解析模块存在差异,当用户配置了mod_proxy_ajp模块与后端tomcat服务器通信时,攻击者能够在正常的HTTP请求中走私一条自定义HTTP请求到后端tomcat。攻击者可利用该漏洞实现对tomcat AJP端口的访问,比如自定义attributes属性值来攻击存在幽灵猫漏洞但AJP协议端口未对外开放的tomcat服务器。

影响范围

受影响版本:

Apache HTTP Server < 2.4.54

漏洞分析

AJP协议分析

ajp数据包格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
AJP13_FORWARD_REQUEST :=
prefix_code (byte) 0x02 = JK_AJP13_FORWARD_REQUEST
method (byte)
protocol (string)
req_uri (string)
remote_addr (string)
remote_host (string)
server_name (string)
server_port (integer)
is_ssl (boolean)
num_headers (integer)
request_headers *(req_header_name req_header_value)
attributes *(attribut_name attribute_value)
request_terminator (byte) OxFF

从server发送到container的数据包以 0x1234 两字节魔术头开头,随后两字节代表数据包长度(不包括前4个字节)。

然后是数据部分,除了POST包发送的请求体外,其他包的数据部分的首字节为其消息类型(code)。这里只需要关注 Forward RequestData

The web server can send the following messages to the servlet container:

Code Type of Packet Meaning
2 Forward Request Begin the request-processing cycle with the following data
7 Shutdown The web server asks the container to shut itself down.
8 Ping The web server asks the container to take control (secure login phase).
10 CPing The web server asks the container to respond quickly with a CPong.
none Data Size (2 bytes) and corresponding body data.

The servlet container can send the following types of messages to the web server:

Code Type of Packet Meaning
3 Send Body Chunk Send a chunk of the body from the servlet container to the web server (and presumably, onto the browser).
4 Send Headers Send the response headers from the servlet container to the web server (and presumably, onto the browser).
5 End Response Marks the end of the response (and thus the request-handling cycle).
6 Get Body Chunk Get further data from the request if it hasn’t all been transferred yet.
9 CPong Reply The reply to a CPing request

apache httpd

Apache 解析AJP协议的部分在 mod_proxy_ajp 模块 。在发送 DATA 类型的数据包时,会在前两位填充数据,分别是魔术头 0x1234 、数据包长度、body 长度。其中数据包长度为 body 长度加2字节,而 body 长度即是 Forward Request 数据包中 Content-Length 的长度。

image-20221114153905207

通过对比 DATA 数据包和 Forward Request 数据包,可以发现由于发送的 DATA 数据包前面填充了六位,因此六位后的数据是我们完全可控的,而前六位中两者只有第五第六位是有差别的。在 Forward Request 数据包中分别对应 prefix_codemethod ,在 DATA 数据包中对应的是body的长度,因此我们可以通过控制DATA 数据包中 body 的长度令其等于正常Forward Request 数据包的prefix_codemethod

比如正常的GET Forward Request 数据包prefix_codemethod 分别为 0x02 0x020x0202=514,即需要填充够514个长度的body。body的具体内容为正常的 Forward Request 数据包6位之后的数据即可。

漏洞利用

我们可以构造一个长度正好为0x202(514)的请求体

image-20221114202058015

可以看到我们发送的data请求第五和第六个字节的确是0202,但这个请求依然被解析成了body,并没有成为Forward Request

image-20221114202141089

这是因为tomcat AjpProcessor 在接收到 Content-Length 大于0的请求头时,会调用 AjpProcessor.receive() 读取后续的body数据,我们的第二个请求包依然被当作了一个body而没有被当作一个新的 Forward Request 请求。因此我们需要让其获取到的 Content-Length 不大于0或者不发送这个请求头。

image-20221114203022672

除了 Content-Length 外,还有一种指定HTTP消息实体采用何种编码形式的请求头 Transfer-Encoding ,值得注意的是,在AjpProcessor中并没有对TE的处理,那么我们只要能用TE来通过反代的验证,再将这个请求发给AjpProcessor时,我们的body就会被当成一个新的 Forward Request 请求

image-20221114203121641

我们自然想到可以这样构造

image-20221114213000259

但抓包发现我的chunk数据并没有发送出去

image-20221114213133302

原因在于mod_proxy_ajp.c中对TE请求头有约束,如果取到的TE等于chunked,不会进入后续的else,也就不会发送body

image-20221114213212660

这里可以用a,chunked的形式来绕过,高版本的apache是允许用逗号来分隔TE的(这里没有细调),我们的request会走到mod_proxy_ajp.c中,同时也能绕过这里的判断

image-20221114214440812

利用这种构造即可成功进行走私,wireshark已经把这个data包识别成了 Forward Request

image-20221114214546867

修复方案

增加了判断,优化了逻辑,只要是取到TE,就不会进入else

image-20221114205911401

Reference

Apache Httpd AJP请求走私 CVE-2022-26377 漏洞分析 | CN-SEC 中文网