Feb 19, 2026·6 min read·9 visits
BlackSheep's HTTP Client failed to sanitize user input before writing it to the network socket. By injecting '\r\n' sequences, attackers can break out of header fields and forge new requests. Fixed in version 2.4.6.
A classic CRLF injection vulnerability (CWE-113) resurrected in the modern asynchronous BlackSheep framework. Specifically affecting the HTTP Client implementation, this flaw allows attackers to inject arbitrary HTTP headers or split requests entirely by manipulating unsanitized input used in connection serialization. This creates a vector for HTTP Request Smuggling and SSRF escalation.
There is something almost poetic about finding a vulnerability as ancient as CRLF Injection in a framework designed to be the bleeding edge of modern Python. BlackSheep is an asynchronous web framework known for its speed, heavily utilizing Cython to squeeze every microsecond of latency out of the request-response cycle. It's the kind of tool you use when you want your API to scream.
But speed has a price. When you are writing low-level serialization logic to avoid the overhead of safer, slower libraries, you often strip away the guardrails. You stop asking, "Is this input safe?" and start asking, "How fast can I put these bytes on the wire?" In the case of CVE-2026-22779, the answer was: extremely fast, and completely unchecked.
The vulnerability doesn't lie in the server component—which delegates to an ASGI server like Uvicorn—but in ClientSession, the built-in HTTP client. This means if you are using BlackSheep to build a proxy, a webhook dispatcher, or a microservice that talks to other microservices based on user input, you just handed your attackers a loaded gun. They aren't just manipulating data; they are rewriting the HTTP protocol stream itself.
At its core, HTTP is a text-based protocol. It relies on specific character sequences—specifically the Carriage Return (\r, 0x0D) and Line Feed (\n, 0x0A)—to delimit headers and body content. If a library treats these control characters as just another part of a string, the protocol breaks. The browser or server on the receiving end can't tell the difference between a header the developer wrote and a header the attacker injected.
The flaw in BlackSheep is located in the blacksheep.scribe module. This module is the scribe (pun intended) responsible for taking a high-level Request object and translating it into the raw bytes sent over the TCP socket.
Prior to version 2.4.6, the scribe logic performed what we call "naive concatenation." It took the method, the path, and the headers, and glued them together with bytes. It assumed that the Request object was immutable or trusted. But in the real world, developers constantly shove dirty user input into User-Agent strings, custom X-Trace-ID headers, or dynamic URL paths. BlackSheep accepted this input verbatim, including the deadly \r\n sequence.
Let's look at the actual code. The vulnerability exists in the Cython implementation (blacksheep/scribe.pyx) and its pure Python fallback. Here is a simplified view of how headers were being written before the patch.
# pseudo-code of the vulnerable logic in blacksheep/scribe.py
def write_header(header_name: bytes, header_value: bytes):
# Direct concatenation of name + separator + value + CRLF
return header_name + b": " + header_value + b"\r\n"There is zero validation here. If header_value is b"innocent\r\nInjected: malicious", the output is:
Header-Name: innocent
Injected: maliciousThe patch introduced by Roberto Prevato is simple but effective. It introduces a helper, _nocrlf, and enforces regex validation on the HTTP method.
# The sanitizer
def _nocrlf(value: bytes) -> bytes:
if b"\r" in value or b"\n" in value:
return value.replace(b"\r", b"").replace(b"\n", b"")
return value
# The patched write_header
def write_header(header_name: bytes, header_value: bytes):
# Now stripping control characters before writing
return header_name + b": " + _nocrlf(header_value) + b"\r\n"They also added a strict regex check for the HTTP method, ensuring you can't inject garbage into the verb itself (e.g., GET / HTTP/1.1\r\n... as a method name).
if not re.match(r'^[!#$%&\'*+\-.0-9A-Z^_`a-z|~]+$', request.method):
raise ValueError(f"Invalid HTTP method: {request.method!r}")To exploit this, we need a scenario where the application takes input from us and uses it in an outgoing request. A classic example is a "Webhooks" feature where the user can specify a custom X-Source-ID to track their requests.
The vulnerable code might look like this:
async def trigger_webhook(user_id, target_url):
# user_id comes from a database, originally set by the user
async with ClientSession() as client:
# Vulnerable header injection
await client.post(target_url, headers=[(b"X-User-ID", user_id.encode())])If the attacker sets their user_id to:
bob\r\nContent-Length: 0\r\n\r\nPOST /admin/delete_user HTTP/1.1\r\nHost: internal-api\r\nX-Ignore:
BlackSheep will serialize this into a single TCP stream that looks like two separate requests to the upstream server:
POST /webhook HTTP/1.1
Host: target.com
X-User-ID: bob
Content-Length: 0
POST /admin/delete_user HTTP/1.1
Host: internal-api
X-Ignore:
...The first request (POST /webhook) is terminated early by the injected Content-Length: 0. The upstream server (e.g., a reverse proxy or the destination API) processes it, then sees the leftovers in the buffer as a second, completely valid request.
We have effectively forged a request to /admin/delete_user that appears to come from the trusted internal backend logic. This is HTTP Request Splitting, and it bypasses internal authentication checks, WAFs, and logic barriers.
You might think, "CVSS 5.3? Medium? I'll patch it next month." That would be a mistake. The CVSS score here is misleadingly low because it assumes a generic configuration. In specific architectural patterns—like microservices or gateway architectures—this is a critical flaw.
This is not just an "Input Validation Error." It is a breach of the contract between the application and the network protocol.
The remediation is straightforward, but mandatory.
Option 1: Upgrade (Recommended)
Upgrade blacksheep to version 2.4.6 or higher immediately. This version includes the _nocrlf sanitizer and the method regex validator.
pip install blacksheep>=2.4.6Option 2: Monkey Patching (Desperate Measures)
If you are stuck on an older version for legacy reasons, you must manually sanitize every single variable that touches ClientSession. This is error-prone and not recommended. You are better off monkey-patching the scribe module yourself, but seriously, just upgrade.
> [!NOTE] > Remember: Input sanitization is defense-in-depth. Even with the patch, you should generally avoid putting unchecked user input directly into HTTP headers. It's just bad hygiene.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
BlackSheep Neoteroi | < 2.4.6 | 2.4.6 |
| Attribute | Detail |
|---|---|
| CWE | CWE-113 (Improper Neutralization of CRLF Sequences) |
| CVSS v3.1 | 5.3 (Medium) |
| Attack Vector | Network (Input via HTTP) |
| Impact | HTTP Request Splitting / Header Injection |
| Exploit Status | PoC Available (in tests) |
| Affected Component | blacksheep.scribe (ClientSession) |
The software includes data in an HTTP response header, but it does not neutralize or incorrectly neutralizes CR and LF characters, allowing the header to be split or new headers to be injected.