文件包含漏洞的产生原因是在通过 PHP 的函数引入文件时,由于传入的文件名没有经过合理的校验,从而操作了预想之外的文件,就可能导致意外的文件泄露甚至恶意的代码注入。
文件包含分为本地文件包含(LFI)和远程文件包含(RFI)
文件包含的函数和语言结构
1 | require 找不到被包含的文件时会产生致命错误,并停止脚本运行。 |
被包含的文件中含有效的php代码(php标签),则引入当前文件执行,若不含有效php代码,则直接输出文件内容
它们都是特殊的语言结构,不是函数,其参数不需要括号,不能动态调用,在比较返回值时需要注意。
1 | 错误操作: |
file_get_contents() 将整个文件读入一个字符串
file() 把整个文件读入一个数组中
readfile() 输出文件
allow_url_fopen和allow_url_include
这两个都是php.ini的配置项
allow_url_fopen On 默认开启
该选项为on便是激活了URL形式的fopen封装协议使得可以访问URL对象文件
注意: 此选项只能在 php.ini 中设置
allow_url_include off 默认关闭
该选项为on便是允许包含URL对象文件
注意: 此设置需要启用allow_url_fopen
本地文件包含(LFI)
文件包含的典型漏洞代码:
1 |
|
其中,file是我们可控的,此时,我们包含web服务器上的任意文件。
如果包含的文件没有php代码,则会直接显示出来,这就造成了任意文件读取漏洞。
获取敏感信息
目录遍历:
读取账户信息
1 | ?file=../../../../../../../../etc/passwd |
常见敏感信息
Linux:
1 | /etc/passwd // 账户信息 |
包含apache访问日志和错误日志
利用条件:
- 需要知道服务器日志的存储路径
- 日志文件可读。
在用户发起请求时,apache会将请求写入access.log,当发生错误时会将错误写入error.log
因此,我们可以在访问时刻意加上精心构造的语句,让其写入到日志文件中去,再利用目录穿越去包含它,就能够达到getshell的效果。
为了避免url编码,我们在burpsuite中发包
虽然返回400,但是已经写入了访问日志
接下来包含这个文件
包含session文件
利用条件:
- 需要知道服务器session存储路径
- session文件可读
- 可以控制session内容
session文件的存储路径可以在phpinfo的session.save_path中找到
常见的php session文件目录还有/tmp/sessions
session文件的命名规则为sess_PHPSESSID
如果有如下php文件:
1 |
|
则同样可以构造:
当我们的构造的语句写入session文件后,包含即可。
结合文件上传
由上面两个例子,我们已经知道了只要我们包含的文件含有php代码,即可将其执行。
当一个网站拥有文件上传功能但是只能上传指定后缀的文件时,我们可以试着寻找是否有文件包含的点。
当然,想要包含上传的文件,必须要知道其路径。
其他
包含environ
利用条件:
- php以cgi方式运行,这样environ才会保持UA头。
- environ文件存储位置已知,且environ文件可读。
姿势:/proc/self/environ
中会保存user-agent
头。如果在user-agent
中插入php代码,则php代码会被写入到environ中。之后再包含它,即可。
包含fd
类似environ,不同的是需要包含fd文件,而php代码插入的地方是referer头
同样需要可读权限
包含临时文件
php中上传文件,会创建临时文件。在linux下使用/tmp目录,而在windows下使用c:\winsdows\temp目录。在临时文件被删除之前,利用竞争即可包含该临时文件。
由于包含需要知道包含的文件名。一种方法是进行暴力猜解,linux下使用的随机函数有缺陷,而window下只有65535中不同的文件名,所以这个方法是可行的。
另一种方法是配合phpinfo页面的php variables
,可以直接获取到上传文件的存储路径和临时文件名,直接包含即可。
远程文件包含(RFI)
远程文件包含条件较为苛刻,要求 allow_url_fopen和allow_url_include都为On状态才可以。
但远程文件包含更为致命,因为一旦可以控制远程文件包含的文件,则意味着可以包含任意我们想要的代码。
比如,我们的服务器存在shell.txt。
此时我们直接远程包含此文件即可
远程文件除了http协议可以用,还可以使用https和ftp协议来实现远程文件包含。
php伪协议和文件包含的完美结合
php中支持的伪协议
file:// 访问本地文件系统
http:// 访问HTTP(s)网址
ftp:// 访问FTP(s) URLs
php:// 访问各个输入/输出流
zlib:// 压缩流
data:// 数据
glob:// 查找匹配的文件路径模式
phar:// PHP归档
ssh2:// Secure Shell 2
rar:// RAR
ogg:// 音频流
expect:// 处理交互式的流
file://协议
file://协议在双off的情况下也可以正常使用;
1 | allow_url_fopen: Off/On |
file://协议用于访问本地文件系统,在CTF中通常用来读取本地文件,且不受allow_url_fopen与allow_url_include的限制。
使用:file://[文件的绝对路径和文件名]
需要注意的是,php中涉及到文件以及协议的地方,默认都是使用的file协议。也就是说,如果你没有显式的写出协议名或者协议不存在,都会被当成file协议来解析。
php://协议
php://filter
php://filter是一种元封装器,设计用于数据流打开时的筛选过滤应用。这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。
参数:
php://filter 目标使用以下的参数作为它路径的一部分。复合过滤链能够在一个路径上指定。
1 | resource=<要过滤的数据流> 必须。 它指定了要筛选过滤的数据流 |
例如:
1 | php://filter/read=convert.base64-encode/resource=index.php 将数据过滤为base64编码形式读取 |
过滤器
多个过滤器可同时使用,用|
连接起来
字符串过滤器
1 | string.rot13 使用此过滤器等同于str_rot13()函数处理所有的流数据(rot13编码) |
转换过滤器
1 | convert.base64-decode 使用这个过滤器等同于使用base64_decode()函数处理所有的流数据 |
convert.iconv举例:
1 | php://filter/convert.iconv.UTF-8.UTF-7/resource=index.php |
压缩过滤器
1 | zlib.deflate 压缩过滤器 |
加密过滤器
1 | mcrypt.* 加密过滤器 |
php://input
php://input
是个可以访问请求的元数据的只读流,可以读取到POST没有解析的原始数据。
注:entype="multipart/form-data"
的时候php://input
是无效的。
使用条件:
1 | allow_url_fopen: Off/On |
需要特别注意的是,当allow_url_include
为On
的时候,会将POST请求中的数据作为PHP代码执行。
例如:
1 |
|
php://output
是一个只写的数据流,允许以print和echo一样的方式写入到输出缓冲区。
data://
数据流封装器。
使用格式:data:[<mime type>][;charset=<charset>][;base64],<encoded data>
- 第一部分是
data:
协议头,它标识这个内容为一个data URI
资源。 - 第二部分是
MIME
类型,表示这串内容的展现方式,比如:text/plain
,则以文本类型展示,image/jpeg
,以 jpeg 图片形式展示,同样,客户端也会以这个 MIME 类型来解析数据。 - 第三部分是编码设置,默认编码是
charset=US-ASCII
,即数据部分的每个字符都会自动编码为%xx
- 第四部分是
base64
编码设定,这是一个可选项,base64
编码中仅包含0-9,a-z,A-Z,+,/,=
,其中=
是用来编码补白的。 - 最后一部分为这个
Data URI
承载的内容,它可以是纯文本编写的内容,也可以是经过base64
编码 的内容。
使用条件:
- data://协议必须allow_url_fopen为on才能使用
1
2allow_url_fopen: on
allow_url_include: on/off - php版本≥php5.2
需要注意的是,当allow_url_include
为on
时,任意文件包含就会成为任意命令执行
1 | ?file=data://text/plain,<?php phpinfo()?> |
zip://,bzip2://,zlib://协议
使用条件:
1 | zip://, bzip2://, zlib://协议在双off的情况下也可以正常使用; |
3个封装协议,都是直接打开压缩文件。
1 | compress.zlib://file.gz - 处理的是 '.gz' 后缀的压缩包 |
zip://, bzip2://, zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名。
zip://协议
使用条件:php版本≥php5.3.0
使用方法:zip://[压缩文件绝对路径]#[压缩文件内的子文件名]
例如:zip://archive.zip#dir/file.txt