From 5d25f768ded3a4c38072085d3a6a17d36c44ac0d Mon Sep 17 00:00:00 2001 From: Artur Kyryliuk Date: Mon, 19 Jan 2026 12:49:55 +0100 Subject: [PATCH 1/2] [FIX] vulnerability in evil zip file unpacking and Windows path support --- .gitignore | 3 + core/functions/actions/files.php | 213 +++++++++----- install/src/controllers/mode.php | 8 +- manager/actions/files.dynamic.php | 453 ++++++++++++++++-------------- 4 files changed, 392 insertions(+), 285 deletions(-) diff --git a/.gitignore b/.gitignore index dbb8db2017..f1e8cf5613 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,9 @@ ftpsync.settings Thumbs.db Desktop.ini +# SQLite database +/core/database/*.sqlite + # all dotfiles and dotfolders except do not ignore .gitignore **/.* !.gitignore diff --git a/core/functions/actions/files.php b/core/functions/actions/files.php index e2fb6d2b20..b4673fec47 100644 --- a/core/functions/actions/files.php +++ b/core/functions/actions/files.php @@ -73,6 +73,10 @@ function ls($curpath, array $options = []) { extract($options, EXTR_OVERWRITE); + $curpath = rtrim(str_replace('\\', '/', $curpath), '/') . '/'; + $filemanager_path = rtrim(str_replace('\\', '/', $filemanager_path), '/'); + $base_path = rtrim(str_replace('\\', '/', $base_path), '/'); + $_lang = ManagerTheme::getLexicon(); $_style = ManagerTheme::getStyle(); $dircounter = 0; @@ -80,10 +84,9 @@ function ls($curpath, array $options = []) $filesizes = 0; $dirs_array = []; $files_array = []; - $curpath = str_replace('//', '/', $curpath . '/'); if (!is_dir($curpath)) { - echo 'Invalid path "', $curpath, '"
'; + echo 'Invalid path "', htmlspecialchars($curpath, ENT_QUOTES, 'UTF-8'), '"
'; return; } @@ -95,13 +98,17 @@ function ls($curpath, array $options = []) if ($file === '..' || $file === '.') { continue; } + $rel_newpath = ltrim(substr($newpath, strlen($filemanager_path)), '/'); + $rel_web = ltrim(substr($newpath, strlen($base_path)), '/'); if (is_dir($newpath)) { $dirs_array[$dircounter]['dir'] = $newpath; $dirs_array[$dircounter]['stats'] = lstat($newpath); if ($file === '..' || $file === '.') { continue; } elseif (!in_array($file, $excludes) && !in_array($newpath, $protected_path)) { - $dirs_array[$dircounter]['text'] = ' ' . $file . ''; + $dirs_array[$dircounter]['text'] = ' ' + . '' + . htmlspecialchars($file, ENT_QUOTES, 'UTF-8') . ''; $dfiles = scandir($newpath); foreach ($dfiles as $i => $infile) { @@ -114,38 +121,70 @@ function ls($curpath, array $options = []) } $file_exists = (0 < count($dfiles)) ? 'file_exists' : ''; - $dirs_array[$dircounter]['delete'] = is_writable($curpath) ? '' : ''; + $dirs_array[$dircounter]['delete'] = is_writable($curpath) ? '' : ''; } else { - $dirs_array[$dircounter]['text'] = ' ' . $file . ''; - $dirs_array[$dircounter]['delete'] = is_writable($curpath) ? '' : ''; + $dirs_array[$dircounter]['text'] = ' ' . htmlspecialchars($file, ENT_QUOTES, 'UTF-8') + . ''; + $dirs_array[$dircounter]['delete'] = is_writable($curpath) ? '' : ''; } - $dirs_array[$dircounter]['rename'] = is_writable($curpath) ? ' ' : ''; + $dirs_array[$dircounter]['rename'] = is_writable($curpath) ? ' ' : ''; // increment the counter $dircounter++; } else { $type = getExtension($newpath); - $files_array[$filecounter]['file'] = $newpath; + $files_array[$filecounter]['file'] = $rel_newpath; $files_array[$filecounter]['stats'] = lstat($newpath); - $files_array[$filecounter]['text'] = determineIcon($newpath, get_by_key($_REQUEST, 'path', ''), get_by_key($_REQUEST, 'mode', '')) . ' ' . $file; - $files_array[$filecounter]['view'] = (in_array($type, - $viewablefiles)) ? '' : (($enablefiledownload && in_array($type, - $uploadablefiles)) ? '' : ''); - $files_array[$filecounter]['view'] = (in_array($type, - $inlineviewablefiles)) ? '' : $files_array[$filecounter]['view']; - $files_array[$filecounter]['unzip'] = ($enablefileunzip && $type == '.zip') ? '' : ''; + $files_array[$filecounter]['text'] = determineIcon($rel_newpath, get_by_key($_REQUEST, 'path', ''), + get_by_key($_REQUEST, 'mode', '')) . ' ' . htmlspecialchars($file, ENT_QUOTES, + 'UTF-8'); + $files_array[$filecounter]['view'] = in_array($type, $viewablefiles) + ? '' + : (($enablefiledownload && in_array($type, $uploadablefiles)) + ? '' : ''); + $files_array[$filecounter]['view'] = (in_array($type, $inlineviewablefiles)) + ? '' + : $files_array[$filecounter]['view']; + $files_array[$filecounter]['unzip'] = ($enablefileunzip && $type == '.zip') + ? '' + : ''; $files_array[$filecounter]['edit'] = (in_array($type, - $editablefiles) && is_writable($curpath) && is_writable($newpath)) ? '' : ''; - $files_array[$filecounter]['duplicate'] = (in_array($type, - $editablefiles) && is_writable($curpath) && is_writable($newpath)) ? '' : ''; - $files_array[$filecounter]['rename'] = (in_array($type, - $editablefiles) && is_writable($curpath) && is_writable($newpath)) ? '' : ''; - $files_array[$filecounter]['delete'] = is_writable($curpath) && is_writable($newpath) ? '' : ''; + $editablefiles) && is_writable($curpath) && is_writable($newpath)) + ? '' + : ''; + $files_array[$filecounter]['duplicate'] = (in_array($type, $editablefiles) && is_writable($curpath) + && is_writable($newpath)) + ? '' + : ''; + $files_array[$filecounter]['rename'] = (in_array($type, $editablefiles) && is_writable($curpath) + && is_writable($newpath)) + ? '' + : ''; + $files_array[$filecounter]['delete'] = is_writable($curpath) && is_writable($newpath) + ? '' + : ''; // increment the counter $filecounter++; @@ -333,37 +372,43 @@ function unzip($file, $path) return 0; } // end mod - $zip = zip_open($file); - if ($zip) { - $old_umask = umask(0); - $path = rtrim($path, '/') . '/'; - while ($zip_entry = zip_read($zip)) { - if (zip_entry_filesize($zip_entry) > 0) { - // str_replace must be used under windows to convert "/" into "\" - $zip_entry_name = zip_entry_name($zip_entry); - $complete_path = $path . str_replace('\\', '/', dirname($zip_entry_name)); - $complete_name = $path . str_replace('\\', '/', $zip_entry_name); - if (!file_exists($complete_path)) { - $tmp = ''; - foreach (explode('/', $complete_path) AS $k) { - $tmp .= $k . '/'; - if (!is_dir($tmp)) { - mkdir($tmp, 0777); - } - } - } - if (zip_entry_open($zip, $zip_entry, 'r')) { - file_put_contents($complete_name, zip_entry_read($zip_entry, zip_entry_filesize($zip_entry))); - zip_entry_close($zip_entry); - } + + $old_umask = umask(0); + $path = rtrim(str_replace('\\', '/', realpath($path)), '/\\'); // No trailing slash + + $zip = new ZipArchive(); + if ($zip->open($file) !== true) { + umask($old_umask); + return false; + } + for ($i = 0; $i < $zip->numFiles; $i++) { + $stat = $zip->statIndex($i); + $filename = str_replace('\\', '/', $stat['name']); + if (substr($filename, 0, 1) == '/' || strpos($filename, '..') !== false || + strpos($filename, ':') !== false) { + continue; // skip malicious paths + } + $target = $path . '/' . $filename; + // Additional check to ensure target is within path + $target_dir = rtrim(str_replace('\\', '/', realpath(dirname($target)) ?: dirname($target)), '/\\'); + if (strpos($target_dir, $path) !== 0) { + continue; + } + if (substr($filename, -1) == '/') { + if (!is_dir($target)) { + mkdir($target, $newfolderaccessmode ?: 0777, true); } + } else { + $dirname = dirname($target); + if (!is_dir($dirname)) { + mkdir($dirname, $newfolderaccessmode ?: 0777, true); + } + file_put_contents($target, $zip->getFromIndex($i)); } - umask($old_umask); - zip_close($zip); - - return true; } - zip_close($zip); + $zip->close(); + umask($old_umask); + return true; } } @@ -374,6 +419,7 @@ function unzip($file, $path) */ function rrmdir($dir) { + $dir = str_replace('\\', '/', realpath($dir)); // Canonicalize path foreach (glob($dir . '/*') as $file) { if (is_dir($file)) { rrmdir($file); @@ -393,8 +439,14 @@ function rrmdir($dir) function fileupload() { $modx = evolutionCMS(); - $startpath = is_dir($_REQUEST['path']) ? $_REQUEST['path'] : removeLastPath($_REQUEST['path']); - $filemanager_path = evolutionCMS()->getConfig('filemanager_path', MODX_BASE_PATH); + $filemanager_path = rtrim(str_replace('\\', '/', realpath(evolutionCMS()->getConfig('filemanager_path', MODX_BASE_PATH))), '/'); // Canonicalize base path + $requested_path = ltrim($_REQUEST['path'] ?? '', '/'); + $startpath = str_replace('\\', '/', realpath($filemanager_path . '/' . $requested_path)); + $startpath = rtrim($startpath, '/'); + // Ensure startpath is within filemanager_path + if (strpos($startpath, $filemanager_path) !== 0 || !is_dir($startpath)) { + return '

Invalid path.

'; + } $new_file_permissions = octdec(evolutionCMS()->getConfig('new_file_permissions', '0666')); global $_lang, $uploadablefiles; $msg = ''; @@ -415,17 +467,21 @@ function fileupload() ], $nameparts, ['file_manager']); $name = implode('.', $nameparts); } + // Sanitize name to prevent traversal or invalid chars + $name = preg_replace('/[^\w\.-]/', '', $name); + $name = ltrim($name, '.'); $userfile['name'] = $name; $userfile['type'] = $_FILES['userfile']['type'][$i]; // this seems to be an upload action. - $path = MODX_SITE_URL . substr($startpath, strlen($filemanager_path), strlen($startpath)); - $path = rtrim($path, '/') . '/' . $userfile['name']; - $msg .= $path; + $rel_path = ltrim(substr($startpath, strlen($filemanager_path)), '/'); + $path = MODX_SITE_URL . ($rel_path ? $rel_path . '/' : '') . $userfile['name']; + $msg .= htmlspecialchars($path, ENT_QUOTES, 'UTF-8'); if ($userfile['error'] == 0) { - $img = (strpos($userfile['type'], - 'image') !== false) ? '
' : ''; - $msg .= "

" . $_lang['files_file_type'] . $userfile['type'] . ", " . niceSize(filesize($userfile['tmp_name'])) . $img . '

'; + $img = (strpos($userfile['type'],'image') !== false) ? '
' : ''; + $msg .= "

" . $_lang['files_file_type'] . htmlspecialchars($userfile['type'], ENT_QUOTES, + 'UTF-8') . ", " . niceSize(filesize($userfile['tmp_name'])) . $img . '

'; } $userfilename = $userfile['tmp_name']; @@ -435,23 +491,25 @@ function fileupload() if (!checkExtension($userfile['name'])) { $msg .= '

' . $_lang['files_filetype_notok'] . '

'; } else { - if (@move_uploaded_file($userfile['tmp_name'], $_POST['path'] . '/' . $userfile['name'])) { + $targetFile = $startpath . '/' . $userfile['name']; + if (@move_uploaded_file($userfile['tmp_name'], $targetFile)) { // Ryan: Repair broken permissions issue with file manager if (strtoupper(substr(PHP_OS, 0, 3)) != 'WIN') { - @chmod($_POST['path'] . "/" . $userfile['name'], $new_file_permissions); + @chmod($targetFile, $new_file_permissions); } // Ryan: End $msg .= '

' . $_lang['files_upload_ok'] . '


'; // invoke OnFileManagerUpload event $modx->invokeEvent('OnFileManagerUpload', [ - 'filepath' => $_POST['path'], + 'filepath' => $startpath, 'filename' => $userfile['name'] ]); // Log the change - logFileChange('upload', $_POST['path'] . '/' . $userfile['name']); + logFileChange('upload', $targetFile); } else { - $msg .= '

' . $_lang['files_upload_copyfailed'] . ' ' . $_lang["files_upload_permissions_error"] . '

'; + $msg .= '

' . $_lang['files_upload_copyfailed'] . ' ' + . $_lang["files_upload_permissions_error"] . '

'; } } } else { @@ -492,15 +550,19 @@ function textsave() { global $_lang; - $msg = $_lang['editing_file']; - $filename = $_POST['path']; + $filemanager_path = rtrim(str_replace('\\', '/', realpath(evolutionCMS()->getConfig('filemanager_path', MODX_BASE_PATH))), '/'); + $requested_path = ltrim($_POST['path'] ?? '', '/'); + $filename = str_replace('\\', '/', realpath($filemanager_path . '/' . $requested_path)); + if (strpos($filename, $filemanager_path) !== 0 || !is_file($filename)) { + return 'Invalid path.

'; + } $content = $_POST['content']; // Write $content to our opened file. if (file_put_contents($filename, $content) === false) { - $msg .= '' . $_lang['file_not_saved'] . '

'; + $msg = '' . $_lang['file_not_saved'] . '

'; } else { - $msg .= '' . $_lang['file_saved'] . '

'; + $msg = '' . $_lang['file_saved'] . '

'; $_REQUEST['mode'] = 'edit'; } // Log the change @@ -518,9 +580,14 @@ function delete_file() { global $_lang; - $msg = sprintf($_lang['deleting_file'], str_replace('\\', '/', $_REQUEST['path'])); + $filemanager_path = rtrim(str_replace('\\', '/', realpath(evolutionCMS()->getConfig('filemanager_path', MODX_BASE_PATH))), '/'); + $requested_path = ltrim($_REQUEST['path'] ?? '', '/'); + $file = str_replace('\\', '/', realpath($filemanager_path . '/' . $requested_path)); + if (strpos($file, $filemanager_path) !== 0 || !is_file($file)) { + return 'Invalid path.

'; + } + $msg = sprintf($_lang['deleting_file'], str_replace('\\', '/', $file)); - $file = $_REQUEST['path']; if (!evolutionCMS()->hasPermission('file_manager') || !@unlink($file)) { $msg .= '' . $_lang['file_not_deleted'] . '

'; } else { @@ -582,7 +649,7 @@ function checkToken() */ function makeToken() { - $newToken = uniqid(''); + $newToken = uniqid('', true); $_SESSION['token'] = $newToken; return $newToken; diff --git a/install/src/controllers/mode.php b/install/src/controllers/mode.php index 7013b7f4c9..46fec655e7 100644 --- a/install/src/controllers/mode.php +++ b/install/src/controllers/mode.php @@ -11,8 +11,12 @@ if (isset($db_config['database'])) { try { $pdoOptions = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]; - $dbh = new PDO($db_config['driver'] . ':host=' . $db_config['host'] . ';dbname=' - . $db_config['database'], $db_config['username'], $db_config['password'], $pdoOptions); + if ($db_config['driver'] === 'sqlite') { + $dbh = new PDO('sqlite:' . $db_config['database']); + } else { + $dbh = new PDO($db_config['driver'] . ':host=' . $db_config['host'] . ';dbname=' + . $db_config['database'], $db_config['username'], $db_config['password'], $pdoOptions); + } $isConnectable = true; } catch (PDOException $e) { $isConnectable = false; diff --git a/manager/actions/files.dynamic.php b/manager/actions/files.dynamic.php index 9f2170fd50..9e8ffff321 100755 --- a/manager/actions/files.dynamic.php +++ b/manager/actions/files.dynamic.php @@ -7,67 +7,57 @@ } $token_check = checkToken(); $newToken = makeToken(); - // settings $theme_image_path = MODX_MANAGER_URL . 'media/style/' . evo()->getConfig('manager_theme') . '/images/'; $excludes = [ - '.', - '..', - '.svn', - '.git', - '.idea' + '.', + '..', + '.svn', + '.git', + '.idea' ]; $alias_suffix = (!empty($friendly_url_suffix)) ? ',' . ltrim($friendly_url_suffix, '.') : ''; $editablefiles = explode(',', 'txt,php,tpl,less,sass,scss,shtml,html,htm,xml,js,css,pageCache,htaccess,json,ini' . $alias_suffix); $inlineviewablefiles = explode(',', 'txt,php,tpl,less,sass,scss,html,htm,xml,js,css,pageCache,htaccess,json,ini' . $alias_suffix); $viewablefiles = explode(',', 'jpg,gif,png,ico'); - $editablefiles = add_dot($editablefiles); $inlineviewablefiles = add_dot($inlineviewablefiles); $viewablefiles = add_dot($viewablefiles); - $protected_path = []; -/* jp only -if($_SESSION['mgrRole']!=1) -{ -*/ -$protected_path[] = MODX_MANAGER_PATH; -$protected_path[] = MODX_BASE_PATH . 'temp/backup'; -$protected_path[] = MODX_BASE_PATH . 'assets/backup'; - +/* jp only if($_SESSION['mgrRole']!=1) { */ +$protected_path[] = str_replace('\\', '/', MODX_MANAGER_PATH); +$protected_path[] = str_replace('\\', '/', MODX_BASE_PATH . 'temp/backup'); +$protected_path[] = str_replace('\\', '/', MODX_BASE_PATH . 'assets/backup'); if (!evo()->hasPermission('save_plugin')) { - $protected_path[] = MODX_BASE_PATH . 'assets/plugins'; + $protected_path[] = str_replace('\\', '/', MODX_BASE_PATH . 'assets/plugins'); } if (!evo()->hasPermission('save_snippet')) { - $protected_path[] = MODX_BASE_PATH . 'assets/snippets'; + $protected_path[] = str_replace('\\', '/', MODX_BASE_PATH . 'assets/snippets'); } if (!evo()->hasPermission('save_template')) { - $protected_path[] = MODX_BASE_PATH . 'assets/templates'; + $protected_path[] = str_replace('\\', '/', MODX_BASE_PATH . 'assets/templates'); } if (!evo()->hasPermission('save_module')) { - $protected_path[] = MODX_BASE_PATH . 'assets/modules'; + $protected_path[] = str_replace('\\', '/', MODX_BASE_PATH . 'assets/modules'); } if (!evo()->hasPermission('empty_cache')) { - $protected_path[] = MODX_BASE_PATH . 'assets/cache'; + $protected_path[] = str_replace('\\', '/', MODX_BASE_PATH . 'assets/cache'); } if (!evo()->hasPermission('import_static')) { - $protected_path[] = MODX_BASE_PATH . 'temp/import'; - $protected_path[] = MODX_BASE_PATH . 'assets/import'; + $protected_path[] = str_replace('\\', '/', MODX_BASE_PATH . 'temp/import'); + $protected_path[] = str_replace('\\', '/', MODX_BASE_PATH . 'assets/import'); } if (!evo()->hasPermission('export_static')) { - $protected_path[] = MODX_BASE_PATH . 'temp/export'; - $protected_path[] = MODX_BASE_PATH . 'assets/export'; + $protected_path[] = str_replace('\\', '/', MODX_BASE_PATH . 'temp/export'); + $protected_path[] = str_replace('\\', '/', MODX_BASE_PATH . 'assets/export'); } -/* -} -*/ - +/* } */ // Mod added by Raymond $enablefileunzip = true; $enablefiledownload = true; $newfolderaccessmode = octdec(evo()->getConfig('new_folder_permissions', '0777')); $new_file_permissions = octdec(evo()->getConfig('new_file_permissions', '0666')); -// End Mod - by Raymond +// End Mod - by Raymond // make arrays from the file upload settings $upload_files = explode(',', evo()->getConfig('upload_files', '')); $upload_images = explode(',', evo()->getConfig('upload_images', '')); @@ -76,40 +66,34 @@ $uploadablefiles = array_merge($upload_files, $upload_images, $upload_media); $uploadablefiles = add_dot($uploadablefiles); $upload_maxsize = evo()->getConfig('upload_maxsize'); -$filemanager_path = evo()->getConfig('filemanager_path', MODX_BASE_PATH); - +$filemanager_path = rtrim(str_replace('\\', '/', realpath(evo()->getConfig('filemanager_path', MODX_BASE_PATH))), '/'); +$base_path = rtrim(str_replace('\\', '/', realpath(MODX_BASE_PATH)), '/'); // end settings - // get the current work directory -if (isset($_REQUEST['path']) && !empty($_REQUEST['path'])) { - $_REQUEST['path'] = str_replace('..', '', $_REQUEST['path']); - $startpath = is_dir($_REQUEST['path']) ? $_REQUEST['path'] : removeLastPath($_REQUEST['path']); +$requested_path = ltrim(isset($_REQUEST['path']) ? $_REQUEST['path'] : '', '/'); +$fullpath = str_replace('\\', '/', realpath($filemanager_path . '/' . $requested_path)); +$selFile = ''; +if (is_file($fullpath)) { + $selFile = $requested_path; + $startpath = rtrim(str_replace('\\', '/', realpath(dirname($fullpath))), '/'); +} elseif (is_dir($fullpath)) { + $startpath = $fullpath; } else { $startpath = $filemanager_path; } -$startpath = rtrim($startpath, '/'); - -if (!is_readable($startpath)) { - evo()->webAlertAndQuit($_lang["not_readable_dir"]); +if ($startpath === false || strpos($startpath, $filemanager_path) !== 0 || !is_readable($startpath)) { + evo()->webAlertAndQuit($_lang["files_access_denied"]); } - // Raymond: get web start path for showing pictures -$rf = realpath($filemanager_path); -$rw = realpath('../'); -$webstart_path = str_replace('\\', '/', str_replace($rw, '', $rf)); -if (substr($webstart_path, 0, 1) == '/') { - $webstart_path = '..' . $webstart_path; -} else { - $webstart_path = '../' . $webstart_path; -} ?> +$relative_path = ltrim(substr($startpath, strlen($filemanager_path)), '/'); +?> -

-
@@ -213,12 +184,12 @@ function renameFile(file) { $tpl = '[+subject+]'; $ph['image'] = $_style['icon_folder_open']; $ph['subject'] = $_lang['add_folder']; - $ph['href'] = 'index.php?a=31&mode=newfolder&path=' . urlencode($startpath) . '&name='; + $ph['href'] = 'index.php?a=31&mode=newfolder&path=' . urlencode($relative_path) . '&token=' . $newToken . '&name='; $_ = parsePlaceholder($tpl, $ph); $tpl = '' . $_lang['files.dynamic.php1'] . ''; $ph['image'] = $_style['icon_document']; - $ph['href'] = 'index.php?a=31&mode=newfile&path=' . urlencode($startpath) . '&name='; + $ph['href'] = 'index.php?a=31&mode=newfile&path=' . urlencode($relative_path) . '&token=' . $newToken . '&name='; $_ .= parsePlaceholder($tpl, $ph); echo $_; } @@ -228,22 +199,32 @@ function renameFile(file) {
-
- webAlertAndQuit($_lang["files_access_denied"]); + open($file) !== true) { + return false; + } + for ($i = 0; $i < $zip->numFiles; $i++) { + $stat = $zip->statIndex($i); + $filename = str_replace('\\', '/', $stat['name']); + if (substr($filename, 0, 1) == '/' || strpos($filename, '..') !== false || strpos($filename, ':') !== false) { + continue; // skip malicious paths + } + $target = $path . '/' . $filename; + $target_real = rtrim(str_replace('\\', '/', realpath(dirname($target)) ?: dirname($target)), '/\\'); + if (strpos($target_real, $path) !== 0) { + continue; + } + if (substr($filename, -1) == '/') { + if (!is_dir($target)) { + mkdir($target, 0777, true); + } + } else { + $dirname = dirname($target); + if (!is_dir($dirname)) { + mkdir($dirname, 0777, true); + } + file_put_contents($target, $zip->getFromIndex($i)); + } + } + $zip->close(); + return true; } - // Unzip .zip files - by Raymond + // Unzip .zip files - by Raymond, with safe_unzip if ($enablefileunzip && get_by_key($_REQUEST, 'mode') == 'unzip' && is_writable($startpath)) { - if (!$err = unzip(realpath("{$startpath}/" . $_REQUEST['file']), realpath($startpath))) { - echo '' . $_lang['file_unzip_fail'] . ($err === 0 ? 'Missing zip library (php_zip.dll / zip.so)' : '') . '

'; + if ($token_check) { + $zipfile = str_replace('\\', '/', realpath($startpath . '/' . $_REQUEST['file'])); + if (strpos($zipfile, $filemanager_path) !== 0) { + echo 'Invalid path.

'; + } else { + $success = safe_unzip($zipfile, $startpath); + if (!$success) { + echo '' . $_lang['file_unzip_fail'] . '

'; + } else { + echo '' . $_lang['file_unzip'] . '

'; + } + } } else { - echo '' . $_lang['file_unzip'] . '

'; + echo 'Invalid token

'; } } // End Unzip - Raymond - // New Folder & Delete Folder option - Raymond if (is_writable($startpath)) { // Delete Folder if (get_by_key($_REQUEST, 'mode') == 'deletefolder') { - $folder = $_REQUEST['folderpath']; - if (!$token_check || !@rrmdir($folder)) { - echo '' . $_lang['file_folder_not_deleted'] . '

'; + if ($token_check) { + $requested_folderpath = ltrim($_REQUEST['folderpath'] ?? '', '/'); + $folder = str_replace('\\', '/', realpath($filemanager_path . '/' . $requested_folderpath)); + if (strpos($folder, $filemanager_path) !== 0 || !is_dir($folder)) { + echo 'Invalid path.

'; + } elseif (!@rrmdir($folder)) { + echo '' . $_lang['file_folder_not_deleted'] . '

'; + } else { + echo '' . $_lang['file_folder_deleted'] . '

'; + } } else { - echo '' . $_lang['file_folder_deleted'] . '

'; + echo 'Invalid token

'; } } - // Create folder here if (get_by_key($_REQUEST, 'mode') == 'newfolder') { - $old_umask = umask(0); - $foldername = str_replace('..\\', '', str_replace('../', '', $_REQUEST['name'])); - if (!mkdirs("{$startpath}/{$foldername}", 0777)) { - echo '', $_lang['file_folder_not_created'], '

'; - } else { - if (!@chmod($startpath . '/' . $foldername, $newfolderaccessmode)) { - echo '' . $_lang['file_folder_chmod_error'] . '

'; + if ($token_check) { + $old_umask = umask(0); + $foldername = str_replace([ '..\\', '../', '\\', '/' ], '', $_REQUEST['name']); + $newdir = $startpath . '/' . $foldername; + if (!mkdirs($newdir, 0777)) { + echo '', $_lang['file_folder_not_created'], '

'; } else { - echo '' . $_lang['file_folder_created'] . '

'; + if (!@chmod($newdir, $newfolderaccessmode)) { + echo '' . $_lang['file_folder_chmod_error'] . '

'; + } else { + echo '' . $_lang['file_folder_created'] . '

'; + } } + umask($old_umask); + } else { + echo 'Invalid token

'; } - umask($old_umask); } // Create file here if (get_by_key($_REQUEST, 'mode') == 'newfile') { - $old_umask = umask(0); - $filename = str_replace('..\\', '', str_replace('../', '', $_REQUEST['name'])); - - if (!checkExtension($filename)) { - echo '' . $_lang['files_filetype_notok'] . '

'; - } elseif (preg_match('@(\\\\|\/|\:|\;|\,|\*|\?|\"|\<|\>|\||\?)@', $filename) !== 0) { - echo $_lang['files.dynamic.php3']; - } else { - $rs = file_put_contents("{$startpath}/{$filename}", ''); - if ($rs === false) { - echo '', $_lang['file_folder_not_created'], '

'; + if ($token_check) { + $old_umask = umask(0); + $filename = str_replace([ '..\\', '../', '\\', '/' ], '', $_REQUEST['name']); + if (!checkExtension($filename)) { + echo '' . $_lang['files_filetype_notok'] . '

'; + } elseif (preg_match('@(\\\\|\/|\:|\;|\,|\*|\?|\"|\<|\>|\||\?)@', $filename) !== 0) { + echo $_lang['files.dynamic.php3']; } else { - echo $_lang['files.dynamic.php4']; + $rs = file_put_contents($startpath . '/' . $filename, ''); + if ($rs === false) { + echo '', $_lang['file_folder_not_created'], '

'; + } else { + echo $_lang['files.dynamic.php4']; + } + umask($old_umask); } - umask($old_umask); + } else { + echo 'Invalid token

'; } } // Duplicate file here if (get_by_key($_REQUEST, 'mode') == 'duplicate') { - $old_umask = umask(0); - $filename = $_REQUEST['path']; - $newFilename = str_replace('..\\', '', str_replace('../', '', $_REQUEST['newFilename'])); - - if (!checkExtension($newFilename)) { - echo '' . $_lang['files_filetype_notok'] . '

'; - } elseif (preg_match('@(\\\\|\/|\:|\;|\,|\*|\?|\"|\<|\>|\||\?)@', $newFilename) !== 0) { - echo $_lang['files.dynamic.php3']; - } else { - if (!copy($filename, MODX_BASE_PATH . $newFilename)) { - echo $_lang['files.dynamic.php5']; + if ($token_check) { + $old_umask = umask(0); + $requested_file = ltrim($_REQUEST['path'] ?? '', '/'); + $filename = str_replace('\\', '/', realpath($filemanager_path . '/' . $requested_file)); + if (strpos($filename, $filemanager_path) !== 0 || !is_file($filename)) { + echo 'Invalid path.

'; + } else { + $newFilename = str_replace([ '..\\', '../', '\\', '/' ], '', $_REQUEST['newFilename']); + if (!checkExtension($newFilename)) { + echo '' . $_lang['files_filetype_notok'] . '

'; + } elseif (preg_match('@(\\\\|\/|\:|\;|\,|\*|\?|\"|\<|\>|\||\?)@', $newFilename) !== 0) { + echo $_lang['files.dynamic.php3']; + } else { + // Fix: Copy to same directory, not base path + $newpath = dirname($filename) . '/' . $newFilename; + if (!copy($filename, $newpath)) { + echo $_lang['files.dynamic.php5']; + } + umask($old_umask); + } } - umask($old_umask); + } else { + echo 'Invalid token

'; } } // Rename folder here if (get_by_key($_REQUEST, 'mode') == 'renameFolder') { - $old_umask = umask(0); - $dirname = $_REQUEST['path'] . '/' . $_REQUEST['dirname']; - $newDirname = str_replace([ - '..\\', - '../', - '\\', - '/' - ], '', $_REQUEST['newDirname']); - - if (preg_match('@(\\\\|\/|\:|\;|\,|\*|\?|\"|\<|\>|\||\?)@', $newDirname) !== 0) { - echo $_lang['files.dynamic.php3']; - } else if (!rename($dirname, $_REQUEST['path'] . '/' . $newDirname)) { - echo '', $_lang['file_folder_not_created'], '

'; + if ($token_check) { + $old_umask = umask(0); + $requested_dir = ltrim($_REQUEST['path'] ?? '', '/'); + $dirname = str_replace('\\', '/', realpath($filemanager_path . '/' . $requested_dir . '/' . $_REQUEST['dirname'])); + if (strpos($dirname, $filemanager_path) !== 0 || !is_dir($dirname)) { + echo 'Invalid path.

'; + } else { + $newDirname = str_replace([ '..\\', '../', '\\', '/' ], '', $_REQUEST['newDirname']); + if (preg_match('@(\\\\|\/|\:|\;|\,|\*|\?|\"|\<|\>|\||\?)@', $newDirname) !== 0) { + echo $_lang['files.dynamic.php3']; + } else if (!rename($dirname, dirname($dirname) . '/' . $newDirname)) { + echo '', $_lang['file_folder_not_created'], '

'; + } + umask($old_umask); + } + } else { + echo 'Invalid token

'; } - umask($old_umask); } // Rename file here if (get_by_key($_REQUEST, 'mode') == 'renameFile') { - $old_umask = umask(0); - $path = dirname($_REQUEST['path']); - $filename = $_REQUEST['path']; - $newFilename = str_replace([ - '..\\', - '../', - '\\', - '/' - ], '', $_REQUEST['newFilename']); - - if (!checkExtension($newFilename)) { - echo '' . $_lang['files_filetype_notok'] . '

'; - } elseif (preg_match('@(\\\\|\/|\:|\;|\,|\*|\?|\"|\<|\>|\||\?)@', $newFilename) !== 0) { - echo $_lang['files.dynamic.php3']; - } else { - if (!rename($filename, $path . '/' . $newFilename)) { - echo $_lang['files.dynamic.php5']; + if ($token_check) { + $old_umask = umask(0); + $requested_file = ltrim($_REQUEST['path'] ?? '', '/'); + $filename = str_replace('\\', '/', realpath($filemanager_path . '/' . $requested_file)); + if (strpos($filename, $filemanager_path) !== 0 || !is_file($filename)) { + echo 'Invalid path.

'; + } else { + $path = dirname($filename); + $newFilename = str_replace([ '..\\', '../', '\\', '/' ], '', $_REQUEST['newFilename']); + if (!checkExtension($newFilename)) { + echo '' . $_lang['files_filetype_notok'] . '

'; + } elseif (preg_match('@(\\\\|\/|\:|\;|\,|\*|\?|\"|\<|\>|\||\?)@', $newFilename) !== 0) { + echo $_lang['files.dynamic.php3']; + } else { + if (!rename($filename, $path . '/' . $newFilename)) { + echo $_lang['files.dynamic.php5']; + } + umask($old_umask); + } } - umask($old_umask); + } else { + echo 'Invalid token

'; } } - } - // End New Folder - Raymond - + } // End New Folder - Raymond if (strlen(MODX_BASE_PATH) < strlen($filemanager_path)) { $len--; - } ?> + } + ?>
@@ -420,63 +469,49 @@ function renameFile(file) { - '; - } + if ($folders == 0 && $files == 0) { echo ''; } ?>
' . $_lang['files_directory_is_empty'] . '
' . $_lang['files_directory_is_empty'] . '
-

- ' . $folders . ' '; + ' . $folders . ' '; echo $_lang['files_files'] . ': ' . $files . ' '; echo $_lang['files_data'] . ': ' . niceSize($filesizes) . ' '; - echo $_lang['files_dirwritable'] . ' ' . (is_writable($startpath) == 1 ? $_lang['yes'] . '.' : $_lang['no']) . '.' + echo $_lang['files_dirwritable'] . ' ' . (is_writable($startpath) == 1 ? $_lang['yes'] : $_lang['no']) . '.' ?>

- - - +
- - - - + + + +
- " . $_lang['files_upload_inhibited_msg'] . "

"; - } - ?> + } ?>
-
- - +
webAlertAndQuit("Invalid path."); + } $buffer = file_get_contents($filename); // Log the change logFileChange('view', $filename); @@ -487,7 +522,8 @@ function renameFile(file) {
- + +
@@ -516,15 +552,12 @@ function renameFile(file) { $contentType = 'htmlmixed'; }; $evtOut = evo()->invokeEvent('OnRichTextEditorInit', [ - 'editor' => 'Codemirror', - 'elements' => [ - 'content', - ], - 'contentType' => $contentType, - 'readOnly' => $_REQUEST['mode'] == 'edit' ? false : true + 'editor' => 'Codemirror', + 'elements' => ['content'], + 'contentType' => $contentType, + 'readOnly' => $_REQUEST['mode'] !== 'edit' ]); if (is_array($evtOut)) { echo implode('', $evtOut); } } - From a16faed5af0d3c58fdf29eaf03c2d81bc4f78e30 Mon Sep 17 00:00:00 2001 From: Artur Kyryliuk Date: Tue, 20 Jan 2026 05:06:39 +0100 Subject: [PATCH 2/2] [FIX] old config file check --- install/index.php | 6 +++++- install/src/controllers/connection.php | 5 +++-- install/src/controllers/connection/collation.php | 16 ++++++++++------ .../src/controllers/connection/databasetest.php | 8 +++++--- .../src/controllers/connection/servertest.php | 14 +++++++++----- install/src/controllers/install.php | 4 +++- install/src/controllers/language.php | 1 + install/src/controllers/mode.php | 1 + install/src/controllers/options.php | 1 + install/src/controllers/summary.php | 1 + install/src/functions.php | 11 +++++++++-- 11 files changed, 48 insertions(+), 20 deletions(-) diff --git a/install/index.php b/install/index.php index d32c5010a5..4ab0a6fe5b 100644 --- a/install/index.php +++ b/install/index.php @@ -138,7 +138,11 @@ if (! file_exists($controller)) { die("Invalid install action attempted. [action={$action}]"); } - require $controller; + try { + require $controller; + } catch (Exception $e) { + echo $e->getMessage(); + } $ph['content'] = ob_get_contents(); ob_end_clean(); diff --git a/install/src/controllers/connection.php b/install/src/controllers/connection.php index 9068104d25..6f920b487b 100644 --- a/install/src/controllers/connection.php +++ b/install/src/controllers/connection.php @@ -1,4 +1,5 @@ 'MySQL', 'pgsql' => 'PostgreSQL', 'sqlite' => 'SQLite']; @@ -72,7 +73,7 @@ } } if (!$conn || !$result) { - $upgradeable = (isset($_POST['installmode']) && $_POST['installmode'] === 'new') ? 0 : 2; + $upgradeable = ($installMode === 0) ? 0 : 2; } else { $upgradeable = 1; } @@ -143,7 +144,7 @@ $ph['database_name'] = escapeHtmlAttribute(isset($_POST['database_name']) ? $_POST['database_name'] : $database_name); $ph['tableprefix'] = escapeHtmlAttribute(isset($_POST['tableprefix']) ? $_POST['tableprefix'] : $table_prefix); $ph['database_collation'] = escapeHtmlAttribute(isset($_POST['database_collation']) ? $_POST['database_collation'] : $database_collation); -$ph['show#AUH'] = ($installMode == 0) ? '' : 'hidden'; +$ph['show#AUH'] = ($installMode === 0) ? '' : 'hidden'; $ph['cmsadmin'] = escapeHtmlAttribute(isset($_POST['cmsadmin']) ? $_POST['cmsadmin'] : 'admin'); $ph['cmsadminemail'] = escapeHtmlAttribute(isset($_POST['cmsadminemail']) ? $_POST['cmsadminemail'] : ''); $ph['cmspassword'] = escapeHtmlAttribute(isset($_POST['cmspassword']) ? $_POST['cmspassword'] : ''); diff --git a/install/src/controllers/connection/collation.php b/install/src/controllers/connection/collation.php index 55c71ae7d2..bb6241b5c9 100644 --- a/install/src/controllers/connection/collation.php +++ b/install/src/controllers/connection/collation.php @@ -1,10 +1,14 @@ ' . $_lang['status_failed'] . ' ' . $e->getMessage() . ''; +} try { $dsn = $driver . ':host=' . $host; $output = '