From d642262de69bf6fc8c3c16a53255feafbe96a8a2 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Tue, 7 Apr 2020 14:00:55 +0200 Subject: [PATCH] CVE-2020-10108 https://github.com/twisted/twisted/commit/4a7d22e490bb8ff836892cc99a1f54b85ccb0281 --- twisted/web/http.py | 69 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/twisted/web/http.py b/twisted/web/http.py index 390b79e..ee8f005 100644 --- a/twisted/web/http.py +++ b/twisted/web/http.py @@ -1564,6 +1564,51 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): self.setLineMode(data) + def _maybeChooseTransferDecoder(self, header, data): + """ + If the provided header is C{content-length} or + C{transfer-encoding}, choose the appropriate decoder if any. + Returns L{True} if the request can proceed and L{False} if not. + """ + + def fail(): + self._respondToBadRequestAndDisconnect() + self.length = None + + # Can this header determine the length? + if header == b'content-length': + try: + length = int(data) + except ValueError: + fail() + return False + newTransferDecoder = _IdentityTransferDecoder( + length, self.requests[-1].handleContentChunk, self._finishRequestBody) + elif header == b'transfer-encoding': + # XXX Rather poorly tested code block, apparently only exercised by + # test_chunkedEncoding + if data.lower() == b'chunked': + length = None + newTransferDecoder = _ChunkedTransferDecoder( + self.requests[-1].handleContentChunk, self._finishRequestBody) + elif data.lower() == b'identity': + return True + else: + fail() + return False + else: + # It's not a length related header, so exit + return True + + if self._transferDecoder is not None: + fail() + return False + else: + self.length = length + self._transferDecoder = newTransferDecoder + return True + + def headerReceived(self, line): """ Do pre-processing (for content-length) and store this header away. @@ -1576,14 +1621,10 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): header, data = line.split(':', 1) header = header.lower() data = data.strip() - if header == 'content-length': - self.length = int(data) - self._transferDecoder = _IdentityTransferDecoder( - self.length, self.requests[-1].handleContentChunk, self._finishRequestBody) - elif header == 'transfer-encoding' and data.lower() == 'chunked': - self.length = None - self._transferDecoder = _ChunkedTransferDecoder( - self.requests[-1].handleContentChunk, self._finishRequestBody) + + if not self._maybeChooseTransferDecoder(header, data): + return False + reqHeaders = self.requests[-1].requestHeaders values = reqHeaders.getRawHeaders(header) if values is not None: @@ -1707,6 +1748,18 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): for request in self.requests: request.connectionLost(reason) + def _respondToBadRequestAndDisconnect(self): + """ + This is a quick and dirty way of responding to bad requests. + As described by HTTP standard we should be patient and accept the + whole request from the client before sending a polite bad request + response, even in the case when clients send tons of data. + @param transport: Transport handling connection to the client. + @type transport: L{interfaces.ITransport} + """ + self.transport.write(b"HTTP/1.1 400 Bad Request\r\n\r\n") + self.transport.loseConnection() + class HTTPFactory(protocol.ServerFactory): """ -- 2.25.2