CVE-2021-28280: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: CSRF + Cross-site scripting (XSS) vulnerability in search.php in PHPFusion 9.03.110 allows remote attackers to inject arbitrary web script or HTML
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: 2021-04-29T15:15Z
Repository: https://github.com/PHPFusion/PHPFusion
Language: PHP
Files changed: includes/classes/PHPFusion/Search/Search_Engine.php, includes/dynamics/includes/form_main.php
Commit date: 2021-03-10 17:13:01 +0800
Commit message: Fixes broken rendering trying to parse against <script> input values.

Diff: diff --git a/includes/classes/PHPFusion/Search/Search_Engine.php b/includes/classes/PHPFusion/Search/Search_Engine.php
index 284742623..d0af9993d 100644
--- a/includes/classes/PHPFusion/Search/Search_Engine.php
+++ b/includes/classes/PHPFusion/Search/Search_Engine.php
@@ -19,55 +19,32 @@ namespace PHPFusion\Search;
 
 use PHPFusion\Search;
 
 class Search_Engine extends Search_Model {
 
-    protected static $search_instance = NULL;
+    public static $locale = [];
 
     /*
      * Template
      * Adds a third option to replace output template.
      * Just extend and declare your own by mutating the following
      * strings.
      *
      * In order to access the variables, extend your class to Search_Engine!
      */
+    protected static $search_instance = NULL;
     protected static $render_search = '';
     protected static $search_no_result = '';
     protected static $search_count = '';
     protected static $search_item_wrapper = '';
-
     protected static $search_item = '';
     protected static $search_item_list = '';
     protected static $search_item_image = '';
-    public static $locale = [];
-
-    /**
-     * Returns params
-     *
-     * @param null $key
-     *
-     * @return array|string
-     */
-    public static function get_param($key = NULL) {
-        $info = [
-            'stype'        => self::$search_type,
-            'stext'        => self::$search_text,
-            'method'       => self::$search_method,
-            'datelimit'    => self::$search_date_limit,
-            'fields'       => self::$search_fields,
-            'sort'         => self::$search_sort,
-            'chars'        => self::$search_chars,
-            'order'        => self::$search_order,
-            'forum_id'     => self::$forum_id,
-            'memory_limit' => self::$memory_limit,
-            'composevars'  => self::$composevars,
-            'rowstart'     => self::$rowstart,
-            'search_param' => self::$search_param,
-        ];
 
-        return $key === NULL ? $info : (isset($info[$key]) ? $info[$key] : NULL);
+    protected function __construct() {
+        parent::__construct();
+        self::$locale = fusion_get_locale('', LOCALE.LOCALESET.'search.php');
     }
 
     /**
      * Returns the search engine instance
      *
@@ -80,15 +57,10 @@ class Search_Engine extends Search_Model {
         }
 
         return self::$search_instance;
     }
 
-    protected function __construct() {
-        parent::__construct();
-        self::$locale = fusion_get_locale('', LOCALE.LOCALESET.'search.php');
-    }
-
     /**
      * Controller for search form
      */
     protected static function display_search_form() {
         $locale = self::$locale;
@@ -313,10 +285,37 @@ class Search_Engine extends Search_Model {
         $search_js .= "document.getElementById('chars').disabled = false;\n";
         $search_js .= "break;}}";
         add_to_footer("<script type='text/javascript'>".jsminify($search_js)."</script>");
     }
 
+    /**
+     * Returns params
+     *
+     * @param null $key
+     *
+     * @return array|string
+     */
+    public static function get_param($key = NULL) {
+        $info = [
+            'stype'        => htmlentities(self::$search_type),
+            'stext'        => htmlentities(self::$search_text),
+            'method'       => htmlentities(self::$search_method),
+            'datelimit'    => self::$search_date_limit,
+            'fields'       => self::$search_fields,
+            'sort'         => self::$search_sort,
+            'chars'        => htmlentities(self::$search_chars),
+            'order'        => self::$search_order,
+            'forum_id'     => self::$forum_id,
+            'memory_limit' => self::$memory_limit,
+            'composevars'  => self::$composevars,
+            'rowstart'     => self::$rowstart,
+            'search_param' => htmlentities(self::$search_param),
+        ];
+
+        return $key === NULL ? $info : (isset($info[$key]) ? $info[$key] : NULL);
+    }
+
     /**
      * Controller for display the search results
      */
     protected static function display_results() {
         $locale = self::$locale;
@@ -331,11 +330,11 @@ class Search_Engine extends Search_Model {
          * @todo: roadmap on author
          */
         self::$fields_count = self::get_param('fields') + 1;
         for ($i = 0, $k = 0; $i < count($search_text); $i++) {
             if (strlen($search_text[$i]) >= 3) {
-                $qualified_search_text[] = $search_text[$i];
+                $qualified_search_text[] = htmlentities($search_text[$i]);
                 for ($j = 0; $j < self::$fields_count; $j++) {
                     // It is splitting to 2 parts.
                     self::$search_param[':sword'.$k.$j] = '%'.$search_text[$i].'%';
                 }
                 $k++;
@@ -358,10 +357,11 @@ class Search_Engine extends Search_Model {
         foreach ($qualified_search_text as $value) {
             $highlighted_text .= "'".$value."'";
             $highlighted_text .= ($i < self::$c_swords ? "," : "");
             $i++;
         }
+
         add_to_footer("<script type='text/javascript' src='".INCLUDES."jquery/jquery.highlight.js'></script>");
         add_to_jquery("$('.search_result').highlight([".$highlighted_text."],{wordsOnly:true}); $('.highlight').css({backgroundColor:'#FFFF88'});");
 
         /*
          * Run the drivers via include.. but this method need to change to simplify the kiss concept.
@@ -458,10 +458,20 @@ class Search_Engine extends Search_Model {
         if (self::get_param('stype') != "all") {
             echo self::$navigation_result;
         }
     }
 
+    /**
+     * Load the search driver file
+     * - Prevents string mutation
+     *
+     * @param $path
+     */
+    protected static function __Load($path) {
+        include_once($path);
+    }
+
     /**
      * Controller for omitting search
      */
     protected static function display_noResults() {
         $locale = self::$locale;
@@ -475,16 +485,6 @@ class Search_Engine extends Search_Model {
     /**
      * Prevents class cloning
      */
     private function __clone() {
     }
-
-    /**
-     * Load the search driver file
-     * - Prevents string mutation
-     *
-     * @param $path
-     */
-    protected static function __Load($path) {
-        include_once($path);
-    }
 }
diff --git a/includes/dynamics/includes/form_main.php b/includes/dynamics/includes/form_main.php
index 2c1b82ae7..dd9a36f08 100644
--- a/includes/dynamics/includes/form_main.php
+++ b/includes/dynamics/includes/form_main.php
@@ -86,19 +86,18 @@ function clean_input_name($value) {
     return preg_replace($re, '', $value);
 }
 
 function clean_input_value($value) {
     if (is_string($value)) {
-        return descript($value);
+        return stripinput($value);
     }
     if (is_array($value)) {
-        return array_map('descript', $value);
+        return array_map('stripinput', $value);
     }
     return '';
 }
 
-
 function load_select2_script() {
     static $loaded = FALSE;
     if ($loaded === FALSE) {
         /**
          * @return string

Vulnerable code:     protected static $search_instance = NULL;

    public static $locale = [];

    /**
     * Returns params
     *
     * @param null $key
     *
     * @return array|string
     */
    public static function get_param($key = NULL) {
        $info = [
            'stype'        => self::$search_type,
            'stext'        => self::$search_text,
            'method'       => self::$search_method,
            'datelimit'    => self::$search_date_limit,
            'fields'       => self::$search_fields,
            'sort'         => self::$search_sort,
            'chars'        => self::$search_chars,
            'order'        => self::$search_order,
            'forum_id'     => self::$forum_id,
            'memory_limit' => self::$memory_limit,
            'composevars'  => self::$composevars,
            'rowstart'     => self::$rowstart,
            'search_param' => self::$search_param,
        ];
        return $key === NULL ? $info : (isset($info[$key]) ? $info[$key] : NULL);
    protected function __construct() {
        parent::__construct();
        self::$locale = fusion_get_locale('', LOCALE.LOCALESET.'search.php');
    }

                $qualified_search_text[] = $search_text[$i];

    /**
     * Load the search driver file
     * - Prevents string mutation
     *
     * @param $path
     */
    protected static function __Load($path) {
        include_once($path);
    }
        return descript($value);
        return array_map('descript', $value);

Fixed code:     public static $locale = [];
    protected static $search_instance = NULL;
    protected function __construct() {
        parent::__construct();
        self::$locale = fusion_get_locale('', LOCALE.LOCALESET.'search.php');
    /**
     * Returns params
     *
     * @param null $key
     *
     * @return array|string
     */
    public static function get_param($key = NULL) {
        $info = [
            'stype'        => htmlentities(self::$search_type),
            'stext'        => htmlentities(self::$search_text),
            'method'       => htmlentities(self::$search_method),
            'datelimit'    => self::$search_date_limit,
            'fields'       => self::$search_fields,
            'sort'         => self::$search_sort,
            'chars'        => htmlentities(self::$search_chars),
            'order'        => self::$search_order,
            'forum_id'     => self::$forum_id,
            'memory_limit' => self::$memory_limit,
            'composevars'  => self::$composevars,
            'rowstart'     => self::$rowstart,
            'search_param' => htmlentities(self::$search_param),
        ];

        return $key === NULL ? $info : (isset($info[$key]) ? $info[$key] : NULL);
    }

                $qualified_search_text[] = htmlentities($search_text[$i]);

    /**
     * Load the search driver file
     * - Prevents string mutation
     *
     * @param $path
     */
    protected static function __Load($path) {
        include_once($path);
    }

        return stripinput($value);
        return array_map('stripinput', $value);