From f2624d10de513620337eb394386c0df9514c8cb0 Mon Sep 17 00:00:00 2001 From: loiclau Date: Fri, 10 Nov 2017 16:49:07 +0100 Subject: [PATCH] fix(security) Secure upload images (#5863) * add function uploadImg * secure upload image * rename uploader to manager * secure update * fix style * move image * clean dead code * fix upload * fix error msg * fix license * update license --- www/class/centreonFileManager.php | 216 ++++++++++++++++++ www/class/centreonImageManager.php | 226 +++++++++++++++++++ www/class/iFileManager.php | 48 ++++ www/include/options/media/images/DB-Func.php | 1 - www/include/options/media/images/formImg.php | 36 +-- 5 files changed, 510 insertions(+), 17 deletions(-) create mode 100644 www/class/centreonFileManager.php create mode 100644 www/class/centreonImageManager.php create mode 100644 www/class/iFileManager.php diff --git a/www/class/centreonFileManager.php b/www/class/centreonFileManager.php new file mode 100644 index 00000000000..3ba20f754f0 --- /dev/null +++ b/www/class/centreonFileManager.php @@ -0,0 +1,216 @@ +. + * + * Linking this program statically or dynamically with other modules is making a + * combined work based on this program. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this program give Centreon + * permission to link this program with independent modules to produce an executable, + * regardless of the license terms of these independent modules, and to copy and + * distribute the resulting executable under terms of Centreon choice, provided that + * Centreon also meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module which is not + * derived from this program. If you modify this program, you may extend this + * exception to your version of the program, but you are not obliged to do so. If you + * do not wish to do so, delete this exception statement from your version. + * + * For more information : contact@centreon.com + * + */ +/** + * Created by PhpStorm. + * User: loic + * Date: 31/10/17 + * Time: 11:55 + */ + +class CentreonFileManager implements iFileManager +{ + + protected $rawFile; + protected $comment; + protected $tmpFile; + protected $mediaPath; + protected $destinationPath; + protected $destinationDir; + protected $originalFile; + protected $fileName; + protected $size; + protected $extension; + protected $newFile; + protected $completePath; + protected $legalExtensions; + protected $legalSize; + + /** + * centreonFileManager constructor. + * @param \Pimple\Container $dependencyInjector + * @param $rawFile + * @param $mediaPath + * @param $destinationDir + * @param string $comment + */ + public function __construct( + $rawFile, + $mediaPath, + $destinationDir, + $comment = '' + ) { + $this->mediaPath = $mediaPath; + $this->comment = $comment; + $this->rawFile = $rawFile["filename"]; + $this->destinationDir = $this->secureName($destinationDir); + $this->destinationPath = $this->mediaPath . $this->destinationDir; + $this->dirExist($this->destinationPath); + $this->originalFile = $this->rawFile['name']; + $this->tmpFile = $this->rawFile['tmp_name']; + $this->size = $this->rawFile['size']; + $this->extension = pathinfo($this->originalFile, PATHINFO_EXTENSION); + $this->fileName = $this->secureName(basename($this->originalFile, '.' . $this->extension)); + $this->newFile = $this->fileName . '.' . $this->extension; + $this->completePath = $this->destinationPath . '/' . $this->newFile; + $this->legalExtensions = array(); + $this->legalSize = 500000; + } + + /** + * @return mixed + */ + public function upload() + { + if ($this->securityCheck()) { + $this->moveFile(); + return true; + } else { + return false; + }; + } + + /** + * @return bool + */ + protected function securityCheck() + { + if (!$this->validFile() || + !$this->validSize() || + !$this->secureExtension() || + $this->fileExist() + ) { + return false; + } else { + return true; + } + } + + /** + * @param $text + * @return mixed + */ + protected function secureName($text) + { + $utf8 = array( + '/[áàâãªä]/u' => 'a', + '/[ÁÀÂÃÄ]/u' => 'A', + '/[ÍÌÎÏ]/u' => 'I', + '/[íìîï]/u' => 'i', + '/[éèêë]/u' => 'e', + '/[ÉÈÊË]/u' => 'E', + '/[óòôõºö]/u' => 'o', + '/[ÓÒÔÕÖ]/u' => 'O', + '/[úùûü]/u' => 'u', + '/[ÚÙÛÜ]/u' => 'U', + '/ç/' => 'c', + '/Ç/' => 'C', + '/ñ/' => 'n', + '/Ñ/' => 'N', + '/–/' => '-', + '/[“”«»„"’‘‹›‚]/u' => '', + '/ /' => '', + '/\//' => '', + '/\'/' => '', + ); + return preg_replace(array_keys($utf8), array_values($utf8), $text); + } + + /** + * @return bool + */ + protected function secureExtension() + { + + if (in_array(strtolower($this->extension), $this->legalExtensions)) { + return true; + } else { + return false; + } + } + + /** + * @return bool + */ + protected function validFile() + { + if (empty($this->tmpFile) || $this->size == 0) { + return false; + } else { + return true; + } + } + + /** + * @return bool + */ + protected function validSize() + { + if ($this->size < $this->legalSize) { + return true; + } else { + return false; + } + } + + /** + * @return bool + */ + protected function fileExist() + { + if (file_exists($this->completePath)) { + return true; + } else { + return false; + } + } + + /** + * @param $dir + */ + protected function dirExist($dir) + { + if (!is_dir($dir)) { + @mkdir($dir); + } + } + + /** + * @return mixed + */ + protected function moveFile() + { + move_uploaded_file($this->tmpFile, $this->completePath); + } +} diff --git a/www/class/centreonImageManager.php b/www/class/centreonImageManager.php new file mode 100644 index 00000000000..16e3361c73a --- /dev/null +++ b/www/class/centreonImageManager.php @@ -0,0 +1,226 @@ +. + * + * Linking this program statically or dynamically with other modules is making a + * combined work based on this program. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this program give Centreon + * permission to link this program with independent modules to produce an executable, + * regardless of the license terms of these independent modules, and to copy and + * distribute the resulting executable under terms of Centreon choice, provided that + * Centreon also meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module which is not + * derived from this program. If you modify this program, you may extend this + * exception to your version of the program, but you are not obliged to do so. If you + * do not wish to do so, delete this exception statement from your version. + * + * For more information : contact@centreon.com + * + */ +/** + * Created by PhpStorm. + * User: loic + * Date: 31/10/17 + * Time: 11:55 + */ + +class CentreonImageManager extends centreonFileManager +{ + + protected $legalExtensions; + protected $legalSize; + + /** + * centreonImageUploader constructor. + * @param $rawFile + * @param $basePath + * @param string $destinationDir + * @param string $comment + */ + public function __construct($rawFile, $basePath, $destinationDir, $comment = '') + { + parent::__construct($rawFile, $basePath, $destinationDir, $comment); + $this->legalExtensions = array("jpg", "jpeg", "png", "gif"); + $this->legalSize = 2000000; + } + + /** + * @param bool $insert + * @return array + */ + public function upload($insert = true) + { + $parentUpload = parent::upload(); + if ($parentUpload) { + if ($insert) { + $img_ids[] = $this->insertImg( + $this->destinationPath, + $this->destinationDir, + $this->newFile, + $this->comment + ); + return $img_ids; + } + } else { + return false; + } + } + + /** + * @param $imgId + * @param $imgName + * @return bool + */ + public function update($imgId, $imgName) + { + if (!$imgId || empty($imgName)) { + return false; + } + + global $pearDB; + $query = "SELECT dir_id, dir_alias, img_path, img_comment FROM view_img, view_img_dir, view_img_dir_relation " . + "WHERE img_id = '" . $imgId . "' AND img_id = img_img_id AND dir_dir_parent_id = dir_id"; + $dbResult = $pearDB->query($query); + + if (!$dbResult) { + return false; + } + $img_info = $dbResult->fetchRow(); + + // update if new file + if (!empty($this->originalFile) && !empty($this->tmpFile)) { + $this->deleteImg($this->mediaPath . $img_info["dir_alias"] . '/' . $img_info["img_path"]); + $this->upload(false); + $query = "UPDATE view_img SET img_path = '" . $this->newFile . "' WHERE img_id = '" . $imgId . "'"; + $pearDB->query($query); + } + + // update image info + $query = "UPDATE view_img SET img_name = '" . $this->secureName($imgName) . + "', img_comment = '" . $this->comment . "' WHERE img_id = '" . $imgId . "'"; + $pearDB->query($query); + + //check directory + if (!($dirId = $this->checkDirectoryExistence())) { + $dirId = $this->insertDirectory(); + } elseif ($img_info['dir_alias'] != $this->destinationDir) { + $old = $this->mediaPath . $img_info['dir_alias'] . '/' . $img_info["img_path"]; + $new = $this->mediaPath . $this->destinationDir . '/' . $img_info["img_path"]; + $this->moveImage($old, $new); + } + + //update relation + $query = "UPDATE view_img_dir_relation SET dir_dir_parent_id = '" . $dirId . + "' WHERE img_img_id = '" . $imgId . "'"; + $pearDB->query($query); + + return true; + } + + /** + * @param $fullPath + */ + protected function deleteImg($fullPath) + { + unlink($fullPath); + } + + /** + * @return int + */ + protected function checkDirectoryExistence() + { + global $pearDB; + $dirId = 0; + $dbResult = $pearDB->query( + "SELECT dir_name, dir_id FROM view_img_dir WHERE dir_name = '" . $this->destinationDir . "'" + ); + if ($dbResult->numRows() >= 1) { + $dir = $dbResult->fetchRow(); + $dirId = $dir["dir_id"]; + } + return $dirId; + } + + /** + * @return mixed + */ + protected function insertDirectory() + { + global $pearDB; + touch($this->destinationPath . "/index.html"); + $query = "INSERT INTO view_img_dir " . + "(dir_name, dir_alias) " . + "VALUES " . + "('" . $this->destinationDir . "', '" . $this->destinationDir . "')"; + $pearDB->query($query); + $dbResult = $pearDB->query("SELECT MAX(dir_id) FROM view_img_dir"); + $dirId = $dbResult->fetchRow(); + $dbResult->free(); + return ($dirId["MAX(dir_id)"]); + } + + /** + * @param $dirId + */ + protected function updateDirectory($dirId) + { + global $pearDB; + $query = "UPDATE view_img_dir SET dir_name = '" . $this->destinationDir . + "', dir_alias = '" . $this->destinationDir . "' WHERE dir_id = " . $dirId; + $pearDB->query($query); + } + + /** + * @return mixed + */ + protected function insertImg() + { + global $pearDB; + if (!($dirId = $this->checkDirectoryExistence())) { + $dirId = $this->insertDirectory(); + } + $query = "INSERT INTO view_img " . + "(img_name, img_path, img_comment) " . + "VALUES " . + "('" . $this->fileName . "', '" . $this->newFile . "', '" . $pearDB->escape($this->comment) . "')"; + $pearDB->query($query); + + $res = $pearDB->query("SELECT MAX(img_id) FROM view_img"); + $imgId = $res->fetchRow(); + $imgId = $imgId["MAX(img_id)"]; + + $pearDB->query( + "INSERT INTO view_img_dir_relation (dir_dir_parent_id, img_img_id) " . + "VALUES ('" . $dirId . "', '" . $imgId . "')" + ); + $res->free(); + return ($imgId); + } + + /** + * @param $old + * @param $new + */ + protected function moveImage($old, $new) + { + copy($old, $new); + $this->deleteImg($old); + } + +} diff --git a/www/class/iFileManager.php b/www/class/iFileManager.php new file mode 100644 index 00000000000..ce11992677b --- /dev/null +++ b/www/class/iFileManager.php @@ -0,0 +1,48 @@ +. + * + * Linking this program statically or dynamically with other modules is making a + * combined work based on this program. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this program give Centreon + * permission to link this program with independent modules to produce an executable, + * regardless of the license terms of these independent modules, and to copy and + * distribute the resulting executable under terms of Centreon choice, provided that + * Centreon also meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module which is not + * derived from this program. If you modify this program, you may extend this + * exception to your version of the program, but you are not obliged to do so. If you + * do not wish to do so, delete this exception statement from your version. + * + * For more information : contact@centreon.com + * + */ +/** + * Created by PhpStorm. + * User: loic + * Date: 31/10/17 + * Time: 11:53 + */ + +interface iFileManager +{ + /** + * @return mixed + */ + public function upload(); +} diff --git a/www/include/options/media/images/DB-Func.php b/www/include/options/media/images/DB-Func.php index 7837d95a338..82ad0be9fd2 100644 --- a/www/include/options/media/images/DB-Func.php +++ b/www/include/options/media/images/DB-Func.php @@ -107,7 +107,6 @@ function is_gd2($filename) return false; } - function handleUpload($HTMLfile, $dir_alias, $img_comment = "") { if (!$HTMLfile || !$dir_alias) { diff --git a/www/include/options/media/images/formImg.php b/www/include/options/media/images/formImg.php index b53cf3965a3..287a56c4f87 100644 --- a/www/include/options/media/images/formImg.php +++ b/www/include/options/media/images/formImg.php @@ -36,6 +36,9 @@ * */ +require_once _CENTREON_PATH_ . "www/class/centreonImageManager.php"; + + if (!isset($centreon)) { exit(); } @@ -43,20 +46,20 @@ /* * Database retrieve information */ -$img = array("img_path"=>null); +$img = array("img_path" => null); if ($o == "ci" || $o == "w") { - $res = $pearDB->query("SELECT * FROM view_img WHERE img_id = '".$img_id."' LIMIT 1"); + $res = $pearDB->query("SELECT * FROM view_img WHERE img_id = '" . $img_id . "' LIMIT 1"); # Set base value $img = array_map("myDecode", $res->fetchRow()); # Set Directories - $q = "SELECT dir_id, dir_name, dir_alias, img_path FROM view_img"; + $q = "SELECT dir_id, dir_name, dir_alias, img_path FROM view_img"; $q .= " JOIN view_img_dir_relation ON img_id = view_img_dir_relation.img_img_id"; $q .= " JOIN view_img_dir ON dir_id = dir_dir_parent_id"; - $q .= " WHERE img_id = '".$img_id."' LIMIT 1"; + $q .= " WHERE img_id = '" . $img_id . "' LIMIT 1"; $DBRESULT = $pearDB->query($q); $dir = $DBRESULT->fetchRow(); - $img_path = "./img/media/".$dir["dir_alias"]."/".$dir["img_path"]; + $img_path = "./img/media/" . $dir["dir_alias"] . "/" . $dir["img_path"]; $img["directories"] = $dir["dir_name"]; $DBRESULT->free(); } @@ -73,14 +76,14 @@ /* * Styles */ -$attrsText = array("size"=>"35"); +$attrsText = array("size" => "35"); $attrsAdvSelect = array("style" => "width: 200px; height: 100px;"); -$attrsTextarea = array("rows"=>"5", "cols"=>"80"); +$attrsTextarea = array("rows" => "5", "cols" => "80"); /* * Form begin */ -$form = new HTML_QuickForm('Form', 'post', "?p=".$p); +$form = new HTML_QuickForm('Form', 'post', "?p=" . $p); if ($o == "a") { $form->addElement('header', 'title', _("Add Image(s)")); $form->addElement( @@ -97,7 +100,7 @@ $dir_list_sel, array('onchange' => 'document.getElementById("directories").value = this.options[this.selectedIndex].text;') ); - $file = $form->addElement('file', 'filename', _("Image or archive")); + $form->addElement('file', 'filename', _("Image or archive")); $subA = $form->addElement('submit', 'submitA', _("Save"), array("class" => "btc bt_success")); } elseif ($o == "ci") { $form->addElement('header', 'title', _("Modify Image")); @@ -117,7 +120,7 @@ array('onchange' => 'document.getElementById("directories").value = this.options[this.selectedIndex].text;') ); $list_dir->setSelected($dir['dir_id']); - $file = $form->addElement('file', 'filename', _("Image")); + $form->addElement('file', 'filename', _("Image")); $subC = $form->addElement('submit', 'submitC', _("Save"), array("class" => "btc bt_success")); $form->setDefaults($img); $form->addRule('img_name', _("Compulsory image name"), 'required'); @@ -126,7 +129,7 @@ $form->addElement('text', 'img_name', _("Image Name"), $attrsText); $form->addElement('text', 'img_path', $img_path, null); $form->addElement('autocomplete', 'directories', _("Directory"), $dir_ids, array('id', 'directories')); - $file = $form->addElement('file', 'filename', _("Image")); + $form->addElement('file', 'filename', _("Image")); $form->addElement( "button", "change", @@ -154,7 +157,7 @@ $tab[] = HTML_QuickForm::createElement('radio', 'action', null, _("Return to list"), '1'); $tab[] = HTML_QuickForm::createElement('radio', 'action', null, _("Review form after save"), '0'); $form->addGroup($tab, 'action', _("Action"), ' '); -$form->setDefaults(array('action'=>'1')); +$form->setDefaults(array('action' => '1')); $form->addElement('hidden', 'img_id'); $redirect = $form->addElement('hidden', 'o'); @@ -194,20 +197,21 @@ $helptext = ""; include_once("help.php"); foreach ($help as $key => $text) { - $helptext .= ''."\n"; + $helptext .= '' . "\n"; } $tpl->assign("helptext", $helptext); $valid = false; if ($form->validate()) { - $imgID = $form->getElement('img_id'); + $imgId = $form->getElement('img_id')->getValue(); $imgPath = $form->getElement('directories')->getValue(); $imgComment = $form->getElement('img_comment')->getValue(); + $oImageUploader = new CentreonImageManager($_FILES, './img/media/', $imgPath, $imgComment); if ($form->getSubmitValue("submitA")) { - $valid = handleUpload($file, $imgPath, $imgComment); + $valid = $oImageUploader->upload(); } elseif ($form->getSubmitValue("submitC")) { $imgName = $form->getElement('img_name')->getValue(); - $valid = updateImg($imgID->getValue(), $file, $imgPath, $imgName, $imgComment); + $valid = $oImageUploader->update($imgId, $imgName); } $form->freeze(); if (false === $valid) {