RCE篇之⽆参数rce
转载⾃:
⽆参数rce
⽆参rce,就是说在⽆法传⼊参数的情况下,仅仅依靠传⼊没有参数的函数套娃就可以达到命令执⾏的效果,这在ctf中也算是⼀个⽐较常见的考点,接下来就来详细总结总结它的利⽤姿势
核⼼代码
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
这段代码的核⼼就是只允许函数⽽不允许函数中的参数,就是说传进去的值是⼀个字符串接⼀个(),那么这个字符串就会被替换为空,如果替换后只剩下;,那么这段代码就会被eval执⾏。⽽且因为这个正则表达式是递归调⽤的,所以说像a(b(c()));第⼀次匹配后就还剩
下a(b());,第⼆次匹配后就还剩a();,第三次匹配后就还剩;了,所以说这⼀串a(b(c())),就会被eval执⾏,但相反,像a(b('111'));这种存在参数的就不⾏,因为⽆论正则匹配多少次它的参数总是存在的。那假如遇到这种情况,我们就只能使⽤没有参数的php函数,
下⾯就来具体介绍⼀下:
1、getallheaders()
这个函数的作⽤是获取http所有的头部信息,也就是headers,然后我们可以⽤var_dump把它打印出来,但这个有个限制条件就是必须
在apache的环境下可以使⽤,其它环境都是⽤不了的,我们到burp中去做演⽰,测试代码如下:
<?php
highlight_file(__FILE__);
if(isset($_GET['code'])){
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);}
else
die('nonono');}
else
echo('please input code');
>
可以看到,所有的头部信息都已经作为了⼀个数组打印了出来,在实际的运⽤中,我们肯定不需要这么多条,不然它到底执⾏哪⼀条呢?所以我们需要选择⼀条出来然后就执⾏它,这⾥就需要⽤到php中操纵数组的函数了,这⾥常见的是利⽤end()函数取出最后⼀位,这⾥的效果如下图所⽰,⽽且它只会以字符串的形式取出值⽽不会取出键,所以说键名随便取就⾏:
那我们把最前⾯的var_dump改成eval,不就可以执⾏phpinfo了吗,换⾔之,就可以实现任意php代码的代码执⾏了,那在没有过滤的情况下执⾏命令也就轻⽽易举了,具体效果如下图所⽰:
2、get_defined_vars()
可以看到,它并不是获取的headers,⽽是获取的四个全局变量$_GET $_POST $_FILES $_COOKIE,⽽它的返回值是⼀个⼆维数组,我们利
⽤GET⽅式传⼊的参数在第⼀个数组中。这⾥我们就需要先将⼆维数组转换为⼀维数组,这⾥我们⽤到current()函数,这个函数的作⽤是返回数组中的当前单元,⽽它的默认是第⼀个单元,也就是我们GET⽅式传⼊的参数,我们可以看看实际效果:
这⾥可以看到成功输出了我们⼆维数组中的第⼀个数据,也就是将GET的数据全部输出了出来,相当于它就已经变成了⼀个⼀维数组了,那按照我们上⾯的⽅法,我们就可以利⽤end()函数以字符串的形式取出最后的值,然后直接eval执⾏就⾏了,这⾥和上⾯就是⼀样的了:
总结⼀下,这种⽅法和第⼀种⽅法⼏乎是⼀样的,就多了⼀步,就是利⽤current()函数将⼆维数组转换为⼀维数组,如果⼤家还是不了
解current()函数的⽤法,可以接着往下看⽂章,会具体介绍的哦
3、session_id()
这种⽅法和前⾯的也差不太多,这种⽅法简单来说就是把恶意代码写到COOKIE的PHPSESSID中,然后利⽤session_id()这个函数去读取它,返回⼀个字符串,然后我们就可以⽤eval去直接执⾏了,这⾥有
⼀点要注意的就是session_id()要开启session才能⽤,所以说要先session_start(),这⾥我们先试着把PHPSESSID的值取出来:
直接出来就是字符串,那就⾮常完美,我们就不⽤去做任何的转换了,但这⾥要注意的是,PHPSESSIID中只能有A-Z a-z 0-9,-,所以说我们要先将恶意代码16进制编码以后再插⼊进去,⽽在php中,将16进制转换为字符串的函数为hex2bin
那我们就可以开始构造了,⾸先把PHPSESSID的值替换成这个,然后在前⾯把var_dump换成eval就可以成功执⾏了,如图:
成功出现phpinfo,稳稳当当,这种⽅法我认为是最好的⼀种⽅法,很容易理解,只是记得要将恶意代码先16进制编码⼀下哦
4.php函数直接读取⽂件
上⾯我们⼀直在想办法在进⾏rce,但有的情况下确实⽆法进⾏rce时,我们就要想办法直接利⽤php函数完成对⽬录以及⽂件的操作,接下来我们就来介绍这些函数:
1.localeconv
官⽅解释:localeconv() 函数返回⼀个包含本地数字及货币格式信息的数组。
这个函数其实之前我⼀直搞不懂它是⼲什么的,为什么在这⾥有⽤,但实践出真知,我们在测试代码中将localeconv()的返回结果输出出来,这⾥很神奇的事就发⽣了,它返回的是⼀个⼆维数组,⽽它的第⼀位居然是⼀个点.,那按照我们上⾯讲的,是可以利⽤current()函数将这个点取出来的,但这个点有什么⽤呢?点代表的是当前⽬录!那就很好理解了,我们可以利⽤这个点完成遍历⽬录的操作!相当于就是linux中
的ls,具体请看下图:
2.scandir
字符串函数传参这个函数很好理解,就是列出⽬录中的⽂件和⽬录
3.current(pos)
这⾥⾸先声明,pos()函数是current()函数的别名,他们俩是完全⼀样的哈
这个函数我们前⾯已经⽤的很多了,它的作⽤就是输出数组中当前元素的值,只输出值⽽忽略掉键,默认是数组中的第⼀个值,如果要移动可以⽤下列⽅法进⾏移动:
4.chdir()
这个函数是⽤来跳⽬录的,有时想读的⽂件不在当前⽬录下就⽤这个来切换,因为scandir()会将这个⽬录下的⽂件和⽬录都列出来,那么利⽤操作数组的函数将内部指针移到我们想要的⽬录上然后直接⽤chdir切就好了,如果要向上跳就要构造chdir('..')
5.array_reverse()
将整个数组倒过来,有的时候当我们想读的⽂件⽐较靠后时,就可以⽤这个函数把它倒过来,就可以少⽤⼏个next()
6.highlight_file()
打印输出或者返回 filename ⽂件中语法⾼亮版本的代码,相当于就是⽤来读取⽂件的
例题解析——–GXYCTF 2019禁⽌套娃
这道题⾸先是⼀个git源码泄露,我们先⽤GitHack把源码跑下来,内容如下:
<?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'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差⼀点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
>
可以看出它是⼀个有过滤的⽆参rce,由于它过滤掉了et,导致我们前两种的⽅法都⽤不了,⽽且它也过滤了hex bin,第三种⽅法也不能像我们上⾯讲的⼀样先16进制编码了,⽽且我抓包以后都看不到PHPSESSID的参数,估计第三种⽅法也⽤不了,但有了前⾯的铺垫,⽤第四种⽅法就可以很简单的解决了,⾸先遍历当前⽬录:
可以看到flag.php是倒数第⼆个,那我们就把它反转⼀下,然后再⽤⼀个next()就是flag.php这个⽂件了
胜利就在眼前,直接highlight_file读取这个⽂件就拿到flag了:
思路总结
scandir(current(localeconv()))是查看当前⽬录
加上array_reverse()是将数组反转,即Array([0]=>index.php[1]=>flag.php=>[2].git[3]=>..[4]=>.)再加上next()表⽰内部指针指向数组的下⼀个元素,并输出,即指向flag.php
highlight_file()打印输出或者返回 filename ⽂件中语法⾼亮版本的代码
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论