Skip to content

PHP RCE: A Bypass of CVE-2012-1823, Argument Injection in PHP-CGI

High
bukka published GHSA-3qgc-jrrr-25jv Sep 28, 2024

Package

No package listed

Affected versions

< 8.3.8
< 8.2.20
< 8.1.29

Patched versions

8.3.8
8.2.20
8.1.29

Description

Hi,

I am Orange Tsai from DEVCORE Research Team. We recently found a vulnerability on PHP. We have reproduced the issues in the latest version of PHP 8.3.6 and would like to report it to you. Please check the attached document for details.

This advisory is in accordance with our vulnerability disclosure policy, which will be publicly disclosed after 90 days. Our aim is to ensure that vulnerabilities can be patched in a timely manner. Although it’s not a hard deadline, we still hope you can fix this vulnerability before August 04, 2024.

Please let me know if you have any questions, thanks!

Impact

An attacker can execute arbitrary PHP code on the remote server. This vulnerability affects the PHP built with Windows.

Details

The PHP-CGI which configured under Apache HTTP Server accepts the command-line arguments by default. This behavior was prove to vulnerable, and tracked as CVE-2012-1823 before. The PHP published the following code to patch the vulnerability:

# Path: sapi/cgi/cgi_main.c:1801 https://github.com/php/php-src/blob/php-8.0.30/sapi/cgi/cgi_main.c#L1801

/* {{{ main */
int main(int argc, char *argv[])
{
	int free_query_string = 0;
	int exit_status = SUCCESS;
	int cgi = 0, c, i;
	size_t len;
	zend_file_handle file_handle;
	char *s;
    
	// ...
    
	// [1] this is the patch of CVE-2012-1823
	if((query_string = getenv("QUERY_STRING")) != NULL && strchr(query_string, '=') == NULL) {
		/* we've got query string that has no = - apache CGI will pass it to command line */
		unsigned char *p;
		decoded_query_string = strdup(query_string);
		php_url_decode(decoded_query_string, strlen(decoded_query_string));
		for (p = (unsigned char *)decoded_query_string; *p &&  *p <= ' '; p++) {
			/* skip all leading spaces */
		}
		if(*p == '-') {
			skip_getopt = 1;
		}
		free(decoded_query_string);
	}
    
	while (!skip_getopt && (c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) {
		switch (c) {
			// process the arguments
        }
        
    }

This argument argv is based on the QUERY_STRING, and the patch checks for no leading hyphen on the QUERY_STRING to ensure there are no dangerous arguments. However, if the binding between argv and QUERY_STRING is broken, the vulnerability of PHP-CGI Argument Injection would reappear!

This patch will fail under the Windows system. Internally, Windows uses UCS-2 as its character set. Any characters passed to the ANSI Win32 API (such as GetCommandLineA), especially those with code point greater than 0x80, will be mapped to the corresponding UCS-2 code points by the current code page.

The Windows internal also introduces a "Best-Fit" feature that replace the character if there is a more suitable character to display in the destination code page.

The description and specification can be found here:

This "Best-Fit" behavior also introduces some quirks to bypass the original fix. For example: the best-fit replacement of the code point 0xAD (soft-hyphen) is the normal hyphen 0x2D under the code page 932, 936 and 950. That leads to the bypass.

For a default PHP-CGI configuration under Apache HTTP Server + Windows, the original PoC would no longer work because of the patch of CVE-2012-1823.

http://server/index.php?-s

However, if the attacker change the - to %ad, it bypasses the patch and inject -s to show the PHP source code...

http://server/index.php?%ads

Here is a small PoC to demonstrate the quirk:

#include <stdio.h>

int main(int argc, char *argv[], char *env[]) {
    if (argc == 2) printf("[1] = %s\n", argv[1]);
    return 0;
}

You can build it under the Visual Studio, and check the above quirk:

C:\Users\orange\source\repos\test-cli\x64\Debug> dir 
test-cli.exe    test.py

C:\Users\orange\source\repos\test-cli\x64\Debug> type test.py
from subprocess import check_output
print(check_output(['./test-cli.exe', '\xads']))

C:\Users\orange\source\repos\test-cli\x64\Debug> python3 test.py
b'./test-cli.exe -s\r\n

PoC

In theory, this vulnerability affects all PHP-CGI installations that are running under Apache HTTP Server on Windows. However, the Apache HTTP Server did not accept the UTF-8 during the request processing. To meet the vulnerable scenario, the condition must meets (1) The code page has the "Best-Fit" behavior and (2) A code point smaller than 0xFF that maps to 0x2D.

Only the PHP-CGI under the Windows whose region are from Japan, China, Hong Kong, Macao, Singapore and Taiwan are affected.

To reproduce the vulnerability, we have set up a demo on a system running Windows 11 and the latest version of XAMPP. Although the PHP version included with the latest XAMPP release is not up-to-date, it is sufficient for this demonstration. We have verified that the bug can be reproduced on the latest PHP version, 8.3.6. This setup is solely for demonstration purposes.

1. Check the Windows 11 is the latest version

image

2. Ensure the locale is in one of the vulnerable region from Windows -> Settings -> Time & language -> Language & region -> Administrative language settings -> Change system locale.... Here we use Japan as an example.

image

3. Exploit scenarios:

3-1. If the target server is running under the PHP-CGI mode, you can just verify the vulnerability by appending the -s option:

http://server/index.php?%ads

image

3-2. If the php-cgi.exe is exposed externally, it can also be exploited without any additional change. XAMPP is exactly in this situation. The default XAMPP installation is VULNERABLE BY DEFAULT. You can just install the latest version of XAMPP here, and get arbitrary PHP code execution by auto_prepend_file:

http://server/php-cgi/php-cgi.exe?%add+allow_url_include%3don+-d+auto_prepend_file%3dphp%3a//input

PS: Please note the Redirect-Status header matters.

image

Credit Discovery To

Orange Tsai (@orange_8361) from DEVCORE Research Team

Severity

High

CVE ID

CVE-2024-4577

Weaknesses

Credits