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) ? '" . $_lang['files_file_type'] . $userfile['type'] . ", " . niceSize(filesize($userfile['tmp_name'])) . $img . '
'; + $img = (strpos($userfile['type'],'image') !== false) ? '" . $_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'] . '
' . $_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.