From 222eb47bd23f81f82d8e51dcadec8dd85ab8b469 Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Thu, 7 May 2020 10:13:46 +0200 Subject: [PATCH 1/7] =?UTF-8?q?N=C2=B02989=20ajax.backup=20:=20refactor=20?= =?UTF-8?q?exit=20conditions=20Adding=20a=20die()=20call=20so=20that=20we?= =?UTF-8?q?=20are=20sure=20to=20exit=20on=20errors=20!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- datamodels/2.x/itop-backup/ajax.backup.php | 168 ++++++++++++--------- 1 file changed, 93 insertions(+), 75 deletions(-) diff --git a/datamodels/2.x/itop-backup/ajax.backup.php b/datamodels/2.x/itop-backup/ajax.backup.php index 76e70ff72..e9b57ea2d 100644 --- a/datamodels/2.x/itop-backup/ajax.backup.php +++ b/datamodels/2.x/itop-backup/ajax.backup.php @@ -31,6 +31,26 @@ require_once(APPROOT.'/application/ajaxwebpage.class.inc.php'); require_once(APPROOT.'core/mutex.class.inc.php'); + +/** + * @param WebPage $oPage + * @param string $sHtmlErrorMessage the whole HTML error, cinluding div/p/... + * @param int|string $exitCode + * + * @uses \die() https://www.php.net/manual/fr/function.die.php + * + * @since 2.6.5 2.7.1 N°2989 + */ +function DisplayErrorAndDie($oPage, $sHtmlErrorMessage, $exitCode = null) +{ + $oPage->add($sHtmlErrorMessage); + $oPage->output(); + + die($exitCode); +} + + + try { $sOperation = utils::ReadParam('operation', ''); @@ -48,22 +68,21 @@ try if (utils::GetConfig()->Get('demo_mode')) { - $oPage->add("
Sorry, iTop is in demonstration mode: the feature is disabled.
"); + DisplayErrorAndDie($oPage, '
Sorry, iTop is in demonstration mode: the feature is disabled.
'); } - else + + try { - try - { - set_time_limit(0); - $oBB = new BackupExec(APPROOT.'data/backups/manual/', 0 /*iRetentionCount*/); - $sRes = $oBB->Process(time() + 36000); // 10 hours to complete should be sufficient! - } - catch (Exception $e) - { - $oPage->p('Error: '.$e->getMessage()); - IssueLog::Error($sOperation.' - '.$e->getMessage()); - } + set_time_limit(0); + $oBB = new BackupExec(APPROOT.'data/backups/manual/', 0 /*iRetentionCount*/); + $sRes = $oBB->Process(time() + 36000); // 10 hours to complete should be sufficient! } + catch (Exception $e) + { + $oPage->p('Error: '.$e->getMessage()); + IssueLog::Error($sOperation.' - '.$e->getMessage()); + } + $oPage->output(); break; @@ -85,23 +104,22 @@ try $sEnvironment = utils::ReadParam('environment', 'production', false, 'raw_data'); $oRestoreMutex = new iTopMutex('restore.'.$sEnvironment); - if (!$oRestoreMutex->IsLocked()) + if ($oRestoreMutex->IsLocked()) { - $sFile = utils::ReadParam('file', '', false, 'raw_data'); - $sToken = str_replace(' ', '', (string)microtime()); - $sTokenFile = APPROOT.'/data/restore.'.$sToken.'.tok'; - file_put_contents($sTokenFile, $sFile); + DisplayErrorAndDie($oPage, '

'.Dict::S('bkp-restore-running').'

'); + } + + $sFile = utils::ReadParam('file', '', false, 'raw_data'); + $sToken = str_replace(' ', '', (string)microtime()); + $sTokenFile = APPROOT.'/data/restore.'.$sToken.'.tok'; + file_put_contents($sTokenFile, $sFile); + + $oPage->add_ready_script( + <<add_ready_script( - <<p(Dict::S('bkp-restore-running')); - } $oPage->output(); break; @@ -123,51 +141,54 @@ EOF if (utils::GetConfig()->Get('demo_mode')) { - $oPage->add("
Sorry, iTop is in demonstration mode: the feature is disabled.
"); + DisplayErrorAndDie($oPage, '
Sorry, iTop is in demonstration mode: the feature is disabled.
'); } - else + + $sToken = utils::ReadParam('token', '', false, 'raw_data'); + $sTokenFile = APPROOT.'/data/restore.'.$sToken.'.tok'; + if (!is_file($sTokenFile)) { - $sEnvironment = utils::ReadParam('environment', 'production', false, 'raw_data'); - $oRestoreMutex = new iTopMutex('restore.'.$sEnvironment); - IssueLog::Info("Backup Restore - Acquiring the LOCK 'restore.$sEnvironment'"); - $oRestoreMutex->Lock(); - IssueLog::Info('Backup Restore - LOCK acquired, executing...'); - try - { - set_time_limit(0); - - // Get the file and destroy the token (single usage) - $sToken = utils::ReadParam('token', '', false, 'raw_data'); - $sTokenFile = APPROOT.'/data/restore.'.$sToken.'.tok'; - if (!is_file($sTokenFile)) - { - throw new Exception("Error: missing token file: '$sTokenFile'"); - } - $sFile = file_get_contents($sTokenFile); - unlink($sTokenFile); - - // Loading config file : we don't have the MetaModel but we have the current env ! - $sConfigFilePath = utils::GetConfigFilePath($sEnvironment); - $oItopConfig = new Config($sConfigFilePath, true); - $sMySQLBinDir = $oItopConfig->GetModuleSetting('itop-backup', 'mysql_bindir', ''); - - $oDBRS = new DBRestore($oItopConfig); - $oDBRS->SetMySQLBinDir($sMySQLBinDir); - - $sBackupDir = APPROOT.'data/backups/'; - $sBackupFile = $sBackupDir.$sFile; - $sRes = $oDBRS->RestoreFromCompressedBackup($sBackupFile, $sEnvironment); - - IssueLog::Info('Backup Restore - Done, releasing the LOCK'); - $oRestoreMutex->Unlock(); - } - catch (Exception $e) - { - $oRestoreMutex->Unlock(); - $oPage->p('Error: '.$e->getMessage()); - IssueLog::Error($sOperation.' - '.$e->getMessage()); - } + IssueLog::Error("ajax.backup.php operation=$sOperation ERROR = inexisting token $sToken"); + DisplayErrorAndDie($oPage, "

Error: missing token file: '$sTokenFile'

"); } + + $sEnvironment = utils::ReadParam('environment', 'production', false, 'raw_data'); + $oRestoreMutex = new iTopMutex('restore.'.$sEnvironment); + IssueLog::Info("Backup Restore - Acquiring the LOCK 'restore.$sEnvironment'"); + $oRestoreMutex->Lock(); + IssueLog::Info('Backup Restore - LOCK acquired, executing...'); + try + { + set_time_limit(0); + + // Get the file and destroy the token (single usage) + $sFile = file_get_contents($sTokenFile); + unlink($sTokenFile); + + // Loading config file : we don't have the MetaModel but we have the current env ! + $sConfigFilePath = utils::GetConfigFilePath($sEnvironment); + $oItopConfig = new Config($sConfigFilePath, true); + $sMySQLBinDir = $oItopConfig->GetModuleSetting('itop-backup', 'mysql_bindir', ''); + + $oDBRS = new DBRestore($oItopConfig); + $oDBRS->SetMySQLBinDir($sMySQLBinDir); + + $sBackupDir = APPROOT.'data/backups/'; + $sBackupFile = $sBackupDir.$sFile; + $sRes = $oDBRS->RestoreFromCompressedBackup($sBackupFile, $sEnvironment); + + IssueLog::Info('Backup Restore - Done, releasing the LOCK'); + } + catch (Exception $e) + { + $oPage->p('Error: '.$e->getMessage()); + IssueLog::Error($sOperation.' - '.$e->getMessage()); + } + finally + { + $oRestoreMutex->Unlock(); + } + $oPage->output(); break; @@ -185,14 +206,11 @@ EOF $oBackup = new DBBackupScheduled(); $sBackupDir = APPROOT.'data/backups/'; $sPathNoDotDotPattern = "/^((?![\/\\\\]\.\.[\/\\\\]).)*$/"; - if(preg_match($sPathNoDotDotPattern, $sBackupDir.$sFile) == 1) - { - $oBackup->DownloadBackup($sBackupDir.$sFile); - } - else + if(preg_match($sPathNoDotDotPattern, $sBackupDir.$sFile) != 1) { throw new InvalidParameterException('Invalid file path'); } + $oBackup->DownloadBackup($sBackupDir.$sFile); break; } } From 0a3f7d7ef7b94e3af4a77987d2ba59e7d3702531 Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Thu, 7 May 2020 11:15:48 +0200 Subject: [PATCH 2/7] =?UTF-8?q?N=C2=B02989=20ajax.backup=20small=20updates?= =?UTF-8?q?=20*=20update=20copyright=20*=20in=20messages=20replace=20iTop?= =?UTF-8?q?=20by=20constant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- datamodels/2.x/itop-backup/ajax.backup.php | 44 ++++++++++------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/datamodels/2.x/itop-backup/ajax.backup.php b/datamodels/2.x/itop-backup/ajax.backup.php index e9b57ea2d..7a28abfe5 100644 --- a/datamodels/2.x/itop-backup/ajax.backup.php +++ b/datamodels/2.x/itop-backup/ajax.backup.php @@ -1,26 +1,20 @@ - /** - * Backup from an interactive session + * Copyright (C) 2010-2020 Combodo SARL * - * @copyright Copyright (C) 2013-2017 Combodo SARL - * @license http://opensource.org/licenses/AGPL-3.0 + * This file is part of iTop. + * + * iTop is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * iTop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License */ if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__)); @@ -50,7 +44,9 @@ function DisplayErrorAndDie($oPage, $sHtmlErrorMessage, $exitCode = null) } - +/** + * Backup from an interactive session + */ try { $sOperation = utils::ReadParam('operation', ''); @@ -68,7 +64,7 @@ try if (utils::GetConfig()->Get('demo_mode')) { - DisplayErrorAndDie($oPage, '
Sorry, iTop is in demonstration mode: the feature is disabled.
'); + DisplayErrorAndDie($oPage, '
Sorry, '.ITOP_APPLICATION_SHORT.' is in demonstration mode: the feature is disabled.
'); } try @@ -141,7 +137,7 @@ JS if (utils::GetConfig()->Get('demo_mode')) { - DisplayErrorAndDie($oPage, '
Sorry, iTop is in demonstration mode: the feature is disabled.
'); + DisplayErrorAndDie($oPage, '
Sorry, '.ITOP_APPLICATION_SHORT.' is in demonstration mode: the feature is disabled.
'); } $sToken = utils::ReadParam('token', '', false, 'raw_data'); @@ -200,7 +196,7 @@ JS if (utils::GetConfig()->Get('demo_mode')) { - throw new Exception('iTop is in demonstration mode: the feature is disabled'); + throw new Exception(ITOP_APPLICATION_SHORT.' is in demonstration mode: the feature is disabled'); } $sFile = utils::ReadParam('file', '', false, 'raw_data'); $oBackup = new DBBackupScheduled(); From f8e39877b39dd0e4136ce8162736667888205184 Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Thu, 7 May 2020 11:32:56 +0200 Subject: [PATCH 3/7] =?UTF-8?q?N=C2=B02988=20Security=20hardening?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/utils.inc.php | 29 ++++++++++++++++++++++ datamodels/2.x/itop-backup/ajax.backup.php | 13 ++++++---- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/application/utils.inc.php b/application/utils.inc.php index 2cf915aa2..e6f231120 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -2048,4 +2048,33 @@ class utils { return ITOP_REVISION === 'svn'; } + + /** + * @param string $sPath for example '/var/www/html/itop/data/backups/manual/itop_27-2019-10-03_15_35.tar.gz' + * @param string $sBasePath for example '/var/www/html/itop/data/' + * + * @return bool false if path : + * * invalid + * * not allowed + * * not contained in base path + * Otherwise return the real path (see realpath()) + * + * @since 2.6.5 2.7.0 N°2538 + */ + final public static function RealPath($sPath, $sBasePath) + { + $sFileRealPath = realpath($sPath); + if ($sFileRealPath === false) + { + return false; + } + + $sRealBasePath = realpath($sBasePath); // avoid problems when having '/' on Windows for example + if (!self::StartsWith($sFileRealPath, $sRealBasePath)) + { + return false; + } + + return $sFileRealPath; + } } diff --git a/datamodels/2.x/itop-backup/ajax.backup.php b/datamodels/2.x/itop-backup/ajax.backup.php index 7a28abfe5..b81e720d4 100644 --- a/datamodels/2.x/itop-backup/ajax.backup.php +++ b/datamodels/2.x/itop-backup/ajax.backup.php @@ -141,11 +141,14 @@ JS } $sToken = utils::ReadParam('token', '', false, 'raw_data'); - $sTokenFile = APPROOT.'/data/restore.'.$sToken.'.tok'; - if (!is_file($sTokenFile)) + $sBasePath = APPROOT.'/data/'; + $sTokenFile = $sBasePath.'restore.'.$sToken.'.tok'; + $tokenRealPath = utils::RealPath($sTokenFile, $sBasePath); + if (($tokenRealPath === false) || (!is_file($tokenRealPath))) { IssueLog::Error("ajax.backup.php operation=$sOperation ERROR = inexisting token $sToken"); - DisplayErrorAndDie($oPage, "

Error: missing token file: '$sTokenFile'

"); + $sEscapedToken = utils::HtmlEntities($sToken); + DisplayErrorAndDie($oPage, "

Error: missing token file: '$sEscapedToken'

"); } $sEnvironment = utils::ReadParam('environment', 'production', false, 'raw_data'); @@ -158,8 +161,8 @@ JS set_time_limit(0); // Get the file and destroy the token (single usage) - $sFile = file_get_contents($sTokenFile); - unlink($sTokenFile); + $sFile = file_get_contents($tokenRealPath); + unlink($tokenRealPath); // Loading config file : we don't have the MetaModel but we have the current env ! $sConfigFilePath = utils::GetConfigFilePath($sEnvironment); From 30d10b6f11e055bec0793c88f6f4ee72585e5efb Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Tue, 12 May 2020 09:40:58 +0200 Subject: [PATCH 4/7] =?UTF-8?q?N=C2=B02990=20Security=20hardening?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/audit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/audit.php b/pages/audit.php index d4558bef1..c60216783 100644 --- a/pages/audit.php +++ b/pages/audit.php @@ -353,7 +353,7 @@ try $aRow['nb_errors'] = "n/a"; $aRow['percent_ok'] = ''; $aRow['class'] = 'red'; - $sMessage = Dict::Format('UI:Audit:ErrorIn_Category_Reason', $oAuditCategory->GetHyperlink(), $e->getMessage()); + $sMessage = Dict::Format('UI:Audit:ErrorIn_Category_Reason', $oAuditCategory->GetHyperlink(), utils::HtmlEntities($e->getMessage())); $oP->p(" ".$sMessage); $aResults[] = $aRow; From 3c9318d56a8f1cf87cc94a32466fc0e5b83c7fd0 Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Tue, 12 May 2020 09:41:24 +0200 Subject: [PATCH 5/7] =?UTF-8?q?N=C2=B02990=20Fix=20count=20warning=20on=20?= =?UTF-8?q?audit=20OQL=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/audit.php | 1 + 1 file changed, 1 insertion(+) diff --git a/pages/audit.php b/pages/audit.php index c60216783..268adad5a 100644 --- a/pages/audit.php +++ b/pages/audit.php @@ -287,6 +287,7 @@ try { try { + $iCount = 0; $oDefinitionFilter = DBObjectSearch::FromOQL($oAuditCategory->Get('definition_set')); $oDefinitionFilter->UpdateContextFromUser(); FilterByContext($oDefinitionFilter, $oAppContext); From 834297e675a9d219a36a78396b60719f56eb751f Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Wed, 13 May 2020 10:04:40 +0200 Subject: [PATCH 6/7] =?UTF-8?q?N=C2=B02985=20Security=20hardening=20(#140)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks @bruno-ds for the review ! --- application/utils.inc.php | 33 ++++++ datamodels/2.x/itop-backup/ajax.backup.php | 130 +++++++++++---------- datamodels/2.x/itop-backup/status.php | 68 ++++++----- 3 files changed, 140 insertions(+), 91 deletions(-) diff --git a/application/utils.inc.php b/application/utils.inc.php index e6f231120..08d816459 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -2049,6 +2049,39 @@ class utils return ITOP_REVISION === 'svn'; } + /** + * helper to test if a string starts with another + * @param $haystack + * @param $needle + * + * @return bool + */ + final public static function StartsWith($haystack, $needle) + { + if (strlen($needle) > strlen($haystack)) + { + return false; + } + + return substr_compare($haystack, $needle, 0, strlen($needle)) === 0; + } + + /** + * helper to test if a string ends with another + * @param $haystack + * @param $needle + * + * @return bool + */ + final public static function EndsWith($haystack, $needle) { + if (strlen($needle) > strlen($haystack)) + { + return false; + } + + return substr_compare($haystack, $needle, -strlen($needle)) === 0; + } + /** * @param string $sPath for example '/var/www/html/itop/data/backups/manual/itop_27-2019-10-03_15_35.tar.gz' * @param string $sBasePath for example '/var/www/html/itop/data/' diff --git a/datamodels/2.x/itop-backup/ajax.backup.php b/datamodels/2.x/itop-backup/ajax.backup.php index b81e720d4..efd93dd70 100644 --- a/datamodels/2.x/itop-backup/ajax.backup.php +++ b/datamodels/2.x/itop-backup/ajax.backup.php @@ -44,29 +44,80 @@ function DisplayErrorAndDie($oPage, $sHtmlErrorMessage, $exitCode = null) } + + + + +$sOperation = utils::ReadParam('operation', ''); + +$oPage = new ajax_page(''); +$oPage->no_cache(); +$oPage->SetContentType('text/html'); + + + +/** + * Check security + */ +switch ($sOperation) +{ + /** + * Can't use normal check methods (DoLogin for ex) as the datamodel can't be loaded here + * So we're only using a token generated in the restore_token operation + */ + case 'restore_exec': + IssueLog::Enable(APPROOT.'log/error.log'); + if (utils::GetConfig()->Get('demo_mode')) + { + DisplayErrorAndDie($oPage, '
Sorry, '.ITOP_APPLICATION_SHORT.' is in demonstration mode: the feature is disabled.
'); + } + + $sToken = utils::ReadParam('token', '', false, 'raw_data'); + $sBasePath = APPROOT.'/data/'; + $sTokenFile = $sBasePath.'restore.'.$sToken.'.tok'; + $tokenRealPath = utils::RealPath($sTokenFile, $sBasePath); + if (($tokenRealPath === false) || (!is_file($tokenRealPath))) + { + IssueLog::Error("ajax.backup.php operation=$sOperation ERROR = inexisting token $sToken"); + $sEscapedToken = utils::HtmlEntities($sToken); + DisplayErrorAndDie($oPage, "

Error: missing token file: '$sEscapedToken'

"); + } + + break; + + default: + require_once(APPROOT.'/application/startup.inc.php'); + require_once(APPROOT.'/application/loginwebpage.class.inc.php'); + + LoginWebPage::DoLogin(); + + $sTransactionId = utils::ReadParam('transaction_id', '', true, 'transaction_id'); + // the consumer page is not reloaded after download, we need to keep the transaction_id + $bRemoveTransactionId = ($sOperation !== 'download'); + if (!utils::IsTransactionValid($sTransactionId, $bRemoveTransactionId)) + { + $sEscapedOperation = utils::HtmlEntities($sOperation); + DisplayErrorAndDie($oPage, "
Error: invalid Transaction ID. The operation '$sEscapedOperation' was NOT performed!
"); + } + + ApplicationMenu::CheckMenuIdEnabled('BackupStatus'); + + if (utils::GetConfig()->Get('demo_mode')) + { + DisplayErrorAndDie($oPage, '
Sorry, '.ITOP_APPLICATION_SHORT.' is in demonstration mode: the feature is disabled.
'); + } + break; +} + + /** * Backup from an interactive session */ try { - $sOperation = utils::ReadParam('operation', ''); - switch ($sOperation) { case 'backup': - require_once(APPROOT.'/application/startup.inc.php'); - require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - LoginWebPage::DoLogin(); // Check user rights and prompt if needed - ApplicationMenu::CheckMenuIdEnabled('BackupStatus'); - $oPage = new ajax_page(""); - $oPage->no_cache(); - $oPage->SetContentType('text/html'); - - if (utils::GetConfig()->Get('demo_mode')) - { - DisplayErrorAndDie($oPage, '
Sorry, '.ITOP_APPLICATION_SHORT.' is in demonstration mode: the feature is disabled.
'); - } - try { set_time_limit(0); @@ -83,21 +134,13 @@ try break; /* - * Fix a token : + * Fix a specific token : * We can't load the MetaModel because in DBRestore, after restore is done we're launching a compile ! - * So as \LoginWebPage::DoLogin needs a loaded DataModel, we can't use it + * So as LoginWebPage::DoLogin needs a loaded DataModel, we can't use it + * Also, we can't use \utils::IsTransactionValid as it uses \MetaModel::GetConfig * As a result we're setting a token file to make sure the restore is called by an authenticated user with the correct rights ! */ case 'restore_get_token': - require_once(APPROOT.'/application/startup.inc.php'); - require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - LoginWebPage::DoLogin(); // Check user rights and prompt if needed - ApplicationMenu::CheckMenuIdEnabled('BackupStatus'); - - $oPage = new ajax_page(""); - $oPage->no_cache(); - $oPage->SetContentType('text/html'); - $sEnvironment = utils::ReadParam('environment', 'production', false, 'raw_data'); $oRestoreMutex = new iTopMutex('restore.'.$sEnvironment); if ($oRestoreMutex->IsLocked()) @@ -106,7 +149,7 @@ try } $sFile = utils::ReadParam('file', '', false, 'raw_data'); - $sToken = str_replace(' ', '', (string)microtime()); + $sToken = str_replace(' ', '', (string)microtime()).$sTransactionId; $sTokenFile = APPROOT.'/data/restore.'.$sToken.'.tok'; file_put_contents($sTokenFile, $sFile); @@ -129,28 +172,6 @@ JS require_once(APPROOT.'/setup/backup.class.inc.php'); require_once(dirname(__FILE__).'/dbrestore.class.inc.php'); - IssueLog::Enable(APPROOT.'log/error.log'); - - $oPage = new ajax_page(""); - $oPage->no_cache(); - $oPage->SetContentType('text/html'); - - if (utils::GetConfig()->Get('demo_mode')) - { - DisplayErrorAndDie($oPage, '
Sorry, '.ITOP_APPLICATION_SHORT.' is in demonstration mode: the feature is disabled.
'); - } - - $sToken = utils::ReadParam('token', '', false, 'raw_data'); - $sBasePath = APPROOT.'/data/'; - $sTokenFile = $sBasePath.'restore.'.$sToken.'.tok'; - $tokenRealPath = utils::RealPath($sTokenFile, $sBasePath); - if (($tokenRealPath === false) || (!is_file($tokenRealPath))) - { - IssueLog::Error("ajax.backup.php operation=$sOperation ERROR = inexisting token $sToken"); - $sEscapedToken = utils::HtmlEntities($sToken); - DisplayErrorAndDie($oPage, "

Error: missing token file: '$sEscapedToken'

"); - } - $sEnvironment = utils::ReadParam('environment', 'production', false, 'raw_data'); $oRestoreMutex = new iTopMutex('restore.'.$sEnvironment); IssueLog::Info("Backup Restore - Acquiring the LOCK 'restore.$sEnvironment'"); @@ -162,7 +183,6 @@ JS // Get the file and destroy the token (single usage) $sFile = file_get_contents($tokenRealPath); - unlink($tokenRealPath); // Loading config file : we don't have the MetaModel but we have the current env ! $sConfigFilePath = utils::GetConfigFilePath($sEnvironment); @@ -185,6 +205,7 @@ JS } finally { + unlink($tokenRealPath); $oRestoreMutex->Unlock(); } @@ -192,15 +213,6 @@ JS break; case 'download': - require_once(APPROOT.'/application/startup.inc.php'); - require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - LoginWebPage::DoLogin(); // Check user rights and prompt if needed - ApplicationMenu::CheckMenuIdEnabled('BackupStatus'); - - if (utils::GetConfig()->Get('demo_mode')) - { - throw new Exception(ITOP_APPLICATION_SHORT.' is in demonstration mode: the feature is disabled'); - } $sFile = utils::ReadParam('file', '', false, 'raw_data'); $oBackup = new DBBackupScheduled(); $sBackupDir = APPROOT.'data/backups/'; diff --git a/datamodels/2.x/itop-backup/status.php b/datamodels/2.x/itop-backup/status.php index 423901be3..feeeb9b1b 100644 --- a/datamodels/2.x/itop-backup/status.php +++ b/datamodels/2.x/itop-backup/status.php @@ -1,27 +1,20 @@ - - /** - * Monitor the backup + * Copyright (C) 2010-2020 Combodo SARL * - * @copyright Copyright (C) 2016-2018 Combodo SARL - * @license http://opensource.org/licenses/AGPL-3.0 + * This file is part of iTop. + * + * iTop is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * iTop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License */ if (!defined('__DIR__')) define('__DIR__', dirname(__FILE__)); @@ -40,13 +33,9 @@ require_once(APPROOT.'application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed ApplicationMenu::CheckMenuIdEnabled('BackupStatus'); -//$sOperation = utils::ReadParam('operation', 'menu'); -//$oAppContext = new ApplicationContext(); - - - try { + $sTransactionId = utils::GetNewTransactionId(); $oP = new iTopWebPage(Dict::S('bkp-status-title')); $oP->set_base(utils::GetAbsoluteUrlAppRoot().'pages/'); @@ -193,7 +182,13 @@ try } else { - $sAjax = utils::GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php', array('operation' => 'download', 'file' => $sFilePath)); + $sAjax = utils::GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php', + array( + 'operation' => 'download', + 'file' => $sFilePath, + 'transaction_id' => $sTransactionId, + ) + ); $sName = "".$sFileName.''; } $sSize = SetupUtils::HumanReadableSize(filesize($sBackupFile)); @@ -241,7 +236,13 @@ try } else { - $sAjax = utils::GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php', array('operation' => 'download', 'file' => $sFilePath)); + $sAjax = utils::GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php', + array( + 'operation' => 'download', + 'file' => $sFilePath, + 'transaction_id' => $sTransactionId, + ) + ); $sName = "".$sFileName.''; } $sSize = SetupUtils::HumanReadableSize(filesize($sBackupFile)); @@ -305,9 +306,9 @@ try $sDBSubName = addslashes(MetaModel::GetConfig()->Get('db_subname')); $sEnvironment = addslashes(utils::GetCurrentEnvironment()); - + $oP->add_script( -<< 0) { $.post(GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php'), oParams, function(data){ @@ -380,7 +384,7 @@ function LaunchRestoreNow(sBackupFile, sConfirmationMessage) }); } } -EOF +JS ); if (MetaModel::GetConfig()->Get('demo_mode')) From 228a945da966f6b082418e3e35b35b83e18ed411 Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Thu, 14 May 2020 10:59:31 +0200 Subject: [PATCH 7/7] =?UTF-8?q?N=C2=B02984=20Security=20hardening?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 +++++- conf/.htaccess | 13 +++++++++++++ conf/web.config | 13 +++++++++++++ data/web.config | 17 +++++++++++------ log/web.config | 17 +++++++++++------ 5 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 conf/.htaccess create mode 100644 conf/web.config diff --git a/.gitignore b/.gitignore index 845a1a0d6..e205e6fad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,17 @@ # no slash at the end to handle also symlinks /toolkit -/conf /env-* # composer reserver directory, from sources, populate/update using "composer install" vendor/* test/vendor/* +# all conf but listing prevention +/conf/** +!/conf/.htaccess +!/conf/web.config + # all datas but listing prevention /data/** !/data/.htaccess diff --git a/conf/.htaccess b/conf/.htaccess new file mode 100644 index 000000000..782472c78 --- /dev/null +++ b/conf/.htaccess @@ -0,0 +1,13 @@ +# Apache 2.4 + +Require all denied + + +# Apache 2.2 + +deny from all +Satisfy All + + +# Apache 2.2 and 2.4 +IndexIgnore * diff --git a/conf/web.config b/conf/web.config new file mode 100644 index 000000000..58c9c3ac3 --- /dev/null +++ b/conf/web.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/web.config b/data/web.config index 599a5f260..58c9c3ac3 100644 --- a/data/web.config +++ b/data/web.config @@ -1,8 +1,13 @@ - + - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/log/web.config b/log/web.config index 599a5f260..58c9c3ac3 100644 --- a/log/web.config +++ b/log/web.config @@ -1,8 +1,13 @@ - + - - - - - + + + + + + + + + + \ No newline at end of file