diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py index 2803c6d45c27bf..d090e0af860002 100644 --- a/Lib/test/test_xmlrpc.py +++ b/Lib/test/test_xmlrpc.py @@ -1338,6 +1338,21 @@ def get(self, key, failobj=None): return 'I am broken' return super().get(key, failobj) +class FailingThrowProtocol(unittest.TestCase): + def setUp(self): + self.url = 'https://user:password@test.com' + + def test_throw_protocol_error(self): + try: + with xmlrpclib.ServerProxy(self.url) as p: + p.pow(6,8) + except (xmlrpclib.ProtocolError, OSError) as e: + if not is_unavailable_exception(e) and hasattr(e, "headers"): + uinfo = e.url.split('@')[0] + passwd = uinfo.split(':')[1] + self.assertTrue(passwd == 'xxx') + else: + self.fail('ProtocolError not raised') class FailingServerTestCase(unittest.TestCase): def setUp(self): @@ -1382,7 +1397,6 @@ def test_basic(self): def test_fail_no_info(self): # use the broken message class xmlrpc.server.SimpleXMLRPCRequestHandler.MessageClass = FailingMessageClass - try: p = xmlrpclib.ServerProxy(URL) p.pow(6,8) diff --git a/Lib/xmlrpc/client.py b/Lib/xmlrpc/client.py index f441376d09c4aa..c2a5a753792a84 100644 --- a/Lib/xmlrpc/client.py +++ b/Lib/xmlrpc/client.py @@ -1167,8 +1167,9 @@ def single_request(self, host, handler, request_body, verbose=False): #Discard any response data and raise exception if resp.getheader("content-length", ""): resp.read() + uname = f'{host.split(':')[0]}@{host.split('@')[1]}' raise ProtocolError( - host + handler, + uname + handler, resp.status, resp.reason, dict(resp.getheaders()) ) diff --git a/Misc/NEWS.d/next/Library/2024-09-13-20-48-05.gh-issue-90996.MPGcGC.rst b/Misc/NEWS.d/next/Library/2024-09-13-20-48-05.gh-issue-90996.MPGcGC.rst new file mode 100644 index 00000000000000..1eb5e80935a923 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-09-13-20-48-05.gh-issue-90996.MPGcGC.rst @@ -0,0 +1,3 @@ +Fixed a security issue where a ``ProtocolError`` raised by +:meth:`xmlrpc.client.Transport.send_request`` in the :mod:`xmlrpc.client` could lead +to password leaks.