CVE-2022-1504: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: XSS in /demo/module/?module=HERE in GitHub repository microweber/microweber prior to 1.2.15. Typical impact of XSS attacks.
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: 2022-04-27T11:15Z
Repository: https://github.com/microweber/microweber
Language: PHP
Files changed: src/MicroweberPackages/App/Http/Controllers/ApiController.php, src/MicroweberPackages/App/Http/Controllers/FrontendController.php, src/MicroweberPackages/Comment/Models/CommentsCrud.php, src/MicroweberPackages/Content/Models/ModelFilters/Traits/FilterByKeywordTrait.php, src/MicroweberPackages/Helper/HTMLClean.php, src/MicroweberPackages/Helper/XSSClean.php, src/MicroweberPackages/Helper/tests/SecurityTest.php
Commit date: 2022-04-27 13:33:14 +0300
Commit message: update

Diff: diff --git a/src/MicroweberPackages/App/Http/Controllers/ApiController.php b/src/MicroweberPackages/App/Http/Controllers/ApiController.php
index 9e72121a9e65..de2fffb630b4 100644
--- a/src/MicroweberPackages/App/Http/Controllers/ApiController.php
+++ b/src/MicroweberPackages/App/Http/Controllers/ApiController.php
@@ -6,14 +6,14 @@ namespace MicroweberPackages\App\Http\Controllers;
 use Illuminate\Support\Facades\App;
 use Illuminate\Support\Facades\Request;
 use MicroweberPackages\App\Http\Middleware\ApiAuth;
 use MicroweberPackages\App\Http\Middleware\SameSiteRefererMiddleware;
 use MicroweberPackages\App\Managers\Helpers\VerifyCsrfTokenHelper;
+use MicroweberPackages\Helper\XSSClean;
 use MicroweberPackages\View\View;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\Response;
-use voku\helper\AntiXSS;
 
 
 class ApiController  extends FrontendController
 {
 
@@ -607,31 +607,40 @@ class ApiController  extends FrontendController
         $request_data = array_merge($_GET, $_POST);
 
         // sanitize attributes
         if($request_data){
             $request_data_new = [];
-            $antixss = new AntiXSS();
+
+            $xssClean = new XSSClean();
+
             foreach ($request_data as $k=>$v){
                 if(is_string($v)) {
                     $v = str_replace('<', '-', $v);
                     $v = str_replace('>', '-', $v);
                 }
-                $v = $antixss->xss_clean($v);
+                if(is_array($v)) {
+                    $v = $xssClean->cleanArray($v);
+                } else {
+                    $v = $xssClean->clean($v);
+                }
 
                 if(is_string($k)){
                     $k = str_replace('<', '-', $k);
                     $k = str_replace('>', '-', $k);
-                    $k = $antixss->xss_clean($k);
+
+                    $k = $xssClean->clean($k);
                     if($k){
                         $request_data_new[$k] = $v;
                     }
                 } else {
                     $request_data_new[$k] = $v;
                 }
 
             }
             $request_data = $request_data_new;
+            var_dump($request_data);
+            exit;
         }
 
         $page = false;
 
         $custom_display = false;
diff --git a/src/MicroweberPackages/App/Http/Controllers/FrontendController.php b/src/MicroweberPackages/App/Http/Controllers/FrontendController.php
index 154f89d676b9..e1d2859bc830 100644
--- a/src/MicroweberPackages/App/Http/Controllers/FrontendController.php
+++ b/src/MicroweberPackages/App/Http/Controllers/FrontendController.php
@@ -21,11 +21,10 @@ use MicroweberPackages\Install\Http\Controllers\InstallController;
 use MicroweberPackages\Page\Models\Page;
 use MicroweberPackages\View\View;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
 use Symfony\Component\HttpFoundation\Response;
-use voku\helper\AntiXSS;
 
 
 class FrontendController extends Controller
 {
     use LiveEditTrait;
diff --git a/src/MicroweberPackages/Comment/Models/CommentsCrud.php b/src/MicroweberPackages/Comment/Models/CommentsCrud.php
index 3ddd358b0ef3..30d7a7734728 100644
--- a/src/MicroweberPackages/Comment/Models/CommentsCrud.php
+++ b/src/MicroweberPackages/Comment/Models/CommentsCrud.php
@@ -2,11 +2,10 @@
 
 namespace MicroweberPackages\Comment\Models;
 
 use MicroweberPackages\Database\Crud;
 use MicroweberPackages\Helper\HTMLClean;
-use voku\helper\AntiXSS;
 
 
 class CommentsCrud extends Crud
 {
 
diff --git a/src/MicroweberPackages/Content/Models/ModelFilters/Traits/FilterByKeywordTrait.php b/src/MicroweberPackages/Content/Models/ModelFilters/Traits/FilterByKeywordTrait.php
index e4f36ac23519..059d8f7060a9 100644
--- a/src/MicroweberPackages/Content/Models/ModelFilters/Traits/FilterByKeywordTrait.php
+++ b/src/MicroweberPackages/Content/Models/ModelFilters/Traits/FilterByKeywordTrait.php
@@ -8,25 +8,27 @@
 
 namespace MicroweberPackages\Content\Models\ModelFilters\Traits;
 
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Support\Facades\Config;
+use MicroweberPackages\Helper\XSSClean;
 use MicroweberPackages\Multilanguage\Models\MultilanguageTranslations;
 use MicroweberPackages\Multilanguage\MultilanguageHelpers;
-use voku\helper\AntiXSS;
 
 trait FilterByKeywordTrait
 {
     public function keyword($keyword)
     {
         $model = $this->getModel();
         $table = $model->getTable();
         $searchInFields = $model->getSearchable();
         $keywordToSearch = false;
-        $antixss = new AntiXSS();
+
+        $xssClean = new XSSClean();
+
         if (is_string($keyword)) {
-            $keyword = $antixss->xss_clean($keyword);
+            $keyword = $xssClean->clean($keyword);
             if ($keyword) {
                 $keywordToSearch = $keyword;
             }
         }
 
diff --git a/src/MicroweberPackages/Helper/HTMLClean.php b/src/MicroweberPackages/Helper/HTMLClean.php
index 57a10ea8914e..fc74e7f42cba 100644
--- a/src/MicroweberPackages/Helper/HTMLClean.php
+++ b/src/MicroweberPackages/Helper/HTMLClean.php
@@ -31,12 +31,14 @@ class HTMLClean
         }
     }
 
     public function clean($html) {
 
-        $antiXss = new \voku\helper\AntiXSS();
-        $html = $antiXss->xss_clean($html);
+
+
+        $xssClean = new XSSClean();
+        $html = $xssClean->clean($html);
 
         $config = \HTMLPurifier_Config::createDefault();
 
         if ($this->purifierPath) {
             $config->set('Cache.SerializerPath', $this->purifierPath);
diff --git a/src/MicroweberPackages/Helper/XSSClean.php b/src/MicroweberPackages/Helper/XSSClean.php
new file mode 100644
index 000000000000..e79a3d33f0cc
--- /dev/null
+++ b/src/MicroweberPackages/Helper/XSSClean.php
@@ -0,0 +1,155 @@
+<?php
+
+namespace MicroweberPackages\Helper;
+
+use voku\helper\AntiXSS;
+
+class XSSClean
+{
+
+
+    public function cleanArray($array): array
+    {
+
+        if (is_array($array)) {
+
+            $cleanedArray = [];
+            foreach ($array as $key => $value) {
+                if (is_string($key)) {
+                    $key = $this->clean($key);
+                }
+
+                if (is_array($value)) {
+                    $cleanedArray[$key] = $this->cleanArray($value);
+                } else {
+                    $cleanedArray[$key] = $this->clean($value);
+                }
+            }
+
+            return $cleanedArray;
+        }
+    }
+
+     public function clean($html): string
+    {
+ // from https://portswigger.net/web-security/cross-site-scripting/cheat-sheet#ontransitionend
+        $cleanStrings = [
+            'ontransitionstart',
+           // 'onwebkitanimationend',
+            'onwebkitanimationiteration',
+            'onwebkitanimationstart',
+            'onwebkittransitionend',
+            'ontransitionrun',
+            'onloadedmetadata',
+            'ondurationchange',
+            'oncanplaythrough',
+            'oncuechange',
+            'onbounce',
+            'onbegin',
+            'onbeforeunload',
+            'onbeforescriptexecute',
+            'onbeforeprint',
+            'onanimationstart',
+            'onanimationiteration',
+            'onanimationend',
+            'onanimationcancel',
+            'onafterscriptexecute',
+            'onfocusin',
+            'onhashchange',
+            'onload',
+            'onunload',
+            'onloadend',
+            'onloadstart',
+            'onmessage',
+            'onpageshow',
+            'onloadedmetadata',
+            'onloadeddata',
+            'onplay',
+            'onplaying',
+            'onpopstate',
+            'onprogress',
+            'onrepeat',
+            'onresize',
+            'onscroll',
+            'onstart',
+            'ontimeupdate',
+            'ontoggle',
+            'ontransitionend',
+            'ontransitioncancel',
+            'ontransitionrun',
+            'ontransitionstart',
+            'onafterprint',
+            'onauxclick',
+            'onbeforecopy',
+            'onbeforecut',
+            'onblur',
+            'onchange',
+            'onclick',
+            'onclose',
+            'oncontextmenu',
+            'oncopy',
+            'oncut',
+            'ondblclick',
+            'ondrag',
+            'ondragend',
+            'ondragenter',
+            'ondragleave',
+            'ondragover',
+            'ondragstart',
+            'ondrop',
+            'onfocusout',
+            'onfullscreenchange',
+            'oninput',
+            'oninvalid',
+            'onkeydown',
+            'onkeypress',
+            'onkeyup',
+            'onmousedown',
+            'onmouseenter',
+            'onmouseleave',
+            'onmousemove',
+            'onmouseout',
+            'onmouseover',
+            'onmouseup',
+            'onmousewheel',
+            'onmozfullscreenchange',
+            'onpagehide',
+            'onpaste',
+            'onpause',
+            'onpointerdown',
+            'onpointerenter',
+            'onpointerleave',
+            'onpointermove',
+            'onpointerout',
+            'onpointerover',
+            'onpointerrawupdate',
+            'onpointerup',
+            'onreset',
+            'onsearch',
+            'onseeked',
+            'onseeking',
+            'onselect',
+            'onselectionchange',
+            'onselectstart',
+            'onshow',
+            'onsubmit',
+            'ontouchend',
+            'ontouchmove',
+            'ontouchstart',
+            'onvolumechange',
+            'onwheel',
+            'onunhandledrejection'
+        ];
+
+        $antiXss = new AntiXSS();
+        $antiXss->addEvilHtmlTags($cleanStrings);
+        $antiXss->addEvilAttributes($cleanStrings);
+        $antiXss->addNeverAllowedOnEventsAfterwards($cleanStrings);
+
+        $html = $antiXss->xss_clean($html);
+
+
+        return $html;
+    }
+
+}
diff --git a/src/MicroweberPackages/Helper/tests/SecurityTest.php b/src/MicroweberPackages/Helper/tests/SecurityTest.php
index c87b453fc50f..f5baa7c71ca0 100644
--- a/src/MicroweberPackages/Helper/tests/SecurityTest.php
+++ b/src/MicroweberPackages/Helper/tests/SecurityTest.php
@@ -1,28 +1,31 @@
 <?php
+
 namespace MicroweberPackages\Helper\tests;
 
+use MicroweberPackages\Helper\XSSClean;
+
 class SecurityTest extends BaseTest
 {
     public function testComments()
     {
         $antiXss = new \MicroweberPackages\Helper\HTMLClean();
 
         $string = '<a href="https://example.com">test</a>';
         $content = $antiXss->onlyTags($string);
 
-       $this->assertEquals($string, $content);
+        $this->assertEquals($string, $content);
     }
 
 
     public function testXssExternalLinkImg()
     {
         $antiXss = new \MicroweberPackages\Helper\HTMLClean();
 
-        $string = '<img src="'.site_url().'test.jpg" />';
+        $string = '<img src="' . site_url() . 'test.jpg" />';
         $content = $antiXss->clean($string);
-        $this->assertEquals('<img src="'.site_url().'test.jpg" alt="test.jpg" />', $content);
+        $this->assertEquals('<img src="' . site_url() . 'test.jpg" alt="test.jpg" />', $content);
 
 
         $string = '<img src="https://google.bg/test.jpg" />';
         $content = $antiXss->clean($string);
         $this->assertEquals('', $content);
@@ -32,11 +35,11 @@ class SecurityTest extends BaseTest
 
     public function testXssList()
     {
 
         $zip = new \ZipArchive();
-        $zip->open(__DIR__.'/misc/xss-test-files.zip');
+        $zip->open(__DIR__ . '/misc/xss-test-files.zip');
         $xssList = $zip->getFromName('xss-payload-list.txt');
         $zip->close();
 
         $xssList = preg_replace('~\R~u', "\r\n", $xssList);
         $xssList = explode(PHP_EOL, $xssList);
@@ -53,6 +56,15 @@ class SecurityTest extends BaseTest
             $this->assertNotEquals($string, $content);
 
         }
     }
 
+    public function testXSSCleanArrtibutesNewEvents()
+    {
+        $xssClean = new XSSClean();
+        $str = "class='x module module-'ontransitionrun=alert(1) '";
+        $clean = $xssClean->clean($str);
+        $this->assertEquals("class='x module module-'=alert&#40;1&#41; '", $clean);
+
+    }
+
 }

Vulnerable code: use voku\helper\AntiXSS;
            $antixss = new AntiXSS();
                $v = $antixss->xss_clean($v);
                    $k = $antixss->xss_clean($k);
use voku\helper\AntiXSS;
use voku\helper\AntiXSS;
use voku\helper\AntiXSS;
        $antixss = new AntiXSS();
            $keyword = $antixss->xss_clean($keyword);
        $antiXss = new \voku\helper\AntiXSS();
        $html = $antiXss->xss_clean($html);
       $this->assertEquals($string, $content);
        $string = '<img src="'.site_url().'test.jpg" />';
        $this->assertEquals('<img src="'.site_url().'test.jpg" alt="test.jpg" />', $content);
        $zip->open(__DIR__.'/misc/xss-test-files.zip');
Fixed code: use MicroweberPackages\Helper\XSSClean;

            $xssClean = new XSSClean();

                if(is_array($v)) {
                    $v = $xssClean->cleanArray($v);
                } else {
                    $v = $xssClean->clean($v);
                }

                    $k = $xssClean->clean($k);
            var_dump($request_data);
            exit;
use MicroweberPackages\Helper\XSSClean;

        $xssClean = new XSSClean();

            $keyword = $xssClean->clean($keyword);


        $xssClean = new XSSClean();
        $html = $xssClean->clean($html);
<?php

namespace MicroweberPackages\Helper;

use voku\helper\AntiXSS;

class XSSClean
{


    public function cleanArray($array): array
    {

        if (is_array($array)) {

            $cleanedArray = [];
            foreach ($array as $key => $value) {
                if (is_string($key)) {
                    $key = $this->clean($key);
                }

                if (is_array($value)) {
                    $cleanedArray[$key] = $this->cleanArray($value);
                } else {
                    $cleanedArray[$key] = $this->clean($value);
                }
            }

            return $cleanedArray;
        }
    }

     public function clean($html): string
    {
 // from https://portswigger.net/web-security/cross-site-scripting/cheat-sheet#ontransitionend
        $cleanStrings = [
            'ontransitionstart',
           // 'onwebkitanimationend',
            'onwebkitanimationiteration',
            'onwebkitanimationstart',
            'onwebkittransitionend',
            'ontransitionrun',
            'onloadedmetadata',
            'ondurationchange',
            'oncanplaythrough',
            'oncuechange',
            'onbounce',
            'onbegin',
            'onbeforeunload',
            'onbeforescriptexecute',
            'onbeforeprint',
            'onanimationstart',
            'onanimationiteration',
            'onanimationend',
            'onanimationcancel',
            'onafterscriptexecute',
            'onfocusin',
            'onhashchange',
            'onload',
            'onunload',
            'onloadend',
            'onloadstart',
            'onmessage',
            'onpageshow',
            'onloadedmetadata',
            'onloadeddata',
            'onplay',
            'onplaying',
            'onpopstate',
            'onprogress',
            'onrepeat',
            'onresize',
            'onscroll',
            'onstart',
            'ontimeupdate',
            'ontoggle',
            'ontransitionend',
            'ontransitioncancel',
            'ontransitionrun',
            'ontransitionstart',
            'onafterprint',
            'onauxclick',
            'onbeforecopy',
            'onbeforecut',
            'onblur',
            'onchange',
            'onclick',
            'onclose',
            'oncontextmenu',
            'oncopy',
            'oncut',
            'ondblclick',
            'ondrag',
            'ondragend',
            'ondragenter',
            'ondragleave',
            'ondragover',
            'ondragstart',
            'ondrop',
            'onfocusout',
            'onfullscreenchange',
            'oninput',
            'oninvalid',
            'onkeydown',
            'onkeypress',
            'onkeyup',
            'onmousedown',
            'onmouseenter',
            'onmouseleave',
            'onmousemove',
            'onmouseout',
            'onmouseover',
            'onmouseup',
            'onmousewheel',
            'onmozfullscreenchange',
            'onpagehide',
            'onpaste',
            'onpause',
            'onpointerdown',
            'onpointerenter',
            'onpointerleave',
            'onpointermove',
            'onpointerout',
            'onpointerover',
            'onpointerrawupdate',
            'onpointerup',
            'onreset',
            'onsearch',
            'onseeked',
            'onseeking',
            'onselect',
            'onselectionchange',
            'onselectstart',
            'onshow',
            'onsubmit',
            'ontouchend',
            'ontouchmove',
            'ontouchstart',
            'onvolumechange',
            'onwheel',
            'onunhandledrejection'
        ];

        $antiXss = new AntiXSS();
        $antiXss->addEvilHtmlTags($cleanStrings);
        $antiXss->addEvilAttributes($cleanStrings);
        $antiXss->addNeverAllowedOnEventsAfterwards($cleanStrings);

        $html = $antiXss->xss_clean($html);


        return $html;
    }

}

use MicroweberPackages\Helper\XSSClean;

        $this->assertEquals($string, $content);
        $string = '<img src="' . site_url() . 'test.jpg" />';
        $this->assertEquals('<img src="' . site_url() . 'test.jpg" alt="test.jpg" />', $content);
        $zip->open(__DIR__ . '/misc/xss-test-files.zip');
    public function testXSSCleanArrtibutesNewEvents()
    {
        $xssClean = new XSSClean();
        $str = "class='x module module-'ontransitionrun=alert(1) '";
        $clean = $xssClean->clean($str);
        $this->assertEquals("class='x module module-'=alert&#40;1&#41; '", $clean);

    }