Compare commits

..

10 Commits

Author SHA1 Message Date
openeuler-ci-bot
db8d6371e4
!33 [sync] PR-29: Fix CVE-2023-46137
From: @openeuler-sync-bot 
Reviewed-by: @cherry530 
Signed-off-by: @cherry530
2024-01-02 02:07:54 +00:00
starlet-dx
0f0d5c0d1a Fix CVE-2023-46137
(cherry picked from commit b5bfd19252fa7cfd7c12325a36db3bc87593e3ac)
2024-01-02 09:05:29 +08:00
openeuler-ci-bot
3b5d12508a
!27 [sync] PR-24: Fix CVE-2022-39348
From: @openeuler-sync-bot 
Reviewed-by: @cherry530 
Signed-off-by: @cherry530
2023-12-12 03:06:18 +00:00
starlet-dx
2e49a942b0 Fix CVE-2022-39348
(cherry picked from commit b492fb6cf245d70b4484343b31a8c60d04815d79)
2023-12-12 10:29:25 +08:00
openeuler-ci-bot
688ac9c5e2
!15 upgrade version to 22.4.0
From: @starlet-dx 
Reviewed-by: @caodongxia 
Signed-off-by: @caodongxia
2022-10-27 09:45:27 +00:00
starlet-dx
b9b64fcd05 upgrade version to 22.4.0 2022-10-27 11:02:29 +08:00
openeuler-ci-bot
4eaf2ffb18 !7 fix CVE-2019-12855
From: @programmer12
Reviewed-by: @yangzhao_kl
Signed-off-by: @yangzhao_kl
2021-08-18 06:21:08 +00:00
programmer12
c78c099bfa fix CVE 2021-08-18 09:29:20 +08:00
openeuler-ci-bot
1495e857ca !5 fix CVE-2020-10108 CVE-2020-10109
From: @programmer12
Reviewed-by: @yangzhao_kl,@licihua,@myeuler
Signed-off-by: @yangzhao_kl,@licihua,@myeuler
2021-08-16 14:18:00 +00:00
programmer12
11839d0a13 fix CVES 2021-08-16 16:59:23 +08:00
6 changed files with 1672 additions and 924 deletions

View File

@ -1,907 +0,0 @@
diff --git a/src/twisted/web/_newclient.py b/src/twisted/web/_newclient.py
index 370f47d..74a8a6c 100644
--- a/src/twisted/web/_newclient.py
+++ b/src/twisted/web/_newclient.py
@@ -29,6 +29,8 @@ Various other classes in this module support this usage:
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 @@ class HTTPClientParser(HTTPParser):
+_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 @@ class Request:
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 @@ class Request:
# 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:
diff --git a/src/twisted/web/client.py b/src/twisted/web/client.py
index 02eb6e9..a1554d3 100644
--- a/src/twisted/web/client.py
+++ b/src/twisted/web/client.py
@@ -46,6 +46,9 @@ from twisted.web.iweb import UNKNOWN_LENGTH, IAgent, IBodyProducer, IResponse
from twisted.web.http_headers import Headers
from twisted.logger import Logger
+from twisted.web._newclient import _ensureValidURI, _ensureValidMethod
+
+
class PartialDownloadError(error.Error):
"""
@@ -77,11 +80,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:
@@ -361,7 +366,7 @@ class HTTPClientFactory(protocol.ClientFactory):
# 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)
@@ -388,6 +393,7 @@ class HTTPClientFactory(protocol.ClientFactory):
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:
@@ -732,7 +738,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
@@ -1422,6 +1428,9 @@ class _AgentBase(object):
Issue a new request, given the endpoint and the path sent as part of
the request.
"""
+
+ method = _ensureValidMethod(method)
+
# Create minimal headers, if necessary:
if headers is None:
headers = Headers()
@@ -1646,6 +1655,7 @@ class Agent(_AgentBase):
@see: L{twisted.web.iweb.IAgent.request}
"""
+ uri = _ensureValidURI(uri.strip())
parsedURI = URI.fromBytes(uri)
try:
endpoint = self._getEndpoint(parsedURI)
@@ -1679,6 +1689,8 @@ class ProxyAgent(_AgentBase):
"""
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)
diff --git a/src/twisted/web/newsfragments/9647.bugfix b/src/twisted/web/newsfragments/9647.bugfix
new file mode 100644
index 0000000..b76916c
--- /dev/null
+++ b/src/twisted/web/newsfragments/9647.bugfix
@@ -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
diff --git a/src/twisted/web/test/injectionhelpers.py b/src/twisted/web/test/injectionhelpers.py
new file mode 100644
index 0000000..ffeb862
--- /dev/null
+++ b/src/twisted/web/test/injectionhelpers.py
@@ -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")
diff --git a/src/twisted/web/test/test_agent.py b/src/twisted/web/test/test_agent.py
index 7a7669b..9b57512 100644
--- a/src/twisted/web/test/test_agent.py
+++ b/src/twisted/web/test/test_agent.py
@@ -11,7 +11,7 @@ from io import BytesIO
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
@@ -50,6 +50,10 @@ from twisted.internet.endpoints import HostnameEndpoint
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
@@ -886,6 +890,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
@@ -1307,6 +1312,48 @@ class AgentTests(TestCase, FakeReactorAndConnectMixin, AgentTestsMixin,
+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):
"""
@@ -3105,3 +3152,100 @@ class ReadBodyTests(TestCase):
warnings = self.flushWarnings()
self.assertEqual(len(warnings), 0)
+
+
+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())
diff --git a/src/twisted/web/test/test_webclient.py b/src/twisted/web/test/test_webclient.py
index 41cff54..680e027 100644
--- a/src/twisted/web/test/test_webclient.py
+++ b/src/twisted/web/test/test_webclient.py
@@ -7,6 +7,7 @@ Tests for the old L{twisted.web.client} APIs, C{getPage} and friends.
from __future__ import division, absolute_import
+import io
import os
from errno import ENOSPC
@@ -20,7 +21,8 @@ from twisted.trial import unittest, util
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 import test
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 @@ class DeprecationTests(unittest.TestCase):
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)

1449
CVE-2022-39348.patch Normal file

File diff suppressed because it is too large Load Diff

192
CVE-2023-46137.patch Normal file
View File

@ -0,0 +1,192 @@
From 8d500550fdee4c55e3158f8d8c293c2dc1587869 Mon Sep 17 00:00:00 2001
From: starlet-dx <15929766099@163.com>
Date: Fri, 29 Dec 2023 15:36:52 +0800
Subject: [PATCH 1/1] 11976 stop processing pipelined HTTP/1.1 requests that are received together #11979
Origin:
https://github.com/twisted/twisted/commit/1e6e9d23cac59689760558dcb6634285e694b04c
---
src/twisted/web/http.py | 32 +++++++--
src/twisted/web/newsfragments/11976.bugfix | 7 ++
src/twisted/web/test/test_web.py | 81 +++++++++++++++++++++-
3 files changed, 114 insertions(+), 6 deletions(-)
create mode 100644 src/twisted/web/newsfragments/11976.bugfix
diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
index b80a55a..23f8817 100644
--- a/src/twisted/web/http.py
+++ b/src/twisted/web/http.py
@@ -2443,14 +2443,38 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
self._handlingRequest = True
+ # We go into raw mode here even though we will be receiving lines next
+ # in the protocol; however, this data will be buffered and then passed
+ # back to line mode in the setLineMode call in requestDone.
+ self.setRawMode()
+
req = self.requests[-1]
req.requestReceived(command, path, version)
- def dataReceived(self, data):
+ def rawDataReceived(self, data: bytes) -> None:
"""
- Data was received from the network. Process it.
+ This is called when this HTTP/1.1 parser is in raw mode rather than
+ line mode.
+
+ It may be in raw mode for one of two reasons:
+
+ 1. All the headers of a request have been received and this
+ L{HTTPChannel} is currently receiving its body.
+
+ 2. The full content of a request has been received and is currently
+ being processed asynchronously, and this L{HTTPChannel} is
+ buffering the data of all subsequent requests to be parsed
+ later.
+
+ In the second state, the data will be played back later.
+
+ @note: This isn't really a public API, and should be invoked only by
+ L{LineReceiver}'s line parsing logic. If you wish to drive an
+ L{HTTPChannel} from a custom data source, call C{dataReceived} on
+ it directly.
+
+ @see: L{LineReceive.rawDataReceived}
"""
- # If we're currently handling a request, buffer this data.
if self._handlingRequest:
self._dataBuffer.append(data)
if (
@@ -2462,9 +2486,7 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
# ready. See docstring for _optimisticEagerReadSize above.
self._networkProducer.pauseProducing()
return
- return basic.LineReceiver.dataReceived(self, data)
- def rawDataReceived(self, data):
self.resetTimeout()
try:
diff --git a/src/twisted/web/newsfragments/11976.bugfix b/src/twisted/web/newsfragments/11976.bugfix
new file mode 100644
index 0000000..8ac292b
--- /dev/null
+++ b/src/twisted/web/newsfragments/11976.bugfix
@@ -0,0 +1,7 @@
+In Twisted 16.3.0, we changed twisted.web to stop dispatching HTTP/1.1
+pipelined requests to application code. There was a bug in this change which
+still allowed clients which could send multiple full HTTP requests in a single
+TCP segment to trigger asynchronous processing of later requests, which could
+lead to out-of-order responses. This has now been corrected and twisted.web
+should never process a pipelined request over HTTP/1.1 until the previous
+request has fully completed.
diff --git a/src/twisted/web/test/test_web.py b/src/twisted/web/test/test_web.py
index 3eb35a9..b2b2ad7 100644
--- a/src/twisted/web/test/test_web.py
+++ b/src/twisted/web/test/test_web.py
@@ -8,6 +8,7 @@ Tests for various parts of L{twisted.web}.
import os
import zlib
from io import BytesIO
+from typing import List
from zope.interface import implementer
from zope.interface.verify import verifyObject
@@ -17,10 +18,13 @@ from twisted.internet.address import IPv4Address, IPv6Address
from twisted.internet.task import Clock
from twisted.logger import LogLevel, globalLogPublisher
from twisted.python import failure, reflect
+from twisted.python.compat import iterbytes
from twisted.python.filepath import FilePath
-from twisted.test.proto_helpers import EventLoggingObserver
+from twisted.test.proto_helpers import EventLoggingObserver, StringTransport
from twisted.trial import unittest
from twisted.web import error, http, iweb, resource, server
+from twisted.web.resource import Resource
+from twisted.web.server import NOT_DONE_YET, Request, Site
from twisted.web.static import Data
from twisted.web.test.requesthelper import DummyChannel, DummyRequest
from ._util import assertIsFilesystemTemporary
@@ -1849,3 +1853,78 @@ class ExplicitHTTPFactoryReactor(unittest.TestCase):
factory = http.HTTPFactory()
self.assertIs(factory.reactor, reactor)
+
+
+class QueueResource(Resource):
+ """
+ Add all requests to an internal queue,
+ without responding to the requests.
+ You can access the requests from the queue and handle their response.
+ """
+
+ isLeaf = True
+
+ def __init__(self) -> None:
+ super().__init__()
+ self.dispatchedRequests: List[Request] = []
+
+ def render_GET(self, request: Request) -> int:
+ self.dispatchedRequests.append(request)
+ return NOT_DONE_YET
+
+
+class TestRFC9112Section932(unittest.TestCase):
+ """
+ Verify that HTTP/1.1 request ordering is preserved.
+ """
+
+ def test_multipleRequestsInOneSegment(self) -> None:
+ """
+ Twisted MUST NOT respond to a second HTTP/1.1 request while the first
+ is still pending.
+ """
+ qr = QueueResource()
+ site = Site(qr)
+ proto = site.buildProtocol(None)
+ serverTransport = StringTransport()
+ proto.makeConnection(serverTransport)
+ proto.dataReceived(
+ b"GET /first HTTP/1.1\r\nHost: a\r\n\r\n"
+ b"GET /second HTTP/1.1\r\nHost: a\r\n\r\n"
+ )
+ # The TCP data contains 2 requests,
+ # but only 1 request was dispatched,
+ # as the first request was not yet finalized.
+ self.assertEqual(len(qr.dispatchedRequests), 1)
+ # The first request is finalized and the
+ # second request is dispatched right away.
+ qr.dispatchedRequests[0].finish()
+ self.assertEqual(len(qr.dispatchedRequests), 2)
+
+ def test_multipleRequestsInDifferentSegments(self) -> None:
+ """
+ Twisted MUST NOT respond to a second HTTP/1.1 request while the first
+ is still pending, even if the second request is received in a separate
+ TCP package.
+ """
+ qr = QueueResource()
+ site = Site(qr)
+ proto = site.buildProtocol(None)
+ serverTransport = StringTransport()
+ proto.makeConnection(serverTransport)
+ raw_data = (
+ b"GET /first HTTP/1.1\r\nHost: a\r\n\r\n"
+ b"GET /second HTTP/1.1\r\nHost: a\r\n\r\n"
+ )
+ # Just go byte by byte for the extreme case in which each byte is
+ # received in a separate TCP package.
+ for chunk in iterbytes(raw_data):
+ proto.dataReceived(chunk)
+ # The TCP data contains 2 requests,
+ # but only 1 request was dispatched,
+ # as the first request was not yet finalized.
+ self.assertEqual(len(qr.dispatchedRequests), 1)
+ # The first request is finalized and the
+ # second request is dispatched right away.
+ qr.dispatchedRequests[0].finish()
+ self.assertEqual(len(qr.dispatchedRequests), 2)
--
2.30.0

Binary file not shown.

View File

@ -1,13 +1,16 @@
%define debug_package %{nil}
Name: python-twisted
Version: 18.9.0
Release: 4
Version: 22.4.0
Release: 3
Summary: An event-driven networking engine written in Python
License: MIT
URL: http://twistedmatrix.com/
Source0: https://files.pythonhosted.org/packages/source/T/Twisted/Twisted-%{version}.tar.bz2
Source0: https://github.com/twisted/twisted/archive/twisted-%{version}/twisted-%{version}.tar.gz
# https://github.com/twisted/twisted/commit/f2f5e81c03f14e253e85fe457e646130780db40b
Patch0: CVE-2022-39348.patch
# https://github.com/twisted/twisted/commit/1e6e9d23cac59689760558dcb6634285e694b04c
Patch1: CVE-2023-46137.patch
# https://github.com/twisted/twisted/commit/6c61fc4503ae39ab8ecee52d10f10ee2c371d7e2
Patch0000: CVE-2019-12387.patch
%description
Twisted is an event-based framework for internet applications,
@ -73,15 +76,11 @@ BuildArch: noarch
The python-twisted-help package contains related documents.
%prep
%autosetup -n Twisted-%{version} -p1
%autosetup -n twisted-twisted-%{version} -p1
%build
%py3_build
PYTHONPATH=${PWD}/src/ sphinx-build-3 -a docs html
rm -rf html/.doctrees
rm -rf html/.buildinfo
%install
%py3_install
mv %{buildroot}%{_bindir}/trial %{buildroot}%{_bindir}/trial-%{python3_version}
@ -90,29 +89,44 @@ ln -s ./trial-%{python3_version} %{buildroot}%{_bindir}/trial-3
ln -s ./twistd-%{python3_version} %{buildroot}%{_bindir}/twistd-3
ln -s ./trial-%{python3_version} %{buildroot}%{_bindir}/trial
ln -s ./twistd-%{python3_version} %{buildroot}%{_bindir}/twistd
chmod +x %{buildroot}%{python3_sitearch}/twisted/mail/test/pop3testserver.py
chmod +x %{buildroot}%{python3_sitearch}/twisted/trial/test/scripttest.py
chmod +x %{buildroot}%{python3_sitelib}/twisted/mail/test/pop3testserver.py
chmod +x %{buildroot}%{python3_sitelib}/twisted/trial/test/scripttest.py
pathfix.py -pn -i %{__python3} %{buildroot}%{python3_sitearch}
pathfix.py -pn -i %{__python3} %{buildroot}%{python3_sitelib}
install -d %{buildroot}%{_mandir}/man1/
cp -a docs/conch/man/*.1 %{buildroot}%{_mandir}/man1/
cp -a docs/core/man/*.1 %{buildroot}%{_mandir}/man1/
cp -a docs/mail/man/*.1 %{buildroot}%{_mandir}/man1/
%check
PATH=%{buildroot}%{_bindir}:$PATH PYTHONPATH=%{buildroot}%{python3_sitearch} %{buildroot}%{_bindir}/trial-3 twisted ||:
PATH=%{buildroot}%{_bindir}:$PATH PYTHONPATH=%{buildroot}%{python3_sitelib} %{buildroot}%{_bindir}/trial-3 twisted ||:
%files -n python3-twisted
%doc CONTRIBUTING NEWS.rst README.rst html LICENSE
%doc NEWS.rst README.rst LICENSE
%{_bindir}/{trial-3*,twistd-3*}
%{python3_sitearch}/twisted
%{python3_sitearch}/Twisted-%{version}-py?.?.egg-info
%{python3_sitelib}/twisted
%{python3_sitelib}/Twisted-%{version}-py%{python3_version}.egg-info
%{_bindir}/{cftp,ckeygen,conch,mailmail,pyhtmlizer,tkconch,trial,twist,twistd}
%files help
%{_mandir}/man1/{cftp.1*,ckeygen.1*,conch.1*,mailmail.1*,pyhtmlizer.1*,tkconch.1*,trial.1*,twistd.1*}
%changelog
* Fri Dec 29 2023 yaoxin <yao_xin001@hoperun.com> - 22.4.0-3
- Fix CVE-2023-46137
* Tue Dec 12 2023 yaoxin <yao_xin001@hoperun.com> - 22.4.0-2
- Fix CVE-2022-39348
* Thu Oct 27 2022 yaoxin <yaoxin30@h-partners.com> - 22.4.0-1
- upgrade version to 22.4.0
* Tue Aug 17 2021 liwu <liwu13@huawei.com> - 18.9.0-6
- fix CVE-2019-12855
* Mon Aug 16 2021 liwu <liwu13@huawei.com> - 18.9.0-5
- fix CVE-2020-10109 CVE-2020-10108
* Mon Oct 12 2020 zhangjiapeng <zhangjiapeng9@huawei.com> - 18.9.0-4
- Drop python2 support

BIN
twisted-22.4.0.tar.gz Normal file

Binary file not shown.