目录
一、文件下载漏洞
1.1 文件下载案例(黑盒角度)
1.2 文件读取案例(黑盒角度)
二、文件删除
三、目录遍历与目录穿越
四、审计分析-文件下载漏洞-XHCMS
五、审计分析-文件读取漏洞-MetInfo-函数搜索
六、审计分析-文件删除-74CMS-函数搜索
一、文件下载漏洞
1. 白盒审计流程:功能点抓包-代码文件的特定函数-寻变量控制-构造测试
2. 黑盒流程:查看网站是否有文件下载的功能
在文件下载中一般会遇到两种情况:
常规下载URL:http://www.xiaodi8.com/upload/123.pdf
在常规下载中,如过下载一个代码文件,比如1.php是不行的,因为.php结尾会被执行,不会被下载也看不到源码。
而在高版本中00截断也是不存在的
可能存在安全URL:http://www.xiaodi8.com/xx.xx?file=123.pdf
下载的文件以参数值去发送解析下载
下载和读取,其实是差不多的,最终的目的都是读取敏感信息,获取敏感文件等
1.1 文件下载案例(黑盒角度)
这是一个文件下载功能,URL:http://67.202.70.133/files/readfile.php,如果是直连下载,一般是直接指向资源的,如上面例子http://www.xiaodi8.com/upload/123.pdf,在这里其实是通过一个file参数接收路径的方式来获取的
利用我们已知的文件,如当前页面readfile.php,通过参数是否可以下载或者读取到代码,发现是可以的
但是从 readfile.php代码上看就是一个简单的封包,然后根据file参数,使用一个readfile函数来获取文件,并没有什么关键信息
但每个网站一般都会有索引文件,尝试去获取index.php,从url可以看到这里经过了file目录,所以需要使用../返回上一级目录
成功获取到,并从index的代码看到这是一个Joomla
在网上找一写他的关键配置文件,尝试去读取 ,如数据库配置文件../configuration.php,
泄露了数据库的账号密码等隐私信息
.
文件下载/读取常见的敏感信息路径
1、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配置信息2、Linux系统:
/root/.ssh/authorized_keys //如需登录到远程主机,需要到.ssh目录下,新建authorized_keys文件,并将id_rsa.pub内容复制进去
/root/.ssh/id_rsa //ssh私钥,ssh公钥是id_rsa.pub
/root/.ssh/id_ras.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/.mysql_history //mysql历史命令记录文件
/proc/self/fd/fd[0-9]*(文件标识符)
/proc/mounts //记录系统挂载设备
/porc/config.gz //内核配置文件
/var/lib/mlocate/mlocate.db //全文件路径
/porc/self/cmdline //当前进程的cmdline参数
1.2 文件读取案例(黑盒角度)
靶场注册登录及应用
https://portswigger.net/web-security/all-labs,最好使用gmail邮箱进行注册,其他可能收不到验证邮箱,验证邮箱后,会让你设置用户名,但密码是由portswigger自动生成,复制登录即可(记得关闭代理,不然会登录失败)
实验室:文件路径遍历,简单案例 |网络安全学院 (portswigger.net)
访问实验室即可开启靶场
任意以url的方式打开页面上的图片https://0ae3006a032d097482c3eea700df003b.web-security-academy.net/image?filename=20.jpg发现是由filename参数控制的
开启burp抓包,并将抓取的数据包发送至Repeater处,修改其文件头?filename=../../../etc/passwd
则返回数据包中会显示,linux服务器中的账户信息
二、文件删除
文件删除漏洞就是程序在删文件的时候出了问题。一是缺乏权限验证,谁都能来删文件;二是没输入验证不严格,允许删除任何文件等;
删除是高危操作,即使是授权站也需独立部署才能测试
举个例子:常见cms安装后会有install_lock.txt文件用于检测已安装,如果删除该文件,访问就会跳转安装的页面。
还有index.php
通常是网站的默认首页文件或重要的入口文件,当index被删除,服务器在找不到index.php
这个默认指定的首页文件时,会尝试展示当前目录下的其他文件列表来作为替代,这是服务器配置的一种默认回退机制,就会显示出一堆文件名
前提:
Apache开启Option +Indexes, +代表开启,-代表禁用
Nginx在location 块中添加autoindex on; 开启文件列表显示功能。
Apache实例:
三、目录遍历与目录穿越
-
攻击意图不同
- 目录遍历:攻击者的主要意图往往是对目标系统的目录结构进行全面的探索和查看。就好像攻击者想要在一个大型图书馆里随意走动,查看各个书架、各个楼层的书籍摆放情况,目的是了解整个图书馆的布局和有哪些藏书,以寻找有价值的信息或潜在的漏洞,比如找到包含敏感数据的目录或文件。
- 目录穿越:攻击者的目的更具针对性和破坏性,是想要直接突破限制到达特定的、通常是受限制或敏感的目录位置。类似于攻击者知道图书馆里有一个存放珍贵古籍的密室,他的目标就是直接穿过各种限制进入这个密室,获取或破坏里面的珍贵古籍,比如访问系统的核心配置文件目录进行篡改。
-
攻击行为特点不同
- 目录遍历:通常依靠多次使用 “../” 等路径操作符,逐步向上或向下移动目录层级,进行大量的尝试性访问。例如,攻击者可能会构造 “../../dir1/../../dir2/../file.txt” 这样的路径,从当前目录逐步回溯和探索其他目录。
- 目录穿越:更多地是精心构造一个能够直接跨越多个目录层级的路径,利用应用程序对路径处理的漏洞,直接定位到目标敏感目录。比如,在一个 Web 应用中,利用文件上传功能的漏洞,构造类似 “/var/www/html/../etc/passwd” 的路径,试图直接穿越到系统的用户密码文件所在目录。
-
利用漏洞的方式不同
- 目录遍历:更多地是利用系统或应用程序对目录路径解析的一般性漏洞,没有严格限制对目录层级的访问,使得攻击者能够随意在目录结构中移动。比如系统对用户输入的路径没有进行充分的合法性检查,攻击者就可以利用这一点进行目录遍历。
- 目录穿越:常常是利用应用程序在特定功能或操作中对路径处理的缺陷,比如在文件上传、下载功能中,对目标路径的验证不严格,导致攻击者可以通过构造特殊的路径,将文件上传到或下载自不应该访问的目录,从而实现目录穿越。
-
造成的危害程度和范围不同
- 目录遍历:可能会导致系统中的大量文件和目录信息被泄露,攻击者可以获取到系统的目录结构、文件列表等信息,为后续的攻击提供信息支持,但一般不会直接对系统造成严重的破坏。
- 目录穿越:一旦成功,往往会直接对特定的敏感文件或目录造成威胁,可能导致敏感数据泄露、文件被篡改或删除等严重后果,对系统的安全性和稳定性造成直接的、严重的破坏。
四、审计分析-文件下载漏洞-XHCMS
看到有下载文件的功能
可以看到3个下载,使用F12打开开发者工具,看下三个请求地址的区别,可以看到line的不同,和根据英文推测应该是区分网盘、电信、联通下载
可以看出从当前页面index的 r参数来接收跳转的页面或api,找到index.php
看到这个include的,回顾上一篇文件包含说到,前后不能指定,否则伪协议失效,比如include('x/'.$file); 或者 include($file.'php') ,这不细说,不是这章的重点
从index代码可以验证我们的想法,通过r加载某个文件,顺着这个思路找到下载文件所加载的files/downloads.php
<?php
require 'inc/conn.php';
$line=addslashes($_GET['line']);
$type=addslashes($_GET['type']);
$fileid=addslashes($_GET['cid']);
if(!is_numeric($fileid)){
echo "错误的下载请求!";
exit;}
$query = "SELECT * FROM download WHERE ( id='$fileid')";
$result = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$down= mysql_fetch_array($result);
$fileadd=$down['softadd'];
$fileadd2=$down['softadd2'];
if ($type=='soft' AND $line=='pan'){
if ($fileadd2==""){
echo "<script language=JavaScript>alert('抱歉,程序尚在开发当中,请稍后再试!');history.back();window.close();</script>";
exit;
}
//下载计数
$query = "UPDATE download SET xiazai = xiazai+1 WhERE id='$fileid'";
@mysql_query($query) or die('修改错误:'.mysql_error());
header("Location: $fileadd2");
exit;
}
if ($type=='soft' AND ($line=="telcom" OR $line=="unicom")){
$filename=$down['title'];
$filename2=$down['version'];
$filename=iconv("UTF-8", "GBK", $filename);
$houzhui=substr($fileadd,strrpos($fileadd,"."));
$sourceFile = $fileadd; //要下载的临时文件名
$outFile = $filename." ".$filename2.$houzhui; //下载保存到客户端的文件名
$file_extension = strtolower(substr(strrchr($sourceFile, "."), 1)); //获取文件扩展名
//echo $sourceFile;
//if (!ereg("[tmp|txt|rar|pdf|doc]", $file_extension))exit ("非法资源下载");
//检测文件是否存在
if (!is_file($sourceFile)) {
die("<script language=JavaScript>alert('抱歉,本地下载未发现文件,请选择网盘下载!');history.back();window.close();</script>");
}
$len = filesize($sourceFile); //获取文件大小
$filename = basename($sourceFile); //获取文件名字
$outFile_extension = strtolower(substr(strrchr($outFile, "."), 1)); //获取文件扩展名
//根据扩展名 指出输出浏览器格式
switch ($outFile_extension) {
case "exe" :
$ctype = "application/octet-stream";
break;
case "zip" :
$ctype = "application/zip";
break;
case "mp3" :
$ctype = "audio/mpeg";
break;
case "mpg" :
$ctype = "video/mpeg";
break;
case "avi" :
$ctype = "video/x-msvideo";
break;
default :
$ctype = "application/force-download";
}
//Begin writing headers
header("Cache-Control:");
header("Cache-Control: public");
//设置输出浏览器格式
header("Content-Type: $ctype");
header("Content-Disposition: attachment; filename=" . $outFile);
header("Accept-Ranges: bytes");
$size = filesize($sourceFile);
//如果有$_SERVER['HTTP_RANGE']参数
if (isset ($_SERVER['HTTP_RANGE'])) {
/*Range头域 Range头域可以请求实体的一个或者多个子范围。
例如,
表示头500个字节:bytes=0-499
表示第二个500字节:bytes=500-999
表示最后500个字节:bytes=-500
表示500字节以后的范围:bytes=500-
第一个和最后一个字节:bytes=0-0,-1
同时指定几个范围:bytes=500-600,601-999
但是服务器可以忽略此请求头,如果无条件GET包含Range请求头,响应会以状态码206(PartialContent)返回而不是以200 (OK)。
*/
// 断点后再次连接 $_SERVER['HTTP_RANGE'] 的值 bytes=4390912-
list ($a, $range) = explode("=", $_SERVER['HTTP_RANGE']);
//if yes, download missing part
str_replace($range, "-", $range); //这句干什么的呢。。。。
$size2 = $size -1; //文件总字节数
$new_length = $size2 - $range; //获取下次下载的长度
header("HTTP/1.1 206 Partial Content");
header("Content-Length: $new_length"); //输入总长
header("Content-Range: bytes $range$size2/$size"); //Content-Range: bytes 4908618-4988927/4988928 95%的时候
} else {
//第一次连接
$size2 = $size -1;
header("Content-Range: bytes 0-$size2/$size"); //Content-Range: bytes 0-4988927/4988928
header("Content-Length: " . $size); //输出总长
}
//打开文件
$fp = fopen("$sourceFile", "rb");
//设置指针位置
fseek($fp, $range);
//虚幻输出
while (!feof($fp)) {
//设置文件最长执行时间
set_time_limit(0);
print (fread($fp, 1024 * 8)); //输出文件
flush(); //输出缓冲
ob_flush();
}
fclose($fp);
//下载计数
$query = "UPDATE download SET xiazai = xiazai+1 WhERE id='$fileid'";
@mysql_query($query) or die('修改错误:'.mysql_error());
exit ();
}
?>
从url我们知道downloads传入参数type,line和cid ,找到对应位置观察,接收三个值并使用addslashes函数对数据过滤
继续往下看,这是一个数据库操作,从download表根据查询id=cid获取数据,$down接收数据库返回的数据,把softadd字段的值赋给$fileadd,把softadd2字段的值赋给$fileadd2,往下看$type=='soft' AND $line=='pan',这里就是网盘下载softadd2为空就弹出提示框并关闭当前窗口。这里直接通过跳转网盘下载,所以直接往下看
继续往下看一看到联通和电信下载,这里设置了一个临时文件名
$sourceFile = $fileadd; //要下载的临时文件名
后续都是以这部分做的一些处理,如获取后缀,文件名等
通过设置 HTTP 响应头告知浏览器如何处理接收到的数据。
Content - Type
:指定返回数据的 MIME 类型,比如application/octet - stream
一般用于二进制文件下载,audio/mpeg
用于 MP3 音频文件等,让浏览器知道数据的类型。Content - Disposition
:设置为attachment
,表明这是一个要下载的文件,而不是直接在浏览器中打开。filename
指定了下载文件保存时的名称。Accept - Ranges
:告诉浏览器服务器支持按字节范围请求数据,这是实现断点续传功能的基础。
然后设置断点续传,不一一细说
最后通过fopen打开 ,把数据流返回给客户端,而fopen打开的正是$sourceFile
读完代码,我们知道整体流程下
数据库查询出来-->$down['softadd'];-->$sourceFile = $fileadd; -->$sourceFile(从右到左倒推)
显然download表的 fileadd就应该是个路径,只要我们能控制这个路径,就能任意下载,但是该路径是保存在数据库的
所以我们需要找到,是谁操作了download表写进fileadd字段的,首先全局搜索download表插入操作insert into download/insert download,into是可以省略的,一般这两种写法,找不到就直接搜索download表
可以看到newsoft对download进行了插入操作
但是需要后台操作,尝试能不能直接访问这个后台发布,利用参数跳转查看
很显然是不行的,从黑盒角度基本是就是要获取后台权限才有可能了,不过该系统是存在sql注入的,任意登录漏洞,这里不细说,可以参考:xhcms v1.0漏洞复现_xhcms文件上传漏洞-CSDN博客
-
账号:-1' union select 1,2,'test','0192023a7bbd73250516f069df18b500',5,6,7,8;#
-
密码:admin123
如果黑盒测出任意登录,那就可以继续做这个下载漏洞了
但这里讲的是白盒,所以一般在公司的话可以要一个测试账户的,登录之后, 假设我们已知D://www.txt是一个敏感文件
回到下载页面去下载文件
使用电信或联通下载
五、审计分析-文件读取漏洞-MetInfo-函数搜索
流程:特定函数搜索-寻触发调用-构造Payload测试
文件读取一般在页面中很难看出,所以需要搜索php中文件读取的函数
php文件读取的函数,7种:https://www.cnblogs.com/gyrgyr/p/5774436.html
比如:搜索其中一种
全局搜索readfile,在/app/system/include/module/old_thumb.class.php目录下
(正常情况下需要逐个文件分析,这里做演示所以直接给答案)
打开可以看到追踪$dir变量看一看到old_thumb类doshow方法通过GET获取的dir参数并且str_replace(array('../','./')过滤了../和./,但我们可以用反斜杠,其次还需要路径以 http开头, (substr(str_replace($_M['url']['site'], '', $dir),0,4) == 'http');。这里应该是通过http请求一张图片
public function doshow(){
global $_M;
$dir = str_replace(array('../','./'), '', $_GET['dir']);
echo $dir;
echo "<br/>";
echo !(substr(str_replace($_M['url']['site'], '', $dir),0,4) == 'http');
echo "<br/>";
if(substr(str_replace($_M['url']['site'], '', $dir),0,4) == 'http' && strpos($dir, './') === false){
header("Content-type: image/jpeg");
echo $dir;
echo 11111111;
ob_start();
readfile($dir);
ob_flush();
flush();
die;
}
也就是说我们需要知道谁调用了doshow,同样的全局搜索,在include\thumb.php中定义了一个常量
从代码看这里仅仅是定义了一个常量
<?php
# MetInfo Enterprise Content Management System
# Copyright (C) MetInfo Co.,Ltd (http://www.metinfo.cn). All rights reserved.
define('M_NAME', 'include');
define('M_MODULE', 'include');
define('M_CLASS', 'old_thumb');
define('M_ACTION', 'doshow');
require_once '../app/system/entrance.php';
# This program is an open source system, commercial use, please consciously to purchase commercial license.
# Copyright (C) MetInfo Co., Ltd. (http://www.metinfo.cn). All rights reserved.
?>
但是这里还引入了require_once '../app/system/entrance.php';在这个文件最后引入require_once PATH_SYS_CLASS.'load.class.php';并加载load::module()
代码逻辑有点复杂,就不细说了,有时间的可以自己去看下,我简单阐述一下:load::module()
方法会根据 M_CLASS
和 M_ACTION
等常量的值,调用 _load_class
方法来加载相应的类并执行对应的方法。
如果 M_ACTION
不为空且不是 'new'
,并且方法名以 'do'
开头(这里 'doshow'
满足以 'do'
开头),则会实例化类并使用 call_user_func
函数调用该类的 doshow
方法,从而执行具体的业务逻辑。
引用关系:
thumb.php -> entrance.php -> load.class.php下的load::module()-> _load_class() -> call_user_func()调用M_CLASS下的M_ACTION方法 所以通过thump.php控制dir值去获取本地文件,因为doshow不允许../和./,但可以使用\,且也需要http开头 构造payload: http\..\..\config\config_db.php(假设知道config_db.php) 首先抓包,然后添加payload: 127.0.0.1/metinfo/include/thumb.php?dir=http\..\..\config\config_db.php
六、审计分析-文件删除-74CMS-函数搜索
文件删除一般在会员中心或者后台进行操作,基本上不会在页面中展示。
① php文件删除函数是unlink,直接全局搜索
在/upload/admin/admin_article.php路径下
看到当$acl='del_img'满足条件时,会接受img参数,并删除img传进来的相关的文件
追踪act发现他也是通过请求头获取的
其次这里调用了两次unlink,由upfiles_dir和thumb_dir拼接而成,追踪查看,而两个变量都用了../data,也就是上一级目录的data,这个文件被admin_acticle.php引用,所以相对路径是基于admin_acticle.php的,所以是upload下的data文件夹下$CFG['x']文件夹下的文件,即upload/data/$CFG['x']/.png,有点绕,自己数几次就好,主要是判断要用多少个../,如果不想数,一个一个加../也是可以的
注意:追踪 upfiles_dir的时候条到的是elseif($act == 'addsave')这个代码段下的,从逻辑上分析,我们直接请求ack=del_img的话elseif($act == 'addsave')是不会被执行的,所以肯定不会是引用的这个的
而追踪是会指向第一次出现的地方,第一次出现就是if条件内,那么可以判断应该不是当前文件的变量,查看引用了什么文件,然后去全局搜索upfiles_dir
最终进去看到两个变量都被定义在这个文件/include/admin_common.inc.php
首先在upload创建一个1.txt,构造url去删除他,从上面可以知道删除的是upload/data/xxx/下的文件,所以回到upload需要../../
最后得到payload:upload/admin/admin_article.php?act=del_img&img=../../1.txt
这个系统是有CSRF处理来源请求的,返回admin_index.php下操作就能绕过...不需要抓包改