DotClear
文件上传漏洞
影响版本:<=2.11.2
官网链接: http://dotclear.org
漏洞分析文件上传出现在 /admin/media.php 第250行
if ($dir && !empty($_FILES['upfile'])) { // only one file per request : @see option singleFileUploads in admin/js/jsUpload/jquery.fileupload $upfile = array('name' => $_FILES['upfile']['name'][0], 'type' => $_FILES['upfile']['type'][0], 'tmp_name' => $_FILES['upfile']['tmp_name'][0], 'error' => $_FILES['upfile']['error'][0], 'size' => $_FILES['upfile']['size'][0] ); if (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])) { header('Content-type: application/json'); $message = array(); try { files::uploadStatus($upfile); $new_file_id = $core->media->uploadFile($upfile['tmp_name'], $upfile['name']); $message['files'][] = array( 'name' => $upfile['name'], 'size' => $upfile['size'], 'html' => mediaItemLine($core->media->getFile($new_file_id),1,$query) ); } catch (Exception $e) { $message['files'][] = array('name' => $upfile['name'], 'size' => $upfile['size'], 'error' => $e->getMessage() ); } echo json_encode($message); exit(); } else { try { files::uploadStatus($upfile); $f_title = (isset($_POST['upfiletitle']) ? html::escapeHTML($_POST['upfiletitle']) : ''); $f_private = (isset($_POST['upfilepriv']) ? $_POST['upfilepriv'] : false); $core->media->uploadFile($upfile['tmp_name'],$upfile['name'],$f_title,$f_private); dcPage::addSuccessNotice(__('Files have been successfully uploaded.')); $core->adminurl->redirect('admin.media',$page_url_params); } catch (Exception $e) { $core->error->add($e->getMessage()); } } }然后,我们跟踪到第265行
$new_file_id = $core->media->uploadFile($upfile['tmp_name'], $upfile['name']);在 /inc/core/class.dc.media.php 中定义了函数 uploadFile
public function uploadFile($tmp,$name,$title=null,$private=false,$overwrite=false) { if (!$this->core->auth->check('media,media_admin',$this->core->blog->id)) { throw new Exception(__('Permission denied.')); } $name = files::tidyFileName($name); parent::uploadFile($tmp,$name,$overwrite); return $this->createFile($name,$title,$private); }最后调用了 /inc/libs/clearbricks/filemanager/class.filemanager.php 中的 uploadFile 函数
public function uploadFile($tmp,$dest,$overwrite=false) { $dest = $this->pwd.'/'.path::clean($dest); if ($this->isFileExclude($dest)) { throw new Exception(__('Uploading this file is not allowed.')); } if (!$this->inJail(dirname($dest))) { throw new Exception(__('Destination directory is not in jail.')); } if (!$overwrite && file_exists($dest)) { throw new Exception(__('File already exists.')); } if (!is_writable(dirname($dest))) { throw new Exception(__('Cannot write in this directory.')); } if (@move_uploaded_file($tmp,$dest) === false) { throw new Exception(__('An error occurred while writing the file.')); } files::inheritChmod($dest); return path::real($dest); }在304行用 isFileExclude 函数判断了文件的合法性
protected function isFileExclude($f) { if (!$this->exclude_pattern) { return false; } return preg_match($this->exclude_pattern,$f); }回到 /inc/core/class.dc.media.php ,第94行定义了正则表达式:
$this->exclude_pattern = $core->blog->settings->system->media_exclusion;它其实是从数据库里面取出来的正则表达式,在安装CMS的时候就已经将配置文件写进了数据库;配置在 /inc/core/class.dc.core.php 第1405行
'/\.(phps?|pht(ml)?|phl|s?html?|js|htaccess)[0-9]*$/i'好了,到此为止差不多已经理清楚了这一套流程,问题就出在这个正则表达式上面。
在 windows 系统下面,文件名末尾的 点 和 空格 会在创建或者重命名的时候自动删除,也就是说,我们只要创建 evil.php.... 之类的文件,就可以绕过此正则表达式。
此漏洞的重点不在于上传,而是正则表达式出现了问题,其它文件重命名的地方也会有此类问题出现。