CVE-2021-38721: Cross-Site Request Forgery (CSRF) (CWE-352)
value: FUEL CMS 1.5.0 login.php contains a cross-site request forgery (CSRF) vulnerability
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: 6.5
Published: 2021-09-09T15:15Z
Repository: https://github.com/daylightstudio/FUEL-CMS
Language: PHP
Files changed: fuel/modules/fuel/controllers/Login.php, fuel/modules/fuel/language/english/fuel_lang.php
Commit date: 2021-08-10 10:38:11 -0700
Commit message: fix: for issue #584

Diff: diff --git a/fuel/modules/fuel/controllers/Login.php b/fuel/modules/fuel/controllers/Login.php
index 852deb266..d3fd65a07 100644
--- a/fuel/modules/fuel/controllers/Login.php
+++ b/fuel/modules/fuel/controllers/Login.php
@@ -1,11 +1,12 @@
 <?php
-class Login extends CI_Controller {
+require_once(FUEL_PATH.'/libraries/Fuel_base_controller.php');
+class Login extends Fuel_base_controller {
 
 	public function __construct()
 	{
-		parent::__construct();
+		parent::__construct(false);
 
 		// for flash data
 		$this->load->library('session');
 
 		if (!$this->fuel->config('admin_enabled')) show_404();
@@ -66,12 +67,18 @@ class Login extends CI_Controller {
 
 		$user_data = $this->session->userdata($session_key);
 
 		if ( ! empty($_POST))
 		{
+			// XSS key check
+			if (!$this->_is_valid_csrf())
+			{
+				add_error(lang('error_csrf'));
+			}
+
 			// check if they are locked out out or not
-			if (isset($user_data['failed_login_timer']) AND (time() - $user_data['failed_login_timer']) < (int)$this->fuel->config('seconds_to_unlock'))
+			elseif (isset($user_data['failed_login_timer']) AND (time() - $user_data['failed_login_timer']) < (int)$this->fuel->config('seconds_to_unlock'))
 			{
  				$this->fuel_users_model->add_error(lang('error_max_attempts', $this->fuel->config('seconds_to_unlock')));
 				$user_data['failed_login_timer'] = time();
 			}
 			else
@@ -132,10 +139,12 @@ class Login extends CI_Controller {
 		$this->form_builder->show_required = FALSE;
 		$this->form_builder->submit_value = lang('login_btn');
 		$this->form_builder->set_fields($fields);
 		$this->form_builder->remove_js();
 		if (!empty($_POST)) $this->form_builder->set_field_values($this->input->post(NULL, TRUE));
+		$this->_prep_csrf();
+
 		$vars['form'] = $this->form_builder->render();
 		
 		// set any errors that 
 		if ($this->session->flashdata('error'))
 		{
@@ -168,11 +177,16 @@ class Login extends CI_Controller {
 
 		$user_data = $this->session->userdata($session_key);
 
 		if ( ! empty($_POST))
 		{
-			if (isset($user_data['failed_login_timer']) AND (time() - $user_data['failed_login_timer']) < (int)$this->fuel->config('seconds_to_unlock'))
+			// XSS key check
+			if (!$this->_is_valid_csrf())
+			{
+				add_error(lang('error_csrf'));
+			}
+			elseif (isset($user_data['failed_login_timer']) AND (time() - $user_data['failed_login_timer']) < (int)$this->fuel->config('seconds_to_unlock'))
 			{
  				$this->fuel_users_model->add_error(lang('error_max_attempts', $this->fuel->config('seconds_to_unlock')));
 				$user_data['failed_login_timer'] = time();
 			}
 			elseif ($this->input->post('email'))
@@ -236,10 +250,11 @@ class Login extends CI_Controller {
 		$fields['Reset Password'] = array('type' => 'section', 'label' => lang('login_reset_pwd'));
 		$fields['email'] = array('required' => TRUE, 'size' => 30, 'placeholder' => 'email', 'display_label' => FALSE);
 
 		$this->form_builder->show_required = FALSE;
 		$this->form_builder->set_fields($fields);
+		$this->_prep_csrf();
 
 		$vars['form'] = $this->form_builder->render();
 		
 		// notifications template
 		$vars['error'] = $this->fuel_users_model->get_errors();
@@ -306,11 +321,16 @@ class Login extends CI_Controller {
 			}
 		}
 		
 		if ( ! empty($_POST))
 		{
-			if ($this->input->post('email') && $this->input->post('password') && $this->input->post('password_confirm') && $this->input->post('_token'))
+			// XSS key check
+			if (!$this->_is_valid_csrf())
+			{
+				add_error(lang('error_csrf'));
+			}
+			elseif ($this->input->post('email') && $this->input->post('password') && $this->input->post('password_confirm') && $this->input->post('_token'))
 			{
 				$this->load->library('user_agent');
 			
 				if ($this->input->post('password') == $this->input->post('password_confirm'))
 				{
@@ -349,10 +369,11 @@ class Login extends CI_Controller {
 		$fields['password_confirm'] = array('type' => 'password', 'required' => TRUE, 'size' => 30, 'placeholder' => 'confirm password', 'display_label' => FALSE);
 		$fields['_token'] = array('type' => 'hidden', 'value' => $token);
 
 		$this->form_builder->show_required = FALSE;
 		$this->form_builder->set_fields($fields);
+		$this->_prep_csrf();
 
 		$vars['form'] = $this->form_builder->render();
 		
 		// notifications template
 		$vars['error'] = $this->fuel_users_model->get_errors();
@@ -366,11 +387,16 @@ class Login extends CI_Controller {
 	{
 		$this->config->set_item('allow_forgotten_password', FALSE);
 
 		if ( ! empty($_POST))
 		{
-			if ( ! $this->fuel->config('dev_password'))
+			// XSS key check
+			if (!$this->_is_valid_csrf())
+			{
+				add_error(lang('error_csrf'));
+			}
+			elseif ( ! $this->fuel->config('dev_password'))
 			{
 				redirect('');
 			}
 			else if ($this->fuel->config('dev_password') == $this->input->post('password', TRUE))
 			{
@@ -389,12 +415,12 @@ class Login extends CI_Controller {
 		$fields['forward'] = array('type' => 'hidden', 'value' => fuel_uri_segment(2));
 
 		$this->form_builder->show_required = FALSE;
 		$this->form_builder->submit_value = 'Login';
 		$this->form_builder->set_fields($fields);
-
 		if ( ! empty($_POST)) $this->form_builder->set_field_values($this->input->post(NULL, TRUE));
+		$this->_prep_csrf();
 
 		$vars['form'] = $this->form_builder->render();
 		$vars['notifications'] = $this->load->module_view(FUEL_FOLDER, '_blocks/notifications', $vars, TRUE);
 		$vars['display_forgotten_pwd'] = FALSE;
 		$vars['instructions'] = lang('dev_pwd_instructions');
diff --git a/fuel/modules/fuel/language/english/fuel_lang.php b/fuel/modules/fuel/language/english/fuel_lang.php
index 84548336a..640cc56b0 100644
--- a/fuel/modules/fuel/language/english/fuel_lang.php
+++ b/fuel/modules/fuel/language/english/fuel_lang.php
@@ -21,10 +21,11 @@ $lang['error_no_access'] = 'You do not have access to this page. <a href="%1s">T
 $lang['error_missing_module'] = "You are missing the module %1s.";
 $lang['error_invalid_login'] = 'Invalid login.';
 $lang['error_max_attempts'] = 'Sorry, but your login information was incorrect and you are temporarily locked out. Please try again in %s seconds.';
 $lang['error_empty_user_pwd'] = 'Please enter in a user name and password.';
 $lang['error_pwd_reset'] = 'There was an error in resetting your password.';
+$lang['error_csrf'] = 'Invalid submission.';
 
 $lang['error_pwd_too_short'] = 'Password entered does not meet the %1s character min length requirement.';
 $lang['error_pwd_too_long'] = 'Password entered exceeds the %1s character max length requirement.';
 $lang['error_pwd_invalid'] = 'Please choose a stronger password. Try a mix of %1s.';
 

Vulnerable code: class Login extends CI_Controller {
		parent::__construct();
			if (isset($user_data['failed_login_timer']) AND (time() - $user_data['failed_login_timer']) < (int)$this->fuel->config('seconds_to_unlock'))
			if (isset($user_data['failed_login_timer']) AND (time() - $user_data['failed_login_timer']) < (int)$this->fuel->config('seconds_to_unlock'))
			if ($this->input->post('email') && $this->input->post('password') && $this->input->post('password_confirm') && $this->input->post('_token'))
			if ( ! $this->fuel->config('dev_password'))

Fixed code: require_once(FUEL_PATH.'/libraries/Fuel_base_controller.php');
class Login extends Fuel_base_controller {
		parent::__construct(false);
			// XSS key check
			if (!$this->_is_valid_csrf())
			{
				add_error(lang('error_csrf'));
			}

			elseif (isset($user_data['failed_login_timer']) AND (time() - $user_data['failed_login_timer']) < (int)$this->fuel->config('seconds_to_unlock'))
		$this->_prep_csrf();

			// XSS key check
			if (!$this->_is_valid_csrf())
			{
				add_error(lang('error_csrf'));
			}
			elseif (isset($user_data['failed_login_timer']) AND (time() - $user_data['failed_login_timer']) < (int)$this->fuel->config('seconds_to_unlock'))
		$this->_prep_csrf();
			// XSS key check
			if (!$this->_is_valid_csrf())
			{
				add_error(lang('error_csrf'));
			}
			elseif ($this->input->post('email') && $this->input->post('password') && $this->input->post('password_confirm') && $this->input->post('_token'))
		$this->_prep_csrf();
			// XSS key check
			if (!$this->_is_valid_csrf())
			{
				add_error(lang('error_csrf'));
			}
			elseif ( ! $this->fuel->config('dev_password'))
		$this->_prep_csrf();
$lang['error_csrf'] = 'Invalid submission.';