CVE-2022-41938: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Flarum is an open source discussion platform. Flarum's page title system allowed for page titles to be converted into HTML DOM nodes when pages were rendered. The change was made after `v1.5` and was not noticed. This allowed an attacker to inject malicious HTML markup using a discussion title input, either by creating a new discussion or renaming one. The XSS attack occurs after a visitor opens the relevant discussion page. All communities running Flarum from `v1.5.0` to `v1.6.1` are impacted. The vulnerability has been fixed and published as flarum/core `v1.6.2`. All communities running Flarum from `v1.5.0` to `v1.6.1` have to upgrade as soon as possible to v1.6.2. There are no known workarounds for this issue.
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: 2022-11-19T01:15Z
Repository: https://github.com/flarum/framework
Language: PHP
Files changed: framework/core/js/src/common/Application.tsx, framework/core/views/frontend/app.blade.php
Commit date: 2022-11-18 22:09:22 +0100
Commit message: fix: evaluated page title content (#3684)

* fix: evaluated page title content
* chore: add comment
* chore: use DOMParser instead
* fix: use `innerHTML` for the actual value

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>
Co-authored-by: David Wheatley <hi@davwheat.dev>
Diff: diff --git a/framework/core/js/src/common/Application.tsx b/framework/core/js/src/common/Application.tsx
index 5db7fa84e..8d0140fde 100644
--- a/framework/core/js/src/common/Application.tsx
+++ b/framework/core/js/src/common/Application.tsx
@@ -408,20 +408,26 @@ export default class Application {
       // Until we add page numbers to the frontend, this is constant at 1
       // so that the page number portion doesn't show up in the URL.
       pageNumber: 1,
     };
 
-    const title =
+    let title =
       onHomepage || !this.title
         ? extractText(app.translator.trans('core.lib.meta_titles.without_page_title', params))
         : extractText(app.translator.trans('core.lib.meta_titles.with_page_title', params));
 
-    const tempEl = document.createElement('div');
-    tempEl.innerHTML = title;
-    const decodedTitle = tempEl.innerText;
+    title = count + title;
 
-    document.title = count + decodedTitle;
+    // We pass the title through a DOMParser to allow HTML entities
+    // to be rendered correctly, while still preventing XSS attacks
+    // from user input by using a script-disabled environment.
+    // https://github.com/flarum/framework/issues/3514
+    // https://github.com/flarum/framework/pull/3684
+    const parser = new DOMParser();
+    const safeTitle = parser.parseFromString(title, 'text/html').body.innerHTML;
+
+    document.title = safeTitle;
   }
 
   protected transformRequestOptions<ResponseType>(flarumOptions: FlarumRequestOptions<ResponseType>): InternalFlarumRequestOptions<ResponseType> {
     const { background, deserialize, extract, modifyText, ...tmpOptions } = { ...flarumOptions };
 
diff --git a/framework/core/views/frontend/app.blade.php b/framework/core/views/frontend/app.blade.php
index 249aa3f99..2027af5c2 100644
--- a/framework/core/views/frontend/app.blade.php
+++ b/framework/core/views/frontend/app.blade.php
@@ -1,11 +1,11 @@
 <!doctype html>
 <html @if ($direction) dir="{{ $direction }}" @endif
       @if ($language) lang="{{ $language }}" @endif>
     <head>
         <meta charset="utf-8">
-        <title>{!! $title !!}</title>
+        <title>{{ $title }}</title>
 
         {!! $head !!}
     </head>
 
     <body>

Vulnerable code:     const title =
    const tempEl = document.createElement('div');
    tempEl.innerHTML = title;
    const decodedTitle = tempEl.innerText;
    document.title = count + decodedTitle;
        <title>{!! $title !!}</title>
Fixed code:     let title =
    title = count + title;
    // We pass the title through a DOMParser to allow HTML entities
    // to be rendered correctly, while still preventing XSS attacks
    // from user input by using a script-disabled environment.
    // https://github.com/flarum/framework/issues/3514
    // https://github.com/flarum/framework/pull/3684
    const parser = new DOMParser();
    const safeTitle = parser.parseFromString(title, 'text/html').body.innerHTML;

    document.title = safeTitle;
        <title>{{ $title }}</title>