CVE-2023-3142: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - Stored in GitHub repository microweber/microweber prior to 2.0.
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: 5.4
Published: 2023-06-07T15:15Z
Repository: https://github.com/microweber/microweber
Language: PHP
Files changed: src/MicroweberPackages/Content/ContentManagerHelpers.php, src/MicroweberPackages/Content/tests/ContentApiControllerLiveEditSaveTest.php, src/MicroweberPackages/Database/DatabaseManager.php, src/MicroweberPackages/Module/Models/Module.php
Commit date: 2023-06-07 14:18:07 +0300
Commit message: update

Diff: diff --git a/src/MicroweberPackages/Content/ContentManagerHelpers.php b/src/MicroweberPackages/Content/ContentManagerHelpers.php
index 0a720e324ce..db6a26acd5c 100644
--- a/src/MicroweberPackages/Content/ContentManagerHelpers.php
+++ b/src/MicroweberPackages/Content/ContentManagerHelpers.php
@@ -657,10 +657,11 @@ class ContentManagerHelpers extends ContentManagerCrud
             return false;
         }
         $ref_page_url = false;
         if (isset($_SERVER['HTTP_REFERER'])) {
             $ref_page_url = $_SERVER['HTTP_REFERER'];
+            $ref_page_url = xss_clean($ref_page_url);
         }
 
         if (isset($post_data['id']) and intval($post_data['id']) > 0) {
             $page_id = intval($post_data['id']);
         } elseif ($ref_page_url != '') {
diff --git a/src/MicroweberPackages/Content/tests/ContentApiControllerLiveEditSaveTest.php b/src/MicroweberPackages/Content/tests/ContentApiControllerLiveEditSaveTest.php
index f472903ffde..66464b88712 100644
--- a/src/MicroweberPackages/Content/tests/ContentApiControllerLiveEditSaveTest.php
+++ b/src/MicroweberPackages/Content/tests/ContentApiControllerLiveEditSaveTest.php
@@ -1,12 +1,10 @@
 <?php
 
 namespace MicroweberPackages\Content\tests;
 
-use Illuminate\Database\Eloquent\Model;
 use Illuminate\Support\Facades\Auth;
-use MicroweberPackages\Category\Models\Category;
 use MicroweberPackages\Core\tests\TestCase;
 use MicroweberPackages\Helper\XSSClean;
 use MicroweberPackages\Multilanguage\MultilanguageHelpers;
 use MicroweberPackages\Page\Models\Page;
 use MicroweberPackages\User\Models\User;
@@ -62,18 +60,15 @@ Call to action
  <!--a comment here to check -->
 </div>
 HTML;
 
 
-
-
         $pq = \phpQuery::newDocument($contentFieldHtml);
 
         $contentFieldHtml = $pq->htmlOuter();
 
 
-
         $fieldsData = [
             'field_data_0' => [
                 'attributes' => [
                     'class' => 'container edit',
                     'rel' => 'content',
@@ -128,11 +123,10 @@ HTML;
             'preview_layout_file' => 'clean.php',
             'is_active' => 1,
         ]);
 
 
-
         $findPage = Page::whereId($newCleanMlPage)->first();
 
         $pageId = $findPage->id;
         $this->assertEquals($findPage->id, $newCleanMlPage);
 
@@ -170,16 +164,16 @@ HTML;
 
         foreach ($htmls as $k => $html) {
 
 
             //remove empty tags
-           $html =  preg_replace('/<[^\/>]*>([\s]?)*<\/[^>]*>/', '', $html);
+            $html = preg_replace('/<[^\/>]*>([\s]?)*<\/[^>]*>/', '', $html);
 
             // remove script tags
             $html = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', "", $html);
 
-             // remove code tags
+            // remove code tags
             $html = preg_replace('/<code\b[^>]*>(.*?)<\/code>/is', "", $html);
 
 
             $l = $html;
             $pq = \phpQuery::newDocument($l);
@@ -199,12 +193,10 @@ HTML;
                     'html' => $contentFieldHtml
                 ]
             ];
 
 
-
-
             $encoded = base64_encode(json_encode($fieldsData));
 
             $response = $this->call(
                 'POST',
                 route('api.content.save_edit'),
@@ -223,11 +215,10 @@ HTML;
             $this->assertEquals(trim($fieldSaved[0]['content']), trim($contentFieldHtml));
             $this->assertEquals($fieldSaved[0]['rel_type'], 'content');
             $this->assertEquals($fieldSaved[0]['field'], 'content');
 
 
-
             $pq2 = \phpQuery::newDocument($contentFieldHtml);
             $this->assertEquals($contentFieldHtml, $pq2->htmlOuter());
 
 
             $findPage = Page::whereId($fieldSaved[0]['id'])->first();
@@ -271,11 +262,10 @@ HTML;
         $_SERVER['REQ`UEST_URI'] = $pageLink;
         $_SERVER['REDIRECT_URL'] = $pageLink;
         $_SERVER['HTTP_REFERER'] = content_link($pageId);
 
 
-
         $zip = new \ZipArchive();
         $zip->open(__DIR__ . '/../../Helper/tests/misc/xss-test-files.zip');
         $xssList = $zip->getFromName('xss-payload-list.txt');
         $zip->close();
 
@@ -287,18 +277,17 @@ HTML;
 
 
         foreach ($xssListChunks as $stringChunk) {
 
             $string = implode(PHP_EOL, $stringChunk);
-             if (empty(trim($string))) {
+            if (empty(trim($string))) {
                 continue;
             }
 
             $contentFieldHtml = $string;
 
 
-
             $fieldsData = [
                 'field_data_0' => [
                     'attributes' => [
                         'class' => 'container edit',
                         'rel' => 'content',
@@ -331,27 +320,25 @@ HTML;
 
             $findPage = Page::whereId($fieldSaved[0]['id'])->first();
             $contentFieldHtml1 = trim($contentFieldHtml);
             $contentFieldHtml2 = trim($findPage->content);
 
-           $this->assertNotEquals($contentFieldHtml1, $contentFieldHtml2);
+            $this->assertNotEquals($contentFieldHtml1, $contentFieldHtml2);
 
 
             foreach ($stringChunk as $stringItem) {
                 if (trim($stringItem) == '') {
                     continue;
                 }
                 $this->assertStringNotContainsString($stringItem, $findPage->content);
             }
 
 
-
         }
     }
 
 
-
     public function testSaveContentOnPageLiveEditFromOtherExamples()
     {
         $this->cleanupAndPrepare();
 
 
@@ -388,11 +375,11 @@ HTML;
             for ($i = 0; $i < $zip->numFiles; $i++) {
                 $fn = $zip->getNameIndex($i);
                 $ext = get_file_extension($fn);
                 if ($ext == 'html') {
                     $string = $zip->getFromName($fn);
-                    if(trim($string) == ''){
+                    if (trim($string) == '') {
                         continue;
                     }
                     $htmls[$fn] = $string;
                 }
             }
@@ -419,18 +406,17 @@ HTML;
         }
 
         $htmlsChunks = array_chunk($htmls, 100);
 
 
+        foreach ($htmlsChunks as $k => $htmlChunk) {
 
-      foreach ($htmlsChunks as $k => $htmlChunk) {
-
-          $html = implode('--------chunk-------', $htmlChunk);
-         $contentFieldHtml = $html;
+            $html = implode('--------chunk-------', $htmlChunk);
+            $contentFieldHtml = $html;
 
 
-           // $contentFieldHtml = str_replace('{SITE_URL}', site_url(), $contentFieldHtml);
+            // $contentFieldHtml = str_replace('{SITE_URL}', site_url(), $contentFieldHtml);
             $fieldsData = [
                 'field_data_0' => [
                     'attributes' => [
                         'class' => 'container edit',
                         'rel' => 'content',
@@ -460,26 +446,49 @@ HTML;
 
 
             $fieldSaved = $response->decodeResponseJson();
 
 
-
             $findPage = Page::whereId($fieldSaved[0]['id'])->first();
             $contentFieldHtml1 = trim($contentFieldHtml);
             $contentFieldHtml2 = trim($findPage->content);
 
             $this->assertEquals($contentFieldHtml1, $contentFieldHtml2);
             $this->assertEquals($contentFieldHtml, $findPage->content);
 
 
-       }
+        }
+
+
+    }
+
+
+    public function testSaveContentXssSpaceInUrlAttribute()
+    {
+
+        $this->cleanupAndPrepare();
+        $unique = uniqid('testSaveContentXssInUrlAttribute');
+        $newCleanPage = save_content([
+            'subtype' => 'static',
+            'content_type' => 'page',
+            'layout_file' => 'clean.php',
+            'title' => 'testSaveContentXssInUrlAttribute-save' . $unique,
+            'preview_layout_file' => 'clean.php',
+            'url' => 'url with space ' . $unique . '  <script>alert(1)</script>',
+            'is_active' => 1,
+        ]);
+
+        $findPage = Page::whereId($newCleanPage)->first();
 
+        $this->assertEquals($findPage->url, 'url-with-space-' . strtolower($unique));
 
     }
 
 
-    private function cleanupAndPrepare(){
+
+    private function cleanupAndPrepare()
+    {
         $user = User::where('is_admin', '=', '1')->first();
         Auth::login($user);
         \Config::set('microweber.disable_model_cache', 1);
 
 
@@ -487,22 +496,21 @@ HTML;
         Page::truncate();
     }
 
     public static function fixLinksPrecentAttributes($text)
     {
-        $text = str_ireplace('{SITE_URL}','___mw-site-url-temp-replace-on-clean___', $text);
+        $text = str_ireplace('{SITE_URL}', '___mw-site-url-temp-replace-on-clean___', $text);
         $pq = \phpQuery::newDocument($text);
 
-        foreach($pq->find('a') as $stuffs)
-        {
+        foreach ($pq->find('a') as $stuffs) {
             $href = pq($stuffs)->attr('href');
-            if($href){
-            pq($stuffs)->attr('href', str_replace(' ', '%20', $href));
+            if ($href) {
+                pq($stuffs)->attr('href', str_replace(' ', '%20', $href));
             }
         }
         $text = $pq->htmlOuter();
-        $text = str_ireplace('___mw-site-url-temp-replace-on-clean___','{SITE_URL}',     $text);
+        $text = str_ireplace('___mw-site-url-temp-replace-on-clean___', '{SITE_URL}', $text);
         return $text;
 
     }
 
 
diff --git a/src/MicroweberPackages/Database/DatabaseManager.php b/src/MicroweberPackages/Database/DatabaseManager.php
index 7f4bc13af75..76381946b81 100644
--- a/src/MicroweberPackages/Database/DatabaseManager.php
+++ b/src/MicroweberPackages/Database/DatabaseManager.php
@@ -666,20 +666,36 @@ class DatabaseManager extends DbUtils
                 }
                 if (!empty($criteria)) {
                     $criteria['id'] = $next_id;
                 }
             }
-            $id_to_return = $this->table($table_assoc_name)->insert($criteria);
+
+            if(method_exists($this->table($table),'create')){
+                $id_to_return = $this->table($table)->create($criteria);
+            } else {
+                $id_to_return = $this->table($table)->insert($criteria);
+            }
+             //$id_to_return = $this->table($table_assoc_name)->insert($criteria);
+         //   $id_to_return = $this->table($table_assoc_name)->create($criteria);
             $id_to_return = $this->last_id($table);
 
         } else {
+
             $insert_or_update = $highestId = $this->table($table)->where('id', $criteria['id'])->count();
             if ($insert_or_update != 0) {
                 $insert_or_update = 'update';
             } else {
-                $insert_or_update = 'insert';
+              $insert_or_update = 'insert';
+              if(method_exists($this->table($table),'create')){
+                    $insert_or_update = 'create';
+              }
+          //  $insert_or_update = 'create';
             }
+
+
+
+
             $id_to_return = $this->table($table_assoc_name)->where('id', $criteria['id'])->$insert_or_update($criteria);
             $id_to_return = $criteria['id'];
         }
 
         if ($id_to_return == false) {
@@ -963,12 +979,12 @@ class DatabaseManager extends DbUtils
         if ($table == 'content' || $table == 'categories' || $table == 'options') {
 
             $this->use_model_cache[$table]= true;
 
             if ($table == 'content') {
-              //  $model = new Content($params);
-                 $model = app()->make(Content::class);
+                 $model = new Content($params);
+              //   $model = app()->make(Content::class);
 
                 //    $model::boot();
             } else if ($table == 'categories') {
                  $model = new Category($params);
 
diff --git a/src/MicroweberPackages/Module/Models/Module.php b/src/MicroweberPackages/Module/Models/Module.php
index e84774e691e..6601e531910 100644
--- a/src/MicroweberPackages/Module/Models/Module.php
+++ b/src/MicroweberPackages/Module/Models/Module.php
@@ -44,17 +44,19 @@ class Module extends Model
         return $icon;
     }
 
     public function getIconInline()
     {
-        $icon = str_replace( '{SITE_URL}', '', $this->icon);
-        if (get_file_extension($icon) == 'svg') {
-            $content =  file_get_contents($icon);
-            $content = str_replace('<?xml version="1.0" encoding="utf-8"?>', '', $content);
-            return $content;
-        } else {
-            return '<img src="' . $icon . '" />';
+        $icon = str_replace('{SITE_URL}', '', $this->icon);
+        if (file_exists($icon)) {
+            if (get_file_extension($icon) == 'svg') {
+                $content = file_get_contents($icon);
+                $content = str_replace('<?xml version="1.0" encoding="utf-8"?>', '', $content);
+                return $content;
+            } else {
+                return '<img src="' . $icon . '" />';
+            }
         }
     }
 
     public function register($module)
     {

Vulnerable code: use Illuminate\Database\Eloquent\Model;
use MicroweberPackages\Category\Models\Category;




           $html =  preg_replace('/<[^\/>]*>([\s]?)*<\/[^>]*>/', '', $html);
             // remove code tags




             if (empty(trim($string))) {

           $this->assertNotEquals($contentFieldHtml1, $contentFieldHtml2);


                    if(trim($string) == ''){
      foreach ($htmlsChunks as $k => $htmlChunk) {

          $html = implode('--------chunk-------', $htmlChunk);
         $contentFieldHtml = $html;
           // $contentFieldHtml = str_replace('{SITE_URL}', site_url(), $contentFieldHtml);

       }
    private function cleanupAndPrepare(){
        $text = str_ireplace('{SITE_URL}','___mw-site-url-temp-replace-on-clean___', $text);
        foreach($pq->find('a') as $stuffs)
        {
            if($href){
            pq($stuffs)->attr('href', str_replace(' ', '%20', $href));
        $text = str_ireplace('___mw-site-url-temp-replace-on-clean___','{SITE_URL}',     $text);
            $id_to_return = $this->table($table_assoc_name)->insert($criteria);
                $insert_or_update = 'insert';
              //  $model = new Content($params);
                 $model = app()->make(Content::class);
        $icon = str_replace( '{SITE_URL}', '', $this->icon);
        if (get_file_extension($icon) == 'svg') {
            $content =  file_get_contents($icon);
            $content = str_replace('<?xml version="1.0" encoding="utf-8"?>', '', $content);
            return $content;
        } else {
            return '<img src="' . $icon . '" />';
Fixed code:             $ref_page_url = xss_clean($ref_page_url);
            $html = preg_replace('/<[^\/>]*>([\s]?)*<\/[^>]*>/', '', $html);
            // remove code tags
            if (empty(trim($string))) {
            $this->assertNotEquals($contentFieldHtml1, $contentFieldHtml2);
                    if (trim($string) == '') {
        foreach ($htmlsChunks as $k => $htmlChunk) {
            $html = implode('--------chunk-------', $htmlChunk);
            $contentFieldHtml = $html;
            // $contentFieldHtml = str_replace('{SITE_URL}', site_url(), $contentFieldHtml);
        }


    }


    public function testSaveContentXssSpaceInUrlAttribute()
    {

        $this->cleanupAndPrepare();
        $unique = uniqid('testSaveContentXssInUrlAttribute');
        $newCleanPage = save_content([
            'subtype' => 'static',
            'content_type' => 'page',
            'layout_file' => 'clean.php',
            'title' => 'testSaveContentXssInUrlAttribute-save' . $unique,
            'preview_layout_file' => 'clean.php',
            'url' => 'url with space ' . $unique . '  <script>alert(1)</script>',
            'is_active' => 1,
        ]);

        $findPage = Page::whereId($newCleanPage)->first();
        $this->assertEquals($findPage->url, 'url-with-space-' . strtolower($unique));

    private function cleanupAndPrepare()
    {
        $text = str_ireplace('{SITE_URL}', '___mw-site-url-temp-replace-on-clean___', $text);
        foreach ($pq->find('a') as $stuffs) {
            if ($href) {
                pq($stuffs)->attr('href', str_replace(' ', '%20', $href));
        $text = str_ireplace('___mw-site-url-temp-replace-on-clean___', '{SITE_URL}', $text);

            if(method_exists($this->table($table),'create')){
                $id_to_return = $this->table($table)->create($criteria);
            } else {
                $id_to_return = $this->table($table)->insert($criteria);
            }
             //$id_to_return = $this->table($table_assoc_name)->insert($criteria);
         //   $id_to_return = $this->table($table_assoc_name)->create($criteria);

              $insert_or_update = 'insert';
              if(method_exists($this->table($table),'create')){
                    $insert_or_update = 'create';
              }
          //  $insert_or_update = 'create';




                 $model = new Content($params);
              //   $model = app()->make(Content::class);
        $icon = str_replace('{SITE_URL}', '', $this->icon);
        if (file_exists($icon)) {
            if (get_file_extension($icon) == 'svg') {
                $content = file_get_contents($icon);
                $content = str_replace('<?xml version="1.0" encoding="utf-8"?>', '', $content);
                return $content;
            } else {
                return '<img src="' . $icon . '" />';
            }