[3.11] gh-146211: Reject CR/LF in HTTP tunnel request headers (GH-146… (#148351)

[3.11] gh-146211: Reject CR/LF in HTTP tunnel request headers (GH-146212)
(cherry picked from commit 05ed7ce7ae)

Co-authored-by: Illia Volochii <illia.volochii@gmail.com>
This commit is contained in:
Seth Larson 2026-06-27 12:59:10 -05:00 committed by GitHub
parent ef12d0dc82
commit 56b7100b04
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 57 additions and 1 deletions

View file

@ -941,12 +941,21 @@ def _wrap_ipv6(self, ip):
return ip
def _tunnel(self):
if _contains_disallowed_url_pchar_re.search(self._tunnel_host):
raise ValueError('Tunnel host can\'t contain control characters %r'
% (self._tunnel_host,))
connect = b"CONNECT %s:%d HTTP/1.0\r\n" % (
self._wrap_ipv6(self._tunnel_host.encode("ascii")),
self._tunnel_port)
headers = [connect]
for header, value in self._tunnel_headers.items():
headers.append(f"{header}: {value}\r\n".encode("latin-1"))
header_bytes = header.encode("latin-1")
value_bytes = value.encode("latin-1")
if not _is_legal_header_name(header_bytes):
raise ValueError('Invalid header name %r' % (header_bytes,))
if _is_illegal_header_value(value_bytes):
raise ValueError('Invalid header value %r' % (value_bytes,))
headers.append(b"%s: %s\r\n" % (header_bytes, value_bytes))
headers.append(b"\r\n")
# Making a single send() call instead of one per line encourages
# the host OS to use a more optimal packet size instead of

View file

@ -371,6 +371,51 @@ def test_invalid_headers(self):
with self.assertRaisesRegex(ValueError, 'Invalid header'):
conn.putheader(name, value)
def test_invalid_tunnel_headers(self):
cases = (
('Invalid\r\nName', 'ValidValue'),
('Invalid\rName', 'ValidValue'),
('Invalid\nName', 'ValidValue'),
('\r\nInvalidName', 'ValidValue'),
('\rInvalidName', 'ValidValue'),
('\nInvalidName', 'ValidValue'),
(' InvalidName', 'ValidValue'),
('\tInvalidName', 'ValidValue'),
('Invalid:Name', 'ValidValue'),
(':InvalidName', 'ValidValue'),
('ValidName', 'Invalid\r\nValue'),
('ValidName', 'Invalid\rValue'),
('ValidName', 'Invalid\nValue'),
('ValidName', 'InvalidValue\r\n'),
('ValidName', 'InvalidValue\r'),
('ValidName', 'InvalidValue\n'),
)
for name, value in cases:
with self.subTest((name, value)):
conn = client.HTTPConnection('example.com')
conn.set_tunnel('tunnel', headers={
name: value
})
conn.sock = FakeSocket('')
with self.assertRaisesRegex(ValueError, 'Invalid header'):
conn._tunnel() # Called in .connect()
def test_invalid_tunnel_host(self):
cases = (
'invalid\r.host',
'\ninvalid.host',
'invalid.host\r\n',
'invalid.host\x00',
'invalid host',
)
for tunnel_host in cases:
with self.subTest(tunnel_host):
conn = client.HTTPConnection('example.com')
conn.set_tunnel(tunnel_host)
conn.sock = FakeSocket('')
with self.assertRaisesRegex(ValueError, 'Tunnel host can\'t contain control characters'):
conn._tunnel() # Called in .connect()
def test_headers_debuglevel(self):
body = (
b'HTTP/1.1 200 OK\r\n'

View file

@ -0,0 +1,2 @@
Reject CR/LF characters in tunnel request headers for the
HTTPConnection.set_tunnel() method.