BlackSheep ClientSession is vulnerable to CRLF injection
Description
BlackSheep is an asynchronous web framework to build event based web applications with Python. Prior to 2.4.6, the HTTP Client implementation in BlackSheep is vulnerable to CRLF injection. Missing headers validation makes it possible for an attacker to modify the HTTP requests (e.g. insert a new header) or even create a new HTTP request. Exploitation requires developers to pass unsanitized user input directly into headers.The server part is not affected because BlackSheep delegates to an underlying ASGI server handling of response headers. This vulnerability is fixed in 2.4.6.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
blacksheepPyPI | < 2.4.6 | 2.4.6 |
Affected products
1- Range: before-asgi, v0.2.7, v0.2.8, …
Patches
1bd4ecb9542b5Merge commit from fork
6 files changed · +76 −13
blacksheep/scribe.pxd+2 −0 modified@@ -27,6 +27,8 @@ cdef bint is_small_response(Response response) cdef bytes write_small_response(Response response) +cdef bytes _nocrlf(bytes value) + cdef void set_headers_for_content(Message message) cdef void set_headers_for_response_content(Response message)
blacksheep/scribe.py+21 −8 modified@@ -8,9 +8,19 @@ MAX_RESPONSE_CHUNK_SIZE = 61440 # 64kb +def _nocrlf(value: bytes) -> bytes: + """Sanitize the given value to prevent CRLF injection.""" + return value.replace(b"\r", b"").replace(b"\n", b"") + + # Header writing utilities def write_header(header): - return header[0] + b": " + header[1] + b"\r\n" + """ + This function writes a single HTTP header. It is used only by the HTTP Client part, + because the server relies on the ASGI server to handle headers. + """ + # Sanitize header name and value to prevent CRLF injection + return _nocrlf(header[0]) + b": " + _nocrlf(header[1]) + b"\r\n" def write_headers(headers): @@ -43,28 +53,31 @@ def _get_status_line(status_code: int): } -def get_status_line(status: int): +def get_status_line(status: int) -> bytes: return STATUS_LINES[status] -def write_request_method(request: Request): +def write_request_method(request: Request) -> bytes: + # RFC 7230: method must be a valid token + if not re.match(r'^[!#$%&\'*+\-.0-9A-Z^_`a-z|~]+$', request.method): + raise ValueError(f"Invalid HTTP method: {request.method!r}") return request.method.encode() -def write_request_uri(request: Request): +def write_request_uri(request: Request) -> bytes: url = request.url - p = url.path or b"/" + p = _nocrlf(url.path or b"/") if url.query: - return p + b"?" + url.query + return p + b"?" + _nocrlf(url.query) return p -def ensure_host_header(request: Request): +def ensure_host_header(request: Request) -> None: if request.url.host: request._add_header_if_missing(b"host", request.url.host) -def should_use_chunked_encoding(content: Content): +def should_use_chunked_encoding(content: Content) -> bool: return content.length < 0
blacksheep/scribe.pyx+17 −4 modified@@ -10,8 +10,18 @@ from .url cimport URL cdef int MAX_RESPONSE_CHUNK_SIZE = 61440 # 64kb +cdef bytes _nocrlf(bytes value): + """Sanitize the given value to prevent CRLF injection.""" + return value.replace(b"\r", b"").replace(b"\n", b"") + + cdef bytes write_header(tuple header): - return header[0] + b': ' + header[1] + b'\r\n' + """ + This function writes a single HTTP header. It is used only by the HTTP Client part, + because the server relies on the ASGI server to handle headers. + """ + # Sanitize header name and value to prevent CRLF injection + return _nocrlf(header[0]) + b": " + _nocrlf(header[1]) + b"\r\n" cdef bytes write_headers(list headers): @@ -48,15 +58,18 @@ cpdef bytes get_status_line(int status): cdef bytes write_request_method(Request request): - return request.method.encode() + # RFC 7230: method must be a valid token + if not re.match(r'^[!#$%&\'*+\-.0-9A-Z^_`a-z|~]+$', request.method): + raise ValueError(f"Invalid HTTP method: {request.method!r}") + return _nocrlf(request.method.encode()) cdef bytes write_request_uri(Request request): cdef bytes p cdef URL url = request.url - p = url.path or b'/' + p = _nocrlf(url.path or b'/') if url.query: - return p + b'?' + url.query + return p + b'?' + _nocrlf(url.query) return p
CHANGELOG.md+3 −1 modified@@ -5,8 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2.4.6] - 2025-12-?? +## [2.4.6] - 2026-01-13 +- Fix CRLF injection vulnerability in the BlackSheep HTTP Client, reported by Jinho Ju (@tr4ce-ju). +- Add a `SECURITY.md` file. - Fix [#646](https://github.com/Neoteroi/BlackSheep/issues/646). - Modify the `Cookie` `repr` to not include the value in full, as it can contain secrets that would leak in logs.
SECURITY.md+14 −0 added@@ -0,0 +1,14 @@ +# Security Policy + +## Supported Versions + +In general, due to limited maintainer bandwidth, only the latest version of +BlackSheep is supported with patch releases. Exceptions may be made depending +on the severity of the bug and the feasibility of backporting a fix to +older releases. + +## Reporting a Vulnerability + +BlackSheep uses GitHub's security advisory functionality for private vulnerability +reports. To make a private report, please use the "Report a vulnerability" button at +[https://github.com/Neoteroi/BlackSheep/security/advisories](https://github.com/Neoteroi/BlackSheep/security/advisories).
tests/test_requests.py+19 −0 modified@@ -651,3 +651,22 @@ async def content_gen(): def test_request_charset(content_type_header, expected_charset): request = Request("POST", b"/", [(b"Content-Type", content_type_header.encode())]) assert request.charset == expected_charset + + +def test_write_request_prevents_crlf_injection_in_headers(): + request = Request("GET", b"https://hello-world", [ + (b"X-Injection", b"Hello\nInjected: Bad"), + ]) + raw_bytes = write_small_request(request) + + assert b"X-Injection: HelloInjected: Bad\r\n" in raw_bytes + + +def test_write_request_prevents_crlf_injection_in_method(): + # Attempt to inject additional headers via CRLF in the HTTP method + request = Request("GET\r\nX-Injected: Bad\r\nAnother: Header", b"https://hello-world", []) + + with pytest.raises(ValueError) as exc_info: + write_small_request(request) + + assert "Invalid HTTP method" in str(exc_info.value)
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-6pw3-h7xf-x4gpghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-22779ghsaADVISORY
- github.com/Neoteroi/BlackSheep/commit/bd4ecb9542b5d52442276b5a6907931b90f38d12ghsax_refsource_MISCWEB
- github.com/Neoteroi/BlackSheep/releases/tag/v2.4.6ghsax_refsource_MISCWEB
- github.com/Neoteroi/BlackSheep/security/advisories/GHSA-6pw3-h7xf-x4gpghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.