CVE-2023-43658: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: dicourse-calendar is a plugin for the Discourse messaging platform which adds the ability to create a dynamic calendar in the first post of a topic. Improper escaping of event titles could lead to Cross-site Scripting (XSS) within the 'email preview' UI when a site has CSP disabled. Having CSP disabled is a non-default configuration, so the vast majority of sites are unaffected. This problem is resolved in the latest version of the discourse-calendar plugin. Users are advised to upgrade. Users unable to upgrade should ensure CSP is enabled on the forum.
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-10-16T22:15Z
Repository: https://github.com/discourse/discourse-calendar
Language: Ruby
Files changed: .discourse-compatibility, plugin.rb, spec/lib/discourse_post_event/pretty_text_spec.rb
Commit date: 2023-10-16 15:00:44 +0100
Commit message: SECURITY: Properly escape user input when generating email HTML (#455)


Diff: diff --git a/.discourse-compatibility b/.discourse-compatibility
index 9015b40f..3fd0806a 100644
--- a/.discourse-compatibility
+++ b/.discourse-compatibility
@@ -1,6 +1,5 @@
-3.1.999: afc2ee684de41601d6cecc46713d139760f176a6
 3.1.0.beta3: 10077ca904956005f9fa83c3d9fb124b59e8c47b
 2.9.0.beta13: b4c366b35d6f9778e54a878aa083348e8a45e86e
 2.9.0.beta9: 6f16ae10dc0306d6e1369e0d1414416d7e72141d
 2.9.0.beta7: ec34a96f215dce9c7321dfe04f6dd0172a197711
 2.9.0.beta3: 9ab1b76a1425304f3434b49db49cb4eec8919cf6
diff --git a/plugin.rb b/plugin.rb
index 6fb30b4b..80028e00 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -427,12 +427,12 @@ after_initialize do
           dates = "#{dates} → #{ends_at} (#{event_node["data-timezone"] || "UTC"})" if ends_at
 
           event_name = event_node["data-name"] || post.topic.title
           event_node.replace <<~TXT
           <div style='border:1px solid #dedede'>
-            <p><a href="#{Discourse.base_url}#{post.url}">#{event_name}</a></p>
-            <p>#{dates}</p>
+            <p><a href="#{Discourse.base_url}#{post.url}">#{CGI.escape_html(event_name)}</a></p>
+            <p>#{CGI.escape_html(dates)}</p>
           </div>
         TXT
         end
     end
   end
diff --git a/spec/lib/discourse_post_event/pretty_text_spec.rb b/spec/lib/discourse_post_event/pretty_text_spec.rb
index 1f0377d3..d74b4f25 100644
--- a/spec/lib/discourse_post_event/pretty_text_spec.rb
+++ b/spec/lib/discourse_post_event/pretty_text_spec.rb
@@ -28,10 +28,13 @@ describe PrettyText do
         end
       end
 
       context "when the event has a name" do
         let(:post_1) { create_post_with_event(user_1, 'name="Pancakes event"') }
+        let(:post_2) do
+          create_post_with_event(user_1, 'name="Pancakes event <a>with html chars</a>"')
+        end
 
         it "displays the event name" do
           cooked = PrettyText.cook(post_1.raw)
 
           expect(PrettyText.format_for_email(cooked, post_1)).to match_html(<<~HTML)
@@ -39,10 +42,21 @@ describe PrettyText do
               <p><a href="#{Discourse.base_url}#{post_1.url}">Pancakes event</a></p>
               <p>2018-06-05T18:39:50.000Z (UTC)</p>
             </div>
           HTML
         end
+
+        it "properly escapes title" do
+          cooked = PrettyText.cook(post_2.raw)
+
+          expect(PrettyText.format_for_email(cooked, post_2)).to match_html(<<~HTML)
+            <div style='border:1px solid #dedede'>
+              <p><a href="#{Discourse.base_url}#{post_2.url}">Pancakes event &lt;a&gt;with html chars&lt;/a&gt;</a></p>
+              <p>2018-06-05T18:39:50.000Z (UTC)</p>
+            </div>
+          HTML
+        end
       end
 
       context "when the event has an end date" do
         let(:post_1) { create_post_with_event(user_1, 'end="2018-06-22"') }
 

Vulnerable code: 3.1.999: afc2ee684de41601d6cecc46713d139760f176a6
            <p><a href="#{Discourse.base_url}#{post.url}">#{event_name}</a></p>
            <p>#{dates}</p>
Fixed code:             <p><a href="#{Discourse.base_url}#{post.url}">#{CGI.escape_html(event_name)}</a></p>
            <p>#{CGI.escape_html(dates)}</p>
        let(:post_2) do
          create_post_with_event(user_1, 'name="Pancakes event <a>with html chars</a>"')
        end

        it "properly escapes title" do
          cooked = PrettyText.cook(post_2.raw)

          expect(PrettyText.format_for_email(cooked, post_2)).to match_html(<<~HTML)
            <div style='border:1px solid #dedede'>
              <p><a href="#{Discourse.base_url}#{post_2.url}">Pancakes event &lt;a&gt;with html chars&lt;/a&gt;</a></p>
              <p>2018-06-05T18:39:50.000Z (UTC)</p>
            </div>
          HTML
        end