Browse Source

Merge pull request #565 from hestiacp/release-1.0.5

Release 1.0.5
Raphael Schneeberger 6 years ago
parent
commit
28bf784469

+ 63 - 0
CHANGELOG.md

@@ -0,0 +1,63 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+## [1.0.5] - 2019-08-06 - Hotfix
+### Bugfixes
+- Fix several security issues, thanks to Andrea Cardaci (https://cardaci.xyz/)
+- Rework Let's Encrypt acme staging to use hestia conform standard.
+- Fix if condition, use nginx for Let's Encrypt acme request if present.
+
+## [1.0.4] - 2019-07-09 - Hotfix
+### Bugfixes
+- Delay start of services to prevent restart limit
+
+## [1.0.3] - 2019-07-09 - Hotfix
+### Bugfixes
+- Fix Let's Encrypt Mail SSL permission issue
+
+## [1.0.1] - 2019-06-25
+### Features
+- Improved support for Let's Encrypt certificate generation
+- Addition of Let's Encrypt support for Control Panel – see v-add-letsencrypt-host
+- Enabled use of per-domain SSL certificates for inbound and outbound mail services
+- Consolidated template structure, removing over 50% duplicate code
+- Re-organized file system structure for domain configuration files
+- Added the ability to change release branches through the user interface and command line
+- Added the ability to update using Git from the command line - see v-sys-update-hestia-git
+- Implemented support for SFTP chroot jails
+- A newly redesigned user interface which features:
+    - A softer color palette which better matches the Hestia Control Panel logo colors.
+    - A consolidated overview of domains and other information
+    - Improved navigation paths to make things easier to find
+    - Improved compatibility when viewing the Control Panel interface from a mobile device
+- Improved handling of mail domain DNS zone values
+- Enabled OCSP stapling on SSL-enabled web domains
+- Enabled support for HTTP Strict Transport Security on SSL-enabled web domains in the system backend– see v-change-web-domain-hsts
+- Improved logging and console output during new installations and upgrades
+
+### Bugfixes
+- Fixed issues with HTTP-to-HTTPS redirecton
+- Fixed an issue where another website would load if browsing to a non-SSL enabled domaing using HTTPS.
+
+## [1.0.0-190618] - 2019-06-25
+### Features
+- 
+
+### Bugfixes
+- 
+
+## [0.9.8-28] - 2019-05-16
+### Features
+- Implement force ssl function for web domains
+
+### Bugfixes
+- 
+
+
+[CURRENT]: https://github.com/hestiacp/hestiacp
+[1.0.4]: https://github.com/hestiacp/hestiacp/releases/tag/1.0.5
+[1.0.4]: https://github.com/hestiacp/hestiacp/releases/tag/1.0.4
+[1.0.3]: https://github.com/hestiacp/hestiacp/releases/tag/1.0.3
+[1.0.1]: https://github.com/hestiacp/hestiacp/releases/tag/1.0.1
+[1.0.0-190618]: https://github.com/hestiacp/hestiacp/releases/tag/1.0.0-190618
+[0.9.8-28]: https://github.com/hestiacp/hestiacp/releases/tag/0.9.8-28

+ 33 - 23
bin/v-add-letsencrypt-domain

@@ -23,7 +23,7 @@ source $HESTIA/conf/hestia.conf
 # LE API
 LE_API='https://acme-v02.api.letsencrypt.org'
 
-if [[ "$LE_STAGING" =~ ^(YES|TRUE)$ ]]; then
+if [[ "$LE_STAGING" = 'yes' ]]; then
     LE_API='https://acme-staging-v02.api.letsencrypt.org'
 fi
 
@@ -228,30 +228,33 @@ for auth in $authz; do
         check_result $? "DNS _acme-challenge record wasn't created"
     else
         if [ -z "$mail" ]; then
-            if [ "$WEB_SYSTEM" = 'nginx' ] && [ ! -z "$PROXY_SYSTEM" ]; then
-                if [ ! -z "$mail" ]; then
-                    conf="$HOMEDIR/$user/conf/mail/$root_domain/$PROXY_SYSTEM.conf_letsencrypt"
-                    sconf="$HOMEDIR/$user/conf/mail/$root_domain/$PROXY_SYSTEM.ssl.conf_letsencrypt"
-                else
-                    conf="$HOMEDIR/$user/conf/web/$domain/$PROXY_SYSTEM.conf_letsencrypt"
-                    sconf="$HOMEDIR/$user/conf/web/$domain/$PROXY_SYSTEM.ssl.conf_letsencrypt"
-                fi
-
-                if [ ! -e "$conf" ]; then
-                    echo 'location ~ "^/\.well-known/acme-challenge/(.*)$" {' \
-                        > $conf
-                    echo '    default_type text/plain;' >> $conf
-                    echo '    return 200 "$1.'$THUMB'";' >> $conf
-                    echo '}' >> $conf
-                fi
+            if [ "$WEB_SYSTEM" = 'nginx' ] || [ "$PROXY_SYSTEM" = 'nginx' ]; then
+                conf="$HOMEDIR/$user/conf/web/$domain/nginx.conf_letsencrypt"
+                sconf="$HOMEDIR/$user/conf/web/$domain/nginx.ssl.conf_letsencrypt"
+                echo 'location ~ "^/\.well-known/acme-challenge/(.*)$" {' \
+                    > $conf
+                echo '    default_type text/plain;' >> $conf
+                echo '    return 200 "$1.'$THUMB'";' >> $conf
+                echo '}' >> $conf
                 if [ ! -e "$sconf" ]; then
                     ln -s "$conf" "$sconf"
                 fi
-                $BIN/v-restart-proxy
-                check_result $? "Proxy restart failed" > /dev/null
-
+                if [ ! -z "$PROXY_SYSTEM" ]; then
+                    $BIN/v-restart-proxy
+                    check_result $? "Proxy restart failed" > /dev/null
+                fi
             else
-                well_known="$HOMEDIR/$user/web/$domain/public_html/.well-known"
+                # Get root directory from configuration
+                domain_config="$HOMEDIR/$user/conf/web/$domain"
+                if [ -f "$domain_config/apache2.conf" ]; then
+                    well_known="$(cat $domain_config/apache2.conf | egrep \
+                                '^\s+DocumentRoot'| awk '{split($0, a, " "); \
+                                print a[2]}')/.well-known"
+                else
+                    well_known="$(cat $domain_config/nginx.conf | egrep '^\s+root'| \
+                                awk '{split($0, a, " "); print a[2]}' | \
+                                sed 's/;$//')/.well-known"
+                fi
                 acme_challenge="$well_known/acme-challenge"
                 mkdir -p $acme_challenge
                 echo "$token.$THUMB" > $acme_challenge/$token
@@ -264,8 +267,10 @@ for auth in $authz; do
             echo "$token.$THUMB" > $acme_challenge/$token
             chown -R $user:$user $well_known
         fi
-        $BIN/v-restart-web
-        check_result $? "Web restart failed" > /dev/null
+        if [ "$WEB_SYSTEM" = 'nginx' ]; then
+            $BIN/v-restart-web
+            check_result $? "Web restart failed" > /dev/null
+        fi
     fi
 
     # Requesting ACME validation / STEP 5
@@ -411,6 +416,11 @@ else
     update_object_value 'mail' 'DOMAIN' "$root_domain" '$LETSENCRYPT' 'yes'
 fi
 
+# Remove challenge folder if exist
+if [ ! -z "$well_known" ]; then
+    rm -fr $well_known
+fi
+
 #----------------------------------------------------------#
 #                        Hestia                            #
 #----------------------------------------------------------#

+ 1 - 1
bin/v-add-letsencrypt-user

@@ -19,7 +19,7 @@ source $HESTIA/conf/hestia.conf
 # LE API
 LE_API='https://acme-v02.api.letsencrypt.org'
 
-if [[ "$LE_STAGING" =~ ^(YES|TRUE)$ ]]; then
+if [[ "$LE_STAGING" = 'yes' ]]; then
     LE_API='https://acme-staging-v02.api.letsencrypt.org'
 fi
 

+ 1 - 0
bin/v-add-web-domain-backend

@@ -26,6 +26,7 @@ source $HESTIA/conf/hestia.conf
 #----------------------------------------------------------#
 
 check_args '2' "$#" 'USER DOMAIN [TEMPLATE] [RESTART]'
+is_format_valid 'user' 'domain'
 is_system_enabled "$WEB_BACKEND" 'WEB_BACKEND'
 is_object_valid 'user' 'USER' "$user"
 is_object_unsuspended 'user' 'USER' "$user"

+ 1 - 0
bin/v-delete-user-favourites

@@ -32,6 +32,7 @@ case $system in
     DNS_REC)    is_format_valid 'id' ;;
     *)          is_format_valid 'object'
 esac
+is_format_valid 'user'
 is_object_valid 'user' 'USER' "$user"
 is_object_unsuspended 'user' 'USER' "$user"
 

+ 1 - 0
bin/v-list-dns-domain

@@ -71,6 +71,7 @@ csv_list() {
 #----------------------------------------------------------#
 
 check_args '2' "$#" 'USER DOMAIN [FORMAT]'
+is_format_valid 'user' 'domain'
 is_object_valid 'user' 'USER' "$user"
 is_object_valid 'dns' 'DOMAIN' "$domain"
 

+ 1 - 0
bin/v-list-letsencrypt-user

@@ -56,6 +56,7 @@ csv_list() {
 #----------------------------------------------------------#
 
 check_args '1' "$#" 'USER [FORMAT]'
+is_format_valid 'user'
 is_object_valid 'user' 'USER' "$user"
 if [ ! -e "$USER_DATA/ssl/le.conf" ]; then
     check_result $E_NOTEXIST "LetsEncrypt user account doesn't exist"

+ 1 - 0
bin/v-list-mail-domain-dkim-dns

@@ -57,6 +57,7 @@ csv_list() {
 #----------------------------------------------------------#
 
 check_args '2' "$#" 'USER DOMAIN [FORMAT]'
+is_format_valid 'user' 'domain'
 is_object_valid 'user' 'USER' "$user"
 is_object_valid 'mail' 'DOMAIN' "$domain"
 

+ 1 - 0
bin/v-list-mail-domain-ssl

@@ -101,6 +101,7 @@ csv_list() {
 #----------------------------------------------------------#
 
 check_args '2' "$#" 'USER DOMAIN [FORMAT]'
+is_format_valid 'user' 'domain'
 is_object_valid 'user' 'USER' "$user"
 is_object_valid 'mail' 'DOMAIN' "$domain_idn"
 

+ 1 - 0
bin/v-list-user

@@ -156,6 +156,7 @@ csv_list() {
 #----------------------------------------------------------#
 
 check_args '1' "$#" 'USER [FORMAT]'
+is_format_valid 'user'
 is_object_valid 'user' 'USER' "$user"
 
 

+ 1 - 0
bin/v-list-user-backup

@@ -75,6 +75,7 @@ csv_list() {
 #----------------------------------------------------------#
 
 check_args '2' "$#" 'USER BACKUP [FORMAT]'
+is_format_valid 'user' 'backup'
 is_object_valid 'user' 'USER' "$user"
 is_object_valid 'backup' 'BACKUP' "$backup"
 

+ 1 - 0
bin/v-list-user-stats

@@ -115,6 +115,7 @@ csv_list() {
 #----------------------------------------------------------#
 
 check_args '1' "$#" 'USER [FORMAT]'
+is_format_valid 'user'
 is_object_valid 'user' 'USER' "$user"
 
 

+ 1 - 0
bin/v-list-web-domain

@@ -118,6 +118,7 @@ csv_list() {
 #----------------------------------------------------------#
 
 check_args '2' "$#" 'USER DOMAIN [FORMAT]'
+is_format_valid 'user' 'domain'
 is_object_valid 'user' 'USER' "$user"
 is_object_valid 'web' 'DOMAIN' "$domain"
 

+ 1 - 0
bin/v-list-web-domain-ssl

@@ -101,6 +101,7 @@ csv_list() {
 #----------------------------------------------------------#
 
 check_args '2' "$#" 'USER DOMAIN [FORMAT]'
+is_format_valid 'user' 'domain'
 is_object_valid 'user' 'USER' "$user"
 is_object_valid 'web' 'DOMAIN' "$domain"
 

+ 1 - 0
bin/v-list-web-domains

@@ -100,6 +100,7 @@ csv_list() {
 #----------------------------------------------------------#
 
 check_args '1' "$#" 'USER [FORMAT]'
+is_format_valid 'user'
 is_object_valid 'user' 'USER' "$user"
 
 

+ 1 - 1
bin/v-rebuild-mail-domain

@@ -31,7 +31,7 @@ fi
 #----------------------------------------------------------#
 
 check_args '2' "$#" 'USER DOMAIN'
-is_format_valid 'user'
+is_format_valid 'user' 'domain'
 is_system_enabled "$MAIL_SYSTEM" 'MAIL_SYSTEM'
 is_object_valid 'user' 'USER' "$user"
 is_object_unsuspended 'user' 'USER' "$user"

+ 1 - 1
bin/v-rebuild-web-domain

@@ -27,7 +27,7 @@ source $HESTIA/conf/hestia.conf
 #----------------------------------------------------------#
 
 check_args '2' "$#" 'USER DOMAIN [RESTART]'
-is_format_valid 'user'
+is_format_valid 'user' 'domain'
 is_system_enabled "$WEB_SYSTEM" 'WEB_SYSTEM'
 is_object_valid 'user' 'USER' "$user"
 is_object_unsuspended 'user' 'USER' "$user"

+ 2 - 1
func/main.sh

@@ -221,7 +221,8 @@ is_object_new() {
 # Check if object is valid
 is_object_valid() {
     if [ $2 = 'USER' ]; then
-        if [ ! -d "$HESTIA/data/users/$3" ]; then
+        tstpath="$(readlink -f "$HESTIA/data/users/$3")"
+        if [ "$(dirname "$tstpath")" != "$(readlink -f "$HESTIA/data/users")" ] || [ ! -d "$HESTIA/data/users/$3" ]; then
             check_result $E_NOTEXIST "$1 $3 doesn't exist"
         fi
     else

+ 2 - 2
install/hst-install-debian.sh

@@ -412,7 +412,7 @@ echo ' |  _  |  __/\__ \ |_| | (_| | |___|  __/ '
 echo ' |_| |_|\___||___/\__|_|\__,_|\____|_|    '
 echo
 echo '                      Hestia Control Panel'
-echo '                                    v1.0.4'
+echo '                                    v1.0.5'
 echo -e "\n"
 echo "===================================================================="
 echo -e "\n"
@@ -1099,7 +1099,7 @@ echo "BACKUP_SYSTEM='local'" >> $HESTIA/conf/hestia.conf
 echo "LANGUAGE='$lang'" >> $HESTIA/conf/hestia.conf
 
 # Version & Release Branch
-echo "VERSION='1.0.4'" >> $HESTIA/conf/hestia.conf
+echo "VERSION='1.0.5'" >> $HESTIA/conf/hestia.conf
 echo "RELEASE_BRANCH='release'" >> $HESTIA/conf/hestia.conf
 
 # Installing hosting packages

+ 2 - 2
install/hst-install-ubuntu.sh

@@ -390,7 +390,7 @@ echo ' |  _  |  __/\__ \ |_| | (_| | |___|  __/ '
 echo ' |_| |_|\___||___/\__|_|\__,_|\____|_|    '
 echo
 echo '                      Hestia Control Panel'
-echo '                                    v1.0.4'
+echo '                                    v1.0.5'
 echo -e "\n"
 echo "===================================================================="
 echo -e "\n"
@@ -1061,7 +1061,7 @@ echo "BACKUP_SYSTEM='local'" >> $HESTIA/conf/hestia.conf
 echo "LANGUAGE='$lang'" >> $HESTIA/conf/hestia.conf
 
 # Version & Release Branch
-echo "VERSION='1.0.4'" >> $HESTIA/conf/hestia.conf
+echo "VERSION='1.0.5'" >> $HESTIA/conf/hestia.conf
 echo "RELEASE_BRANCH='release'" >> $HESTIA/conf/hestia.conf
 
 # Installing hosting packages

+ 6 - 1
install/upgrade/version.sh

@@ -40,6 +40,11 @@ if [ $VERSION = "1.0.2" ]; then
 fi
 
 if [ $VERSION = "1.0.3" ]; then
+    source /usr/local/hestia/install/upgrade/versions/1.0.4.sh
+    VERSION="1.0.4"
+fi
+
+if [ $VERSION = "1.0.4" ]; then
     source /usr/local/hestia/install/upgrade/versions/$version.sh
     VERSION="$version"
-fi
+fi

+ 8 - 0
install/upgrade/versions/1.0.5.sh

@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Hestia Control Panel upgrade script for target version 1.0.5
+
+#######################################################################################
+#######                      Place additional commands below.                   #######
+#######################################################################################
+

+ 1 - 1
src/deb/hestia/control

@@ -1,7 +1,7 @@
 Source: hestia
 Package: hestia
 Priority: optional
-Version: 1.0.4
+Version: 1.0.5
 Section: admin
 Maintainer: HestiaCP <[email protected]>
 Homepage: https://www.hestiacp.com

+ 6 - 0
web/bulk/backup/exclusions/index.php

@@ -9,6 +9,12 @@ include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
 $backup = $_POST['system'];
 $action = $_POST['action'];
 
+// Check token
+if ((!isset($_POST['token'])) || ($_SESSION['token'] != $_POST['token'])) {
+    header('Location: /login/');
+    exit();
+}
+
 switch ($action) {
     case 'delete': $cmd='v-delete-user-backup-exclusions';
         break;

+ 6 - 0
web/delete/backup/exclusion/index.php

@@ -9,6 +9,12 @@ if (($_SESSION['user'] == 'admin') && (!empty($_GET['user']))) {
     $user=$_GET['user'];
 }
 
+// Check token
+if ((!isset($_GET['token'])) || ($_SESSION['token'] != $_GET['token'])) {
+    header('Location: /login/');
+    exit();
+}
+
 if (!empty($_GET['system'])) {
     $v_username = escapeshellarg($user);
     $v_system = escapeshellarg($_GET['system']);

+ 0 - 2
web/download/web-log/index.php

@@ -24,5 +24,3 @@ if ($return_var == 0 ) {
         echo $file . "\n";
     }
 }
-
-?>

+ 17 - 2
web/edit/file/index.php

@@ -29,6 +29,20 @@ if (($_SESSION['user'] == 'admin') && (!empty($_SESSION['look']))) {
         $content = '';
         $path = $_REQUEST['path'];
         if (!empty($_POST['save'])) {
+
+            // Check token
+            if ((!isset($_POST['token'])) || ($_SESSION['token'] != $_POST['token'])) {
+                header('Location: /login/');
+                exit();
+            }
+
+            exec (HESTIA_CMD . "v-open-fs-file ".escapeshellarg($user)." ".escapeshellarg($path), $devnull, $return_var);
+            if ($return_var != 0) {
+                print 'Error while opening file';
+                exit;
+            }
+            $devnull=null;
+
             $fn = tempnam ('/tmp', 'vst-save-file-');
             if ($fn) {
                 $contents = $_POST['contents'];
@@ -39,7 +53,7 @@ if (($_SESSION['user'] == 'admin') && (!empty($_SESSION['look']))) {
                 chmod($fn, 0644);
 
                 if ($f) {
-                    exec (HESTIA_CMD . "v-copy-fs-file {$user} {$fn} ".escapeshellarg($path), $output, $return_var);
+                    exec (HESTIA_CMD . "v-copy-fs-file ".escapeshellarg($user)." ".escapeshellarg($fn)." ".escapeshellarg($path), $output, $return_var);
                     $error = check_return_code($return_var, $output);
                     if ($return_var != 0) {
                         print('<p style="color: white">Error while saving file</p>');
@@ -50,7 +64,7 @@ if (($_SESSION['user'] == 'admin') && (!empty($_SESSION['look']))) {
             }
         }
 
-        exec (HESTIA_CMD . "v-open-fs-file {$user} ".escapeshellarg($path), $content, $return_var);
+        exec (HESTIA_CMD . "v-open-fs-file ".escapeshellarg($user)." ".escapeshellarg($path), $content, $return_var);
         if ($return_var != 0) {
             print 'Error while opening file'; // todo: handle this more styled
             exit;
@@ -64,6 +78,7 @@ if (($_SESSION['user'] == 'admin') && (!empty($_SESSION['look']))) {
 <form id="edit-file-form" method="post">
 <!-- input id="do-backup" type="button" onClick="javascript:void(0);" name="save" value="backup (ctrl+F2)" class="backup" / -->
 <input type="submit" name="save" value="Save" class="save" />
+<input type="hidden" name="token" value="<?=$_SESSION['token']?>" />
 
 
 <textarea name="contents" class="editor" id="editor" rows="4" style="display:none;width: 100%; height: 100%;"><?=htmlentities($content)?></textarea>

+ 7 - 0
web/edit/ip/index.php

@@ -51,6 +51,13 @@ unset($output);
 
 // Check POST request
 if (!empty($_POST['save'])) {
+
+    // Check token
+    if ((!isset($_POST['token'])) || ($_SESSION['token'] != $_POST['token'])) {
+        header('Location: /login/');
+        exit();
+    }
+
     $v_ip = escapeshellarg($_POST['v_ip']);
 
     // Change Status

+ 6 - 0
web/generate/ssl/index.php

@@ -27,6 +27,12 @@ if (!isset($_POST['generate'])) {
     exit;
 }
 
+// Check token
+if ((!isset($_POST['token'])) || ($_SESSION['token'] != $_POST['token'])) {
+    header('Location: /login/');
+    exit();
+}
+
 // Check input
 if (empty($_POST['v_domain'])) $errors[] = __('Domain');
 if (empty($_POST['v_country'])) $errors[] = __('Country');

+ 6 - 0
web/restart/service/index.php

@@ -5,6 +5,12 @@ ob_start();
 session_start();
 include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
 
+// Check token
+if ((!isset($_GET['token'])) || ($_SESSION['token'] != $_GET['token'])) {
+    header('Location: /login/');
+    exit();
+}
+
 if ($_SESSION['user'] == 'admin') {
     if (!empty($_GET['srv'])) {
         if ($_GET['srv'] == 'iptables') {

+ 6 - 0
web/schedule/backup/index.php

@@ -5,6 +5,12 @@ ob_start();
 session_start();
 include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
 
+// Check token
+if ((!isset($_GET['token'])) || ($_SESSION['token'] != $_GET['token'])) {
+    header('Location: /login/');
+    exit();
+}
+
 $v_username = escapeshellarg($user);
 exec (HESTIA_CMD."v-schedule-user-backup ".$v_username, $output, $return_var);
 if ($return_var == 0) {

+ 6 - 0
web/schedule/restore/index.php

@@ -6,6 +6,12 @@ session_start();
 
 include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
 
+// Check token
+if ((!isset($_GET['token'])) || ($_SESSION['token'] != $_GET['token'])) {
+    header('Location: /login/');
+    exit();
+}
+
 $backup = escapeshellarg($_GET['backup']);
 
 $web = 'no';

+ 6 - 0
web/start/service/index.php

@@ -5,6 +5,12 @@ ob_start();
 session_start();
 include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
 
+// Check token
+if ((!isset($_GET['token'])) || ($_SESSION['token'] != $_GET['token'])) {
+    header('Location: /login/');
+    exit();
+}
+
 if ($_SESSION['user'] == 'admin') {
     if (!empty($_GET['srv'])) {
         if ($_GET['srv'] == 'iptables') {

+ 6 - 0
web/stop/service/index.php

@@ -5,6 +5,12 @@ ob_start();
 session_start();
 include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
 
+// Check token
+if ((!isset($_GET['token'])) || ($_SESSION['token'] != $_GET['token'])) {
+    header('Location: /login/');
+    exit();
+}
+
 if ($_SESSION['user'] == 'admin') {
     if (!empty($_GET['srv'])) {
         if ($_GET['srv'] == 'iptables') {

+ 1 - 1
web/templates/admin/list_backup.html

@@ -1,7 +1,7 @@
     <div class="l-center">
       <div class="l-sort clearfix noselect">
         <div class="l-unit-toolbar__buttonstrip">
-          <a href="/schedule/backup/" class="ui-button cancel" title="<?=__('Create Backup')?>"><i class="fas fa-plus-circle status-icon green"></i> <?=__('Create Backup')?></a>
+          <a href="/schedule/backup/?token=<?=$_SESSION['token']?>" class="ui-button cancel" title="<?=__('Create Backup')?>"><i class="fas fa-plus-circle status-icon green"></i> <?=__('Create Backup')?></a>
           <a href="/list/backup/exclusions/" class="ui-button cancel" title="<?=__('backup exclusions')?>"><i class="fas fa-folder-minus status-icon orange"></i> <?=__('backup exclusions')?></a>
         </div>
         <div class="l-sort-toolbar clearfix">

+ 1 - 1
web/templates/admin/list_backup_detail.html

@@ -2,7 +2,7 @@
       <div class="l-sort clearfix noselect">
         <div class="l-unit-toolbar__buttonstrip">
           <a class="ui-button cancel" id="btn-back" href="/list/backup/"><i class="fas fa-arrow-left status-icon blue"></i> <?=__('Back')?></a>
-          <a href="/schedule/restore/?backup=<?=htmlentities($_GET['backup'])?>" class="ui-button cancel" title="<?=__('Restore All')?>"><i class="fas fa-undo status-icon green"></i> <?=__('Restore All')?></a>
+          <a href="/schedule/restore/?token=<?=$_SESSION['token']?>&backup=<?=htmlentities($_GET['backup'])?>" class="ui-button cancel" title="<?=__('Restore All')?>"><i class="fas fa-undo status-icon green"></i> <?=__('Restore All')?></a>
         </div>
         <div class="l-sort-toolbar clearfix">
           <table>

+ 20 - 4
web/upload/UploadHandler.php

@@ -92,6 +92,14 @@ class UploadHandler
                 'Content-Range',
                 'Content-Disposition'
             ),
+            // By default, allow redirects to the referer protocol+host:
+            'redirect_allow_target' => '/^'.preg_quote(
+                parse_url($_SERVER['HTTP_REFERER'], PHP_URL_SCHEME)
+                .'://'
+                .parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST)
+                .'/', // Trailing slash to not match subdomains by mistake
+                '/' // preg_quote delimiter param
+            ).'/',
             // Enable to provide file downloads via GET requests to the PHP script:
             //     1. Set to 1 to download files via readfile method through PHP
             //     2. Set to 2 to send a X-Sendfile header for lighttpd/Apache
@@ -1118,7 +1126,7 @@ class UploadHandler
                 $file->size > $this->get_file_size($file_path);
             if ($uploaded_file && is_uploaded_file($uploaded_file)) {
                 chmod($uploaded_file, 0644);
-                exec (HESTIA_CMD . "v-copy-fs-file ". USERNAME ." {$uploaded_file} '{$file_path}'", $output, $return_var);
+                exec (HESTIA_CMD . "v-copy-fs-file ".escapeshellarg(USERNAME)." ".escapeshellarg($uploaded_file)." ".escapeshellarg($file_path), $output, $return_var);
                 $error = check_return_code($return_var, $output);
                 if ($return_var != 0) {
                     $file->error = 'Error while saving file ';
@@ -1177,7 +1185,7 @@ class UploadHandler
             $json = json_encode($content);
             $redirect = isset($_REQUEST['redirect']) ?
                 stripslashes($_REQUEST['redirect']) : null;
-            if ($redirect) {
+            if ($redirect && preg_match($this->options['redirect_allow_target'], $redirect)) {
                 $this->header('Location: '.sprintf($redirect, rawurlencode($json)));
                 return;
             }
@@ -1377,6 +1385,14 @@ class UploadHandler
         );
     }
 
+    private function _cmd_v_delete_fs_file($file) {
+        if (empty($file)) {
+            return false;
+        }
+        exec (HESTIA_CMD . "v-delete-fs-file ".escapeshellarg(USERNAME)." ".escapeshellarg($file), $output, $return_var);
+        return ($return_var === 0);
+    }
+
     public function delete($print_response = true) {
         $file_names = $this->get_file_names_params();
         if (empty($file_names)) {
@@ -1385,13 +1401,13 @@ class UploadHandler
         $response = array();
         foreach($file_names as $file_name) {
             $file_path = $this->get_upload_path($file_name);
-            $success = is_file($file_path) && $file_name[0] !== '.' && unlink($file_path);
+            $success = is_file($file_path) && $file_name[0] !== '.' && $this->_cmd_v_delete_fs_file($file_path);
             if ($success) {
                 foreach($this->options['image_versions'] as $version => $options) {
                     if (!empty($version)) {
                         $file = $this->get_upload_path($file_name, $version);
                         if (is_file($file)) {
-                            unlink($file);
+                            $this->_cmd_v_delete_fs_file($file);
                         }
                     }
                 }