diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py index a7a5c5ba33d493..67d14a0e6358f2 100644 --- a/Lib/test/test_wsgiref.py +++ b/Lib/test/test_wsgiref.py @@ -855,6 +855,13 @@ def write(self, b): self.assertIsNotNone(h.status) self.assertIsNotNone(h.environ) + def testRaisesControlCharacters(self): + for c0 in control_characters_c0(): + with self.subTest(c0): + base = BaseHandler() + headers = [('x','y')] + self.assertRaises(ValueError, base.start_response, f"{c0}", headers) + class TestModule(unittest.TestCase): def test_deprecated__version__(self): diff --git a/Lib/wsgiref/handlers.py b/Lib/wsgiref/handlers.py index 9353fb678625b3..9a4a6c8441bbe1 100644 --- a/Lib/wsgiref/handlers.py +++ b/Lib/wsgiref/handlers.py @@ -1,7 +1,7 @@ """Base classes for server/gateway implementations""" from .util import FileWrapper, guess_scheme, is_hop_by_hop -from .headers import Headers +from .headers import Headers, _name_disallowed_re, _value_disallowed_re import sys, os, time @@ -237,13 +237,13 @@ def start_response(self, status, headers,exc_info=None): self.status = status self.headers = self.headers_class(headers) - status = self._convert_string_type(status, "Status") + status = self._convert_string_type(status, "Status", name=True) self._validate_status(status) if __debug__: for name, val in headers: - name = self._convert_string_type(name, "Header name") - val = self._convert_string_type(val, "Header value") + name = self._convert_string_type(name, "Header name", name=True) + val = self._convert_string_type(val, "Header value", name=False) assert not is_hop_by_hop(name),\ f"Hop-by-hop header, '{name}: {val}', not allowed" @@ -257,9 +257,12 @@ def _validate_status(self, status): if status[3] != " ": raise AssertionError("Status message must have a space after code") - def _convert_string_type(self, value, title): + def _convert_string_type(self, value, title, *, name): """Convert/check value type.""" if type(value) is str: + regex = (_name_disallowed_re if name else _value_disallowed_re) + if regex.search(value): + raise ValueError("Control characters not allowed in headers and status") return value raise AssertionError( "{0} must be of type str (got {1})".format(title, repr(value)) diff --git a/Misc/ACKS b/Misc/ACKS index 4295eff8c1e225..ae91d4ece44f38 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1078,6 +1078,7 @@ Wolfgang Langner Detlef Lannert Rémi Lapeyre Soren Larsen +Seth Michael Larson Amos Latteier Keenan Lau Piers Lauder diff --git a/Misc/NEWS.d/next/Security/2026-01-31-21-56-54.gh-issue-144370.fp9m8t.rst b/Misc/NEWS.d/next/Security/2026-01-31-21-56-54.gh-issue-144370.fp9m8t.rst new file mode 100644 index 00000000000000..988dd7356d6bb6 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-01-31-21-56-54.gh-issue-144370.fp9m8t.rst @@ -0,0 +1,3 @@ +Disallow usage of control characters in headers and status +in :mod:`wsgiref.handlers` to prevent injections. +Patch by Benedikt Johannes.