|
| 1 | +From f529b9470752c28ab69c96f31b0dbc10db69b404 Mon Sep 17 00:00:00 2001 |
| 2 | +From: Stan Ulbrych <stan@python.org> |
| 3 | +Date: Mon, 13 Apr 2026 20:02:52 +0100 |
| 4 | +Subject: [PATCH] gh-148169: Fix webbrowser `%action` substitution bypass of |
| 5 | + dash-prefix check (GH-148170) (cherry picked from commit |
| 6 | + d22922c8a7958353689dc4763dd72da2dea03fff) |
| 7 | + |
| 8 | +Co-authored-by: Stan Ulbrych <stan@python.org> |
| 9 | +--- |
| 10 | + Lib/test/test_webbrowser.py | 9 +++++++++ |
| 11 | + Lib/webbrowser.py | 5 +++-- |
| 12 | + .../2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst | 2 ++ |
| 13 | + 3 files changed, 14 insertions(+), 2 deletions(-) |
| 14 | + create mode 100644 Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst |
| 15 | + |
| 16 | +diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py |
| 17 | +index 404b3a31a5d2c9..bfbcf112b0b085 100644 |
| 18 | +--- ./Lib/test/test_webbrowser.py |
| 19 | ++++ b/Lib/test/test_webbrowser.py |
| 20 | +@@ -119,6 +119,15 @@ def test_open_bad_new_parameter(self): |
| 21 | + arguments=[URL], |
| 22 | + kw=dict(new=999)) |
| 23 | + |
| 24 | ++ def test_reject_action_dash_prefixes(self): |
| 25 | ++ browser = self.browser_class(name=CMD_NAME) |
| 26 | ++ with self.assertRaises(ValueError): |
| 27 | ++ browser.open('%action--incognito') |
| 28 | ++ # new=1: action is "--new-window", so "%action" itself expands to |
| 29 | ++ # a dash-prefixed flag even with no dash in the original URL. |
| 30 | ++ with self.assertRaises(ValueError): |
| 31 | ++ browser.open('%action', new=1) |
| 32 | ++ |
| 33 | + |
| 34 | + class EdgeCommandTest(CommandTestMixin, unittest.TestCase): |
| 35 | + |
| 36 | +diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py |
| 37 | +index 0e0b5034e5f53d..97aad6eea509eb 100644 |
| 38 | +--- ./Lib/webbrowser.py |
| 39 | ++++ b/Lib/webbrowser.py |
| 40 | +@@ -274,7 +274,6 @@ def _invoke(self, args, remote, autoraise, url=None): |
| 41 | + |
| 42 | + def open(self, url, new=0, autoraise=True): |
| 43 | + sys.audit("webbrowser.open", url) |
| 44 | +- self._check_url(url) |
| 45 | + if new == 0: |
| 46 | + action = self.remote_action |
| 47 | + elif new == 1: |
| 48 | +@@ -288,7 +287,9 @@ def open(self, url, new=0, autoraise=True): |
| 49 | + raise Error("Bad 'new' parameter to open(); " |
| 50 | + f"expected 0, 1, or 2, got {new}") |
| 51 | + |
| 52 | +- args = [arg.replace("%s", url).replace("%action", action) |
| 53 | ++ self._check_url(url.replace("%action", action)) |
| 54 | ++ |
| 55 | ++ args = [arg.replace("%action", action).replace("%s", url) |
| 56 | + for arg in self.remote_args] |
| 57 | + args = [arg for arg in args if arg] |
| 58 | + success = self._invoke(args, True, autoraise, url) |
| 59 | +diff --git a/Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst b/Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst |
| 60 | +new file mode 100644 |
| 61 | +index 00000000000000..45cdeebe1b6d64 |
| 62 | +--- /dev/null |
| 63 | ++++ ./Misc/NEWS.d/next/Security/2026-03-31-09-15-51.gh-issue-148169.EZJzz2.rst |
| 64 | +@@ -0,0 +1,2 @@ |
| 65 | ++A bypass in :mod:`webbrowser` allowed URLs prefixed with ``%action`` to pass |
| 66 | ++the dash-prefix safety check. |
0 commit comments