Apache Httpd AJP请求走私 CVE-2022-26377 漏洞分析
Apache Httpd AJP请求走私 CVE-2022-26377 漏洞分析
原创 卫兵实验室 网络安全研究宅基地 2022-06-11 08:00
Smi1e@卫兵实验室
漏洞分析
0x00 影响版本
Apache Httpd < 2.4.54
0x01 AJP协议介绍
Tomcat最主要的功能是提供Servlet/JSP容器,尽管它也可以作为独立的Java Web服务器,它在对静态资源(如HTML文件或图像文件)的处理速度,以及提供的Web服务器管理功能方面都不如其他专业的HTTP服务器,如IIS和Apache服务器。因此在实际应用中,常常把Tomcat与其他HTTP服务器集成。
AJP13 是一个二进制的TCP传输协议,相比HTTP这种纯文本的协议来说,效率和性能更高,也做了很多优化。显然,浏览器并不能直接支持AJP13协议,只支持HTTP协议。所以实际情况是,通过Apache的 proxy_ajp 模块进行反向代理,暴露成 HTTP 协议给客户端访问。而Apache此时的作用就是将HTTP协议解析成AJP协议反代给Tomcat AJP协议端口。
(https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html 中介绍了AJP协议的格式。)
AJP协议中包含4种基本数据类型: Byte、Boolean、Integer、String 。
其中 Byte 和 Boolean 为1个字节,Integer 为两个字节,无符号整数,高位字节在前,String 类型为可变长字符串,最大长度为2的16次方。编码时首先将长度打包成两个字节,然后是字符串(包括终止符 \0 ),长度不包括结尾的 \0 。
从server发送到container的数据包以 0x1234 两字节魔术头开头,随后两字节代表数据包长度(不包括前4个字节)。
然后是数据部分,除了POST包发送的请求体外,其他包的数据部分的首字节为其消息类型(code)。这里只需要关注 Forward Request 和 Data 。
Forward Request 数据包消息格式如下:
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
其中 prefix_code 为 0x02 表示这是一个 Forward Request 类型的数据包。method 字段表示HTTP请求的方法类型。其余部分可以在文档中找到对应说明,这里不是重点。
Data 类型的数据包文档中没有特别的说明,只说了当 Content-Length 存在且不为0时,则 container 会认为请求有body,例如POST请求,并且立即从输入流中读取单独的数据包以获得该 body 。可以看到对于POST请求,AJP协议会发送两个数据包,一个是 Forward Request 一个是 DATA 。而DATA数据包的格式比较简单,依然是 0x1234 做为两个字节的魔术头,然后紧跟的是两个字节的数据包长度,第五位第六位为body数据的长度,最后跟着的是body。
0x02 Apache Httpd mod_proxy_ajp 请求走私
Apache 解析AJP协议的部分在 mod_proxy_ajp 模块 。在发送 DATA 类型的数据包时,会在前两位填充数据,分别是魔术头 0x1234 、数据包长度、body 长度。其中数据包长度为 body 长度加2字节,而 body 长度即是 Forward Request 数据包中 Content-Length 的长度。
通过对比 DATA 数据包和 Forward Request 数据包,可以发现由于发送的 DATA 数据包前面填充了六位,因此六位后的数据是我们完全可控的,而前六位中两者只有第五第六位是有差别的。在 Forward Request 数据包中分别对应 prefix_code 和 method ,在 DATA 数据包中对应的是body的长度,因此我们可以通过控制DATA 数据包中 body 的长度令其等于正常Forward Request 数据包的prefix_code 和 method。
比如正常的GET Forward Request 数据包prefix_code 和 method 分别为 0x02 0x02,0x0202=514,即需要填充够514个长度的body。body的具体内容为正常的 Forward Request 数据包6位之后的数据即可。
发送之后会发现数据包内容和正常的 Forward Request 数据包内容一样,但是wireshark并没有识别出来,依然把他当成了 REQ:Body 。
这是因为tomcat AjpProcessor 在接收到 Content-Length 大于0的请求头时,会调用 AjpProcessor.receive() 读取后续的body数据,我们的第二个请求包依然被当作了一个body而没有被当作一个新的 Forward Request 请求。因此我们需要让其获取到的 Content-Length 不大于0或者不发送这个请求头。
除了 Content-Length 外,还有一种指定HTTP消息实体采用何种编码形式的请求头 Transfer-Encoding 。
Transfer-Encoding 的可选值如下:
由于tomcat AjpProcessor 没有对 Transfer-Encoding 做特殊处理,我们可以不使用 Content-Length 而使用 Transfer-Encoding 进行分块传输,或者两者一起使用,Apache会忽略 Content-Length 。
不过在 mod_proxy_ajp 模块中禁止 Transfer-Encoding 以 chunked 开头,但是 Transfer-Encoding 支持使用逗号分割多个值,可以在前面插任意字符,例如 a,chunked 等。
当使用分块传输时,wireshark把第二个走私的body数据包解析成了GET方式的 Forward Request 数据包。不过在Apache httpd低版本,并不支持 Transfer-Encoding 使用逗号分割多个值,会直接返回501错误, Transfer-Encoding 只能是 chunked 。
POST Forward Request 数据包走私
由于AJP协议对于POST类型的HTTP请求会分成 header 和 body 两个数据包发送,而我们正常走私的请求只有一个header数据包,body数据会无法走私过去。
不过前面提到了当 Content-Length 大于0时,会调用 AjpProcessor.receive() 读取后续的body数据。我们可以在走私的body数据包发过去之后立即发送一个带有POST参数的正常POST数据包,此时该正常数据包的 header 和 body 都会被当做我们走私的POST数据包的body部分。因此我们只需要让走私的POST数据包的Content-Length 小于等于正常数据包header 和 body 的总长度即可,此时可以将我们要发送的参数插在两个 & 之间用来分割前后的垃圾数据。为了提高成功率,可以在发送走私请求后,多线程发送多个包含POST参数的正常POST数据包。
修复建议
不允许出现 Transfer-Encoding 请求头
详见下方链接:
https://github.com/apache/httpd/commit/156ddf6fb575a643007a335237899d9d738a7dc0#diff-f11bfc66b2dfc07a2f0a428b0ca9bd13fba8cec48e8404e5b570eb6c42eb2079R248
关于我们
人才招聘
二进制安全研究员
(Windows内核方向)
岗位职责:
-
负责研究Window内核相关漏洞利用技术;
-
负责分析Window内核漏洞的原理及缓解措施;
任职要求:
-
2年以上windows逆向工作经验。
-
熟悉windows底层架构、运行机制,熟悉汇编语言 C/C++语言,熟悉win32/64开发,并有相关开发经验;
-
熟悉windows驱动开发、熟悉windows平台内核架构;能熟练运用Windows平台下的软件调试方法。
-
熟练使用ida、windbg等调试软件工具调试分析漏洞。
-
有CVE编号、内核研究成果者优先;
-
具备良好的团队沟通、协作能力、良好的职业道德。
二进制安全研究员
(Linux内核方向)
岗位职责:
-
负责研究Linux内核相关漏洞利用技术;
-
负责分析Linux内核漏洞的原理及缓解措施;
任职要求:
-
2年以上Linux逆向工作经验。
-
熟悉Linux底层架构、运行机制,熟悉汇编语言 C/C++语言,熟悉x86/64开发,并有相关开发经验;
-
熟悉Linux驱动开发、熟悉Linux平台内核架构;能熟练运用Linux平台下的软件调试方法。
-
熟练使用ida、gdb、lldb等调试软件工具调试分析漏洞。
-
有CVE编号、内核研究成果者优先;
-
具备良好的团队沟通、协作能力、良好的职业道德。
二进制安全研究员
(系统应用方向)
岗位职责:
-
负责安全技术研究,跟踪国内外最新的安全技术以及安全漏洞的追踪;
-
负责进行二进制漏洞挖掘,包括不限于浏览器、chakara引擎、js引擎、office、pdf等等各种二进制类应用;
任职要求:
-
能主动关注国内外最新安全攻防技术,并在自己擅长和兴趣的领域能够进行深入的学习、研究;
-
熟练掌握windbg、ida、gdb等调试工具;
-
熟悉各类二进制安全漏洞原理(堆溢出、栈溢出、整数溢出、类型混淆等等)以及各种利用技术;
-
能够无障碍阅读英文技术文档;
-
具备良好的团队沟通、协作能力、良好的职业道德。
Web安全研究员
岗位职责:
-
跟踪最新安全技术动态,对高危安全漏洞进行快速分析和响应;
-
负责安全产品的线下、线上功能及流程的验收测试,保证项目进度和品质;
-
从事影响比较大的国内外大型的cms、中间件、框架漏洞挖掘工作
任职要求:
-
深入了解漏洞原理,能够独立挖掘/分析包括但不限于PHP/JAVA/.NET/ASP等大中型应用漏洞,并编写exp;
-
具备优秀的JAVA开发能力,能熟练挖掘 JAVA WEB 方面的漏洞,深入了解tomcat,weblogic,jboss,resin等中间件内部构造;
-
熟练使用至少一门开发语言,如:PHP、python、java;
-
有比较强的开发能力,熟悉java web的常见漏洞原理,有能力挖掘和分析java web方面的漏洞;
-
有重大漏洞发掘或高质量的CVE、0day挖掘能力的优先考虑;
Web安全研究员
(安全测试方向)
岗位职责:
-
安全攻防技术研究,最新web应用及中间件漏洞挖掘研究;
-
跟踪分析国内外的安全动态,对重大安全事件进行快速响应;
-
针对相关产品,进行全面详细的安全测试评估;
任职要求:
-
了解常见的网络协议(TCP/IP,HTTP,FTP等);
-
熟练使用Wireshark等抓包工具,熟悉正则表达式;
-
掌握常见漏洞原理,有一定的漏洞分析能力;
-
具备php、python、java或其他相关语言编码能力;
-
对常见waf绕过有一定的基础经验;
-
具备一定的文档编写能力,具备良好的团队共同能力;
-
对安全有浓厚的兴趣,工作细致耐心。