PHP项目文件包含漏洞如何代码审计

wen PHP项目 28

PHP项目文件包含漏洞:从原理到代码审计实战指南

目录导读

  1. 文件包含漏洞基础概念 – 理解Local File Inclusion与Remote File Inclusion的本质差异
  2. 漏洞触发核心条件 – 动态包含、路径拼接、权限控制三大危险因子
  3. 代码审计实战步骤 – 从函数定位到参数追踪的完整方法论
  4. 高危函数与代码模式includerequirefile_get_contents等函数审计要点
  5. 绕过与防御机制 – 绕过WAF的常见手法与安全编码规范
  6. 问答环节 – 5个高频审计问题深度解答

文件包含漏洞基础概念

文件包含漏洞是PHP项目中最常见的Web安全漏洞之一,攻击者通过控制include()require()include_once()require_once()等函数的参数,实现非预期文件的加载执行。

PHP项目文件包含漏洞如何代码审计

关键分类:

  • LFI (Local File Inclusion): 包含服务器本地文件,如/etc/passwd
  • RFI (Remote File Inclusion): 包含远程服务器上的恶意文件,通常利用allow_url_include=On配置。

实战案例:
某CMS系统使用$_GET['page']参数加载模板文件,若输入../../etc/passwd即可读取系统密码文件,这种利用路径回溯符()的手法,在代码审计中需要重点关注。

问题1:如何区分LFI和RFI的审计重点?
答:LFI重点审计路径过滤不足导致的目录穿越,RFI重点检查allow_url_include是否开启且用户输入未做协议限制(如未禁止php://input等伪协议)。


漏洞触发核心条件

通过分析大量CVE与实战案例,文件包含漏洞的触发需要同时满足三个条件:

  1. 动态包含 – 函数参数直接或间接来自用户输入($_GET$_POST$_COOKIE等)。
  2. 路径拼接 – 未对输入进行规范化处理(如realpath()basename()过滤)。
  3. 权限缺失 – 未限制可包含文件的目录范围(常见于模板系统)。

危险代码示例:

$template = $_GET['tpl'] . '.php';
include('templates/' . $template);

看似添加了.php后缀限制,但攻击者可通过%00截断(PHP < 5.3.4)或路径编码绕过。

问题2:URL编码在文件包含漏洞中如何被利用?
答:攻击者常将关键字符如进行双重URL编码(%2e%2e%2f)绕过简单过滤,审计时需关注对输入的解码处理次数是否匹配。


代码审计实战步骤

步骤1:定位高危函数

使用工具(如RIPS、PHPStan)或手动搜索PHP内置文件操作函数:

  • 包含类:includerequireinclude_oncerequire_once
  • 文件读取类:file_get_contentsfopenreadfile
  • 数据处理类:fgetsfile_put_contents(配合文件包含的写入)

步骤2:追踪参数来源

建立函数调用链:
$_GET['file'] → 经str_replace过滤 → 传递给include()
审计陷阱:过滤函数可能被绕过(如仅替换一次,但攻击者使用)。

步骤3:验证路径可控性

检查路径中是否存在:

  • 绝对路径拼接(/var/www/$file
  • 相对路径拼接(./cache/$file
  • 数据库或配置文件中的路径变量

思维清单:

  1. 参数是否经过htmlspecialchars而非路径过滤?
  2. 是否使用realpath()但忽略返回值?
  3. 是否允许伪协议(php://filterdata://)?

步骤4:测试边界条件

构造Payload进行黑盒验证:

?file=php://filter/read=convert.base64-encode/resource=index.php
?file=../../../etc/passwd%00
?file=/etc/passwd (绝对路径)

问题3:为什么审计时推荐从file_get_contents函数入手?
答:该函数虽然不直接导致RCE,但常配合文件写入函数形成“写文件到可包含目录 → 包含恶意脚本”的链式攻击。file_get_contents对伪协议的过滤经常被忽视。


高危函数与代码模式解析

函数名称 常见被用模式 典型绕过方式
include() include("lang/".$_GET['lang'].".php") 路径回溯、双写、编码绕过
require() require_once $path 伪协议php://input传递PHP代码
file_get_contents() $data = file_get_contents($_POST['url']) 结合file_put_contents写入shell

实战代码审计案例(某电商系统):

function loadModule($module){
    $file = 'modules/' . $module . '.php';
    if(file_exists($file)){
        include $file;
    }
}
$userModule = $_GET['mod'];
loadModule($userModule);

漏洞点: 未验证$module是否包含,且file_exists误报可能导致路径遍历。

问题4:如何审计框架中的路由分发机制?
答:关注框架的ControllerLoaderViewLoader类,例如ThinkPHP的模板加载函数fetch()中若未设置白名单,攻击者可通过指定任意模板文件。


绕过与防御机制

常见绕过技术:

  1. 路径编码混淆:单次替换时使用(双写绕过)
  2. 伪协议利用php://filter配合Base64读取源码,data://直接写入代码
  3. 环境变量注入:在IIS或Apache中利用..%255c..%255c(UTF-8编码双写入)
  4. 服务器特性:Nginx的alias配置不当导致路径穿越

安全编码规范:

  • 限制可包含目录:使用basename()后拼接固定前缀'static/' . basename($file)
  • 白名单校验$allowed = ['home', 'about']; in_array($page, $allowed)
  • 禁用危险配置ini_set('allow_url_include', 0)open_basedir限制目录
  • 输入规范化:使用realpath()解析后检查是否在允许路径内

问题5:realpath()函数在审计中的正确姿势?
答:需注意realpath()在文件不存在时返回false,导致某些逻辑绕过,正确做法是先file_exists()检查文件,再用realpath()验证路径是否在安全目录范围内。


常见问题深度解答(FAQ)

Q1: PHP7以上版本是否还存在文件包含漏洞?
A: 虽然%00截断已被修复,但路径遍历()和伪协议利用仍然普遍,某知名内容管理系统在2023年仍爆出LFI漏洞。

Q2: 使用file_get_contents读取远程文件是否算RFI?
A: 严格来说这属于数据获取而非代码执行,但配合eval()include可将读取内容解释执行形成RFI。

Q3: 审计时如何快速定位用户输入点?
A: 使用静态分析工具的污点分析(Taint Analysis),或手动搜索$_变量后跟踪函数调用链,工具如Seay源码审计系统可自动标记。

Q4: 文件包含漏洞一定会导致RCE吗?
A: 不一定,LFI通常只能读取敏感文件(如/proc/self/environ),但结合日志注入、Session文件包含、上传图片木马等可实现RCE。

Q5: 小团队如何高效进行文件包含漏洞审计?
A: 建立危险函数清单,编写PHPUnit测试用例覆盖所有包含路径,重点审计模板加载、语言文件切换、动态插件加载这三个业务场景。


总结建议: 文件包含漏洞的审计难点在于业务逻辑的多样性,建议开发者遵循“默认禁止”原则,对可包含文件实施白名单管理,使用最新版PHP并关闭危险配置(allow_url_include=Off),可消除80%以上的高危害漏洞,对于遗留代码,优先排查includerequire语句中直接拼接$_GET$_POST的代码段。

抱歉,评论功能暂时关闭!