]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-90309: Base64-encode cookie values embedded in JS (GH-148888)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Thu, 23 Apr 2026 13:05:17 +0000 (15:05 +0200)
committerGitHub <noreply@github.com>
Thu, 23 Apr 2026 13:05:17 +0000 (15:05 +0200)
(cherry picked from commit 76b3923d688c0efc580658476c5f525ec8735104)

Co-authored-by: Seth Larson <seth@python.org>
Lib/http/cookies.py
Lib/test/test_http_cookies.py
Misc/NEWS.d/next/Security/2026-04-21-13-46-30.gh-issue-90309.srvj9q.rst [new file with mode: 0644]

index 63d119ad46c084969e5df0338c63a0ae6a80e1b7..aebc2a163e4487b59142e478142448c75ec2dc26 100644 (file)
@@ -389,17 +389,21 @@ class Morsel(dict):
         return '<%s: %s>' % (self.__class__.__name__, self.OutputString())
 
     def js_output(self, attrs=None):
+        import base64
         # Print javascript
         output_string = self.OutputString(attrs)
         if _has_control_character(output_string):
             raise CookieError("Control characters are not allowed in cookies")
+        # Base64-encode value to avoid template
+        # injection in cookie values.
+        output_encoded = base64.b64encode(output_string.encode('utf-8')).decode("ascii")
         return """
         <script type="text/javascript">
         <!-- begin hiding
-        document.cookie = \"%s\";
+        document.cookie = atob(\"%s\");
         // end hiding -->
         </script>
-        """ % (output_string.replace('"', r'\"'))
+        """ % (output_encoded,)
 
     def OutputString(self, attrs=None):
         # Build up our result
index 7edb026324ebdcee7d67e42b7f9a46d3c891fc5b..88914123d51303074e196f0beb150f91457302cb 100644 (file)
@@ -1,5 +1,5 @@
 # Simple test suite for http/cookies.py
-
+import base64
 import copy
 import unittest
 import doctest
@@ -153,17 +153,19 @@ class CookieTests(unittest.TestCase, ExtraAssertions):
 
         self.assertEqual(C.output(['path']),
             'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
-        self.assertEqual(C.js_output(), r"""
+        cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; Path=/acme; Version=1').decode('ascii')
+        self.assertEqual(C.js_output(), fr"""
         <script type="text/javascript">
         <!-- begin hiding
-        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1";
+        document.cookie = atob("{cookie_encoded}");
         // end hiding -->
         </script>
         """)
-        self.assertEqual(C.js_output(['path']), r"""
+        cookie_encoded = base64.b64encode(b'Customer="WILE_E_COYOTE"; Path=/acme').decode('ascii')
+        self.assertEqual(C.js_output(['path']), fr"""
         <script type="text/javascript">
         <!-- begin hiding
-        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme";
+        document.cookie = atob("{cookie_encoded}");
         // end hiding -->
         </script>
         """)
@@ -260,17 +262,19 @@ class CookieTests(unittest.TestCase, ExtraAssertions):
 
         self.assertEqual(C.output(['path']),
                          'Set-Cookie: Customer="WILE_E_COYOTE"; Path=/acme')
-        self.assertEqual(C.js_output(), r"""
+        expected_encoded_cookie = base64.b64encode(b'Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1').decode('ascii')
+        self.assertEqual(C.js_output(), fr"""
         <script type="text/javascript">
         <!-- begin hiding
-        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme; Version=1";
+        document.cookie = atob("{expected_encoded_cookie}");
         // end hiding -->
         </script>
         """)
-        self.assertEqual(C.js_output(['path']), r"""
+        expected_encoded_cookie = base64.b64encode(b'Customer=\"WILE_E_COYOTE\"; Path=/acme').decode('ascii')
+        self.assertEqual(C.js_output(['path']), fr"""
         <script type="text/javascript">
         <!-- begin hiding
-        document.cookie = "Customer=\"WILE_E_COYOTE\"; Path=/acme";
+        document.cookie = atob("{expected_encoded_cookie}");
         // end hiding -->
         </script>
         """)
@@ -361,13 +365,16 @@ class MorselTests(unittest.TestCase):
             self.assertEqual(
                 M.output(),
                 "Set-Cookie: %s=%s; Path=/foo" % (i, "%s_coded_val" % i))
+            expected_encoded_cookie = base64.b64encode(
+                ("%s=%s; Path=/foo" % (i, "%s_coded_val" % i)).encode("ascii")
+            ).decode('ascii')
             expected_js_output = """
         <script type="text/javascript">
         <!-- begin hiding
-        document.cookie = "%s=%s; Path=/foo";
+        document.cookie = atob("%s");
         // end hiding -->
         </script>
-        """ % (i, "%s_coded_val" % i)
+        """ % (expected_encoded_cookie,)
             self.assertEqual(M.js_output(), expected_js_output)
         for i in ["foo bar", "foo@bar"]:
             # Try some illegal characters
diff --git a/Misc/NEWS.d/next/Security/2026-04-21-13-46-30.gh-issue-90309.srvj9q.rst b/Misc/NEWS.d/next/Security/2026-04-21-13-46-30.gh-issue-90309.srvj9q.rst
new file mode 100644 (file)
index 0000000..d7d3767
--- /dev/null
@@ -0,0 +1,3 @@
+Base64-encode values when embedding cookies to JavaScript using the
+:meth:`http.cookies.BaseCookie.js_output` method to avoid injection
+and escaping.