From 27d5a93ef83c12be28d4a6e99b55589a145e20d5 Mon Sep 17 00:00:00 2001 From: Alan Hardman Date: Thu, 9 Sep 2021 16:29:08 -0600 Subject: [PATCH] Fixing several security/compatibility issues This addresses issues including: - XSS/content escaping - Path traversal - PHP 7/8 compatibility This software is not currently actively maintained, but is something people are still finding useful, so for the time being I am keeping the repository available. I may at some point rewrite it completely, because it really needs it. --- .gitignore | 5 +++++ admin.php | 18 +++++++++--------- ajax.php | 26 +++++++++++++------------- backup-run.php | 14 +++++++------- edit.php | 15 ++++++++------- files.php | 2 +- inc/lib.php | 34 +++++++++++++++++++++++++--------- 7 files changed, 68 insertions(+), 46 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3b2ea4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/data/config.php +/data/users/*.json +/inc/skintmp/ +/.vscode +/.installed diff --git a/admin.php b/admin.php index ebdabfb..5274bd6 100644 --- a/admin.php +++ b/admin.php @@ -6,14 +6,14 @@ // Not logged in, redirect to login page header('Location: .'); exit('Not Authorized'); -} elseif (!$_SESSION['is_admin'] && $user['role'] != 'admin') { +} elseif (empty($_SESSION['is_admin']) && $user['role'] != 'admin') { // Not an admin, redirect to login page header('Location: .'); exit('Not Authorized'); } // Switch users -if ($_POST['action'] == 'user-switch' && $_POST['user']) { +if (isset($_POST['action']) && $_POST['action'] == 'user-switch' && $_POST['user']) { $_SESSION['is_admin'] = true; $_SESSION['user'] = $_POST['user']; header('Location: .'); @@ -21,7 +21,7 @@ } //Manage a backup cron job -if($_POST['action'] == 'backup-manage' && $_POST['user']) { +if (isset($_POST['action']) && $_POST['action'] == 'backup-manage' && $_POST['user']) { //Determine which button (create or delete) was pressed and pass it as an action $action = (isset($_POST['create']) ? "create" : (isset($_POST['delete']) ? "delete" : exit("Action error"))); @@ -30,18 +30,18 @@ } // Add new user -if ($_POST['action'] == 'user-add') +if (isset($_POST['action']) && $_POST['action'] == 'user-add') user_add($_POST['user'], $_POST['pass'], $_POST['role'], $_POST['dir'], $_POST['ram'], $_POST['port']); // Start a server -if ($_POST['action'] == 'server-start') { +if (isset($_POST['action']) && $_POST['action'] == 'server-start') { $stu = user_info($_POST['user']); if (!server_running($stu['user'])) server_start($stu['user']); } // Kill a server -if ($_POST['action'] == 'server-stop') +if (isset($_POST['action']) && $_POST['action'] == 'server-stop') if ($_POST['user'] == 'ALL') server_kill_all(); else @@ -107,11 +107,11 @@ function check_cron() {

Administration

- +

User added successfully.

- +

Server started.

- +

Server killed.

diff --git a/ajax.php b/ajax.php index f2334ce..26a5691 100644 --- a/ajax.php +++ b/ajax.php @@ -12,12 +12,12 @@ $files = array(); // Get directory contents - $h = opendir($user['home'] . $_POST['dir']); + $h = opendir($user['home'] . sanitize_path($_POST['dir'])); while (false !== ($f = readdir($h))) if ($f != '.' && $f != '..') - if (is_dir($user['home'] . $_POST['dir'] . '/' . $f)) + if (is_dir($user['home'] . sanitize_path($_POST['dir']) . '/' . $f)) $dirs[] = $f; - elseif (is_file($user['home'] . $_POST['dir'] . '/' . $f)) + elseif (is_file($user['home'] . sanitize_path($_POST['dir']) . '/' . $f)) $files[] = $f; closedir($h); unset($f); @@ -29,7 +29,7 @@ // Get file sizes $sizes = array(); foreach ($files as $f) - $sizes[] = filesize($user['home'] . $_POST['dir'] . '/' . $f); + $sizes[] = filesize($user['home'] . sanitize_path($_POST['dir']) . '/' . $f); // Output data echo json_encode(array( @@ -40,17 +40,17 @@ break; case 'file_get': - if (is_file($user['home'] . $_POST['file'])) - echo file_get_contents($user['home'] . $_POST['file']); + if (is_file($user['home'] . sanitize_path($_POST['file']))) + echo file_get_contents($user['home'] . sanitize_path($_POST['file'])); break; case 'file_put': - if (is_file($user['home'] . $_POST['file'])) - file_put_contents($user['home'] . $_POST['file'], $_POST['data']); + if (is_file($user['home'] . sanitize_path($_POST['file']))) + file_put_contents($user['home'] . sanitize_path($_POST['file']), $_POST['data']); break; case 'delete': foreach ($_POST['files'] as $f) - if (is_file($user['home'] . $f)) - unlink($user['home'] . $f); + if (is_file($user['home'] . sanitize_path($f))) + unlink($user['home'] . sanitize_path($f)); break; case 'rename': file_rename($_POST['path'], $_POST['newname'], $user['home']); @@ -90,8 +90,8 @@ // 1.6 and earlier echo mclogparse2(file_backread($user['home'] . '/server.log', 64)); } elseif(is_file($user['home'] . "/proxy.log.0")) { - // BungeeCord - echo mclogparse2(file_backread($user['home'] . '/proxy.log.0', 64)); + // BungeeCord + echo mclogparse2(file_backread($user['home'] . '/proxy.log.0', 64)); } else { echo "No log file found."; } @@ -105,7 +105,7 @@ } elseif(is_file($user['home'] . '/server.log')) { $file = $user['home'] . '/server.log'; } elseif(is_file($user['home'] . '/proxy.log.0')) { - $file = $user['home'] . '/proxy.log.0'; + $file = $user['home'] . '/proxy.log.0'; } else { exit(json_encode(array('error' => 1, 'msg' => 'No log file found.'))); } diff --git a/backup-run.php b/backup-run.php index 146c708..fca004e 100644 --- a/backup-run.php +++ b/backup-run.php @@ -1,4 +1,4 @@ -buildFromDirectory($user['home'], '/^((?!backups).)*$/'); $phar->compress(Phar::GZ); - + //Delete the .tar file since now we have a .tar.gz unlink($user['home'] . "/" . "backups/" . $archiveFile); - + } catch (Exception $e) { error_log("MCHostPanel Backup: '" . $user . "' Backup Failure!\r\nException : " . $e); exit("Exception : " . $e . "\r\n"); @@ -116,7 +116,7 @@ function server_cmd($name,$cmd) { sprintf( KT_SCREEN_CMD_EXEC, // Base command KT_SCREEN_NAME_PREFIX.$name, // Screen Name - str_replace(array('\\','"'),array('\\\\','\\"'),(get_magic_quotes_gpc() ? stripslashes($cmd) : $cmd)) // Server command + str_replace(array('\\','"'),array('\\\\','\\"'),((function_exists("get_magic_quotes_gpc") && get_magic_quotes_gpc()) ? stripslashes($cmd) : $cmd)) // Server command ) ); } diff --git a/edit.php b/edit.php index 2fbbd11..76dd379 100644 --- a/edit.php +++ b/edit.php @@ -23,15 +23,16 @@ if (isset($_POST['text']) && !empty($_POST['file'])) { $file = $user['home'] . $_POST['file']; $text = $_POST['text']; - if (get_magic_quotes_gpc()) + if (function_exists("get_magic_quotes_gpc") && get_magic_quotes_gpc()) $text = stripslashes($text); $saved = file_put_contents($file, $text); } // Determine current directory -$dir = rtrim($_REQUEST['file'], basename($_REQUEST['file'])); +$dir = dirname($_REQUEST['file']); $dir = rtrim($dir, '/'); +$file = $user['home'] . sanitize_path($_REQUEST['file']); ?> @@ -77,7 +78,7 @@
-

Editing

+

Editing

File was successfully saved.

@@ -86,12 +87,12 @@

File reloaded.

- - + +
diff --git a/files.php b/files.php index 23052f7..906eded 100644 --- a/files.php +++ b/files.php @@ -147,7 +147,7 @@ $('button.ht').tooltip(); // Load requested directory - loaddir(''); + loaddir(''); }); diff --git a/inc/lib.php b/inc/lib.php index 18ccfb3..608f06f 100644 --- a/inc/lib.php +++ b/inc/lib.php @@ -36,6 +36,8 @@ * @return bool */ function file_rename($path,$newname,$home) { + $path = sanitize_path($path); + $newname = sanitize_path($newname); return rename($home.$path,$home.rtrim($path,basename($path)).$newname); } @@ -47,6 +49,7 @@ function file_rename($path,$newname,$home) { * @return void */ function download($path,$home,$force = true) { + $path = sanitize_path($path); if(is_file($home.$path) && $force) { header('Content-type: application/octet-stream'); header('Content-Disposition: attachment; filename="'.basename($path).'";'); @@ -70,6 +73,7 @@ function download($path,$home,$force = true) { * @return string */ function mimetype($filename) { + $filename = sanitize_path($filename); $mime_types = array( 'txt' => 'text/plain', 'htm' => 'text/html', @@ -138,6 +142,7 @@ function mimetype($filename) { * @return int */ function getsize($file) { + $file = sanitize_path($file); $size = filesize($file); if($size < 0) if(!(strtoupper(substr(PHP_OS,0,3))=='WIN')) @@ -145,7 +150,7 @@ function getsize($file) { else { $fsobj = new COM('Scripting.FileSystemObject'); $f = $fsobj->GetFile($file); - $size = $file->Size; + $size = $f->Size; } return $size; } @@ -171,6 +176,7 @@ function __file_backread_helper(&$haystack,$needle,$x) { * @return string */ function file_backread($file,$lines,&$fsize=0){ + $file = sanitize_path($file); $f=fopen($file,'r'); if(!$f)return Array(); @@ -181,8 +187,7 @@ function file_backread($file,$lines,&$fsize=0){ $fsize=filesize($file); $pos=$fsize; - $buff1=Array(); - $cnt=0; + $buff1=array(); while($pos) { @@ -213,12 +218,15 @@ function file_backread($file,$lines,&$fsize=0){ * @return string|bool */ function file_download($url,$path) { + $url = sanitize_path($url); $file = fopen($url,'rb'); if($file) { $newf = fopen($path,'wb'); - if($newf) + if($newf) { while(!feof($file)) fwrite($newf,fread($file,1024*8),1024*8); + fclose($newf); + } else return false; } @@ -228,9 +236,6 @@ function file_download($url,$path) { else return false; - if($newf) - fclose($newf); - return $path; } @@ -241,6 +246,8 @@ function file_download($url,$path) { * @return bool */ function rmdirr($dirname) { + $dirname = sanitize_path($dirname); + // Sanity check if(!file_exists($dirname)) return false; @@ -451,7 +458,7 @@ function server_cmd($name,$cmd) { sprintf( KT_SCREEN_CMD_EXEC, // Base command KT_SCREEN_NAME_PREFIX.$name, // Screen Name - str_replace(array('\\','"'),array('\\\\','\\"'),(get_magic_quotes_gpc() ? stripslashes($cmd) : $cmd)) // Server command + str_replace(array('\\','"'),array('\\\\','\\"'),((function_exists("get_magic_quotes_gpc") && get_magic_quotes_gpc()) ? stripslashes($cmd) : $cmd)) // Server command ) ); } @@ -675,6 +682,15 @@ function check_email($email) { return filter_var($email,FILTER_VALIDATE_EMAIL); } +// Sanitize a path string +function sanitize_path($path) { + $path = preg_replace('/\.{2,}/', '.', $path); + $path = preg_replace('/\/+$/', '', $path); + $path = str_replace('./', '/', $path); + $path = preg_replace('/\/{2,}/', '/', $path); + return $path; +} + /* .d8888b. 888 888 @@ -695,7 +711,7 @@ function base64_salt($len = 22) { $characterList = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/'; $salt = ''; for($i=0;$i<$len;$i++) - $salt.= $characterList{mt_rand(0,(strlen($characterList)-1))}; + $salt.= $characterList[mt_rand(0,(strlen($characterList)-1))]; return $salt; }