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" 当作函数名去调用(因为后面还有个 () ),这显然会报错。

相关推荐
粉末的沉淀2 小时前
vue:Vite项目中高效管理纯色SVG图标的方案
前端·javascript·vue.js
xinhuanjieyi2 小时前
Android 画板应用kotlin实现
android·开发语言·kotlin
dotnet902 小时前
PDF 页面尺寸上限是 14400。iText 直接加载成功的大图可能超过这个限制,需要在 setPageSize 之前等比缩放。
前端·javascript·html
threelab2 小时前
Three.js 几何图形变换 | 三维可视化 / AI 提示词
开发语言·前端·javascript·人工智能·3d·着色器
道友可好2 小时前
写给 AI 的入职手册,AGENTS.md
前端·人工智能·后端
故渊at2 小时前
第四板块:Android 输入系统与触控事件 | 第十六篇:按键分发与软键盘(IME)的窗口协同
android·软键盘·输入系统·触控事件·按键分发
故渊at2 小时前
第三板块:Android 图形渲染与窗口体系 | 第十四篇:View 绘制体系与 RenderThread 异步渲染
android·图形渲染·ui线程·renderthread·view体系
吠品2 小时前
处理 Python 类继承中那些变来变去的初始化参数
linux·前端·python
云水一下2 小时前
TypeScript 从零基础到精通(七):从配置到全栈项目落地
前端·javascript·typescript