ctf show web入门109

这是一个非常典型的 PHP 代码审计与命令/代码执行(RCE)类型的 CTF 题目。

题目核心在于通过控制 v1 和 v2 两个 GET 参数,在一个 eval() 函数中实现我们想要的恶意代码执行。

其中的核心代码为:

php 复制代码
if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];
    
    if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
        eval("echo new $v1($v2());");
    }
}

输入限制: preg_match('/a-zA-Z+/', v1)。这个正则表达式的意思是:只要变量中包含至少一个英文字母即可通过。它并没有使用 \^ 和 限制开头和结尾。这意味着我们可以在 v1 和 v2 中夹带数字、特殊符号、甚至换行符,只要里面有字母就行。

执行点: eval("echo new v1(v1(v1(v2());");

这行代码在原生 PHP 中是在尝试实例化一个类。形式相当于:echo new 类名(函数名());。

我们先尝试查看当前目录下的文件名:

构造payload为:?v1=Exception&v2=system('ls')

过第一步看到了一个叫 fl36dg.txt 的文件,接下来只需要将 ls 命令替换为 cat 命令去读取它。

构造payload为:?v1=Exception&v2=system('cat fl36dg.txt')

再查看原代码

得到flag为:

ctfshow{d2939111-4c45-4925-b947-60068a86e0f6}

核心解题思路(函数参数优先求值特性)

要突破 new v1(v1(v1(v2()); 这个死板的面向对象结构,有一个非常巧妙的底层逻辑漏洞可以利用。

在 PHP 引擎解析这条语句时,它的执行顺序(优先级)是自内向外的:

PHP 必须先知道传给类的构造函数的参数值是什么。

因此,它会首先尝试执行并计算括号内部的 $v2()。

执行完 v2() 拿到返回值后,再执行外部的 new v1。

如果我们利用前面提到的正则缺陷,直接往 $v2 中注入带有参数的恶意函数,比如:

v2=system('ls')

那么带入 eval 后,拼接出来的代码实际上变成了:

php 复制代码
eval("echo new $v1(system('ls')());");

为什么这样能抢先执行命令?

当 PHP 开始解析内层参数 system('ls')() 时:

它会先把 system('ls') 当作一个整体去调用。此时,系统的 ls 命令已经提前触发,并直接把当前目录下的文件名吐到了页面上。

命令执行完后,system 函数会返回输出结果的最后一行字符串。

接着,PHP 才会尝试把这个"返回的字符串"当作一个动态函数名,加上后面的 () 去执行。这显然会因为找不到对应的函数而报错(抛出类似 eval()'d code 的异常)。

但没关系,因为此时我们想要的命令已经执行完毕,执行结果已经输出了!

为了让外层的 new v1 语法合法而不至于在编译阶段直接卡死,我们只需给 v1 传一个 PHP 自带的合法类名即可,比如 Exception(异常类)或 ReflectionClass(反射类)。

简单来说:v1=Exception 本身并不能执行命令,它的作用是充当一个"语法挡箭牌",让 PHP 顺利通过编译,从而给内层的命令执行创造机会。

我们可以把整个过程拆解为编译阶段和执行阶段来看:

  1. 编译阶段:通过语法检查(为什么必须填 Exception 这样的类名)
    如果你的 Payload 是 ?v1=Exception&v2=system('ls'),带入后端后,eval 拼接出来的完整代码是:

PHP

eval("echo new Exception(system('ls')());");

PHP 的 eval() 在运行这段代码时,首先会进行语法编译。

编译器看到 new v1(...) 的结构时,它必须确保 v1 对应的字符串是一个在 PHP 中真实存在的、合法的类名。

如果你把 v1 留空,或者随便填一个不是类的字符串(比如 v1=abcd),PHP 在编译阶段就会直接抛出 Fatal error: Class 'abcd' not found(致命错误),整段代码直接胎死腹中,根本不会往下执行。

而 Exception 是 PHP 官方自带的内置异常类。填入它,PHP 编译器就会认为:"哦,这是一句合法的'实例化一个异常类'的语法。" 从而允许这段代码通过编译,进入下一个执行阶段。

  1. 执行阶段:参数优先求值与命令"抢跑"
    通过编译后,代码开始真正执行。PHP 遇到 new Exception( 参数 ) 时,底层有一个铁律:必须先计算出括号里面作为参数的值,才能把这个值传给类的构造函数。

于是,PHP 引擎的执行指针开始向内层移动,去解析参数:

Plaintext

echo new Exception( system('ls')() );

└──────┬───┘

  1. 首先执行这里

命令抢先触发:

在解析 system('ls')() 时,PHP 会先将 system('ls') 当作一个函数进行调用。此时,系统的 ls 命令被激活,直接在服务器终端执行,并把结果(比如 fl36dg.txt)直接打印到了前端页面上。

命令执行完毕:

system('ls') 执行完后,会返回它输出的最后一行字符串(假设是 "index.php")。

后续的报错(无关紧要):

此时,原本的代码结构变成了 echo new Exception("index.php"());。PHP 接下来会尝试把字符串 "index.php" 当作函数名去调用(因为后面还有个 () ),这显然会报错。

相关推荐
Zhu7588 分钟前
开源项目-Sablewisp-轻量、现代的 Chrome 书签管理扩展(Manifest V3)
前端·chrome
wespaul8 分钟前
一条自然语言命令的 49 个节点:可视化编排与每段强校验
前端·数据库
OH_TPC9 分钟前
【鸿蒙优选三方库】@ohos/axios:前端转鸿蒙,网络层几乎不用改
前端·华为·harmonyos·鸿蒙
程序员大阳11 分钟前
副队长CSS教程(38)–transform 变换
前端·css·变换·transform
程序员Sunday20 分钟前
Vue 3 响应式原理是什么?Proxy、ref 和 reactive 有什么区别?
前端·javascript
mldong30 分钟前
一个 npm 包,九页审批中心:Vue3 项目接上工作流引擎
前端·javascript·vue.js
架构技术专栏9 小时前
Agent 工具调用:从工具声明到执行反馈
前端·人工智能·后端
别动我齐刘海10 小时前
C++ 工程化学习
java·linux·前端·c++·学习·机器学习·机器人
志尊宝11 小时前
Vue3 零基础每日笔记(071):暗黑模式与主题切换——CSS 变量 + Element Plus 暗黑方案
前端·css·笔记·vue·html·前端开发·软件开发
我不会插花弄玉11 小时前
7.函数【由浅入深-MySQL】
android·数据库·mysql