From b7e8a5479fa89ee3cbf87da0ffecac5b0bb54beb Mon Sep 17 00:00:00 2001
From: ooooooo_q <ooooooo-q@users.noreply.github.com>
Date: Fri, 5 Jan 2024 12:00:02 +0900
Subject: [PATCH] fix XSS vulnerability when using translation

[CVE-2024-26143]
---
 actionpack/CHANGELOG.md                       |  4 +++
 .../lib/abstract_controller/translation.rb    | 24 +++++++++++++-
 actionpack/test/abstract/translation_test.rb  | 31 +++++++++++++++++++
 3 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index f920cf2c95..8f04937f2d 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,7 @@
+*   Fix possible XSS vulnerability with the `translate` method in controllers
+
+    CVE-2024-26143
+
 ## Rails 7.0.8 (September 09, 2023) ##
 
 *   Fix `HostAuthorization` potentially displaying the value of the
diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb
index db71c172ab..bdd44c6893 100644
--- a/actionpack/lib/abstract_controller/translation.rb
+++ b/actionpack/lib/abstract_controller/translation.rb
@@ -25,7 +25,25 @@ def translate(key, **options)
 
       i18n_raise = options.fetch(:raise, self.raise_on_missing_translations)
 
-      ActiveSupport::HtmlSafeTranslation.translate(key, **options, raise: i18n_raise)
+      if options[:default]
+        options[:default] = [options[:default]] unless options[:default].is_a?(Array)
+        options[:default] = options[:default].map do |value|
+          value.is_a?(String) ? ERB::Util.html_escape(value) : value
+        end
+      end
+
+      unless i18n_raise
+        options[:default] = [] unless options[:default]
+        options[:default] << MISSING_TRANSLATION
+      end
+
+      result = ActiveSupport::HtmlSafeTranslation.translate(key, **options, raise: i18n_raise)
+
+      if result == MISSING_TRANSLATION
+        +"translation missing: #{key}"
+      else
+        result
+      end
     end
     alias :t :translate
 
@@ -34,5 +52,9 @@ def localize(object, **options)
       I18n.localize(object, **options)
     end
     alias :l :localize
+
+    private
+      MISSING_TRANSLATION = -(2**60)
+      private_constant :MISSING_TRANSLATION
   end
 end
diff --git a/actionpack/test/abstract/translation_test.rb b/actionpack/test/abstract/translation_test.rb
index 1c0b51c4ed..eca90040c8 100644
--- a/actionpack/test/abstract/translation_test.rb
+++ b/actionpack/test/abstract/translation_test.rb
@@ -93,6 +93,22 @@ def test_default_translation
         end
       end
 
+      def test_default_translation_as_safe_html
+        @controller.stub :action_name, :index do
+          translation = @controller.t(".twoz", default: ["<tag>"])
+          assert_equal "&lt;tag&gt;", translation
+          assert_equal true, translation.html_safe?
+        end
+      end
+
+      def test_default_translation_with_raise_as_safe_html
+        @controller.stub :action_name, :index do
+          translation = @controller.t(".twoz", raise: true, default: ["<tag>"])
+          assert_equal "&lt;tag&gt;", translation
+          assert_equal true, translation.html_safe?
+        end
+      end
+
       def test_localize
         time, expected = Time.gm(2000), "Sat, 01 Jan 2000 00:00:00 +0000"
         I18n.stub :localize, expected do
@@ -136,6 +152,21 @@ def test_translate_escapes_interpolations_in_translations_with_a_html_suffix
           assert_equal true, translation.html_safe?
         end
       end
+
+      def test_translate_marks_translation_with_missing_html_key_as_safe_html
+        @controller.stub :action_name, :index do
+          translation = @controller.t("<tag>.html")
+          assert_equal "translation missing: <tag>.html", translation
+          assert_equal false, translation.html_safe?
+        end
+      end
+      def test_translate_marks_translation_with_missing_nested_html_key_as_safe_html
+        @controller.stub :action_name, :index do
+          translation = @controller.t(".<tag>.html")
+          assert_equal "translation missing: abstract_controller.testing.translation.index.<tag>.html", translation
+          assert_equal false, translation.html_safe?
+        end
+      end
     end
   end
 end
-- 
2.37.1

