CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-22779
5.30.04%

BlackSheep, White Lies: Anatomy of a CRLF Injection in CVE-2026-22779

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 19, 2026·6 min read·9 visits

PoC Available

Executive Summary (TL;DR)

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.

The Hook: Fast, Async, and Dangerously Naive

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.

The Flaw: A Failure of Trust

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.

The Code: The Smoking Gun

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.

The Vulnerable Logic

# 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: malicious

The Fix (Commit bd4ecb9)

The 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}")

The Exploit: HTTP Request Splitting

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 Setup

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())])

The Attack

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 Result

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.

The Impact: Why You Should Care

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.

  1. SSRF Escalation: If the BlackSheep client is running inside a private VPC, Request Splitting allows an attacker to hit internal endpoints (like AWS metadata services or internal admin panels) that are normally unreachable.
  2. Cache Poisoning: If the vulnerable client sits behind a shared cache, an attacker can desynchronize the cache, forcing it to serve malicious content to other users.
  3. WAF Bypass: WAFs inspect the intended request. They rarely inspect the raw byte stream deep enough to realize that one request is actually two. By burying the malicious payload inside a header value, you cloak the attack until it is deserialized by the next hop.

This is not just an "Input Validation Error." It is a breach of the contract between the application and the network protocol.

The Fix: Upgrade or Die

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.6

Option 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.

Official Patches

NeoteroiBlackSheep v2.4.6 Release Notes

Fix Analysis (1)

Technical Appendix

CVSS Score
5.3/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
EPSS Probability
0.04%
Top 88% most exploited

Affected Systems

BlackSheep Web Framework (Python)Applications using BlackSheep.ClientSession

Affected Versions Detail

Product
Affected Versions
Fixed Version
BlackSheep
Neoteroi
< 2.4.62.4.6
AttributeDetail
CWECWE-113 (Improper Neutralization of CRLF Sequences)
CVSS v3.15.3 (Medium)
Attack VectorNetwork (Input via HTTP)
ImpactHTTP Request Splitting / Header Injection
Exploit StatusPoC Available (in tests)
Affected Componentblacksheep.scribe (ClientSession)

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1071.001Application Layer Protocol: Web Protocols
Command and Control
CWE-113
Improper Neutralization of CRLF Sequences in HTTP Headers

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.

Known Exploits & Detection

GitHubThe fix commit includes test cases demonstrating the injection technique.

Vulnerability Timeline

Vulnerability reported by Jinho Ju
2026-01-13
Fix committed by Roberto Prevato
2026-01-13
CVE-2026-22779 Published
2026-01-14

References & Sources

  • [1]GHSA-6pw3-h7xf-x4gp Advisory
  • [2]NVD CVE-2026-22779

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.