apache cve 2021-40438

前言

做题遇到了,但是发现不理解的话,做不了题

URL中的Hostname VS RequestHeader中的Host字段

http://hostname:port_no
报文中:
host: xxxx.xx

proxypass

<VirtualHost *>
ServerAdmin webmaster@localhost
ServerName localhost
DocumentRoot /usr/local/apache2/htdocs

LogLevel notice proxy:trace8

ErrorLog /usr/local/apache2/logs/error.log
CustomLog /usr/local/apache2/logs/access.log combined

ProxyPass / "http://localhost:4554/"
ProxyPassReverse / "http://localhost:4554/"
</VirtualHost>

payload

http://localhost:80/?unix:(A*4096)|http://localhost:7891/

过程

漏洞点

漏洞点出现在proxy_utils.c中的fix_uds_filename函数中对url过滤的不严格,以及对错误处理的逻辑存在问题导致的ssrf漏洞

1.传入fix_uds_filenameurlhostname已经被mod_proxy模块重新修改为配置文件中指定的后端`hostname(http://localhost:4554/)
(在本题中:相当于此时是www.geogle.com?xxx)

2.fix_uds_filename函数的本意主要是用于处理形如unix:/home/www.socket|http://localhost/whatever/含有unix套接字的请求

3.在第二个if条件中,fix_uds_filename首先判断了r->filename的开头是否为proxy:

4.接着将r->filename分为了ptrptr2两部分,ptr2根据unix:关键字对url进行划分,ptr则根据|符号,从ptr2中进一步划分,提取目标url并赋值给变量rurl

5.而r->filename来源于proxy_http_canon函数。这个函数主要的功能是将传入的url进行解析,并拆分为schemehostportpath,和query_args(search)等部分,最后拼接"proxy:", scheme, "://", host, sport,"/", path, (search),赋值给r->filename,此值来源于以下部分

此时schema=http:
host=localhost
sport=:4554
path=
(search)

7.回到fix_uds_filename函数,在对r->filename进行拆分后,接下来就是本次漏洞最核心的部分uds_path的赋值操作。这里使用ap_runtime_dir_relativeurisock.path进行处理,并将结果赋值给sockpath,以键值对的形式(usd_path: sockpath)存储到r->notes中。

8.跟进ap_runtime_dir_relative函数,这里引用了arp库中的apr_filepath_merge函数。望文生义,这个函数是用来处理文件路径的合并操作的。而传入该函数的两个重要参数:runtime_dir = "/usr/local/apache2/logs", file = urisock.path

9.apr_filepath_merge函数计算了strlen(rootpath) + strlen(addpath) + 4的值,并与APR_PATH_MAX作比较。如果比APR_PATH_MAX大,则返回APR_ENAMETOOLONG常量。这使得上面ap_runtime_dir_relative在处理完路径后,返回的rv状态不为if条件中的状态,从而进入else分支,返回NULL

此时rootpath就是runtime_dir传入的值

10.APR_PATH_MAX定义在arp.h头文件中,而在linux系统上,PATH_MAX定义在linux/limits.h中,值为4096。因此需要填充的addpath的最小长度为4096 - strlen(rootpath) - 4 = 4069

11.接下来,在ap_proxy_determine_connection函数中对uds_path进行了初始化。这里我将比较关键的变量都加入到左边的调试窗口中。可以看到,*worker->s->uds_path最开始并没有被初始化,因此uds_path = apr_table_get(r->notes, "uds_path")。而这里,恰恰是在fix_uds_filename中设置过的键值对

12.如果这里的uds_path != null,那么就会使用unix socket进行后序的通信。因此作者在fix_uds_filename函数中通过使用超长的payload这样巧妙的方法来设置uds_path = null的原因就在于此。只要uds_path = null,那么就会进入2553行的else分支,也就回退到tcp连接。

13.此时conn->hostnameconn->port都由uri->hostnameuri->port进行控制。在此之前,已经调用过apr_uri_parse处理url,得到的hostnameport正是|后的目标url,而这部分内容正是攻击者可控的,因此造成了ssrf漏洞。

payload处理过程

分析payload

http://localhost:80/?unix:(A*4096)|http://localhost:7891/

传入以后变成

http://proxypass/?unix:(A*4096)|http://localhost:7891/

然后经过由于unix过长,再次提取,访问以下内容

http://localhost:7891/

payload

curl --header 'Host: geogle.com' "http://httpd.summ3r.top:60010/proxy?unix:$(python3 -c 'print("A"*4901, end="")')|http://internal.host/flag"
Author

vague huang

Posted on

2022-01-29

Updated on

2022-02-04

Licensed under

Comments