N°2985 Security hardening (#140)

Thanks @bruno-ds  for the review !
This commit is contained in:
Pierre Goiffon
2020-05-13 10:04:40 +02:00
committed by GitHub
parent 3c9318d56a
commit 834297e675
3 changed files with 140 additions and 91 deletions

View File

@@ -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/'

View File

@@ -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, '<div data-error-stimulus="Error">Sorry, '.ITOP_APPLICATION_SHORT.' is in <b>demonstration mode</b>: the feature is disabled.</div>');
}
$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, "<p>Error: missing token file: '$sEscapedToken'</p>");
}
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, "<div data-error-stimulus=\"Error\">Error: invalid Transaction ID. The operation '$sEscapedOperation' was NOT performed!</div>");
}
ApplicationMenu::CheckMenuIdEnabled('BackupStatus');
if (utils::GetConfig()->Get('demo_mode'))
{
DisplayErrorAndDie($oPage, '<div data-error-stimulus="Error">Sorry, '.ITOP_APPLICATION_SHORT.' is in <b>demonstration mode</b>: the feature is disabled.</div>');
}
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, '<div data-error-stimulus="Error">Sorry, '.ITOP_APPLICATION_SHORT.' is in <b>demonstration mode</b>: the feature is disabled.</div>');
}
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, '<div data-error-stimulus="Error">Sorry, '.ITOP_APPLICATION_SHORT.' is in <b>demonstration mode</b>: the feature is disabled.</div>');
}
$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, "<p>Error: missing token file: '$sEscapedToken'</p>");
}
$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/';

View File

@@ -1,27 +1,20 @@
<?php
// Copyright (C) 2016-2018 Combodo SARL
//
// 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
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* 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 = "<a href=\"$sAjax\">".$sFileName.'</a>';
}
$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 = "<a href=\"$sAjax\">".$sFileName.'</a>';
}
$sSize = SetupUtils::HumanReadableSize(filesize($sBackupFile));
@@ -305,9 +306,9 @@ try
$sDBSubName = addslashes(MetaModel::GetConfig()->Get('db_subname'));
$sEnvironment = addslashes(utils::GetCurrentEnvironment());
$oP->add_script(
<<<EOF
<<<JS
function LaunchBackupNow()
{
$('#backup_success').hide();
@@ -319,6 +320,7 @@ function LaunchBackupNow()
var oParams = {};
oParams.operation = 'backup';
oParams.transaction_id = "$sTransactionId";
$.post(GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php'), oParams, function(data){
if (data.search(/error|exceptio|notice|warning/i) != -1)
{
@@ -344,7 +346,8 @@ function LaunchRestoreNow(sBackupFile, sConfirmationMessage)
var oParams = {};
oParams.operation = 'restore_get_token';
oParams.file = sBackupFile;
oParams.file = sBackupFile;
oParams.transaction_id = "$sTransactionId";
$.post(GetAbsoluteUrlModulePage('itop-backup', 'ajax.backup.php'), oParams, function(data){
// Get the value of restore_token
@@ -354,6 +357,7 @@ function LaunchRestoreNow(sBackupFile, sConfirmationMessage)
oParams.operation = 'restore_exec';
oParams.token = $("#restore_token").val(); // token to check auth + rights without loading MetaModel
oParams.environment = '$sEnvironment'; // needed to load the config
oParams.transaction_id = "$sTransactionId";
if (oParams.token.length > 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'))