从HTTPD-SSRF CVE-2021-40438起手的RCE漏洞挖掘

Apache官方在 2021-09-16更新发布Apache Httpd 2.4.49版本,在这个版本中修复了CVE-2021-40438、CVE-2021-33193、CVE-2021-34798、CVE-2021-36160、 CVE-2021-39275等5个漏洞。其中CVE-2021-40438 为mod_proxy模块下的SSRF漏洞,影响版本为2.4.48及以下。根据官方描述来看此漏洞是Apache内部安全团队在分析CVE-2021-36160 mod_proxy_uwsg DoS 时无意发现的,由于mod_proxy模块为Apache HTTP Server实现了基础代理/网关功能,支持很多常见协议如http、ajp、fastcgi、ftp等,所以该模块在很多通用系统中都会使用到,漏洞的影响范围是相当广的。

另外笔者在后续对多个使用mod_proxy模块的大型应用系统分析时,大部分都存在此安全问题,且如果可以找到存在敏感功能的后端Unix Domain Socket/本地服务,那么就可以利用代理漏洞请求后端敏感服务 组合串联最终达成RCE的效果。本文只做思路分享、技术交流,不提供任何形式的EXP!

文章前半部分为Apache Httpd的调试环境搭建与漏洞分析,后半部分是对该漏洞在实际挖掘场景中的一些思考与总结。 如果对文章有疑问/建议或者想一起研究交流的师傅,欢迎私信😜

PS:本文仅用于技术讨论。严禁用于任何非法用途,违者后果自负。

1、安装部署

本次演示部署了2.4.43版本的Apache Httpd,并且使用vscode远程调试linux下的Apache Httpd服务。这部分内容参考了P师傅星球的文章:https://t.zsxq.com/RvfmEu3https://t.zsxq.com/7qNfeie 致敬感谢分享!

1.1 linux部署环境

下载2.4.43版本的源码自行编译,另外此次漏洞涉及到了apr、apr-util的函数,所以apache在指定编译时需要指定apr和aprutil

1
2
3
4
5
6
7
8
apt-get install build-essential gdb 
apt-get install --no-install-recommends libapr1-dev libaprutil1-dev libpcre3-dev

https://archive.apache.org/dist/httpd/ 2.4.43
https://downloads.apache.org/apr/ 下载apr-1.6.5.tar.gz apr-util-1.6.1.tar.gz

tar -zxvf apr-1.6.5.tar.gz
tar -zxvf apr-util-1.6.1.tar.gz

编译

1
2
3
4
5
6
7
8
9
10
11
12
13
/root/make-tools/apr-1.6.5>
CFLAGS="-g" ./configure --prefix=/root/apache-ext/apr
make && make install

/root/make-tools/apr-util-1.6.1>
CFLAGS="-g" ./configure --prefix=/root/apache-ext/apr-util --with-apr=/root/apache-ext/apr
make && make install


编译httpd:
/root/apache/httpd-2.4.43>
CFLAGS="-g" ./configure --prefix=/root/apache/httpd-bin --with-apr=/root/apache-ext/apr --with-apr-util=/root/apache-ext/apr-util
make && make install

编译完成后开始配置httpd-bin/conf/httpd.conf,将proxy_module.so和mod_proxy_http.so注释去掉,且在文件末尾增加如下配置

1
2
3
4
5
6
7
8
9
10
<VirtualHost *>
ServerAdmin webmaster@localhost
ServerName 127.0.0.1
DocumentRoot /root/apache/httpd-plus/htdocs
LogLevel notice proxy:trace8
ErrorLog /root/apache/httpd-plus/logs/error.log
CustomLog /root/apache/httpd-plus/logs/access.log combined
ProxyPass /link "http://127.0.0.1:8000/"
ProxyPassReverse /link "http://127.0.0.1:8000/"
</VirtualHost>

1.2 远程调试Httpd

vscode插件下载地址:
https://marketplace.visualstudio.com/search?term=remote&target=VSCode

然后安装一下调试C时所需要的VSCode扩展:

https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools

离线安装remote-ssh插件

https://www.cnblogs.com/litaozijin/p/13202992.html

在vscode调试界面创建配置文件 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
25
{
"version": "0.2.0",
"configurations": [

{
"name": "httpd-bin-gdb-debug",
"type": "cppdbg",
"request": "launch",
"program": "/root/apache/httpd-bin/bin/httpd",
"args": ["-X", "-DFOREGROUND"],
"stopAtEntry": false,
"cwd": "/root/apache/httpd-bin",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

关闭远程服务器的httpd服务,使用本地vscode的gdb重新启动即可

断点下在proxy_util.c#fix_uds_filename(),访问地址 http://192.168.232.134/link 成功断到

2、漏洞分析

2.1 安全公告

CVE-2021-40438 为mod_proxy模块的SSRF漏洞,官方安全公告影响版本为2.4.48及以下

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

2.2 补丁对比

CVE-2021-40438 修复的committed: https://github.com/apache/httpd/commit/520dcd80a45ce237e9a46ee28697e1b8af3fcd7e?diff=split

我们查下这两个函数的用法

ap_strcasestr(s1,s2):从s1中搜索s2, 返回s2开始的指针,不考虑大小写,相同则返回0

ap_cstr_casecmpn(s1,s2,n): 对比s2与s1的前n位,不考虑大小写,相同则返回0

函数定义在

https://ci.apache.org/projects/httpd/trunk/doxygen/group__APACHE__CORE__DAEMON.html#ga2ebda07cacb3088e5cbaca755303594b

https://ci.apache.org/projects/httpd/trunk/doxygen/group__APACHE__CORE__DAEMON.html#gaa6a7169f7d3801b11d3b113b27284dbd

简单解释下此次补丁变化:原来是从s1整个字符串搜索”unix:”,现在限定只能是前5位,也就是s1需要以”unix:”开头。我们查看下漏洞函数proxy_util.c#fix_uds_filename() 方法代码

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
static void fix_uds_filename(request_rec *r, char **url) 
{
char *ptr, *ptr2;
if (!r || !r->filename) return;
//1、strncmp 比较r->filename的前6位与proxy:,相同返回0,包含返回正值 不包含返回负值
if (!strncmp(r->filename, "proxy:", 6) &&
(ptr2 = ap_strcasestr(r->filename, "unix:")) && //2、从r->filename 整个字符串中搜索 unix:
(ptr = ap_strchr(ptr2, '|'))) {
apr_uri_t urisock;
apr_status_t rv;
*ptr = '\0';
rv = apr_uri_parse(r->pool, ptr2, &urisock);
if (rv == APR_SUCCESS) {
char *rurl = ptr+1;
char *sockpath = ap_runtime_dir_relative(r->pool, urisock.path); //3、得到uds路径
apr_table_setn(r->notes, "uds_path", sockpath);
*url = apr_pstrdup(r->pool, rurl); /* so we get the scheme for the uds */
/* r->filename starts w/ "proxy:", so add after that */
memmove(r->filename+6, rurl, strlen(rurl)+1);
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
"*: rewrite of url due to UDS(%s): %s (%s)",
sockpath, *url, r->filename);
}
else {
*ptr = '|';
}
}
}

代码从r->filename中根据unix:|的位置解析出uds路径及url,并且判断r->filename是否以proxy:开头

2.3 原理分析

请求代理路由/link,看到了这样一条日志URI path '/link' matches proxy handler 'proxy:http://127.0.0.1:8000/'

根据该日志信息找到了mod_proxy.c#ap_proxy_trans_match(),再次发送后,看到r->filename的值为proxy:http://127.0.0.1:8000/,代理请求r->proxyreq为反向代理PROXYREQ_REVERSE

这样就解释了为什么漏洞函数proxy_util.c#fix_uds_filename() 需要先检测是否以proxy:开头。我们查看apache mod_proxy uds的正常用法

https://www.docs4dev.com/docs/zh/apache/2.4/reference/mod-mod_proxy.html

apache从 2.4.7 开始支持了uds(但经过实际测试,我在2.4.9才找到了支持uds路径的写法),具体用法是ProxyPass /links "unix:/home/www.socket|http://localhost/whatever/",当访问/links 代理配置经过proxy_util.c#fix_uds_filename()解析后,uds路径为/home/www.socket及转发url为http://localhost/whatever/,结合补丁代码修改部分,我们将uds代理字符串添加到参数位置进行测试:/link?unix:/home/www.socket|http://localhost/whatever/

可以看到经过proxy_util.c#fix_uds_filename()解析处理,成功赋值给uds_path及转发url。到这里漏洞触发点就明了了:当目标apache使用代理模块时,攻击者通过在代理路由的参数注入uds路径及转发url实现任意uds请求/url请求。现在可以请求任意uds地址。但是距离任意SSRF,还有一个问题:在经过fix_uds_filename()解析出uds、url后,会在mod_proxy_http.c#ap_proxy_determine_connection()中去请求uds/url,当uds不存在时,才会去请求url

所以问题就变成了如果使解析出来的uds值为null,老外使用了一个巧妙的方法,在fix_uds_filename()解析uds的代码:

1
2
char *sockpath = ap_runtime_dir_relative(r->pool, urisock.path);
apr_table_setn(r->notes, "uds_path", sockpath);

config.c#ap_runtime_dir_relative()

在filepath.c#apr_filepath_merge()中,当rootpath(/root/apache/httpd-bin/logs)与传入的路径长度之和+4大于4096时,返回名称过长错误。就会导致代码走到1625行,返回uds_path的值为null

所以在unix:与|之间传入大于(4097-4-len(rootpath))=4066个字符时就会产生SSRF漏洞,可以看到服务端成功请求了地址: http://localhost:8001/whatever/

这样,漏洞就可以达到SSRF、任意uds请求的效果,接着看看这个漏洞在实际漏洞挖掘场景中的情况

3、实际场景

该漏洞触发的条件是Apache Httpd开启mod_proxy模块,使用到反向代理且路由可以从前台访问。如果目标系统满足条件,就可以从前台请求到服务端存在敏感功能的uds/服务,完成一个前台漏洞的触发串联。本章节演示这个漏洞在实际场景中的一些情况

3.1 特殊配置

在测试过程中发现存在一种特殊配置,当路径以/结尾,但是代理地址不以/进行结尾时,可以通过@绕过代理触发SSRF漏洞,这里记录一下。如果平时测试碰到/admin/module//login 类似路径,可以使用/admin/module/@127.0.0.1:8833 测试,如果恰好httpd使用的路径以/结尾,但是代理地址不以/进行结尾的特殊配置时,目标机器会触发ssrf漏洞请求127.0.0.1:8833

1
2
3
4
5
ProxyPass /lin/ "http://127.0.0.1:8000"
ProxyPassReverse /lin/ "http://127.0.0.1:8000"

会造成SSRF问题
/lin/@127.0.0.1:8001 http://127.0.0.1:8000@127.0.0.1:8001

3.2 漏洞思考?

在分析完CVE-2021-40438后感觉这个漏洞的影响是被低估的,该漏洞能覆盖影响Httpd 2.4.9-2.4.48 近40个小版本、mod_proxy ProxyPass配置很常见、代理也多为前台触发访问,非常适合做为漏洞挖掘绕过权限的突破口 。当时正在研究Vmware家的Vmware Vrealize operations产品,也跟进测试了最新版,发现部署的是httpd 2.4.46版本、配置使用了代理模块、路由也可以从前台触发。所以最新版是受CVE-2021-40438影响的,但是当时并没有找到后端可用的uds完成rce,比较遗憾。只给vmware发了ssrf的报告,后面官方出了安全更新升级了httpd的版本

官方出的公告:https://kb.vmware.com/s/article/87227

复现截图

3.3 向RCE进发

由于在vmware产品中没有找到可利用的uds完成RCE,比较遗憾但是并没有放弃这个攻击面。后续也审计了几款应用,最终成功在XXX与XXX产品中串联起来完成了RCE。如果想要完成RCE的效果需要串联后端的敏感服务,其中WEB服务的审计与平时审计思路差别不大,而自己对于uds部分的知识还是欠缺的,所以利用这次机会做一波知识储备。扩大知识面->知识面决定攻击面!

在查找资料过程中看到百度安全团队在blackhat 2022大会上对安卓上得Unix Domain Socket 攻击面做了梳理, 可以作为知识补充学习一波。议题名称:Unix Domain Socket: A Hidden Door Leading to Privilege Escalation in the Android Ecosystem 议题paper:https://developer.baidu.com/article/detail.html?id=295123 议题slides:https://i.blackhat.com/Asia-22/Thursday-Materials/AS-22-Ke-Unix-Domain-Socket-A-Hidden-Door.pdf

unix域套接字uds(Unix Domain Socket)与传统网络套接字得主要区别是:网络套接字使用ip与端口标识客户端与服务端,而域套接字uds使用系统文件名,这个文件被称为套接字文件。套接字文件有两种形式:一种是绝对路径 类似/var/rpc/rpc.sock;另外一种是抽象路径 类似@jd-control,即一个不存在得路径。这里整理了几个与uds相关的命令

1
2
3
netstat -a -p --unix   //列出本地得unix套接字
lsof -U //显示uds的相关信息
fuser -v /var/rpc.sock //显示使用该套接字的用户、进程pid、访问方式和命令等信息

在审计XXX系统时,发现其使用了Apache httpd 2.4.41版本,在/apache/conf/httpd.conf中看到配置启动了代理模块mod_proxy(使用指令ProxyPass、ProxyPassReverse),且代理路由可以从前台访问,满足该漏洞的条件要求

接下来就是寻找能够串联起来完成利用的后端服务,本次审计主要聚焦于uds。lsof -U命令用于显示Unix Domain Socket (UDS)的相关信息,包括哪些进程正在使用指定的UDS文件以及它们的详细信息

看到有个xmlrpc.sock、xmlrpc0.sock、xmlrpc1.sock,检索下相关进程

这个是python启动的后端进程,而该系统的WEB前台XXX敏感功能处与后端的xmlrpc uds有交互请求

那只要能拿到最终请求到uds的数据包,我们就可以绕过WEB侧鉴权转而利用httpd侧代理漏洞请求uds完成漏洞串联利用。尝试了很多方法,最终通过创建中转流量的8089端口成功抓到了uds的数据包。在过程中使用tcpdump抓取xmlrpc.sock的流量发现为空,在查看了cfg配置文件结合上面的进程信息发现还有xmlrpc0.sock与xmlrpc1.sock,随即对这两个sock也进行了监听,最后成功在xmlrpc1.sock中发现了请求uds的流量

1
2
3
4
5
6
7
8
9
10
11
12
//1、在本地8089端口中转流量
mv /var/run/rpc/xmlrpc1.sock /var/run/rpc/xmlrpc1.sock.original
socat TCP-LISTEN:8089,reuseaddr,fork UNIX-CONNECT:/var/run/rpc/xmlrpc1.sock.original
socat UNIX-LISTEN:/var/run/rpc/xmlrpc1.sock,fork TCP-CONNECT:127.0.0.1:8089

//2、获取最终到uds的流量并输出到lo-test.pcap文件,再使用wireshark分析
tcpdump -i lo -netvv port 8089 -w lo-test.pcap

//3、测试完毕后还原备份
mv /var/run/rpc/xmlrpc1.sock.original /var/run/rpc/xmlrpc1.sock

注:reuseaddr 绑定本地一个端口、fork 多链接模式,每当有一个新连接请求到达时,fork出一个子进程来处理该连接,以保持并发性

在监听uds后返回到web界面,手动点击操作XX功能后,lo-test.pcap就有了内容,使用wireshark打开pcap包筛选host就能找到具体的数据包

返回数据:

那么将该数据包转为使用httpd代理漏洞的格式发送给uds即可完成漏洞利用,第一次在这里测试时多次碰到返回502、503、408等错误状态码的情况。为了定位在httpd c代码中的具体报错信息,我这里手动修改配置文件httpd-ssl.conf与httpd.conf,开启日志记录并重启httpd,配置信息如下

1
2
3
4
5
6
7
8
9
LogLevel notice proxy:trace8
CustomLog "/usr/local/apache/logs/httpd-ssl-accesslog.log" common
ErrorLog "/usr/local/apache/logs/httpd-ssl-error.log"
LogLevel warn

覆盖文件httpd.conf、httpd-ssl.conf
/usr/local/apache/bin/httpd -k stop
/usr/local/apache/bin/httpd -k start
ps -ef|grep httpd //看到httpd进程证明重启成功

最终成功构造出从前台直接请求后端uds的数据包,完成漏洞利用

Just For Fun!

4、参考链接

https://firzen.de/building-a-poc-for-cve-2021-40438

https://www.leavesongs.com/PENETRATION/apache-mod-proxy-ssrf-cve-2021-40438.html

https://mivehind.net/2018/04/20/sniffing-unix-domain-sockets/

https://plantegg.github.io/2018/01/01/%E9%80%9A%E8%BF%87tcpdump%E5%AF%B9Unix%20Socket%20%E8%BF%9B%E8%A1%8C%E6%8A%93%E5%8C%85%E8%A7%A3%E6%9E%90/

https://developer.baidu.com/article/detail.html?id=295123


从HTTPD-SSRF CVE-2021-40438起手的RCE漏洞挖掘
https://pwnull.github.io/2023/From-apache-httpd-ssrf-cve-2021-40438-to-Rce/
作者
pwnull
发布于
2023年3月13日
许可协议