CVE-2019-13389: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: RainLoop Webmail before 1.13.0 lacks XSS protection mechanisms such as xlink:href validation, the X-XSS-Protection header, and the Content-Security-Policy header.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2020-03-20T19:15Z
Repository: https://github.com/RainLoop/rainloop-webmail
Language: PHP
Files changed: rainloop/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php, rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php, rainloop/v/0.0.0/app/libraries/RainLoop/Service.php, rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php
Commit date: 2019-07-02 00:20:49 +0300
Commit message: Improved app security

Diff: diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php
index f7a48d45..21776274 100644
--- a/rainloop/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php
+++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php
@@ -1098,10 +1098,17 @@ class HtmlUtils
 				{
 					$oElement->setAttribute('rel', 'external nofollow noopener noreferrer');
 				}
 			}
 
+			$sLinkHref = \trim($oElement->getAttribute('xlink:href'));
+			if ($sLinkHref && !\preg_match('/^(http[s]?):/i', $sLinkHref) && '//' !== \substr($sLinkHref, 0, 2))
+			{
+				$oElement->setAttribute('data-x-blocked-xlink-href', $sLinkHref);
+				$oElement->removeAttribute('xlink:href');
+			}
+			
 			if (\in_array($sTagNameLower, array('a', 'form', 'area')))
 			{
 				$oElement->setAttribute('target', '_blank');
 			}
 
diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php
index f2d7cfc0..da53c3d3 100644
--- a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php
+++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php
@@ -196,11 +196,12 @@ class Application extends \RainLoop\Config\AbstractConfig
 				'csrf_protection'	=> array(true,
 					'Enable CSRF protection (http://en.wikipedia.org/wiki/Cross-site_request_forgery)'),
 
 				'custom_server_signature'	=> array('RainLoop'),
 				'x_frame_options_header'	=> array(''),
-
+				'x_xss_protection_header'	=> array('1; mode=block'),
+				
 				'openpgp'					=> array(false),
 
 				'admin_login'				=> array('admin', 'Login and password for web admin panel'),
 				'admin_password'			=> array('12345'),
 				'allow_admin_panel'			=> array(true, 'Access settings'),
diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php
index f39ee62c..b6dd4a29 100644
--- a/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php
+++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php
@@ -45,10 +45,16 @@ class Service
 		if (0 < \strlen($sXFrameOptionsHeader))
 		{
 			@\header('X-Frame-Options: '.$sXFrameOptionsHeader, true);
 		}
 
+		$sXssProtectionOptionsHeader = \trim($this->oActions->Config()->Get('security', 'x_xss_protection_header', ''));
+		if (0 < \strlen($sXssProtectionOptionsHeader))
+		{
+			@\header('X-XSS-Protection: '.$sXssProtectionOptionsHeader, true);
+		}
+
 		if ($this->oActions->Config()->Get('labs', 'force_https', false) && !$this->oHttp->IsSecure())
 		{
 			@\header('Location: https://'.$this->oHttp->GetHost(false, false).$this->oHttp->GetUrl(), true);
 			exit(0);
 		}
@@ -246,10 +252,14 @@ class Service
 				$sResult .= '][cloud:true';
 			}
 
 			$sResult .= ']-->';
 		}
+		else
+		{
+			@\header('X-XSS-Protection: 1; mode=block');
+		}
 
 		// Output result
 		echo $sResult;
 		unset($sResult);
 
diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php
index 9f383809..711b95dc 100644
--- a/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php
+++ b/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php
@@ -496,10 +496,11 @@ class ServiceActions
 			{
 				$sMethodName = 'Raw'.$sAction;
 				if (\method_exists($this->oActions, $sMethodName))
 				{
 					@\header('X-Raw-Action: '.$sMethodName, true);
+					@\header('Content-Security-Policy: default-src \'self\'; script-src \'none\'; style-src \'none\'; frame-src \'none\'; child-src \'none\'', true);
 
 					$sRawError = '';
 					$this->oActions->SetActionParams(array(
 						'RawKey' => empty($this->aPaths[3]) ? '' : $this->aPaths[3],
 						'Params' => $this->aPaths

Fixed code: 			$sLinkHref = \trim($oElement->getAttribute('xlink:href'));
			if ($sLinkHref && !\preg_match('/^(http[s]?):/i', $sLinkHref) && '//' !== \substr($sLinkHref, 0, 2))
			{
				$oElement->setAttribute('data-x-blocked-xlink-href', $sLinkHref);
				$oElement->removeAttribute('xlink:href');
			}
			
				'x_xss_protection_header'	=> array('1; mode=block'),
				
		$sXssProtectionOptionsHeader = \trim($this->oActions->Config()->Get('security', 'x_xss_protection_header', ''));
		if (0 < \strlen($sXssProtectionOptionsHeader))
		{
			@\header('X-XSS-Protection: '.$sXssProtectionOptionsHeader, true);
		}

		else
		{
			@\header('X-XSS-Protection: 1; mode=block');
		}
					@\header('Content-Security-Policy: default-src \'self\'; script-src \'none\'; style-src \'none\'; frame-src \'none\'; child-src \'none\'', true);