Closed
fix(cve): 修复 CVE-2026-115877 在 python-twisted 中的漏洞 #76
infra_teamAdd on 8 days agoClosed at 2 days ago
fix(cve): 修复 CVE-2026-115877 在 python-twisted 中的漏洞 #76
Closed
Total: 3 files changed+113-1
| @@ -0,0 +1,31 @@ | |||
| 1 | +From eea1eb546bde0987c038581a11dcbd33fe081b0b Mon Sep 17 00:00:00 2001 | ||
| 2 | +From: arkmarta <arkadiuszmarta@proton.me> | ||
| 3 | +Date: Fri, 31 Jul 2026 14:30:02 +0200 | ||
| 4 | +Subject: [PATCH 2/2] web: check a header value for NUL CR and LF in one pass | ||
| 5 | + | ||
| 6 | +Upstream-commit: https://github.com/twisted/twisted/commit/022b6f7feb5980166c71f54e2c0d8f4f21ff6e1a | ||
| 7 | +Signed-off-by: infra_team <zhaiwenjie1@huawei.com> | ||
| 8 | +--- | ||
| 9 | + src/twisted/web/http.py | 6 +++--- | ||
| 10 | + 1 file changed, 3 insertions(+), 3 deletions(-) | ||
| 11 | + | ||
| 12 | +diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py | ||
| 13 | +index ab9b0c4..2feefa7 100644 | ||
| 14 | +--- a/src/twisted/web/http.py | ||
| 15 | ++++ b/src/twisted/web/http.py | ||
| 16 | +@@ -2500,9 +2500,9 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): | ||
| 17 | + return False | ||
| 18 | + | ||
| 19 | + data = data.strip(b" \t") | ||
| 20 | +- # Reject NUL, CR, and LF in a header value (RFC 9110 section 5.5); a | ||
| 21 | +- # bare CR or LF would let a lenient proxy smuggle a following header. | ||
| 22 | +- if b"\x00" in data or b"\r" in data or b"\n" in data: | ||
| 23 | ++ # A header value must not contain NUL, CR, or LF (RFC 9110 section 5.5). | ||
| 24 | ++ # A bare CR or LF here would let a lenient proxy smuggle a header. | ||
| 25 | ++ if len(data.translate(None, b"\x00\r\n")) != len(data): | ||
| 26 | + self._respondToBadRequestAndDisconnect() | ||
| 27 | + return False | ||
| 28 | + | ||
| 29 | +-- | ||
| 30 | +2.39.5 | ||
| 31 | + | ||
| @@ -0,0 +1,72 @@ | |||
| 1 | +From c110fd151be7ad0aedef21fce85f8051c54afa0f Mon Sep 17 00:00:00 2001 | ||
| 2 | +From: arkmarta <arkadiuszmarta@proton.me> | ||
| 3 | +Date: Fri, 31 Jul 2026 14:04:59 +0200 | ||
| 4 | +Subject: [PATCH 1/2] Reject a bare CR or LF in an HTTP header value (#12754) | ||
| 5 | + | ||
| 6 | +Upstream-commit: https://github.com/twisted/twisted/commit/1616f702d36eda8990a5a5b22ad70fab140a7eb1 | ||
| 7 | +Signed-off-by: infra_team <zhaiwenjie1@huawei.com> | ||
| 8 | +--- | ||
| 9 | + src/twisted/web/http.py | 4 +++- | ||
| 10 | + src/twisted/web/newsfragments/12754.bugfix | 1 + | ||
| 11 | + src/twisted/web/test/test_http.py | 22 ++++++++++++++++++++++ | ||
| 12 | + 3 files changed, 26 insertions(+), 1 deletion(-) | ||
| 13 | + create mode 100644 src/twisted/web/newsfragments/12754.bugfix | ||
| 14 | + | ||
| 15 | +diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py | ||
| 16 | +index 29ac7db..ab9b0c4 100644 | ||
| 17 | +--- a/src/twisted/web/http.py | ||
| 18 | ++++ b/src/twisted/web/http.py | ||
| 19 | +@@ -2500,7 +2500,9 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin): | ||
| 20 | + return False | ||
| 21 | + | ||
| 22 | + data = data.strip(b" \t") | ||
| 23 | +- if b"\x00" in data: | ||
| 24 | ++ # Reject NUL, CR, and LF in a header value (RFC 9110 section 5.5); a | ||
| 25 | ++ # bare CR or LF would let a lenient proxy smuggle a following header. | ||
| 26 | ++ if b"\x00" in data or b"\r" in data or b"\n" in data: | ||
| 27 | + self._respondToBadRequestAndDisconnect() | ||
| 28 | + return False | ||
| 29 | + | ||
| 30 | +diff --git a/src/twisted/web/newsfragments/12754.bugfix b/src/twisted/web/newsfragments/12754.bugfix | ||
| 31 | +new file mode 100644 | ||
| 32 | +index 0000000..9d42c46 | ||
| 33 | +--- /dev/null | ||
| 34 | ++++ b/src/twisted/web/newsfragments/12754.bugfix | ||
| 35 | +@@ -0,0 +1 @@ | ||
| 36 | ++twisted.web.http.HTTPChannel.headerReceived now rejects a header value that contains a bare carriage return or line feed, closing a request-smuggling gap where such a value could hide a following header from the parser (RFC 9110 section 5.5). | ||
| 37 | +diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py | ||
| 38 | +index a670095..cc17336 100644 | ||
| 39 | +--- a/src/twisted/web/test/test_http.py | ||
| 40 | ++++ b/src/twisted/web/test/test_http.py | ||
| 41 | +@@ -2211,6 +2211,28 @@ class ParsingTests(unittest.TestCase): | ||
| 42 | + [b"GET / HTTP/1.1", b"Host: foo.example", header, b"", b""] | ||
| 43 | + ) | ||
| 44 | + | ||
| 45 | ++ def test_invalidHeaderValueCRLF(self): | ||
| 46 | ++ """ | ||
| 47 | ++ A header value containing a bare CR or LF is rejected with a 400, as | ||
| 48 | ++ required by RFC 9110 section 5.5; accepting it would let a following | ||
| 49 | ++ header be hidden from the parser and enable request smuggling. The bytes | ||
| 50 | ++ are fed directly because L{runRequest} normalises a bare LF to CRLF. | ||
| 51 | ++ """ | ||
| 52 | ++ for value in [ | ||
| 53 | ++ b"a\nContent-Length: 5", # bare LF hiding a header | ||
| 54 | ++ b"a\rContent-Length: 5", # bare CR hiding a header | ||
| 55 | ++ b"a\nb", # bare LF | ||
| 56 | ++ b"a\rb", # bare CR | ||
| 57 | ++ ]: | ||
| 58 | ++ channel = http.HTTPChannel() | ||
| 59 | ++ transport = StringTransport() | ||
| 60 | ++ channel.makeConnection(transport) | ||
| 61 | ++ channel.dataReceived( | ||
| 62 | ++ b"GET / HTTP/1.1\r\nHost: foo.example\r\nx-foo: " + value + b"\r\n\r\n" | ||
| 63 | ++ ) | ||
| 64 | ++ self.assertTrue(transport.disconnecting) | ||
| 65 | ++ self.assertEqual(transport.value(), b"HTTP/1.1 400 Bad Request\r\n\r\n") | ||
| 66 | ++ | ||
| 67 | + def test_headerLimitPerRequest(self): | ||
| 68 | + """ | ||
| 69 | + C{HTTPChannel} enforces the limit of C{HTTPChannel.maxHeaders} per | ||
| 70 | +-- | ||
| 71 | +2.39.5 | ||
| 72 | + | ||
| @@ -1,12 +1,14 @@ | |||
| 1 | %define debug_package %{nil} | 1 | %define debug_package %{nil} |
| 2 | Name: python-twisted | 2 | Name: python-twisted |
| 3 | Version: 26.4.0 | 3 | Version: 26.4.0 |
| 4 | -Release: 2 | 4 | +Release: 3 |
| 5 | Summary: An event-driven networking engine written in Python | 5 | Summary: An event-driven networking engine written in Python |
| 6 | License: MIT | 6 | License: MIT |
| 7 | URL: http://twistedmatrix.com/ | 7 | URL: http://twistedmatrix.com/ |
| 8 | Source0: https://github.com/twisted/twisted/archive/refs/tags/twisted-26.4.0.tar.gz | 8 | Source0: https://github.com/twisted/twisted/archive/refs/tags/twisted-26.4.0.tar.gz |
| 9 | Patch0: backport-CVE-2026-106454.patch | 9 | Patch0: backport-CVE-2026-106454.patch |
| 10 | +Patch1: backport-CVE-2026-115877-1616f702.patch | ||
| 11 | +Patch2: backport-CVE-2026-115877-022b6f7f.patch | ||
| 10 | BuildRequires: python3-pip python3-hatchling python3-hatch-vcs python3-wheel | 12 | BuildRequires: python3-pip python3-hatchling python3-hatch-vcs python3-wheel |
| 11 | BuildRequires: python3-hatch-fancy-pypi-readme python3-incremental | 13 | BuildRequires: python3-hatch-fancy-pypi-readme python3-incremental |
| 12 | 14 | ||
| @@ -111,6 +113,13 @@ PATH=%{buildroot}%{_bindir}:$PATH PYTHONPATH=%{buildroot}%{python3_sitelib} %{bu | |||
| 111 | %{_mandir}/man1/{cftp.1*,ckeygen.1*,conch.1*,mailmail.1*,pyhtmlizer.1*,tkconch.1*,trial.1*,twistd.1*} | 113 | %{_mandir}/man1/{cftp.1*,ckeygen.1*,conch.1*,mailmail.1*,pyhtmlizer.1*,tkconch.1*,trial.1*,twistd.1*} |
| 112 | 114 | ||
| 113 | %changelog | 115 | %changelog |
| 116 | +* Sat Oct 31 2026 infra_team <zhaiwenjie1@huawei.com> - 26.4.0-3 | ||
| 117 | +- fix CVE: CVE-2026-115877 | ||
| 118 | +- Backport upstream fix to reject header values containing a bare CR or LF | ||
| 119 | + in twisted.web.http.HTTPChannel.headerReceived, closing a request-smuggling | ||
| 120 | + gap where such a value could hide a following header from the parser | ||
| 121 | + (RFC 9110 section 5.5) | ||
| 122 | + | ||
| 114 | * Wed Oct 14 2026 infra_team <zhaiwenjie1@huawei.com> - 26.4.0-2 | 123 | * Wed Oct 14 2026 infra_team <zhaiwenjie1@huawei.com> - 26.4.0-2 |
| 115 | - fix CVE: CVE-2026-106454 | 124 | - fix CVE: CVE-2026-106454 |
| 116 | - Backport upstream fix to escape IMAP wildcards and metacharacters | 125 | - Backport upstream fix to escape IMAP wildcards and metacharacters |