Skip to content

Commit 4e9d037

Browse files
miss-islingtonEclips4vstinner
authored
[3.15] gh-161569: Check cookie policy before deleting expired cookies in http.cookiejar (GH-161570) (#161579)
gh-161569: Check cookie policy before deleting expired cookies in `http.cookiejar` (GH-161570) gh-161569: Check cookie policy before deleting expired cookies in http.cookiejar An expired Set-Cookie header was allowed to delete a stored cookie using the Domain and Path attributes taken verbatim from the header, before CookiePolicy.set_ok() was consulted. A response from one host could therefore delete cookies belonging to any other domain in a shared jar. The policy is now consulted before an expired cookie may delete an existing one, as required by RFC 6265 section 5.3. (cherry picked from commit 8eb3d2c) Co-authored-by: Kirill Podoprigora <kirill.bast@gmail.com> Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent b1778af commit 4e9d037

3 files changed

Lines changed: 81 additions & 20 deletions

File tree

‎Lib/http/cookiejar.py‎

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,28 +1568,33 @@ def _cookie_from_cookie_tuple(self, tup, request):
15681568
if expires is Absent:
15691569
expires = None
15701570
discard = True
1571-
elif expires <= self._now:
1572-
# Expiry date in past is request to delete cookie. This can't be
1573-
# in DefaultCookiePolicy, because can't delete cookies there.
1574-
try:
1575-
self.clear(domain, path, name)
1576-
except KeyError:
1577-
pass
1578-
_debug("Expiring cookie, domain='%s', path='%s', name='%s'",
1579-
domain, path, name)
1571+
1572+
cookie = Cookie(version,
1573+
name, value,
1574+
port, port_specified,
1575+
domain, domain_specified, domain_initial_dot,
1576+
path, path_specified,
1577+
secure,
1578+
expires,
1579+
discard,
1580+
comment,
1581+
comment_url,
1582+
rest)
1583+
1584+
if expires is not None and expires <= self._now:
1585+
# RFC 6265 section 5.3 ignores the cookie entirely if its Domain
1586+
# attribute does not match the request host (step 6) before an
1587+
# existing cookie is removed (steps 11-12)
1588+
if self._policy.set_ok(cookie, request):
1589+
try:
1590+
self.clear(domain, path, name)
1591+
except KeyError:
1592+
pass
1593+
_debug("Expiring cookie, domain='%s', path='%s', name='%s'",
1594+
domain, path, name)
15801595
return None
15811596

1582-
return Cookie(version,
1583-
name, value,
1584-
port, port_specified,
1585-
domain, domain_specified, domain_initial_dot,
1586-
path, path_specified,
1587-
secure,
1588-
expires,
1589-
discard,
1590-
comment,
1591-
comment_url,
1592-
rest)
1597+
return cookie
15931598

15941599
def _cookies_from_attrs_set(self, attrs_set, request):
15951600
cookie_tuples = self._normalized_cookie_tuples(attrs_set)

‎Lib/test/test_http_cookiejar.py‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,55 @@ def test_expires(self):
743743

744744
# XXX RFC 2965 expiry rules (some apply to V0 too)
745745

746+
def test_expired_cookie_from_other_domain_is_ignored(self):
747+
c = CookieJar()
748+
interact_netscape(c, "http://www.victim.test/",
749+
'session=secret; Domain=.victim.test; Path=/')
750+
751+
for domain in ('.victim.test', 'victim.test'):
752+
for expires in (
753+
'Max-Age=0',
754+
'Expires=Thu, 01 Jan 1970 00:00:00 GMT',
755+
):
756+
hdr = f'session=x; Domain={domain}; Path=/; {expires}'
757+
with self.subTest(hdr=hdr):
758+
interact_netscape(c, "http://www.attacker.test/", hdr)
759+
self.assertEqual(len(c), 1)
760+
self.assertIn(
761+
'session=secret',
762+
interact_netscape(c, "http://www.victim.test/"))
763+
764+
interact_netscape(c, "http://www.victim.test/",
765+
'session=x; Domain=.victim.test; Path=/; Max-Age=0')
766+
self.assertEqual(len(c), 0)
767+
768+
def test_expired_cookie_from_other_path_is_ignored(self):
769+
pol = DefaultCookiePolicy(strict_ns_set_path=True)
770+
c = CookieJar(pol)
771+
interact_netscape(c, "http://www.acme.com/a/",
772+
'token=secret; Path=/a')
773+
self.assertEqual(len(c), 1)
774+
interact_netscape(c, "http://www.acme.com/b/",
775+
'token=x; Path=/a; Max-Age=0')
776+
self.assertEqual(len(c), 1)
777+
interact_netscape(c, "http://www.acme.com/a/",
778+
'token=x; Path=/a; Max-Age=0')
779+
self.assertEqual(len(c), 0)
780+
781+
def test_expired_cookie_for_blocked_domain_is_ignored(self):
782+
c = CookieJar()
783+
interact_netscape(c, "http://www.victim.test/",
784+
'session=secret; Domain=.victim.test; Path=/')
785+
self.assertEqual(len(c), 1)
786+
c.set_policy(DefaultCookiePolicy(blocked_domains=[".victim.test"]))
787+
interact_netscape(c, "http://www.victim.test/",
788+
'session=x; Domain=.victim.test; Path=/; Max-Age=0')
789+
self.assertEqual(len(c), 1)
790+
c.set_policy(DefaultCookiePolicy())
791+
interact_netscape(c, "http://www.victim.test/",
792+
'session=x; Domain=.victim.test; Path=/; Max-Age=0')
793+
self.assertEqual(len(c), 0)
794+
746795
def test_default_path(self):
747796
# RFC 2965
748797
pol = DefaultCookiePolicy(rfc2965=True)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.. cve: CVE-2026-124249
2+
3+
Fix :class:`http.cookiejar.CookieJar` deleting a stored cookie when a
4+
response from an unrelated host sets an already expired cookie with a
5+
``Domain`` or ``Path`` attribute pointing at that cookie. The cookie policy
6+
is now consulted before an expired cookie is allowed to delete an existing
7+
one.

0 commit comments

Comments
 (0)