CVE-2022-4582: Improper Neutralization (CWE-707)
value: A vulnerability was found in starter-public-edition-4 up to 4.6.10. It has been classified as problematic. Affected is an unknown function. The manipulation leads to cross site scripting. It is possible to launch the attack remotely. Upgrading to version 4.6.11 is able to address this issue. The name of the patch is 2606983c20f6ea3430ac4b36b3d2e88aafef45da. It is recommended to upgrade the affected component. The identifier of this vulnerability is VDB-216168.
CWE description: The product does not ensure or incorrectly ensures that structured messages or data are well-formed and that certain security properties are met before being read from an upstream component or sent to a downstream component.
CVSS v3 base score: 6.1
Published: 2022-12-17T13:15Z
Repository: https://github.com/ivantcholakov/starter-public-edition-4
Language: PHP
Files changed: platform/applications/admin/core/Base_Controller.php, platform/applications/admin/modules/login/controllers/Login_controller.php, platform/applications/admin/modules/theme_switcher_widget/controllers/Theme_switcher_widget_controller.php, platform/applications/front/core/Base_Controller.php, platform/applications/front/modules/theme_switcher_widget/controllers/Theme_switcher_widget_controller.php, platform/common/core/Common.php, platform/common/helpers/MY_form_helper.php
Commit date: 2022-09-05 08:59:24 +0300
Commit message: Security improvements have been implemented - about html atributes escaping and XSS-cleaning.
Diff: diff --git a/platform/applications/admin/core/Base_Controller.php b/platform/applications/admin/core/Base_Controller.php
index 799a7fb5a..cfe572f1e 100644
--- a/platform/applications/admin/core/Base_Controller.php
+++ b/platform/applications/admin/core/Base_Controller.php
@@ -12,14 +12,19 @@ class Base_Controller extends Core_Controller {
         //;
 
         $this->load->model('visual_themes');
         $this->load->library('template');
 
+        $theme = (string) $this->input->get('theme');
+
+        if ($theme != '') {
+            $theme = (string) base64_decode($theme);
+        }
+
         // Determine the current visual theme.
-        if ($this->input->get('theme') != '' && $this->input->method() == 'get' && !$this->input->is_ajax_request()) {
+        if ($theme != '' && $this->input->method() == 'get' && !$this->input->is_ajax_request()) {
 
-            $theme = (string) $this->input->get('theme');
             $this->visual_themes->set_current($theme);
 
             parse_str(parse_url(CURRENT_URL, PHP_URL_QUERY), $query);
             unset($query['theme']);
             redirect(http_build_url(current_url(), array('query' => http_build_query($query))));
@@ -119,11 +124,11 @@ class Base_Controller extends Core_Controller {
 
             if ($this->input->method() != 'get') {
                 redirect('login');
             }
 
-            redirect(http_build_url(site_url('login'), array('query' => http_build_query(array('continue' => CURRENT_URL)))));
+            redirect(http_build_url(site_url('login'), array('query' => http_build_query(array('continue' => base64_encode(CURRENT_URL))))));
         }
 
         return true;
     }
 
diff --git a/platform/applications/admin/modules/login/controllers/Login_controller.php b/platform/applications/admin/modules/login/controllers/Login_controller.php
index 13d646ae9..8a3ce264a 100644
--- a/platform/applications/admin/modules/login/controllers/Login_controller.php
+++ b/platform/applications/admin/modules/login/controllers/Login_controller.php
@@ -44,15 +44,21 @@ class Login_controller extends Base_Controller {
             $password = $this->input->post('password');
 
             // Implement your own login system.
             if ($this->_login($username, $password)) {
 
-                if ($this->input->get('continue')) {
-                    redirect($this->input->get('continue'));
+                $continue = (string) $this->input->get('continue');
+
+                if ($continue != '') {
+                    $continue = (string) base64_decode($continue);
+                }
+
+                if ($continue != '' && strpos($continue, site_url()) === 0) {
+                    redirect($continue);
                 }
 
-                $this->session->set_flashdata('confirmation_message', '<nobr>Hello, <strong>'.$username.'</strong>.</nobr>');
+                $this->session->set_flashdata('confirmation_message', '<nobr>Hello, <strong>'.html_escape($username).'</strong>.</nobr>');
                 redirect(site_url());
 
             } else {
 
                 $error_message = 'Wrong username or password.';
diff --git a/platform/applications/admin/modules/theme_switcher_widget/controllers/Theme_switcher_widget_controller.php b/platform/applications/admin/modules/theme_switcher_widget/controllers/Theme_switcher_widget_controller.php
index f2f7f1042..d442c33f3 100644
--- a/platform/applications/admin/modules/theme_switcher_widget/controllers/Theme_switcher_widget_controller.php
+++ b/platform/applications/admin/modules/theme_switcher_widget/controllers/Theme_switcher_widget_controller.php
@@ -28,11 +28,11 @@ class Theme_switcher_widget_controller extends Base_Widget_Controller {
         }
 
         foreach ($theme_switcher as $key => $value) {
 
             $theme_switcher[$key]['label'] = $value['name'];
-            $theme_switcher[$key]['link'] = http_build_url(CURRENT_URL, array('query' => http_build_query(array('theme' => $value['key']))), HTTP_URL_JOIN_QUERY);
+            $theme_switcher[$key]['link'] = http_build_url(CURRENT_URL, array('query' => http_build_query(array('theme' => base64_encode($value['key'])))), HTTP_URL_JOIN_QUERY);
 
             if ($value['key'] == $this->visual_themes->get_current()) {
                 $theme_switcher[$key]['active'] = true;
             }
         }
diff --git a/platform/applications/front/core/Base_Controller.php b/platform/applications/front/core/Base_Controller.php
index 57910b00e..904368685 100644
--- a/platform/applications/front/core/Base_Controller.php
+++ b/platform/applications/front/core/Base_Controller.php
@@ -9,14 +9,19 @@ class Base_Controller extends Core_Controller {
         $this->load
             ->model('visual_themes')
             ->library('template')
         ;
 
+        $theme = (string) $this->input->get('theme');
+
+        if ($theme != '') {
+            $theme = (string) base64_decode($theme);
+        }
+
         // Determine the current visual theme.
-        if ($this->input->get('theme') != '' && $this->input->method() == 'get' && !$this->input->is_ajax_request()) {
+        if ($theme != '' && $this->input->method() == 'get' && !$this->input->is_ajax_request()) {
 
-            $theme = (string) $this->input->get('theme');
             $this->visual_themes->set_current($theme);
 
             parse_str(parse_url(CURRENT_URL, PHP_URL_QUERY), $query);
             unset($query['theme']);
             redirect(http_build_url(current_url(), array('query' => http_build_query($query))));
diff --git a/platform/applications/front/modules/theme_switcher_widget/controllers/Theme_switcher_widget_controller.php b/platform/applications/front/modules/theme_switcher_widget/controllers/Theme_switcher_widget_controller.php
index f2f7f1042..d442c33f3 100644
--- a/platform/applications/front/modules/theme_switcher_widget/controllers/Theme_switcher_widget_controller.php
+++ b/platform/applications/front/modules/theme_switcher_widget/controllers/Theme_switcher_widget_controller.php
@@ -28,11 +28,11 @@ class Theme_switcher_widget_controller extends Base_Widget_Controller {
         }
 
         foreach ($theme_switcher as $key => $value) {
 
             $theme_switcher[$key]['label'] = $value['name'];
-            $theme_switcher[$key]['link'] = http_build_url(CURRENT_URL, array('query' => http_build_query(array('theme' => $value['key']))), HTTP_URL_JOIN_QUERY);
+            $theme_switcher[$key]['link'] = http_build_url(CURRENT_URL, array('query' => http_build_query(array('theme' => base64_encode($value['key'])))), HTTP_URL_JOIN_QUERY);
 
             if ($value['key'] == $this->visual_themes->get_current()) {
                 $theme_switcher[$key]['active'] = true;
             }
         }
diff --git a/platform/common/core/Common.php b/platform/common/core/Common.php
index f9043480d..ec9f08168 100644
--- a/platform/common/core/Common.php
+++ b/platform/common/core/Common.php
@@ -491,11 +491,11 @@ if ( ! function_exists('_stringify_attributes'))
         $attributes = (array) $attributes;
 
         $atts = '';
         foreach ($attributes as $key => $val)
         {
-            $atts .= ($js) ? $key.'='.$val.',' : ' '.$key.'="'.$val.'"';
+            $atts .= ($js) ? $key.'='.$val.',' : ' '.get_instance()->security->xss_clean($key).'="'.html_attr_escape($val).'"';
         }
 
         return rtrim($atts, ',');
     }
 }
@@ -528,11 +528,23 @@ if (!function_exists('html_attr')) {
 
     function html_attr($attributes, $return_as_array = false) {
 
         $attr = new HTML_Attributes($attributes);
 
-        return $attr->getAttributes( ! $return_as_array);
+        if (!$return_as_array) {
+
+            $str = '';
+            $attributes = $attr->getAttributes(false);
+
+            foreach ($attributes as $key => $value) {
+                $str .= ' ' . get_instance()->security->xss_clean($key) . '="' . html_attr_escape($value) . '"';
+            }
+
+            return $str;
+        }
+
+        return $attr->getAttributes(false);
     }
 
 }
 
 if (!function_exists('html_attr_has')) {
diff --git a/platform/common/helpers/MY_form_helper.php b/platform/common/helpers/MY_form_helper.php
index bd40fb490..bf1161fa2 100644
--- a/platform/common/helpers/MY_form_helper.php
+++ b/platform/common/helpers/MY_form_helper.php
@@ -187,17 +187,17 @@ if ( ! function_exists('form_open'))
         if (stripos($attributes, 'accept-charset=') === FALSE)
         {
             $attributes .= ' accept-charset="'.strtolower(config_item('charset')).'"';
         }
 
-        $form = '<form action="'.$action.'"'.$attributes.">\n";
+        $form = '<form action="'.html_attr_escape(get_instance()->security->xss_clean($action)).'"'.$attributes.">\n";
 
         if (is_array($hidden))
         {
             foreach ($hidden as $name => $value)
             {
-                $form .= '<input type="hidden" name="'.$name.'" value="'.form_prep($value).'" />'."\n";
+                $form .= '<input type="hidden" name="'.html_attr_escape($name).'" value="'.form_prep($value).'" />'."\n";
             }
         }
 
         // Add CSRF field if enabled, but leave it out for GET requests and requests to external websites
         // Modified by Ivan Tcholakov, 04-NOV-2011.
@@ -357,11 +357,11 @@ if ( ! function_exists('form_hidden'))
             return $form;
         }
 
         if ( ! is_array($value))
         {
-            $form .= '<input type="hidden" name="'.$name.'" value="'.form_prep($value)."\" />\n";
+            $form .= '<input type="hidden" name="'.html_attr_escape($name).'" value="'.form_prep($value)."\" />\n";
         }
         else
         {
             foreach ($value as $k => $v)
             {
@@ -478,11 +478,11 @@ if ( ! function_exists('form_dropdown'))
                 if (empty($val))
                 {
                     continue;
                 }
 
-                $form .= '<optgroup label="'.$key."\">\n";
+                $form .= '<optgroup label="'.html_attr_escape($key)."\">\n";
 
                 foreach ($val as $optgroup_key => $optgroup_val)
                 {
                     $sel = in_array($optgroup_key, $selected) ? ' selected="selected"' : '';
                     $form .= '<option value="'.form_prep($optgroup_key).'"'.$sel.'>'
@@ -612,21 +612,43 @@ if ( ! function_exists('_parse_form_attributes'))
 
         foreach ($default as $key => $val)
         {
             if ($key === 'value')
             {
-                $val = form_prep($val);
+                $att .= get_instance()->security->xss_clean($key).'="'.form_prep($val).'" ';
             }
             elseif ($key === 'name' && ! strlen($default['name']))
             {
                 continue;
             }
-
-            $att .= $key.'="'.$val.'" ';
+            else
+            {
+                $att .= get_instance()->security->xss_clean($key).'="'.html_attr_escape($val).'" ';
+            }
         }
 
         return $att;
     }
 }
 
 // ------------------------------------------------------------------------
 // End BC functions
+
+
+// Added by Ivan Tcholakov, 05-SEP-2022.
+if ( ! function_exists('_attributes_to_string'))
+{
+    function _attributes_to_string($attributes)
+    {
+        if (empty($attributes))
+        {
+            return '';
+        }
+
+        if (is_array($attributes) || is_string($attributes) || is_object($attributes))
+        {
+            return html_attr($attributes, false);
+        }
+
+        return FALSE;
+    }
+}

Vulnerable code:         if ($this->input->get('theme') != '' && $this->input->method() == 'get' && !$this->input->is_ajax_request()) {
            $theme = (string) $this->input->get('theme');
            redirect(http_build_url(site_url('login'), array('query' => http_build_query(array('continue' => CURRENT_URL)))));
                if ($this->input->get('continue')) {
                    redirect($this->input->get('continue'));
                $this->session->set_flashdata('confirmation_message', '<nobr>Hello, <strong>'.$username.'</strong>.</nobr>');
            $theme_switcher[$key]['link'] = http_build_url(CURRENT_URL, array('query' => http_build_query(array('theme' => $value['key']))), HTTP_URL_JOIN_QUERY);
        if ($this->input->get('theme') != '' && $this->input->method() == 'get' && !$this->input->is_ajax_request()) {
            $theme = (string) $this->input->get('theme');
            $theme_switcher[$key]['link'] = http_build_url(CURRENT_URL, array('query' => http_build_query(array('theme' => $value['key']))), HTTP_URL_JOIN_QUERY);
            $atts .= ($js) ? $key.'='.$val.',' : ' '.$key.'="'.$val.'"';
        return $attr->getAttributes( ! $return_as_array);
        $form = '<form action="'.$action.'"'.$attributes.">\n";
                $form .= '<input type="hidden" name="'.$name.'" value="'.form_prep($value).'" />'."\n";
            $form .= '<input type="hidden" name="'.$name.'" value="'.form_prep($value)."\" />\n";
                $form .= '<optgroup label="'.$key."\">\n";
                $val = form_prep($val);

            $att .= $key.'="'.$val.'" ';
Fixed code:         $theme = (string) $this->input->get('theme');

        if ($theme != '') {
            $theme = (string) base64_decode($theme);
        }

        if ($theme != '' && $this->input->method() == 'get' && !$this->input->is_ajax_request()) {
            redirect(http_build_url(site_url('login'), array('query' => http_build_query(array('continue' => base64_encode(CURRENT_URL))))));
                $continue = (string) $this->input->get('continue');

                if ($continue != '') {
                    $continue = (string) base64_decode($continue);
                }

                if ($continue != '' && strpos($continue, site_url()) === 0) {
                    redirect($continue);
                $this->session->set_flashdata('confirmation_message', '<nobr>Hello, <strong>'.html_escape($username).'</strong>.</nobr>');
            $theme_switcher[$key]['link'] = http_build_url(CURRENT_URL, array('query' => http_build_query(array('theme' => base64_encode($value['key'])))), HTTP_URL_JOIN_QUERY);
        $theme = (string) $this->input->get('theme');

        if ($theme != '') {
            $theme = (string) base64_decode($theme);
        }

        if ($theme != '' && $this->input->method() == 'get' && !$this->input->is_ajax_request()) {
            $theme_switcher[$key]['link'] = http_build_url(CURRENT_URL, array('query' => http_build_query(array('theme' => base64_encode($value['key'])))), HTTP_URL_JOIN_QUERY);
            $atts .= ($js) ? $key.'='.$val.',' : ' '.get_instance()->security->xss_clean($key).'="'.html_attr_escape($val).'"';
        if (!$return_as_array) {

            $str = '';
            $attributes = $attr->getAttributes(false);

            foreach ($attributes as $key => $value) {
                $str .= ' ' . get_instance()->security->xss_clean($key) . '="' . html_attr_escape($value) . '"';
            }

            return $str;
        }

        return $attr->getAttributes(false);
        $form = '<form action="'.html_attr_escape(get_instance()->security->xss_clean($action)).'"'.$attributes.">\n";
                $form .= '<input type="hidden" name="'.html_attr_escape($name).'" value="'.form_prep($value).'" />'."\n";
            $form .= '<input type="hidden" name="'.html_attr_escape($name).'" value="'.form_prep($value)."\" />\n";
                $form .= '<optgroup label="'.html_attr_escape($key)."\">\n";
                $att .= get_instance()->security->xss_clean($key).'="'.form_prep($val).'" ';
            else
            {
                $att .= get_instance()->security->xss_clean($key).'="'.html_attr_escape($val).'" ';
            }


// Added by Ivan Tcholakov, 05-SEP-2022.
if ( ! function_exists('_attributes_to_string'))
{
    function _attributes_to_string($attributes)
    {
        if (empty($attributes))
        {
            return '';
        }

        if (is_array($attributes) || is_string($attributes) || is_object($attributes))
        {
            return html_attr($attributes, false);
        }

        return FALSE;
    }
}