Skip to content

Commit db560cf

Browse files
Reject null bytes in headers (aio-libs#12210) (aio-libs#12214)
(cherry picked from commit bad4131) Co-authored-by: vmfunc <celeste@linux.com>
1 parent d5cd872 commit db560cf

5 files changed

Lines changed: 35 additions & 9 deletions

File tree

aiohttp/_http_parser.pyx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,13 @@ cdef class HttpParser:
384384
name = find_header(self._raw_name)
385385
value = self._raw_value.decode('utf-8', 'surrogateescape')
386386

387+
# reject null bytes in header values - matches the Python parser
388+
# check at http_parser.py. llhttp in lenient mode doesn't reject
389+
# these itself, so we need to catch them here.
390+
# ref: RFC 9110 section 5.5 (CTL chars forbidden in field values)
391+
if "\x00" in value:
392+
raise InvalidHeader(self._raw_value)
393+
387394
self._headers.append((name, value))
388395
if len(self._headers) > self._max_headers:
389396
raise BadHttpMessage("Too many headers received")

aiohttp/_http_writer.pyx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,9 @@ cdef inline int _write_str_raise_on_nlcr(Writer* writer, object s):
111111
out_str = str(s)
112112

113113
for ch in out_str:
114-
if ch == 0x0D or ch == 0x0A:
114+
if ch in {0x0D, 0x0A, 0x00}:
115115
raise ValueError(
116-
"Newline or carriage return detected in headers. "
116+
"Newline, carriage return, or null byte detected in headers. "
117117
"Potential header injection attack."
118118
)
119119
if _write_utf8(writer, ch) < 0:

aiohttp/http_writer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,9 @@ async def drain(self) -> None:
352352

353353

354354
def _safe_header(string: str) -> str:
355-
if "\r" in string or "\n" in string:
355+
if "\r" in string or "\n" in string or "\x00" in string:
356356
raise ValueError(
357-
"Newline or carriage return detected in headers. "
357+
"Newline, carriage return, or null byte detected in headers. "
358358
"Potential header injection attack."
359359
)
360360
return string

tests/test_http_parser.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1197,7 +1197,14 @@ def test_http_response_parser_strict_headers(response) -> None:
11971197
response.feed_data(b"HTTP/1.1 200 test\r\nFoo: abc\x01def\r\n\r\n")
11981198

11991199

1200-
def test_http_response_parser_bad_crlf(response) -> None:
1200+
def test_http_response_parser_null_byte_in_header_value(
1201+
response: HttpResponseParser,
1202+
) -> None:
1203+
with pytest.raises(http_exceptions.InvalidHeader):
1204+
response.feed_data(b"HTTP/1.1 200 OK\r\nFoo: abc\x00def\r\n\r\n")
1205+
1206+
1207+
def test_http_response_parser_bad_crlf(response: HttpResponseParser) -> None:
12011208
"""Still a lot of dodgy servers sending bad requests like this."""
12021209
messages, upgrade, tail = response.feed_data(
12031210
b"HTTP/1.0 200 OK\nFoo: abc\nBar: def\n\nBODY\n"

tests/test_http_writer.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -996,10 +996,22 @@ def test_serialize_headers_raises_on_new_line_or_carriage_return(char: str) -> N
996996

997997
with pytest.raises(
998998
ValueError,
999-
match=(
1000-
"Newline or carriage return detected in headers. "
1001-
"Potential header injection attack."
1002-
),
999+
match="detected in headers",
1000+
):
1001+
_serialize_headers(status_line, headers)
1002+
1003+
1004+
def test_serialize_headers_raises_on_null_byte() -> None:
1005+
status_line = "HTTP/1.1 200 OK"
1006+
headers = CIMultiDict(
1007+
{
1008+
hdrs.CONTENT_TYPE: "text/plain\x00",
1009+
}
1010+
)
1011+
1012+
with pytest.raises(
1013+
ValueError,
1014+
match="null byte detected in headers",
10031015
):
10041016
_serialize_headers(status_line, headers)
10051017

0 commit comments

Comments
 (0)