CVE-2019-12387
Description
In Twisted before 19.2.1, twisted.web did not validate or sanitize URIs or HTTP methods, allowing an attacker to inject invalid characters such as CRLF.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Twisted before 19.2.1 fails to sanitize URIs and HTTP methods, enabling CRLF injection attacks.
Vulnerability
Summary
In Twisted versions before 19.2.1, the twisted.web component does not properly validate or sanitize URIs and HTTP methods. This allows an attacker to inject CRLF (carriage return line feed) characters into HTTP requests [2][3].
Exploitation
An attacker can send a crafted HTTP request containing CRLF sequences in the URI or HTTP method field. Since the server does not filter these characters, the injected CRLF can break the HTTP protocol, allowing the attacker to insert arbitrary headers or modify the response body. The attack is remotely exploitable without authentication, requiring only network access to the Twisted web server [3].
Impact
Successful exploitation enables HTTP response splitting, which can lead to cache poisoning, cross-site scripting, and other attacks. An attacker could potentially poison web caches, redirect users to malicious sites, or perform session fixation [2][3].
Mitigation
The issue is fixed in Twisted version 19.2.1. Users should upgrade to this or a later version. Major distributions such as Ubuntu have released security updates (USN-4308-1, USN-4308-2) to address this vulnerability [2][4].
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
twistedPyPI | < 19.2.1 | 19.2.1 |
Affected products
12- Twisted/twisted.webdescription
- ghsa-coords11 versionspkg:pypi/twistedpkg:rpm/suse/python-Twisted&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/python-Twisted&distro=SUSE%20Enterprise%20Storage%204pkg:rpm/suse/python-Twisted&distro=SUSE%20Enterprise%20Storage%205pkg:rpm/suse/python-Twisted&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Web%20and%20Scripting%2012pkg:rpm/suse/python-Twisted&distro=SUSE%20OpenStack%20Cloud%207pkg:rpm/suse/python-Twisted&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/python-Twisted&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/python-Twisted&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/python-Twisted&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/python-Twisted&distro=SUSE%20Package%20Hub%2015
< 19.2.1+ 10 more
- (no CPE)range: < 19.2.1
- (no CPE)range: < 15.2.1-9.5.2
- (no CPE)range: < 15.2.1-9.5.2
- (no CPE)range: < 15.2.1-9.5.2
- (no CPE)range: < 15.2.1-9.5.2
- (no CPE)range: < 15.2.1-9.5.2
- (no CPE)range: < 15.2.1-9.5.2
- (no CPE)range: < 15.2.1-9.5.2
- (no CPE)range: < 15.2.1-9.5.2
- (no CPE)range: < 15.2.1-9.5.2
- (no CPE)range: < 17.9.0-bp150.4.3.1
Patches
16c61fc4503aePrevent CRLF injections described in CVE-2019-12387
6 files changed · +725 −11
src/twisted/web/client.py+17 −5 modified@@ -47,6 +47,9 @@ def urlunparse(parts): from twisted.web.http_headers import Headers from twisted.logger import Logger +from twisted.web._newclient import _ensureValidURI, _ensureValidMethod + + class PartialDownloadError(error.Error): """ @@ -78,11 +81,13 @@ class HTTPPageGetter(http.HTTPClient): _completelyDone = True - _specialHeaders = set((b'host', b'user-agent', b'cookie', b'content-length')) + _specialHeaders = set( + (b'host', b'user-agent', b'cookie', b'content-length'), + ) def connectionMade(self): - method = getattr(self.factory, 'method', b'GET') - self.sendCommand(method, self.factory.path) + method = _ensureValidMethod(getattr(self.factory, 'method', b'GET')) + self.sendCommand(method, _ensureValidURI(self.factory.path)) if self.factory.scheme == b'http' and self.factory.port != 80: host = self.factory.host + b':' + intToBytes(self.factory.port) elif self.factory.scheme == b'https' and self.factory.port != 443: @@ -362,7 +367,7 @@ def __init__(self, url, method=b'GET', postdata=None, headers=None, # just in case a broken http/1.1 decides to keep connection alive self.headers.setdefault(b"connection", b"close") self.postdata = postdata - self.method = method + self.method = _ensureValidMethod(method) self.setURL(url) @@ -389,6 +394,7 @@ def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self.url) def setURL(self, url): + _ensureValidURI(url.strip()) self.url = url uri = URI.fromBytes(url) if uri.scheme and uri.host: @@ -733,7 +739,7 @@ def _makeGetterFactory(url, factoryFactory, contextFactory=None, @return: The factory created by C{factoryFactory} """ - uri = URI.fromBytes(url) + uri = URI.fromBytes(_ensureValidURI(url.strip())) factory = factoryFactory(url, *args, **kwargs) if uri.scheme == b'https': from twisted.internet import ssl @@ -1493,6 +1499,9 @@ def _requestWithEndpoint(self, key, endpoint, method, parsedURI, if not isinstance(method, bytes): raise TypeError('method={!r} is {}, but must be bytes'.format( method, type(method))) + + method = _ensureValidMethod(method) + # Create minimal headers, if necessary: if headers is None: headers = Headers() @@ -1717,6 +1726,7 @@ def request(self, method, uri, headers=None, bodyProducer=None): @see: L{twisted.web.iweb.IAgent.request} """ + uri = _ensureValidURI(uri.strip()) parsedURI = URI.fromBytes(uri) try: endpoint = self._getEndpoint(parsedURI) @@ -1750,6 +1760,8 @@ def request(self, method, uri, headers=None, bodyProducer=None): """ Issue a new request via the configured proxy. """ + uri = _ensureValidURI(uri.strip()) + # Cache *all* connections under the same key, since we are only # connecting to a single destination, the proxy: key = ("http-proxy", self._proxyEndpoint)
src/twisted/web/_newclient.py+81 −4 modified@@ -29,6 +29,8 @@ from __future__ import division, absolute_import __metaclass__ = type +import re + from zope.interface import implementer from twisted.python.compat import networkString @@ -579,6 +581,74 @@ def connectionLost(self, reason): +_VALID_METHOD = re.compile( + br"\A[%s]+\Z" % ( + bytes().join( + ( + b"!", b"#", b"$", b"%", b"&", b"'", b"*", + b"+", b"-", b".", b"^", b"_", b"`", b"|", b"~", + b"\x30-\x39", + b"\x41-\x5a", + b"\x61-\x7A", + ), + ), + ), +) + + + +def _ensureValidMethod(method): + """ + An HTTP method is an HTTP token, which consists of any visible + ASCII character that is not a delimiter (i.e. one of + C{"(),/:;<=>?@[\\]{}}.) + + @param method: the method to check + @type method: L{bytes} + + @return: the method if it is valid + @rtype: L{bytes} + + @raise ValueError: if the method is not valid + + @see: U{https://tools.ietf.org/html/rfc7230#section-3.1.1}, + U{https://tools.ietf.org/html/rfc7230#section-3.2.6}, + U{https://tools.ietf.org/html/rfc5234#appendix-B.1} + """ + if _VALID_METHOD.match(method): + return method + raise ValueError("Invalid method {!r}".format(method)) + + + +_VALID_URI = re.compile(br'\A[\x21-\x7e]+\Z') + + + +def _ensureValidURI(uri): + """ + A valid URI cannot contain control characters (i.e., characters + between 0-32, inclusive and 127) or non-ASCII characters (i.e., + characters with values between 128-255, inclusive). + + @param uri: the URI to check + @type uri: L{bytes} + + @return: the URI if it is valid + @rtype: L{bytes} + + @raise ValueError: if the URI is not valid + + @see: U{https://tools.ietf.org/html/rfc3986#section-3.3}, + U{https://tools.ietf.org/html/rfc3986#appendix-A}, + U{https://tools.ietf.org/html/rfc5234#appendix-B.1} + """ + if _VALID_URI.match(uri): + return uri + raise ValueError("Invalid URI {!r}".format(uri)) + + + @implementer(IClientRequest) class Request: """ @@ -618,8 +688,8 @@ def __init__(self, method, uri, headers, bodyProducer, persistent=False): connection, defaults to C{False}. @type persistent: L{bool} """ - self.method = method - self.uri = uri + self.method = _ensureValidMethod(method) + self.uri = _ensureValidURI(uri) self.headers = headers self.bodyProducer = bodyProducer self.persistent = persistent @@ -664,8 +734,15 @@ def _writeHeaders(self, transport, TEorCL): # method would probably be good. It would be nice if this method # weren't limited to issuing HTTP/1.1 requests. requestLines = [] - requestLines.append(b' '.join([self.method, self.uri, - b'HTTP/1.1\r\n'])) + requestLines.append( + b' '.join( + [ + _ensureValidMethod(self.method), + _ensureValidURI(self.uri), + b'HTTP/1.1\r\n', + ] + ), + ) if not self.persistent: requestLines.append(b'Connection: close\r\n') if TEorCL is not None:
src/twisted/web/newsfragments/9647.bugfix+1 −0 added@@ -0,0 +1 @@ +All HTTP clients in twisted.web.client now raise a ValueError when called with a method and/or URL that contain invalid characters. This mitigates CVE-2019-12387. Thanks to Alex Brasetvik for reporting this vulnerability. \ No newline at end of file
src/twisted/web/test/injectionhelpers.py+168 −0 added@@ -0,0 +1,168 @@ +""" +Helpers for URI and method injection tests. + +@see: U{CVE-2019-12387} +""" + +import string + + +UNPRINTABLE_ASCII = ( + frozenset(range(0, 128)) - + frozenset(bytearray(string.printable, 'ascii')) +) + +NONASCII = frozenset(range(128, 256)) + + + +class MethodInjectionTestsMixin(object): + """ + A mixin that runs HTTP method injection tests. Define + L{MethodInjectionTestsMixin.attemptRequestWithMaliciousMethod} in + a L{twisted.trial.unittest.SynchronousTestCase} subclass to test + how HTTP client code behaves when presented with malicious HTTP + methods. + + @see: U{CVE-2019-12387} + """ + + def attemptRequestWithMaliciousMethod(self, method): + """ + Attempt to send a request with the given method. This should + synchronously raise a L{ValueError} if either is invalid. + + @param method: the method (e.g. C{GET\x00}) + + @param uri: the URI + + @type method: + """ + raise NotImplementedError() + + + def test_methodWithCLRFRejected(self): + """ + Issuing a request with a method that contains a carriage + return and line feed fails with a L{ValueError}. + """ + with self.assertRaises(ValueError) as cm: + method = b"GET\r\nX-Injected-Header: value" + self.attemptRequestWithMaliciousMethod(method) + self.assertRegex(str(cm.exception), "^Invalid method") + + + def test_methodWithUnprintableASCIIRejected(self): + """ + Issuing a request with a method that contains unprintable + ASCII characters fails with a L{ValueError}. + """ + for c in UNPRINTABLE_ASCII: + method = b"GET%s" % (bytearray([c]),) + with self.assertRaises(ValueError) as cm: + self.attemptRequestWithMaliciousMethod(method) + self.assertRegex(str(cm.exception), "^Invalid method") + + + def test_methodWithNonASCIIRejected(self): + """ + Issuing a request with a method that contains non-ASCII + characters fails with a L{ValueError}. + """ + for c in NONASCII: + method = b"GET%s" % (bytearray([c]),) + with self.assertRaises(ValueError) as cm: + self.attemptRequestWithMaliciousMethod(method) + self.assertRegex(str(cm.exception), "^Invalid method") + + + +class URIInjectionTestsMixin(object): + """ + A mixin that runs HTTP URI injection tests. Define + L{MethodInjectionTestsMixin.attemptRequestWithMaliciousURI} in a + L{twisted.trial.unittest.SynchronousTestCase} subclass to test how + HTTP client code behaves when presented with malicious HTTP + URIs. + """ + + def attemptRequestWithMaliciousURI(self, method): + """ + Attempt to send a request with the given URI. This should + synchronously raise a L{ValueError} if either is invalid. + + @param uri: the URI. + + @type method: + """ + raise NotImplementedError() + + + def test_hostWithCRLFRejected(self): + """ + Issuing a request with a URI whose host contains a carriage + return and line feed fails with a L{ValueError}. + """ + with self.assertRaises(ValueError) as cm: + uri = b"http://twisted\r\n.invalid/path" + self.attemptRequestWithMaliciousURI(uri) + self.assertRegex(str(cm.exception), "^Invalid URI") + + + def test_hostWithWithUnprintableASCIIRejected(self): + """ + Issuing a request with a URI whose host contains unprintable + ASCII characters fails with a L{ValueError}. + """ + for c in UNPRINTABLE_ASCII: + uri = b"http://twisted%s.invalid/OK" % (bytearray([c]),) + with self.assertRaises(ValueError) as cm: + self.attemptRequestWithMaliciousURI(uri) + self.assertRegex(str(cm.exception), "^Invalid URI") + + + def test_hostWithNonASCIIRejected(self): + """ + Issuing a request with a URI whose host contains non-ASCII + characters fails with a L{ValueError}. + """ + for c in NONASCII: + uri = b"http://twisted%s.invalid/OK" % (bytearray([c]),) + with self.assertRaises(ValueError) as cm: + self.attemptRequestWithMaliciousURI(uri) + self.assertRegex(str(cm.exception), "^Invalid URI") + + + def test_pathWithCRLFRejected(self): + """ + Issuing a request with a URI whose path contains a carriage + return and line feed fails with a L{ValueError}. + """ + with self.assertRaises(ValueError) as cm: + uri = b"http://twisted.invalid/\r\npath" + self.attemptRequestWithMaliciousURI(uri) + self.assertRegex(str(cm.exception), "^Invalid URI") + + + def test_pathWithWithUnprintableASCIIRejected(self): + """ + Issuing a request with a URI whose path contains unprintable + ASCII characters fails with a L{ValueError}. + """ + for c in UNPRINTABLE_ASCII: + uri = b"http://twisted.invalid/OK%s" % (bytearray([c]),) + with self.assertRaises(ValueError) as cm: + self.attemptRequestWithMaliciousURI(uri) + self.assertRegex(str(cm.exception), "^Invalid URI") + + + def test_pathWithNonASCIIRejected(self): + """ + Issuing a request with a URI whose path contains non-ASCII + characters fails with a L{ValueError}. + """ + for c in NONASCII: + uri = b"http://twisted.invalid/OK%s" % (bytearray([c]),) + with self.assertRaises(ValueError) as cm: + self.attemptRequestWithMaliciousURI(uri) + self.assertRegex(str(cm.exception), "^Invalid URI")
src/twisted/web/test/test_agent.py+146 −1 modified@@ -11,7 +11,7 @@ from zope.interface.verify import verifyObject -from twisted.trial.unittest import TestCase +from twisted.trial.unittest import TestCase, SynchronousTestCase from twisted.web import client, error, http_headers from twisted.web._newclient import RequestNotSent, RequestTransmissionFailed from twisted.web._newclient import ResponseNeverReceived, ResponseFailed @@ -51,6 +51,10 @@ from twisted.test.proto_helpers import AccumulatingProtocol from twisted.test.iosim import IOPump, FakeTransport from twisted.test.test_sslverify import certificatesForAuthorityAndServer +from twisted.web.test.injectionhelpers import ( + MethodInjectionTestsMixin, + URIInjectionTestsMixin, +) from twisted.web.error import SchemeNotSupported from twisted.logger import globalLogPublisher @@ -897,6 +901,7 @@ class AgentTests(TestCase, FakeReactorAndConnectMixin, AgentTestsMixin, """ Tests for the new HTTP client API provided by L{Agent}. """ + def makeAgent(self): """ @return: a new L{twisted.web.client.Agent} instance @@ -1327,6 +1332,48 @@ def test_endpointFactoryPool(self): +class AgentMethodInjectionTests( + FakeReactorAndConnectMixin, + MethodInjectionTestsMixin, + SynchronousTestCase, +): + """ + Test L{client.Agent} against HTTP method injections. + """ + + def attemptRequestWithMaliciousMethod(self, method): + """ + Attempt a request with the provided method. + + @param method: see L{MethodInjectionTestsMixin} + """ + agent = client.Agent(self.createReactor()) + uri = b"http://twisted.invalid" + agent.request(method, uri, client.Headers(), None) + + + +class AgentURIInjectionTests( + FakeReactorAndConnectMixin, + URIInjectionTestsMixin, + SynchronousTestCase, +): + """ + Test L{client.Agent} against URI injections. + """ + + def attemptRequestWithMaliciousURI(self, uri): + """ + Attempt a request with the provided method. + + @param uri: see L{URIInjectionTestsMixin} + """ + agent = client.Agent(self.createReactor()) + method = b"GET" + agent.request(method, uri, client.Headers(), None) + + + class AgentHTTPSTests(TestCase, FakeReactorAndConnectMixin, IntegrationTestingMixin): """ @@ -3202,3 +3249,101 @@ def test_changeCacheSize(self): self.assertEquals(5, len(policy._cache)) self.assertIn(hostn, policy._cache) + + + +class RequestMethodInjectionTests( + MethodInjectionTestsMixin, + SynchronousTestCase, +): + """ + Test L{client.Request} against HTTP method injections. + """ + + def attemptRequestWithMaliciousMethod(self, method): + """ + Attempt a request with the provided method. + + @param method: see L{MethodInjectionTestsMixin} + """ + client.Request( + method=method, + uri=b"http://twisted.invalid", + headers=http_headers.Headers(), + bodyProducer=None, + ) + + + +class RequestWriteToMethodInjectionTests( + MethodInjectionTestsMixin, + SynchronousTestCase, +): + """ + Test L{client.Request.writeTo} against HTTP method injections. + """ + + def attemptRequestWithMaliciousMethod(self, method): + """ + Attempt a request with the provided method. + + @param method: see L{MethodInjectionTestsMixin} + """ + headers = http_headers.Headers({b"Host": [b"twisted.invalid"]}) + req = client.Request( + method=b"GET", + uri=b"http://twisted.invalid", + headers=headers, + bodyProducer=None, + ) + req.method = method + req.writeTo(StringTransport()) + + + +class RequestURIInjectionTests( + URIInjectionTestsMixin, + SynchronousTestCase, +): + """ + Test L{client.Request} against HTTP URI injections. + """ + + def attemptRequestWithMaliciousURI(self, uri): + """ + Attempt a request with the provided URI. + + @param method: see L{URIInjectionTestsMixin} + """ + client.Request( + method=b"GET", + uri=uri, + headers=http_headers.Headers(), + bodyProducer=None, + ) + + + +class RequestWriteToURIInjectionTests( + URIInjectionTestsMixin, + SynchronousTestCase, +): + """ + Test L{client.Request.writeTo} against HTTP method injections. + """ + + def attemptRequestWithMaliciousURI(self, uri): + """ + Attempt a request with the provided method. + + @param method: see L{URIInjectionTestsMixin} + """ + headers = http_headers.Headers({b"Host": [b"twisted.invalid"]}) + req = client.Request( + method=b"GET", + uri=b"http://twisted.invalid", + headers=headers, + bodyProducer=None, + ) + req.uri = uri + req.writeTo(StringTransport())
src/twisted/web/test/test_webclient.py+312 −1 modified@@ -7,6 +7,7 @@ from __future__ import division, absolute_import +import io import os from errno import ENOSPC @@ -20,7 +21,8 @@ from twisted.web import server, client, error, resource from twisted.web.static import Data from twisted.web.util import Redirect -from twisted.internet import reactor, defer, interfaces +from twisted.internet import address, reactor, defer, interfaces +from twisted.internet.protocol import ClientFactory from twisted.python.filepath import FilePath from twisted.protocols.policies import WrappingFactory from twisted.test.proto_helpers import ( @@ -35,6 +37,12 @@ from twisted.logger import (globalLogPublisher, FilteringLogObserver, LogLevelFilterPredicate, LogLevel, Logger) +from twisted.web.test.injectionhelpers import ( + MethodInjectionTestsMixin, + URIInjectionTestsMixin, +) + + serverPEM = FilePath(test.__file__).sibling('server.pem') serverPEMPath = serverPEM.asBytesMode().path @@ -1519,3 +1527,306 @@ def test_httpDownloaderDeprecated(self): L{client.HTTPDownloader} is deprecated. """ self._testDeprecatedClass("HTTPDownloader") + + + +class GetPageMethodInjectionTests( + MethodInjectionTestsMixin, + unittest.SynchronousTestCase, +): + """ + Test L{client.getPage} against HTTP method injections. + """ + + def attemptRequestWithMaliciousMethod(self, method): + """ + Attempt a request with the provided method. + + @param method: see L{MethodInjectionTestsMixin} + """ + uri = b'http://twisted.invalid' + client.getPage(uri, method=method) + + + +class GetPageURIInjectionTests( + URIInjectionTestsMixin, + unittest.SynchronousTestCase, +): + """ + Test L{client.getPage} against URI injections. + """ + + def attemptRequestWithMaliciousURI(self, uri): + """ + Attempt a request with the provided URI. + + @param uri: see L{URIInjectionTestsMixin} + """ + client.getPage(uri) + + + +class DownloadPageMethodInjectionTests( + MethodInjectionTestsMixin, + unittest.SynchronousTestCase, +): + """ + Test L{client.getPage} against HTTP method injections. + """ + + def attemptRequestWithMaliciousMethod(self, method): + """ + Attempt a request with the provided method. + + @param method: see L{MethodInjectionTestsMixin} + """ + uri = b'http://twisted.invalid' + client.downloadPage(uri, file=io.BytesIO(), method=method) + + + +class DownloadPageURIInjectionTests( + URIInjectionTestsMixin, + unittest.SynchronousTestCase, +): + """ + Test L{client.downloadPage} against URI injections. + """ + + def attemptRequestWithMaliciousURI(self, uri): + """ + Attempt a request with the provided URI. + + @param uri: see L{URIInjectionTestsMixin} + """ + client.downloadPage(uri, file=io.BytesIO()) + + + +def makeHTTPPageGetterFactory(protocolClass, method, host, path): + """ + Make a L{ClientFactory} that can be used with + L{client.HTTPPageGetter} and its subclasses. + + @param protocolClass: The protocol class + @type protocolClass: A subclass of L{client.HTTPPageGetter} + + @param method: the HTTP method + + @param host: the host + + @param path: The URI path + + @return: A L{ClientFactory}. + """ + factory = ClientFactory.forProtocol(protocolClass) + + factory.method = method + factory.host = host + factory.path = path + + factory.scheme = b"http" + factory.port = 0 + factory.headers = {} + factory.agent = b"User/Agent" + factory.cookies = {} + + return factory + + + +class HTTPPageGetterMethodInjectionTests( + MethodInjectionTestsMixin, + unittest.SynchronousTestCase, +): + """ + Test L{client.HTTPPageGetter} against HTTP method injections. + """ + protocolClass = client.HTTPPageGetter + + def attemptRequestWithMaliciousMethod(self, method): + """ + Attempt a request with the provided method. + + @param method: L{MethodInjectionTestsMixin} + """ + transport = StringTransport() + factory = makeHTTPPageGetterFactory( + self.protocolClass, + method=method, + host=b"twisted.invalid", + path=b"/", + ) + getter = factory.buildProtocol( + address.IPv4Address("TCP", "127.0.0.1", 0), + ) + getter.makeConnection(transport) + + + +class HTTPPageGetterURIInjectionTests( + URIInjectionTestsMixin, + unittest.SynchronousTestCase, +): + """ + Test L{client.HTTPPageGetter} against HTTP URI injections. + """ + protocolClass = client.HTTPPageGetter + + def attemptRequestWithMaliciousURI(self, uri): + """ + Attempt a request with the provided URI. + + @param uri: L{URIInjectionTestsMixin} + """ + transport = StringTransport() + # Setting the host and path to the same value is imprecise but + # doesn't require parsing an invalid URI. + factory = makeHTTPPageGetterFactory( + self.protocolClass, + method=b"GET", + host=uri, + path=uri, + ) + getter = factory.buildProtocol( + address.IPv4Address("TCP", "127.0.0.1", 0), + ) + getter.makeConnection(transport) + + + +class HTTPPageDownloaderMethodInjectionTests( + HTTPPageGetterMethodInjectionTests +): + + """ + Test L{client.HTTPPageDownloader} against HTTP method injections. + """ + protocolClass = client.HTTPPageDownloader + + + +class HTTPPageDownloaderURIInjectionTests( + HTTPPageGetterURIInjectionTests +): + """ + Test L{client.HTTPPageDownloader} against HTTP URI injections. + """ + protocolClass = client.HTTPPageDownloader + + + +class HTTPClientFactoryMethodInjectionTests( + MethodInjectionTestsMixin, + unittest.SynchronousTestCase, +): + """ + Tests L{client.HTTPClientFactory} against HTTP method injections. + """ + + def attemptRequestWithMaliciousMethod(self, method): + """ + Attempt a request with the provided method. + + @param method: L{MethodInjectionTestsMixin} + """ + client.HTTPClientFactory(b"https://twisted.invalid", method) + + + +class HTTPClientFactoryURIInjectionTests( + URIInjectionTestsMixin, + unittest.SynchronousTestCase, +): + """ + Tests L{client.HTTPClientFactory} against HTTP URI injections. + """ + + def attemptRequestWithMaliciousURI(self, uri): + """ + Attempt a request with the provided URI. + + @param uri: L{URIInjectionTestsMixin} + """ + client.HTTPClientFactory(uri) + + + +class HTTPClientFactorySetURLURIInjectionTests( + URIInjectionTestsMixin, + unittest.SynchronousTestCase, +): + """ + Tests L{client.HTTPClientFactory.setURL} against HTTP URI injections. + """ + + def attemptRequestWithMaliciousURI(self, uri): + """ + Attempt a request with the provided URI. + + @param uri: L{URIInjectionTestsMixin} + """ + client.HTTPClientFactory(b"https://twisted.invalid").setURL(uri) + + + +class HTTPDownloaderMethodInjectionTests( + MethodInjectionTestsMixin, + unittest.SynchronousTestCase, +): + """ + Tests L{client.HTTPDownloader} against HTTP method injections. + """ + + def attemptRequestWithMaliciousMethod(self, method): + """ + Attempt a request with the provided method. + + @param method: L{MethodInjectionTestsMixin} + """ + client.HTTPDownloader( + b"https://twisted.invalid", + io.BytesIO(), + method=method, + ) + + + +class HTTPDownloaderURIInjectionTests( + URIInjectionTestsMixin, + unittest.SynchronousTestCase, +): + """ + Tests L{client.HTTPDownloader} against HTTP URI injections. + """ + + def attemptRequestWithMaliciousURI(self, uri): + """ + Attempt a request with the provided URI. + + @param uri: L{URIInjectionTestsMixin} + """ + client.HTTPDownloader(uri, io.BytesIO()) + + + +class HTTPDownloaderSetURLURIInjectionTests( + URIInjectionTestsMixin, + unittest.SynchronousTestCase, +): + """ + Tests L{client.HTTPDownloader.setURL} against HTTP URI injections. + """ + + def attemptRequestWithMaliciousURI(self, uri): + """ + Attempt a request with the provided URI. + + @param uri: L{URIInjectionTestsMixin} + """ + downloader = client.HTTPDownloader( + b"https://twisted.invalid", + io.BytesIO(), + ) + downloader.setURL(uri)
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
16- lists.opensuse.org/opensuse-security-announce/2019-07/msg00030.htmlghsavendor-advisoryx_refsource_SUSEWEB
- lists.opensuse.org/opensuse-security-announce/2019-07/msg00042.htmlghsavendor-advisoryx_refsource_SUSEWEB
- github.com/advisories/GHSA-6cc5-2vg4-cc7mghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/2G5RPDQ4BNB336HL6WW5ZJ344MAWNN7N/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2019-12387ghsaADVISORY
- usn.ubuntu.com/4308-1/mitrevendor-advisoryx_refsource_UBUNTU
- usn.ubuntu.com/4308-2/mitrevendor-advisoryx_refsource_UBUNTU
- github.com/pypa/advisory-database/tree/main/vulns/twisted/PYSEC-2019-128.yamlghsaWEB
- github.com/twisted/twisted/commit/6c61fc4503ae39ab8ecee52d10f10ee2c371d7e2ghsax_refsource_CONFIRMWEB
- labs.twistedmatrix.com/2019/06/twisted-1921-released.htmlghsax_refsource_CONFIRMWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/2G5RPDQ4BNB336HL6WW5ZJ344MAWNN7NghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/2G5RPDQ4BNB336HL6WW5ZJ344MAWNN7NghsaWEB
- twistedmatrix.com/pipermail/twisted-python/2019-June/032352.htmlghsax_refsource_CONFIRMWEB
- usn.ubuntu.com/4308-1ghsaWEB
- usn.ubuntu.com/4308-2ghsaWEB
- www.oracle.com/security-alerts/cpuapr2020.htmlghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.