前言
Apache HTTP Server 是 Apache 基础开放的流行的 HTTP 服务器。其存在目录穿越文件读取漏洞,漏洞仅影响 httpd 2.4.49, https 2.4.50不完全修复可绕过,如果开启 mod_cgi
则可RCE。
CVE-2021-41773分析
利用师傅搭建好的docker漏洞环境进行复现并分析
docker run -p 8080:80 -d --privileged turkeys/httpd:cve-2021-41773
该镜像容器中有安装好的 pwndbg 调试组件
保留一个 daemon httpd 用于调试,其它进程直接kill掉
我们已知漏洞函数位于 ap_normalize_path
,所以直接在 ap_normalize_path
处添加断点
构造 payload
此时传入的 path 的值是 /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
在函数的返回值下断点,由于返回函数在601行,所以断点下载601行,然后继续调试代码
pwndbg> b 601
Breakpoint 2 at 0x561aa6e457c7: file util.c, line 601.
pwndbg> c
Continuing.
调试之后看到最后传入的url为/cgi-bin/../../../../etc/passwd
,继续执行发现成果读取到文件内容
然后我们单步调试对 ap_normalize_path 进行分析
AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags)
{
int ret = 1;
apr_size_t l = 1, w = 1;
if (!IS_SLASH(path[0])) {
/* Besides "OPTIONS *", a request-target should start with '/'
* per RFC 7230 section 5.3, so anything else is invalid.
*/
if (path[0] == '*' && path[1] == '\0') {
return 1;
}
/* However, AP_NORMALIZE_ALLOW_RELATIVE can be used to bypass
* this restriction (e.g. for subrequest file lookups).
*/
if (!(flags & AP_NORMALIZE_ALLOW_RELATIVE) || path[0] == '\0') {
return 0;
}
l = w = 0;
}
除了 "OPTIONS *", 每个请求路径都应该是以 '/' 开头
while (path[l] != '\0') {
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))) {
/* Replace last char and fall through as the current
* read position */
l += 2;
path[l] = c;
}
}
如果解码成功l指针移动到编码的最后一位,且将解码后的值复制给path[l]
if (w == 0 || IS_SLASH(path[w - 1])) {
/* Collapse ///// sequences to / */
if ((flags & AP_NORMALIZE_MERGE_SLASHES) && IS_SLASH(path[l])) {
do {
l++;
} while (IS_SLASH(path[l]));
continue;
}
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])) {
/* Wind w back to remove the previous segment */
if (w > 1) {
do {
w--;
} while (w && !IS_SLASH(path[w - 1]));
}
else {
/* Already at root, ignore and return a failure
* if asked to.
*/
if (flags & AP_NORMALIZE_NOT_ABOVE_ROOT) {
ret = 0;
}
}
/* Move l forward to the next segment */
l += 2;
if (path[l]) {
l++;
}
continue;
}
}
}
path[w++] = path[l++];
}
path[w] = '\0';
return ret;
}
在代码 if (path[l + 1] == '.' && IS_SLASH_OR_NUL(path[l + 2])) {
中 遇到 ../ 时才会回退到上一个 / ,在解码的时候时一个字符一个字符进行解码的,当遇到 .%2e/
这种情况时,因为 后面不是 ./
所以会先将 . 赋值给 path[w],之后 %2e 进行解码后此时 path[l] =.
path[l+1] 为 /
,但是因为我们已经保存了一个值 . 到
path[w] , w 不为零 且 path[w - 1]不是 / ,就不满足 if (w == 0 || IS_SLASH(path[w - 1]))
,不会进入判断,值会直接赋予 path[w]。如此就将 /../
成功赋予到了path中。
小结
至此CVE-2021-41773的分析到此结束