From 7d484864fe91ff79c478f57e7ddb1235d701921e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 16 Oct 2023 14:06:02 +0200 Subject: [PATCH] SECURITY: escape display names Ensure we escape the display names before passing it to the regexp used to update quotes whenever a user change their display name. --- lib/quote_rewriter.rb | 17 ++++++++++++----- spec/jobs/change_display_name_spec.rb | 20 ++++++++++++-------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/quote_rewriter.rb b/lib/quote_rewriter.rb index a316b81ca2d..5a45d580d6b 100644 --- a/lib/quote_rewriter.rb +++ b/lib/quote_rewriter.rb @@ -6,17 +6,20 @@ class QuoteRewriter end def rewrite_raw_username(raw, old_username, new_username) + escaped_old_username = Regexp.escape(old_username) pattern = Regexp.union( - /(?
\[quote\s*=\s*["'']?.*username:)#{old_username}(?\,?[^\]]*\])/i,
-        /(?
\[quote\s*=\s*["'']?)#{old_username}(?\,?[^\]]*\])/i,
+        /(?
\[quote\s*=\s*["'']?.*username:)#{escaped_old_username}(?\,?[^\]]*\])/i,
+        /(?
\[quote\s*=\s*["'']?)#{escaped_old_username}(?\,?[^\]]*\])/i,
       )
 
     raw.gsub(pattern, "\\k
#{new_username}\\k")
   end
 
   def rewrite_cooked_username(cooked, old_username, new_username, avatar_img)
-    pattern = /(?<=\s)#{PrettyText::Helpers.format_username(old_username)}(?=:)/i
+    formatted_old_username = PrettyText::Helpers.format_username(old_username)
+    escaped_old_username = Regexp.escape(formatted_old_username)
+    pattern = /(?<=\s)#{escaped_old_username}(?=:)/i
 
     cooked
       .css("aside.quote")
@@ -42,13 +45,17 @@ class QuoteRewriter
   end
 
   def rewrite_raw_display_name(raw, old_display_name, new_display_name)
-    pattern = /(?
\[quote\s*=\s*["'']?)#{old_display_name}(?\,[^\]]*username[^\]]*\])/i
+    escaped_old_display_name = Regexp.escape(old_display_name)
+    pattern =
+      /(?
\[quote\s*=\s*["'']?)#{escaped_old_display_name}(?\,[^\]]*username[^\]]*\])/i
 
     raw.gsub(pattern, "\\k
#{new_display_name}\\k")
   end
 
   def rewrite_cooked_display_name(cooked, old_display_name, new_display_name)
-    pattern = /(?<=\s)#{PrettyText::Helpers.format_username(old_display_name)}(?=:)/i
+    formatted_old_display_name = PrettyText::Helpers.format_username(old_display_name)
+    escaped_old_display_name = Regexp.escape(formatted_old_display_name)
+    pattern = /(?<=\s)#{escaped_old_display_name}(?=:)/i
 
     cooked
       .css("aside.quote")
diff --git a/spec/jobs/change_display_name_spec.rb b/spec/jobs/change_display_name_spec.rb
index e18db2cf346..bb87afee4a0 100644
--- a/spec/jobs/change_display_name_spec.rb
+++ b/spec/jobs/change_display_name_spec.rb
@@ -3,7 +3,11 @@
 RSpec.describe Jobs::ChangeDisplayName do
   before { stub_image_size }
 
-  let(:user) { Fabricate(:user, username: "codinghorror", name: "Jeff") }
+  let(:username) { "codinghorror" }
+  let(:old_display_name) { "|| Jeff ||" }
+  let(:new_display_name) { "|| Mr. Atwood ||" }
+
+  let(:user) { Fabricate(:user, username: username, name: old_display_name) }
   let(:topic) { Fabricate(:topic, user: user) }
   let!(:post) { create_post(post_attributes.merge(topic_id: topic.id)) }
 
@@ -11,25 +15,25 @@ RSpec.describe Jobs::ChangeDisplayName do
   let(:avatar_url) { user.avatar_template_url.gsub("{size}", "48") }
 
   let(:post_attributes) { { raw: <<~RAW } }
-    [quote="Jeff, post:1, topic:#{quoted_post.topic.id}, username:codinghorror"]
+    [quote="#{old_display_name}, post:1, topic:#{quoted_post.topic.id}, username:#{username}"]
     quoted post
     [/quote]
   RAW
 
   let(:revised_post_attributes) { { raw: <<~RAW } }
-    [quote="Jeff, post:1, topic:#{quoted_post.topic.id}, username:codinghorror"]
+    [quote="#{old_display_name}, post:1, topic:#{quoted_post.topic.id}, username:#{username}"]
     quoted post
     [/quote]
     Forgot something.
   RAW
 
-  let(:args) { { user_id: user.id, old_name: "Jeff", new_name: "Mr. Atwood" } }
+  let(:args) { { user_id: user.id, old_name: old_display_name, new_name: new_display_name } }
 
   describe "#execute" do
     context "when the renamed user has been quoted" do
       it "rewrites the raw quote display name" do
         expect { described_class.new.execute(args) }.to change { post.reload.raw }.to(<<~RAW.strip)
-          [quote="Mr. Atwood, post:1, topic:#{quoted_post.topic.id}, username:codinghorror"]
+          [quote="#{new_display_name}, post:1, topic:#{quoted_post.topic.id}, username:#{username}"]
           quoted post
           [/quote]
         RAW
@@ -38,10 +42,10 @@ RSpec.describe Jobs::ChangeDisplayName do
       it "rewrites the cooked quote display name" do
         expect { described_class.new.execute(args) }.to change { post.reload.cooked }.to(
           match_html(<<~HTML.strip),
-