CVE-2023-36459: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Mastodon is a free, open-source social network server based on ActivityPub. Starting in version 1.3 and prior to versions 3.5.9, 4.0.5, and 4.1.3, an attacker using carefully crafted oEmbed data can bypass the HTML sanitization performed by Mastodon and include arbitrary HTML in oEmbed preview cards. This introduces a vector for cross-site scripting (XSS) payloads that can be rendered in the user's browser when a preview card for a malicious link is clicked through. Versions 3.5.9, 4.0.5, and 4.1.3 contain a patch 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: 6.1
Published: 2023-07-06T19:15Z
Repository: https://github.com/mastodon/mastodon
Language: Ruby
Files changed: app/serializers/rest/preview_card_serializer.rb, lib/sanitize_ext/sanitize_config.rb
Commit date: 2023-07-06 15:03:33 +0200
Commit message: Merge pull request from GHSA-ccm4-vgcc-73hp

* Tighten allowed HTML in oEmbed-based preview cards

* Sanitize preview cards at render time

* Add `sandbox` attribute to preview card iframes
Diff: diff --git a/app/serializers/rest/preview_card_serializer.rb b/app/serializers/rest/preview_card_serializer.rb
index 8413b23d85..08bc07edd4 100644
--- a/app/serializers/rest/preview_card_serializer.rb
+++ b/app/serializers/rest/preview_card_serializer.rb
@@ -9,6 +9,10 @@ class REST::PreviewCardSerializer < ActiveModel::Serializer
              :image, :embed_url, :blurhash
 
   def image
     object.image? ? full_asset_url(object.image.url(:original)) : nil
   end
+
+  def html
+    Sanitize.fragment(object.html, Sanitize::Config::MASTODON_OEMBED)
+  end
 end
diff --git a/lib/sanitize_ext/sanitize_config.rb b/lib/sanitize_ext/sanitize_config.rb
index bcd89af67a..bdb4c56a08 100644
--- a/lib/sanitize_ext/sanitize_config.rb
+++ b/lib/sanitize_ext/sanitize_config.rb
@@ -89,28 +89,28 @@ class Sanitize
         UNSUPPORTED_ELEMENTS_TRANSFORMER,
         UNSUPPORTED_HREF_TRANSFORMER,
       ]
     )
 
-    MASTODON_OEMBED ||= freeze_config merge(
-      RELAXED,
-      elements: RELAXED[:elements] + %w(audio embed iframe source video),
+    MASTODON_OEMBED ||= freeze_config(
+      elements: %w(audio embed iframe source video),
 
-      attributes: merge(
-        RELAXED[:attributes],
+      attributes: {
         'audio' => %w(controls),
         'embed' => %w(height src type width),
         'iframe' => %w(allowfullscreen frameborder height scrolling src width),
         'source' => %w(src type),
         'video' => %w(controls height loop width),
-        'div' => [:data]
-      ),
+      },
 
-      protocols: merge(
-        RELAXED[:protocols],
+      protocols: {
         'embed' => { 'src' => HTTP_PROTOCOLS },
         'iframe' => { 'src' => HTTP_PROTOCOLS },
-        'source' => { 'src' => HTTP_PROTOCOLS }
-      )
+        'source' => { 'src' => HTTP_PROTOCOLS },
+      },
+
+      add_attributes: {
+        'iframe' => { 'sandbox' => 'allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-forms' },
+      }
     )
   end
 end

Vulnerable code:     MASTODON_OEMBED ||= freeze_config merge(
      RELAXED,
      elements: RELAXED[:elements] + %w(audio embed iframe source video),
      attributes: merge(
        RELAXED[:attributes],
        'div' => [:data]
      ),
      protocols: merge(
        RELAXED[:protocols],
        'source' => { 'src' => HTTP_PROTOCOLS }
      )
Fixed code: 
  def html
    Sanitize.fragment(object.html, Sanitize::Config::MASTODON_OEMBED)
  end
    MASTODON_OEMBED ||= freeze_config(
      elements: %w(audio embed iframe source video),
      attributes: {
      },
      protocols: {
        'source' => { 'src' => HTTP_PROTOCOLS },
      },

      add_attributes: {
        'iframe' => { 'sandbox' => 'allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-forms' },
      }