Reject control characters in the request target to prevent request splitting - #13593
Reject control characters in the request target to prevent request splitting#13593h-t-m wants to merge 3 commits into
Conversation
…litting ClientRequest.send() builds the status line from url.raw_path_qs without validating it for CR/LF. When the target is a yarl.URL built with encoded=True (a documented public API), CR/LF bytes in the path are serialized verbatim into the request line, letting an attacker-influenced URL emit additional attacker-authored request lines on the connection (request splitting / smuggling, CWE-444). Mirror the CPython http.client check added for CVE-2019-9740 (_contains_disallowed_url_pchar_re, Lib/http/client.py) and extend the existing request-line component validation symmetrically: version is already type-constrained to HttpVersion (aio-libsgh-7835, CVE-2023-49081) and the method fix followed the same principle (CVE-2023-49082) - the request target was the only status-line component still unchecked. Reported via GHSA-pwcp-vr8x-fp2x (private advisory).
for more information, see https://pre-commit.ci
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Reviews (2): Last reviewed commit: "Fix test: use make_client_request fixtur..." | Re-trigger Greptile |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #13593 +/- ##
=======================================
Coverage 99.02% 99.02%
=======================================
Files 135 135
Lines 50500 50527 +27
Branches 2652 2653 +1
=======================================
+ Hits 50007 50034 +27
Misses 370 370
Partials 123 123
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
Merging this PR will not alter performance
Comparing Footnotes
|
The 'loop' fixture no longer exists on master, and ClientRequest.send() was renamed to _send(). Use the repo's current make_client_request fixture pattern (same as test_connection_header) and call _send().
|
Closing this — I misread "a public PR for this one" in the advisory as Thanks for the CI runs and bot reviews. h-t-m (@h-t-m) |
Reject control characters in the request target (request splitting)
Problem
ClientRequest.send()builds the status line fromself.url.raw_path_qswithout any validation:
When the request target is a
yarl.URLconstructed withencoded=True(a documented, supported public API) and its path contains CR/LF bytes,
those bytes are serialized verbatim into the request line. The server then
parses the trailing bytes as additional request lines — full
request splitting / smuggling controlled by whoever influences the URL
(CWE-444).
Dynamically reproduced on 3.8.6 and confirmed by source inspection that the
status-line construction remains unvalidated on
master(3.14.x line):client_reqrep.pystill assignspath = self.url.raw_path_qswith nonewline check, while header names/values ARE checked (
_safe_header).Why fix this in aiohttp
http.client.putrequesthas rejectedcontrol characters in the request target since 2019
(CVE-2019-9740,
_contains_disallowed_url_pchar_re,Lib/http/client.py:159): "Prevents CVE-2019-9740. Includes control
characters such as \r\n." The stdlib treats the client — not the URL
parser — as the enforcement point for request-line integrity.
were fixed at this exact level:
versionis now type-constrained toHttpVersion(1e86b77, CVE-2023-49081) and themethodfix followedthe same principle (CVE-2023-49082). The request target is the only
status-line component still concatenated without validation — this PR
closes that remaining gap symmetrically.
encoded=Trueis documented pass-through behavior ("the URL isassumed to be already encoded"), so it is not a security boundary we can
rely on.
Fix
Mirror the stdlib check: reject the request target when it contains control
characters (
[\x00-\x1f\x7f]), raisingValueErrorat send time. PlainstrURLs are unaffected (yarl re-quotes and strips CR/LF); onlypre-encoded paths carrying raw control bytes are rejected — precisely the
splitting vector.
Testing
test_request_target_with_crlf_raises: builds the encoded URL from thereport PoC and asserts
ValueErroron send.bytes after yarl re-quoting).
Related
follow up via PR — this is that PR).
encoded=Trueisdocumented pass-through behavior, and per the discussion the client-level
request-line integrity check (this PR) is the appropriate enforcement
point — the same conclusion the stdlib reached in 2019.