CVE-2020-11083: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: In October from version 1.0.319 and before version 1.0.466, a user with access to a markdown FormWidget that stores data persistently could create a stored XSS attack against themselves and any other users with access to the generated HTML from the field. This has been fixed in 1.0.466. For users of the RainLab.Blog plugin, this has also been fixed in 1.4.1.
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: 4.8
Published: 2020-07-14T21:15Z
Repository: https://github.com/rainlab/blog-plugin
Language: PHP
Files changed: formwidgets/BlogMarkdown.php, models/Post.php
Commit date: 2020-05-25 18:05:13 -0600
Commit message: Implement support for backend.allow_unsafe_markdown and improve support for Swoole

Diff: diff --git a/formwidgets/BlogMarkdown.php b/formwidgets/BlogMarkdown.php
index 9907c0d..caff718 100644
--- a/formwidgets/BlogMarkdown.php
+++ b/formwidgets/BlogMarkdown.php
@@ -18,25 +18,44 @@ use Exception;
  * @package rainlab\blog
  * @author Alexey Bobkov, Samuel Georges
  */
 class BlogMarkdown extends MarkdownEditor
 {
+    /**
+     * {@inheritDoc}
+     */
     public function init()
     {
         $this->viewPath = base_path().'/modules/backend/formwidgets/markdowneditor/partials';
 
         $this->checkUploadPostback();
 
         parent::init();
     }
 
+    /**
+     * {@inheritDoc}
+     */
     protected function loadAssets()
     {
         $this->assetPath = '/modules/backend/formwidgets/markdowneditor/assets';
         parent::loadAssets();
     }
 
+    /**
+     * Disable HTML cleaning on the widget level since the PostModel will handle it
+     *
+     * @return boolean
+     */
+    protected function shouldCleanHtml()
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public function onRefresh()
     {
         $content = post($this->formField->getName());
 
         $previewHtml = PostModel::formatHtml($content, true);
@@ -44,10 +63,15 @@ class BlogMarkdown extends MarkdownEditor
         return [
             'preview' => $previewHtml
         ];
     }
 
+    /**
+     * Handle images being uploaded to the blog post
+     *
+     * @return void
+     */
     protected function checkUploadPostback()
     {
         if (!post('X_BLOG_IMAGE_UPLOAD')) {
             return;
         }
@@ -88,26 +112,22 @@ class BlogMarkdown extends MarkdownEditor
                 'file' => $uploadedFileName,
                 'path' => $file->getPath()
             ];
 
             $response = Response::make()->setContent($result);
-            $response->send();
+            $this->controller->setResponse($response);
 
-            die();
-        }
-        catch (Exception $ex) {
+        } catch (Exception $ex) {
             $message = $uploadedFileName
                 ? Lang::get('cms::lang.asset.error_uploading_file', ['name' => $uploadedFileName, 'error' => $ex->getMessage()])
                 : $ex->getMessage();
 
             $result = [
                 'error' => $message,
                 'file' => $uploadedFileName
             ];
 
             $response = Response::make()->setContent($result);
-            $response->send();
-
-            die();
+            $this->controller->setResponse($response);
         }
     }
 }
diff --git a/models/Post.php b/models/Post.php
index ba701e2..aa5ab8b 100644
--- a/models/Post.php
+++ b/models/Post.php
@@ -187,10 +187,16 @@ class Post extends Model
 
     public static function formatHtml($input, $preview = false)
     {
         $result = Markdown::parse(trim($input));
 
+        // Check to see if the HTML should be cleaned from potential XSS
+        $user = BackendAuth::getUser();
+        if (!$user || !$user->hasAccess('backend.allow_unsafe_markdown')) {
+            $result = Html::clean($result);
+        }
+
         if ($preview) {
             $result = str_replace('<pre>', '<pre class="prettyprint">', $result);
         }
 
         $result = TagProcessor::instance()->processTags($result, $preview);

Vulnerable code:             $response->send();
            die();
        }
        catch (Exception $ex) {
            $response->send();

            die();
Fixed code:     /**
     * {@inheritDoc}
     */
    /**
     * {@inheritDoc}
     */
    /**
     * Disable HTML cleaning on the widget level since the PostModel will handle it
     *
     * @return boolean
     */
    protected function shouldCleanHtml()
    {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    /**
     * Handle images being uploaded to the blog post
     *
     * @return void
     */
            $this->controller->setResponse($response);
        } catch (Exception $ex) {
            $this->controller->setResponse($response);
        // Check to see if the HTML should be cleaned from potential XSS
        $user = BackendAuth::getUser();
        if (!$user || !$user->hasAccess('backend.allow_unsafe_markdown')) {
            $result = Html::clean($result);
        }