From f1f8c4e98d3ab06f9154e66ee31a222114d122b5 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 18 Aug 2016 10:03:53 +0200 Subject: [PATCH] * Filemanager/WebDAV: generally deny user to delete directories /, /home, /apps, /templates (last 2 incl. subdirectories) --- api/src/Vfs.php | 27 +++++++++++++++++--- api/src/Vfs/Exception/ProtectedDirectory.php | 21 +++++++++++++++ api/src/Vfs/StreamWrapper.php | 10 ++++++++ api/src/Vfs/WebDAV.php | 13 +++++++--- filemanager/inc/class.filemanager_ui.inc.php | 2 +- 5 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 api/src/Vfs/Exception/ProtectedDirectory.php diff --git a/api/src/Vfs.php b/api/src/Vfs.php index 174da52920..28ddad8015 100644 --- a/api/src/Vfs.php +++ b/api/src/Vfs.php @@ -723,23 +723,42 @@ class Vfs return $value > (int) substr($argument,1); } + /** + * Check if given directory is protected (user not allowed to remove or rename) + * + * Following directorys are protected: + * - / + * - /apps incl. subdirectories + * - /home + * - /templates incl. subdirectories + * + * @param string $dir path or url + * @return boolean true for protected dirs, false otherwise + */ + static function isProtectedDir($dir) + { + if ($dir[0] != '/') $dir = self::parse_url($dir, PHP_URL_PATH); + + return preg_match('#^/(apps(/[^/]+)?|home|templates(/[^/]+)?)?/*$#', $dir) > 0; + } + /** * Recursiv remove all given url's, including it's content if they are files * * @param string|array $urls url or array of url's * @param boolean $allow_urls =false allow to use url's, default no only pathes (to stay within the vfs) - * @throws Exception\AssertionFailed when trainig to remove /, /apps or /home + * @throws Vfs\Exception\ProtectedDirectory if trying to delete a protected directory, see Vfs::isProtected() * @return array */ static function remove($urls,$allow_urls=false) { //error_log(__METHOD__.'('.array2string($urls).')'); - // some precaution to never allow to (recursivly) remove /, /apps or /home foreach((array)$urls as $url) { - if (preg_match('/^\/?(home|apps|)\/*$/',self::parse_url($url,PHP_URL_PATH))) + // some precaution to never allow to (recursivly) remove /, /apps or /home, see Vfs::isProtected() + if (self::isProtectedDir($url)) { - throw new Exception\AssertionFailed(__METHOD__.'('.array2string($urls).") Cautiously rejecting to remove folder '$url'!"); + throw new Vfs\Exception\ProtectedDirectory("Deleting protected directory '$url' rejected!"); } } return self::find($urls, array('depth'=>true,'url'=>$allow_urls,'hidden'=>true), __CLASS__.'::_rm_rmdir'); diff --git a/api/src/Vfs/Exception/ProtectedDirectory.php b/api/src/Vfs/Exception/ProtectedDirectory.php new file mode 100644 index 0000000000..dcb30b9625 --- /dev/null +++ b/api/src/Vfs/Exception/ProtectedDirectory.php @@ -0,0 +1,21 @@ + + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package vfs + * @subpackage exception + * @access public + * @version $Id$ + */ + +namespace EGroupware\Api\Vfs\Exception; + +/** + * User or code tried to delete or rename a protected directory, see Vfs::isProtectedDir + * + * This exception extends \Exception to not catch it accidently. + */ +class ProtectedDirectory extends \Exception { } diff --git a/api/src/Vfs/StreamWrapper.php b/api/src/Vfs/StreamWrapper.php index 2be32aa188..1316b13310 100644 --- a/api/src/Vfs/StreamWrapper.php +++ b/api/src/Vfs/StreamWrapper.php @@ -571,9 +571,14 @@ class StreamWrapper implements StreamWrapperIface * @param string $path_from * @param string $path_to * @return boolean TRUE on success or FALSE on failure + * @throws Exception\ProtectedDirectory if trying to delete a protected directory, see Vfs::isProtected() */ function rename ( $path_from, $path_to ) { + if (Vfs::isProtectedDir($path_from)) + { + throw new Exception\ProtectedDirectory("Renaming protected directory '$path_from' rejected!"); + } if (!($url_from = $this->resolve_url_symlinks($path_from,true,false)) || !($url_to = $this->resolve_url_symlinks($path_to,false))) { @@ -665,9 +670,14 @@ class StreamWrapper implements StreamWrapperIface * @param string $path * @param int $options Possible values include STREAM_REPORT_ERRORS. * @return boolean TRUE on success or FALSE on failure. + * @throws Exception\ProtectedDirectory if trying to delete a protected directory, see Vfs::isProtected() */ function rmdir ( $path, $options ) { + if (Vfs::isProtectedDir($path)) + { + throw new Exception\ProtectedDirectory("Deleting protected directory '$path' rejected!"); + } unset($options); // not uses but required by function signature if (!($url = $this->resolve_url_symlinks($path))) { diff --git a/api/src/Vfs/WebDAV.php b/api/src/Vfs/WebDAV.php index bd5ea6121a..34e7630683 100644 --- a/api/src/Vfs/WebDAV.php +++ b/api/src/Vfs/WebDAV.php @@ -95,7 +95,7 @@ class WebDAV extends HTTP_WebDAV_Server_Filesystem $ret = !empty($deleted[$options['path']]); //error_log(__METHOD__."() Vfs::remove($options[path]) returned ".array2string($deleted)." --> ".array2string($ret)); } - catch (\Exception $e) { + catch (Exception\ProtectedDirectory $e) { return '403 Forbidden: '.$e->getMessage(); } } @@ -221,9 +221,14 @@ class WebDAV extends HTTP_WebDAV_Server_Filesystem } if ($del) { - if (!rename($source, $dest)) { - return "500 Internal server error"; - } + try { + if (!rename($source, $dest)) { + return "500 Internal server error"; + } + } + catch (Exception\ProtectedDirectory $e) { + return "403 Forbidden: ".$e->getMessage(); + } } else { if (is_dir($source) && $options['depth'] == 'infinity') { $files = Vfs::find($source,array('depth' => true,'url' => true)); // depth=true: return dirs first, url=true: allow urls! diff --git a/filemanager/inc/class.filemanager_ui.inc.php b/filemanager/inc/class.filemanager_ui.inc.php index 7befe41402..9f436514e2 100644 --- a/filemanager/inc/class.filemanager_ui.inc.php +++ b/filemanager/inc/class.filemanager_ui.inc.php @@ -751,7 +751,7 @@ class filemanager_ui // some precaution to never allow to (recursivly) remove /, /apps or /home foreach((array)$selected as $path) { - if (preg_match('/^\/?(home|apps|)\/*$/',$path)) + if (Vfs::isProtectedDir($path)) { $errs++; return lang("Cautiously rejecting to remove folder '%1'!",Vfs::decodePath($path));