diff --git a/application/ajaxwebpage.class.inc.php b/application/ajaxwebpage.class.inc.php index 3e43244dc..4c2f12634 100644 --- a/application/ajaxwebpage.class.inc.php +++ b/application/ajaxwebpage.class.inc.php @@ -50,6 +50,7 @@ class ajax_page extends WebPage $this->m_sCurrentTabContainer = ''; $this->m_sCurrentTab = ''; $this->m_aTabs = array(); + $this->sContentDisposition = 'inline'; } public function AddTabContainer($sTabContainer, $sPrefix = '') @@ -92,11 +93,22 @@ class ajax_page extends WebPage */ public function output() { + if (!empty($this->sContentType)) + { + $this->add_header('Content-type: '.$this->sContentType); + } + else + { + $this->add_header('Content-type: text/html'); + } + if (!empty($this->sContentDisposition)) + { + $this->add_header('Content-Disposition: '.$this->sContentDisposition.'; filename="'.$this->sContentFileName.'"'); + } foreach($this->a_headers as $s_header) { header($s_header); } - if (count($this->m_aTabs) > 0) { $this->add_ready_script( @@ -165,7 +177,15 @@ EOF $s_captured_output = ob_get_contents(); ob_end_clean(); - echo $this->s_content; + if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline')) + { + // inline content != attachment && html => filter all scripts for malicious XSS scripts + echo self::FilterXSS($this->s_content); + } + else + { + echo $this->s_content; + } //echo $this->s_deferred_content; if (count($this->a_scripts) > 0) { @@ -187,7 +207,7 @@ EOF } if (trim($s_captured_output) != "") { - echo $s_captured_output; + echo self::FilterXSS($s_captured_output); } } @@ -220,9 +240,6 @@ EOF */ public function add_ready_script($sScript) { - // Does nothing in ajax rendered content.. for now... - // Maybe we should add this as a simple '), array(''), $sHTML); + } } ?> diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 5851b5d00..da412cf69 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1287,7 +1287,7 @@ EOF //$oAppContext->Reset($sFilterCode); // Make sure the same parameter will not be passed twice $sHtml .= ''; $sFilterValue = ''; - $sFilterValue = utils::ReadParam($sFilterCode, ''); + $sFilterValue = utils::ReadParam($sFilterCode, '', false, 'raw_data'); $sFilterOpCode = null; // Use the default 'loose' OpCode if (empty($sFilterValue)) { @@ -1970,7 +1970,7 @@ EOF $oDocument = $this->Get($sAttCode); $sDisplayValue = $this->GetAsHTML($sAttCode); $sDisplayValue .= "
".Dict::Format('UI:OpenDocumentInNewWindow_', $oDocument->GetDisplayLink(get_class($this), $this->GetKey(), $sAttCode)).", \n"; - $sDisplayValue .= "
".Dict::Format('UI:DownloadDocument_', $oDocument->GetDisplayLink(get_class($this), $this->GetKey(), $sAttCode)).", \n"; + $sDisplayValue .= "
".Dict::Format('UI:DownloadDocument_', $oDocument->GetDownloadLink(get_class($this), $this->GetKey(), $sAttCode)).", \n"; } else { @@ -2314,7 +2314,7 @@ EOF } else { - $value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null); + $value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); } if (!is_null($value)) { diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 56537bc24..c8fa5e32f 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -232,8 +232,7 @@ class DisplayBlock $sHtml .= "
\n"; $sHtml .= $oPage->GetP(" ".Dict::S('UI:Loading')); $sHtml .= "
\n"; - $sHtml .= ' - '; + '); } if ($bAutoReload) { - $sHtml .= ' - '; + $oPage->add_script('setInterval("ReloadBlock(\''.$sId.'\', \''.$this->m_sStyle.'\', \''.$sFilter.'\', \"'.$sExtraParams.'\")", '.$iReloadInterval.');'); } return $sHtml; } @@ -299,7 +295,7 @@ class DisplayBlock } foreach($aFilterCodes as $sFilterCode) { - $sExternalFilterValue = utils::ReadParam($sFilterCode, ''); + $sExternalFilterValue = utils::ReadParam($sFilterCode, '', false, 'raw_data'); $condition = null; if (isset($aExtraParams[$sFilterCode])) { diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index eea8cd9ce..56cbbe2c1 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -733,16 +733,16 @@ EOF echo '
'; echo ''; echo ' '; echo '
'; echo '
pin
'; - echo '
'.$sForm.'
'; + echo '
'.self::FilterXSS($sForm).'
'; echo '
'; echo ' '; @@ -771,9 +771,9 @@ EOF // Add the captured output if (trim($s_captured_output) != "") { - echo "
$s_captured_output
\n"; + echo "
".self::FilterXSS($s_captured_output)."
\n"; } - echo "
".$this->s_deferred_content."
"; + echo "
".self::FilterXSS($this->s_deferred_content)."
"; // echo $this->s_deferred_content; echo "
Please wait...
\n"; // jqModal Window echo "
"; diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 35f4ba386..5cc280cd8 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -110,8 +110,8 @@ EOF case 'external': case 'form': default: // In case the settings get messed up... - $sAuthUser = utils::ReadParam('auth_user', ''); - $sAuthPwd = utils::ReadParam('suggest_pwd', ''); + $sAuthUser = utils::ReadParam('auth_user', '', true, 'raw_data'); + $sAuthPwd = utils::ReadParam('suggest_pwd', '', true, 'raw_data'); $sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION); $this->add("
\n"); @@ -140,8 +140,8 @@ EOF public function DisplayChangePwdForm($bFailedLogin = false) { - $sAuthUser = utils::ReadParam('auth_user', ''); - $sAuthPwd = utils::ReadParam('suggest_pwd', ''); + $sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data'); + $sAuthPwd = utils::ReadParam('suggest_pwd', '', false, 'raw_data'); $sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION); $sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch'); @@ -320,8 +320,8 @@ EOF case 'form': // iTop standard mode: form based authentication - $sAuthUser = utils::ReadPostedParam('auth_user', ''); - $sAuthPwd = utils::ReadPostedParam('auth_pwd', ''); + $sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data'); + $sAuthPwd = utils::ReadPostedParam('auth_pwd', '', 'raw_data'); if ($sAuthUser != '') { $sLoginMode = 'form'; @@ -361,10 +361,10 @@ EOF case 'url': // Credentials passed directly in the url - $sAuthUser = utils::ReadParam('auth_user', ''); + $sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data'); if ($sAuthUser != '') { - $sAuthPwd = utils::ReadParam('auth_pwd', ''); + $sAuthPwd = utils::ReadParam('auth_pwd', '', false, 'raw_data'); $sLoginMode = 'url'; } break; @@ -472,8 +472,8 @@ EOF { $sAuthUser = $_SESSION['auth_user']; UserRights::Login($sAuthUser); // Set the user's language - $sOldPwd = utils::ReadPostedParam('old_pwd'); - $sNewPwd = utils::ReadPostedParam('new_pwd'); + $sOldPwd = utils::ReadPostedParam('old_pwd', 'raw_data'); + $sNewPwd = utils::ReadPostedParam('new_pwd', 'raw_data'); if (UserRights::CanChangePassword() && ((!UserRights::CheckCredentials($sAuthUser, $sOldPwd)) || (!UserRights::ChangePassword($sOldPwd, $sNewPwd)))) { $oPage = new LoginWebPage(); diff --git a/application/portalwebpage.class.inc.php b/application/portalwebpage.class.inc.php index 412c4b7e7..dc8a5013c 100644 --- a/application/portalwebpage.class.inc.php +++ b/application/portalwebpage.class.inc.php @@ -593,7 +593,7 @@ EOF foreach($aAttList as $sAttSpec) { $sFieldName = str_replace('->', PARAM_ARROW_SEP, $sAttSpec); - $value = utils::ReadPostedParam($sPrefix.$sFieldName, null); + $value = utils::ReadPostedParam($sPrefix.$sFieldName, null, 'raw_data'); if (!is_null($value) && strlen($value) > 0) { $oFilter->AddConditionAdvanced($sAttSpec, $value); diff --git a/application/ui.extkeywidget.class.inc.php b/application/ui.extkeywidget.class.inc.php index c745640ff..9ba27b438 100644 --- a/application/ui.extkeywidget.class.inc.php +++ b/application/ui.extkeywidget.class.inc.php @@ -327,7 +327,7 @@ EOF $aValues = $oValuesSet->GetValues(array('this' => $oObj), $sContains); foreach($aValues as $sKey => $sFriendlyName) { - $oP->add(trim($sFriendlyName)."|".$sKey."\n"); + $oP->add(trim(htmlentities($sFriendlyName, ENT_COMPAT, 'UTF-8'))."|".$sKey."\n"); } } diff --git a/application/ui.passwordwidget.class.inc.php b/application/ui.passwordwidget.class.inc.php index 40296f584..9c73a1628 100644 --- a/application/ui.passwordwidget.class.inc.php +++ b/application/ui.passwordwidget.class.inc.php @@ -52,8 +52,8 @@ class UIPasswordWidget { $sCode = $this->sAttCode.$this->sNameSuffix; $iWidgetIndex = self::$iWidgetIndex; - $sPasswordValue = utils::ReadPostedParam("attr_{$sCode}[value]", '*****'); - $sConfirmPasswordValue = utils::ReadPostedParam("attr_{$sCode}[confirm]", '*****'); + $sPasswordValue = utils::ReadPostedParam("attr_{$sCode}[value]", '*****', 'raw_data'); + $sConfirmPasswordValue = utils::ReadPostedParam("attr_{$sCode}[confirm]", '*****', 'raw_data'); $sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0; $sHtmlValue = ''; $sHtmlValue = ' 
'; diff --git a/application/uilinkswizard.class.inc.php b/application/uilinkswizard.class.inc.php index aaa61e705..e9ec519f5 100644 --- a/application/uilinkswizard.class.inc.php +++ b/application/uilinkswizard.class.inc.php @@ -97,8 +97,7 @@ class UILinksWizard $oP->add("m_sLinkingAttCode}\">\n"); $oP->add("

".Dict::Format('UI:ManageObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "".$oTargetObj->GetHyperlink()."")."

\n"); $oP->add("
\n"); - $oP->add("\n"); $oP->add_ready_script("InitForm();"); $oFilter = new DBObjectSearch($this->m_sClass); $oFilter->AddCondition($this->m_sLinkageAttr, $this->m_iObjectId, '='); diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php index d9b12c8e6..357455ad9 100644 --- a/application/uiwizard.class.inc.php +++ b/application/uiwizard.class.inc.php @@ -112,8 +112,7 @@ class UIWizard \n"); - $this->m_oPage->add(" -\n"); +"); $this->m_oPage->add("\n\n"); } @@ -139,16 +138,15 @@ $sJSHandlerCode $this->m_oPage->add("\n"); $this->m_oPage->add("\n"); $this->m_oPage->add("\n"); - $this->m_oPage->add("\n"); + $sScript .= "\toWizardHelper.Preview('object_preview');\n"; + $sScript .= "\t$('#wizard_json_obj').val(oWizardHelper.ToJSON());\n"; + $sScript .= "}\n"; + $this->m_oPage->add_script($sScript); $this->m_oPage->add("
\n"); $this->m_oPage->add("
\n"); $this->m_oPage->add($oAppContext->GetForForm()); diff --git a/application/utils.inc.php b/application/utils.inc.php index d9a6c921e..7a73b44f4 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -114,7 +114,7 @@ class utils } - public static function ReadParam($sName, $defaultValue = "", $bAllowCLI = false) + public static function ReadParam($sName, $defaultValue = "", $bAllowCLI = false, $sSanitizationFilter = 'parameter') { global $argv; $retValue = $defaultValue; @@ -141,12 +141,71 @@ class utils } } } - return $retValue; + return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter); } - public static function ReadPostedParam($sName, $defaultValue = "") + public static function ReadPostedParam($sName, $defaultValue = '', $sSanitizationFilter = 'parameter') { - return isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue; + $retValue = isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue; + return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter); + } + + public static function Sanitize($value, $defaultValue, $sSanitizationFilter) + { + $retValue = self::Sanitize_Internal($value, $sSanitizationFilter); + if ($retValue === false) + { + $retValue = $defaultValue; + } + return $retValue; + } + + protected static function Sanitize_Internal($value, $sSanitizationFilter) + { + switch($sSanitizationFilter) + { + case 'integer': + $retValue = filter_var($value, FILTER_SANITIZE_NUMBER_INT); + break; + + case 'class': + $retValue = $value; + if (!MetaModel::IsValidClass($value)) + { + $retValue = false; + } + break; + + case 'string': + $retValue = filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS); + break; + + case 'parameter': + if (is_array($value)) + { + $retValue = array(); + foreach($value as $key => $val) + { + $retValue[$key] = self::Sanitize_Internal($val, $sSanitizationFilter); // recursively check arrays + if ($retValue[$key] === false) + { + $retValue = false; + break; + } + } + } + else + { + $retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>'/^[A-Za-z0-9_=-]*$/'))); // the '=' equal character is used in serialized filters + } + break; + + default: + case 'raw_data': + $retValue = $value; + // Do nothing + } + return $retValue; } /** diff --git a/application/webpage.class.inc.php b/application/webpage.class.inc.php index 1d9b6ed14..a1901fe0a 100644 --- a/application/webpage.class.inc.php +++ b/application/webpage.class.inc.php @@ -47,7 +47,10 @@ class WebPage protected $a_base; protected $iNextId; protected $iTransactionId; - + protected $sContentType; + protected $sContentDisposition; + protected $sContentFileName; + public function __construct($s_title) { $this->s_title = $s_title; @@ -61,6 +64,9 @@ class WebPage $this->a_base = array( 'href' => '', 'target' => ''); $this->iNextId = 0; $this->iTransactionId = 0; + $this->sContentType = ''; + $this->sContentDisposition = ''; + $this->sContentFileName = ''; ob_start(); // Start capturing the output } @@ -359,7 +365,7 @@ class WebPage echo "\n"; echo "\n"; echo "\n"; - echo "\n"; + echo "\n"; echo "{$this->s_title}\n"; echo $this->get_base_tag(); foreach($this->a_linked_scripts as $s_script) @@ -409,12 +415,12 @@ class WebPage } echo "\n"; echo "\n"; - echo $this->s_content; + echo self::FilterXSS($this->s_content); if (trim($s_captured_output) != "") { - echo "
$s_captured_output
\n"; + echo "
".self::FilterXSS($s_captured_output)."
\n"; } - echo '
'.$this->s_deferred_content.'
'; + echo '
'.self::FilterXSS($this->s_deferred_content).'
'; echo "\n"; echo "\n"; } @@ -459,7 +465,29 @@ class WebPage { return $this->iNextId++; } - + + /** + * Set the content-type (mime type) for the page's content + * @param $sContentType string + * @return void + */ + public function SetContentType($sContentType) + { + $this->sContentType = $sContentType; + } + + /** + * Set the content-disposition (mime type) for the page's content + * @param $sDisposition string The disposition: 'inline' or 'attachment' + * @param $sFileName string The original name of the file + * @return void + */ + public function SetContentDisposition($sDisposition, $sFileName) + { + $this->sContentDisposition = $sDisposition; + $this->sContentFileName = $sFileName; + } + /** * Set the transactionId of the current form * @param $iTransactionId integer @@ -478,5 +506,10 @@ class WebPage { return $this->iTransactionId; } + + public static function FilterXSS($sHTML) + { + return str_ireplace(' \ No newline at end of file diff --git a/modules/itop-welcome-itil/welcome_menu.html b/modules/itop-welcome-itil/welcome_menu.html index 9259afca8..7c8bdfb50 100644 --- a/modules/itop-welcome-itil/welcome_menu.html +++ b/modules/itop-welcome-itil/welcome_menu.html @@ -84,33 +84,6 @@ a.summary:hover { text-decoration: underline; } -