CVE-2022-0269: Cross-Site Request Forgery (CSRF) (CWE-352)
value: Cross-Site Request Forgery (CSRF) in Packagist yetiforce/yetiforce-crm prior to 6.3.0.
CWE description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
CVSS v3 base score: 8
Published: 2022-01-24T12:15Z
Repository: https://github.com/yetiforcecompany/yetiforcecrm
Language: PHP
Files changed: app/Controller/Action.php, app/Controller/Base.php, app/Request.php, config/ConfigTemplates.php, config/Security.php, config/csrf_config.php, config/version.php, include/main/WebUI.php, install/views/Index.php, modules/Users/views/Login.php
Commit date: 2022-01-18 12:25:46 +0100
Commit message: Improved CSRF protection

Diff: diff --git a/app/Controller/Action.php b/app/Controller/Action.php
index 88a23f299ac..c9278fe0a5c 100644
--- a/app/Controller/Action.php
+++ b/app/Controller/Action.php
@@ -5,22 +5,20 @@
  * @package   Controller
  *
  * @copyright YetiForce Sp. z o.o
  * @license   YetiForce Public License 4.0 (licenses/LicenseEN.txt or yetiforce.com)
  * @author    Mariusz Krzaczkowski <m.krzaczkowski@yetiforce.com>
+ * @author    Radosław Skrzypczak <r.skrzypczak@yetiforce.com>
  */
 
 namespace App\Controller;
 
 /**
  * Abstract action controller class.
  */
 abstract class Action extends Base
 {
-	/** {@inheritdoc} */
-	public $csrfActive = false;
-
 	/**
 	 * Process action.
 	 *
 	 * @param \App\Request $request
 	 */
diff --git a/app/Controller/Base.php b/app/Controller/Base.php
index 0cadef3d7a4..b8f99f2f510 100644
--- a/app/Controller/Base.php
+++ b/app/Controller/Base.php
@@ -5,10 +5,11 @@
  * @package   Controller
  *
  * @copyright YetiForce Sp. z o.o
  * @license   YetiForce Public License 4.0 (licenses/LicenseEN.txt or yetiforce.com)
  * @author    Mariusz Krzaczkowski <m.krzaczkowski@yetiforce.com>
+ * @author    Radosław Skrzypczak <r.skrzypczak@yetiforce.com>
  */
 
 namespace App\Controller;
 
 /**
@@ -16,29 +17,17 @@ namespace App\Controller;
  */
 abstract class Base
 {
 	/** @var \App\Headers Headers instance. */
 	public $headers;
-	/**
-	 * CSRF is active?.
-	 *
-	 * @var bool
-	 */
-	public $csrfActive = true;
 
 	/**
 	 * Activated language locale.
 	 *
 	 * @var bool
 	 */
 	protected static $activatedLocale = false;
-	/**
-	 * Activated csrf.
-	 *
-	 * @var bool
-	 */
-	protected static $activatedCsrf = false;
 
 	/**
 	 * Constructor.
 	 */
 	public function __construct()
@@ -46,19 +35,13 @@ abstract class Base
 		$this->headers = \App\Controller\Headers::getInstance();
 		if (!self::$activatedLocale && \App\Config::performance('CHANGE_LOCALE')) {
 			\App\Language::initLocale();
 			self::$activatedLocale = true;
 		}
-		if (!self::$activatedCsrf) {
-			if ($this->csrfActive && \App\Config::security('csrfActive')) {
-				require_once 'config/csrf_config.php';
-				\CsrfMagic\Csrf::init();
-				$this->csrfActive = true;
-			} else {
-				$this->csrfActive = false;
-			}
-			self::$activatedCsrf = true;
+		if (\App\Config::security('csrfActive')) {
+			require_once 'config/csrf_config.php';
+			\CsrfMagic\Csrf::init();
 		}
 	}
 
 	/**
 	 * Function to check login required permission.
diff --git a/app/Request.php b/app/Request.php
index 706c2daf6e3..65f19f14b83 100644
--- a/app/Request.php
+++ b/app/Request.php
@@ -708,12 +708,12 @@ class Request
 	{
 		if (!$skipRequestTypeCheck && 'POST' !== $_SERVER['REQUEST_METHOD']) {
 			throw new \App\Exceptions\Csrf('Invalid request - validate Write Access');
 		}
 		$this->validateReadAccess();
-		if (class_exists('CSRFConfig') && !\CsrfMagic\Csrf::check(false)) {
-			throw new \App\Exceptions\Csrf('Unsupported request');
+		if (\App\Config::security('csrfActive')) {
+			\CsrfMagic\Csrf::check();
 		}
 	}
 
 	/**
 	 * Static instance initialization.
diff --git a/config/ConfigTemplates.php b/config/ConfigTemplates.php
index c72c6f67cc0..9ccec497caa 100644
--- a/config/ConfigTemplates.php
+++ b/config/ConfigTemplates.php
@@ -1152,10 +1152,16 @@ return [
 			'default' => true,
 			'description' => 'Enable CSRF protection',
 			'validation' => '\App\Validator::bool',
 			'sanitization' => '\App\Purifier::bool'
 		],
+		'csrfLifetimeToken' => [
+			'default' => 28800,
+			'description' => 'Default expire time of CSRF token in seconds',
+			'validation' => '\App\Validator::naturalNumber',
+			'sanitization' => '\App\Purifier::naturalNumber'
+		],
 		'csrfFrameBreaker' => [
 			'default' => true,
 			'description' => 'Enable verified frame protection, used in CSRF',
 			'validation' => '\App\Validator::bool',
 			'sanitization' => '\App\Purifier::bool'
diff --git a/config/Security.php b/config/Security.php
index 94ea1484519..8ef8586946e 100644
--- a/config/Security.php
+++ b/config/Security.php
@@ -149,10 +149,13 @@ class Security
 	public static $hpkpKeysHeader = [];
 
 	/** Enable CSRF protection */
 	public static $csrfActive = true;
 
+	/** Default expire time of CSRF token in seconds */
+	public static $csrfLifetimeToken = 28800;
+
 	/** Enable verified frame protection, used in CSRF */
 	public static $csrfFrameBreaker = true;
 
 	/** Which window should be verified? It is used to check if the system is loaded in the frame, used in CSRF. */
 	public static $csrfFrameBreakerWindow = 'top';
diff --git a/config/csrf_config.php b/config/csrf_config.php
index d36be841205..d6a1bc03059 100644
--- a/config/csrf_config.php
+++ b/config/csrf_config.php
@@ -4,28 +4,30 @@
  * ("License"); You may not use this file except in compliance with the License
  * The Original Code is:  vtiger CRM Open Source
  * The Initial Developer of the Original Code is vtiger.
  * Portions created by vtiger are Copyright (C) vtiger.
  * All Rights Reserved.
+ * Contributor(s): YetiForce Sp. z o.o
  * ****************************************************************************** */
 
 class CSRFConfig
 {
 	/**
 	 * Specific custom config startup for CSRF.
 	 */
 	public static function startup()
 	{
 		//Override the default expire time of token
-		\CsrfMagic\Csrf::$expires = 259200;
+		\CsrfMagic\Csrf::$expires = \App\Config::security('csrfLifetimeToken', 7200);
 		\CsrfMagic\Csrf::$callback = function ($tokens) {
-			throw new \App\Exceptions\AppException('Invalid request - Response For Illegal Access', 403);
+			throw new \App\Exceptions\Csrf('Invalid request - Response For Illegal Access', 403);
 		};
 		$js = 'vendor/yetiforce/csrf-magic/src/Csrf.min.js';
 		if (!IS_PUBLIC_DIR) {
 			$js = 'public_html/' . $js;
 		}
+		\CsrfMagic\Csrf::$defer = true;
 		\CsrfMagic\Csrf::$dirSecret = __DIR__;
 		\CsrfMagic\Csrf::$rewriteJs = $js;
 		\CsrfMagic\Csrf::$cspToken = \App\Session::get('CSP_TOKEN');
 		\CsrfMagic\Csrf::$frameBreaker = \Config\Security::$csrfFrameBreaker;
 		\CsrfMagic\Csrf::$windowVerification = \Config\Security::$csrfFrameBreakerWindow;
diff --git a/config/version.php b/config/version.php
index f8377d50abf..ad7aa939e89 100644
--- a/config/version.php
+++ b/config/version.php
@@ -1,7 +1,7 @@
 <?php
 
 return [
-	'appVersion' => '6.3.42',
-	'patchVersion' => '2022.01.17',
+	'appVersion' => '6.3.43',
+	'patchVersion' => '2022.01.18',
 	'lib_roundcube' => '0.2.10',
 ];
diff --git a/include/main/WebUI.php b/include/main/WebUI.php
index f0405baca82..e02d33cd59b 100644
--- a/include/main/WebUI.php
+++ b/include/main/WebUI.php
@@ -166,13 +166,11 @@ class Vtiger_WebUI extends Vtiger_EntryPoint
 			$handler = new $handlerClass();
 			if (!$handler) {
 				\App\Log::error("HandlerClass: $handlerClass", 'Loader');
 				throw new \App\Exceptions\AppException('LBL_HANDLER_NOT_FOUND', 405);
 			}
-			if ($handler->csrfActive) {
-				$handler->validateRequest($request);
-			}
+			$handler->validateRequest($request);
 			if ($handler->loginRequired() && $this->checkLogin($request)) {
 				return true;
 			}
 			if ($handler->isSessionExtend($request)) {
 				\App\Session::set('last_activity', \App\Process::$startTime);
diff --git a/install/views/Index.php b/install/views/Index.php
index bdc1249f2c7..228c60eb64e 100644
--- a/install/views/Index.php
+++ b/install/views/Index.php
@@ -12,13 +12,10 @@
 
 class Install_Index_View extends \App\Controller\View\Base
 {
 	use \App\Controller\ExposeMethod;
 
-	/** {@inheritdoc} */
-	public $csrfActive = false;
-
 	/**
 	 * @var bool
 	 */
 	protected $debug = false;
 
diff --git a/modules/Users/views/Login.php b/modules/Users/views/Login.php
index 82335d61683..670a39ef318 100644
--- a/modules/Users/views/Login.php
+++ b/modules/Users/views/Login.php
@@ -9,13 +9,10 @@
  * Contributor(s): YetiForce.com
  * ********************************************************************************** */
 
 class Users_Login_View extends \App\Controller\View\Base
 {
-	/** {@inheritdoc} */
-	public $csrfActive = false;
-
 	/** {@inheritdoc} */
 	public function __construct()
 	{
 		parent::__construct();
 		if ($nonce = \App\Session::get('CSP_TOKEN')) {

Vulnerable code: 	/** {@inheritdoc} */
	public $csrfActive = false;

	/**
	 * CSRF is active?.
	 *
	 * @var bool
	 */
	public $csrfActive = true;
	/**
	 * Activated csrf.
	 *
	 * @var bool
	 */
	protected static $activatedCsrf = false;
		if (!self::$activatedCsrf) {
			if ($this->csrfActive && \App\Config::security('csrfActive')) {
				require_once 'config/csrf_config.php';
				\CsrfMagic\Csrf::init();
				$this->csrfActive = true;
			} else {
				$this->csrfActive = false;
			}
			self::$activatedCsrf = true;
		if (class_exists('CSRFConfig') && !\CsrfMagic\Csrf::check(false)) {
			throw new \App\Exceptions\Csrf('Unsupported request');
		\CsrfMagic\Csrf::$expires = 259200;
			throw new \App\Exceptions\AppException('Invalid request - Response For Illegal Access', 403);
	'appVersion' => '6.3.42',
	'patchVersion' => '2022.01.17',
			if ($handler->csrfActive) {
				$handler->validateRequest($request);
			}
	/** {@inheritdoc} */
	public $csrfActive = false;

	/** {@inheritdoc} */
	public $csrfActive = false;

Fixed code:  * @author    Radosław Skrzypczak <r.skrzypczak@yetiforce.com>
 * @author    Radosław Skrzypczak <r.skrzypczak@yetiforce.com>
		if (\App\Config::security('csrfActive')) {
			require_once 'config/csrf_config.php';
			\CsrfMagic\Csrf::init();
		if (\App\Config::security('csrfActive')) {
			\CsrfMagic\Csrf::check();
		'csrfLifetimeToken' => [
			'default' => 28800,
			'description' => 'Default expire time of CSRF token in seconds',
			'validation' => '\App\Validator::naturalNumber',
			'sanitization' => '\App\Purifier::naturalNumber'
		],
	/** Default expire time of CSRF token in seconds */
	public static $csrfLifetimeToken = 28800;

 * Contributor(s): YetiForce Sp. z o.o
		\CsrfMagic\Csrf::$expires = \App\Config::security('csrfLifetimeToken', 7200);
			throw new \App\Exceptions\Csrf('Invalid request - Response For Illegal Access', 403);
		\CsrfMagic\Csrf::$defer = true;
	'appVersion' => '6.3.43',
	'patchVersion' => '2022.01.18',
			$handler->validateRequest($request);