漏洞分析
漏洞发生在上传头像的位置,对应文件为destoon\module\member\avatar.inc.php
.
主要代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php defined('IN_DESTOON') or exit('Access Denied'); login(); require DT_ROOT.'/module/'.$module.'/common.inc.php'; require DT_ROOT.'/include/post.func.php'; $avatar = useravatar($_userid, 'large', 0, 2); switch($action) { case 'upload': if(!$_FILES['file']['size']) { if($DT_PC) dheader('?action=html&reload='.$DT_TIME); exit('{"error":1,"message":"Error FILE"}'); } require DT_ROOT.'/include/upload.class.php';
$ext = file_ext($_FILES['file']['name']); $name = 'avatar'.$_userid.'.'.$ext; $file = DT_ROOT.'/file/temp/'.$name;
if(is_file($file)) file_del($file); $upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
$upload->adduserid = false;
if($upload->save()) { ... } else { ... } break;
|
先 用$ext
变量存储后缀$ext = file_ext($_FILES['file']['name']);
。接着$name
变量生成并存储文件名$name = 'avatar'.$_userid.'.'.$ext;
。然后用$file
存储路径$file = DT_ROOT.'/file/temp/'.$name;
之后再$upload = new upload($_FILES, 'file/temp/', $name, 'jpg|jpeg|gif|png');
new一个upload对象。
接着我们看看upload对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php class upload { function __construct($_file, $savepath, $savename = '', $fileformat = '') { global $DT, $_userid; foreach($_file as $file) { $this->file = $file['tmp_name']; $this->file_name = $file['name']; $this->file_size = $file['size']; $this->file_type = $file['type']; $this->file_error = $file['error'];
} $this->userid = $_userid; $this->ext = file_ext($this->file_name); $this->fileformat = $fileformat ? $fileformat : $DT['uploadtype']; $this->maxsize = $DT['uploadsize'] ? $DT['uploadsize']*1024 : 2048*1024; $this->savepath = $savepath; $this->savename = $savename; } }
|
用foreach依次给$_file
注册为$file
。并用数组的形式存储属性。$savename
和$savepath
是直接传入参数指定。
考虑上传两个文件 第一个文件名是1.php,第二个文件是1.jpg,只要构造合理的表单上传(参考:https://www.cnblogs.com/DeanChopper/p/4673577.html )
1 2 3
| $ext = file_ext($_FILES['file']['name']); // `$ext`即为`php` $name = 'avatar'.$_userid.'.'.$ext; // $name 为 'avatar'.$_userid.'.'php' $file = DT_ROOT.'/file/temp/'.$name; // $file 即为 xx/xx/xx/xx.php
|
而在upload类中,由于多个文件上传,$this->file、$this->file_name、$this->file_type将foreach在第二次循环中被置为jpg文件。测试如下:
接着 回到avatar.inc.php
,调用upload类的save方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function save() { include load('include.lang'); if($this->file_error) return $this->_('Error(21)'.$L['upload_failed'].' ('.$L['upload_error_'.$this->file_error].')'); if($this->maxsize > 0 && $this->file_size > $this->maxsize) return $this->_('Error(22)'.$L['upload_size_limit'].' ('.intval($this->maxsize/1024).'Kb)'); if(!$this->is_allow()) return $this->_('Error(23)'.$L['upload_not_allow']); $this->set_savepath($this->savepath); $this->set_savename($this->savename); if(!is_writable(DT_ROOT.'/'.$this->savepath)) return $this->_('Error(24)'.$L['upload_unwritable']); if(!is_uploaded_file($this->file)) return $this->_('Error(25)'.$L['upload_failed']); if(!move_uploaded_file($this->file, DT_ROOT.'/'.$this->saveto)) return $this->_('Error(26)'.$L['upload_failed']); $this->image = $this->is_image(); if(DT_CHMOD) @chmod(DT_ROOT.'/'.$this->saveto, DT_CHMOD); return true; }
|
用了is_allow
对文件进行检测
1 2 3 4 5 6 7 8 9 10 11
| function is_allow() { if(!$this->fileformat) return false; if(!preg_match("/^(".$this->fileformat.")$/i", $this->ext)) return false; if(preg_match("/^(php|phtml|php3|php4|jsp|exe|dll|cer|shtml|shtm|asp|asa|aspx|asax|ashx|cgi|fcgi|pl)$/i", $this->ext)) return false; if($this->savename) { $ext = file_ext($this->savename); if(!preg_match("/^(".$this->fileformat.")$/i", $ext)) return false; if(preg_match("/^(php|phtml|php3|php4|jsp|exe|dll|cer|shtml|shtm|asp|asa|aspx|asax|ashx|cgi|fcgi|pl)$/i", $ext)) return false; } return true; }
|
在第四行,对文件后缀名进行了检测。而此时的$this->ext
为jpg
所以通过检测
接着保存文件
1 2
| $this->set_savepath($this->savepath); $this->set_savename($this->savename);
|
此时的savepath
、savename
、saveto
均以php为后缀,而$this->file
实际指的是第二个jpg文件