CVE-2024-32464: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Action Text brings rich text content and editing to Rails. Instances of ActionText::Attachable::ContentAttachment included within a rich_text_area tag could potentially contain unsanitized HTML. This vulnerability is fixed in 7.1.3.4 and 7.2.0.beta2.
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: 2024-06-04T20:15Z
Repository: https://github.com/rails/rails
Language: Ruby
Files changed: actiontext/app/helpers/action_text/content_helper.rb, actiontext/lib/action_text/content.rb, actiontext/test/unit/attachment_test.rb
Commit date: 2024-06-04 10:08:12 -0700
Commit message: Sanitize ActionText HTML ContentAttachment in Trix edit view

[CVE-2024-32464]
Instances of ActionText::Attachable::ContentAttachment included
within a rich_text_area tag could potentially contain unsanitized
HTML. This could lead to a potential cross site scripting issue
within the Trix editor.

This change enforces existing sanitization routines on
ActionText::Attachable::ContentAttachment attachments.

Diff: diff --git a/actiontext/app/helpers/action_text/content_helper.rb b/actiontext/app/helpers/action_text/content_helper.rb
index 66eb9b697949..cae096b2a053 100644
--- a/actiontext/app/helpers/action_text/content_helper.rb
+++ b/actiontext/app/helpers/action_text/content_helper.rb
@@ -14,10 +14,19 @@ module ContentHelper
     def render_action_text_content(content)
       self.prefix_partial_path_with_controller_namespace = false
       sanitize_action_text_content(render_action_text_attachments(content))
     end
 
+    def sanitize_content_attachment(content_attachment)
+      sanitizer.sanitize(
+        content_attachment,
+        tags: sanitizer_allowed_tags,
+        attributes: sanitizer_allowed_attributes,
+        scrubber: scrubber,
+      )
+    end
+
     def sanitize_action_text_content(content)
       sanitizer.sanitize(
         content.to_html,
         tags: sanitizer_allowed_tags,
         attributes: sanitizer_allowed_attributes,
diff --git a/actiontext/lib/action_text/content.rb b/actiontext/lib/action_text/content.rb
index ee6b38dd3cad..3c07dad4f0df 100644
--- a/actiontext/lib/action_text/content.rb
+++ b/actiontext/lib/action_text/content.rb
@@ -20,11 +20,11 @@ module ActionText
   #     message = Message.create!(content: "<h1>Funny times!</h1>")
   #     body = message.content.body # => #<ActionText::Content "<div class=\"trix-conte...">
   #     body.to_s # => "<h1>Funny times!</h1>"
   #     body.to_plain_text # => "Funny times!"
   class Content
-    include Rendering, Serialization
+    include Rendering, Serialization, ContentHelper
 
     attr_reader :fragment
 
     delegate :deconstruct, to: :fragment
     delegate :blank?, :empty?, :html_safe, :present?, to: :to_html # Delegating to to_html to avoid including the layout
@@ -95,10 +95,11 @@ def append_attachables(attachables)
       self.class.new([self.to_s.presence, *attachments].compact.join("\n"))
     end
 
     def render_attachments(**options, &block)
       content = fragment.replace(ActionText::Attachment.tag_name) do |node|
+        node["content"] = sanitize_content_attachment(node["content"])
         block.call(attachment_for_node(node, **options))
       end
       self.class.new(content, canonicalize: false)
     end
 
diff --git a/actiontext/test/unit/attachment_test.rb b/actiontext/test/unit/attachment_test.rb
index 12dc190904fb..d214447809bd 100644
--- a/actiontext/test/unit/attachment_test.rb
+++ b/actiontext/test/unit/attachment_test.rb
@@ -77,10 +77,19 @@ class ActionText::AttachmentTest < ActiveSupport::TestCase
     ActionText::Content.with_renderer MessagesController.renderer do
       assert_equal "<p>abc</p>", attachable.to_html.strip
     end
   end
 
+  test "sanitizes HTML content attachment" do
+    attachment = attachment_from_html('<action-text-attachment content-type="text/html" content="<img src=\&quot;.\&quot; onerror=alert>"></action-text-attachment>')
+    attachable = attachment.attachable
+
+    ActionText::Content.with_renderer MessagesController.renderer do
+      assert_equal "<img src=\"\\%22.\\%22\">", attachable.to_html.strip
+    end
+  end
+
   test "defaults trix partial to model partial" do
     attachable = Page.create! title: "Homepage"
     assert_equal "pages/page", attachable.to_trix_content_attachment_partial_path
   end
 

Vulnerable code:     include Rendering, Serialization
Fixed code:     def sanitize_content_attachment(content_attachment)
      sanitizer.sanitize(
        content_attachment,
        tags: sanitizer_allowed_tags,
        attributes: sanitizer_allowed_attributes,
        scrubber: scrubber,
      )
    end

    include Rendering, Serialization, ContentHelper
        node["content"] = sanitize_content_attachment(node["content"])
  test "sanitizes HTML content attachment" do
    attachment = attachment_from_html('<action-text-attachment content-type="text/html" content="<img src=\&quot;.\&quot; onerror=alert>"></action-text-attachment>')
    attachable = attachment.attachable

    ActionText::Content.with_renderer MessagesController.renderer do
      assert_equal "<img src=\"\\%22.\\%22\">", attachable.to_html.strip
    end
  end