Issues in Parsing HTTP Request "Host" Header
Describe the bug
Aiohttp does not strictly validate the Host header in the HTTP request, rejects redundant Host headers but does not reject missing Host header.
To Reproduce
- Run aiohttp.
- Send the following requests.
POST / HTTP/1.1\r\n
Host: victim1.com\r\n
Host: victim2.com\r\n
\r\n
In this case, aiohttp will reject:
$ echo -ne "POST / HTTP/1.1\r\nHost: victim1.com\r\nHost: victim2.com\r\n\r\n" | nc 172.18.0.8 80
HTTP/1.0 400 Bad Request
Content-Type: text/plain; charset=utf-8
Content-Length: 30
Date: Wed, 19 Mar 2025 14:02:00 GMT
Server: Python/3.10 aiohttp/4.0.0a2.dev0
Duplicate 'Host' header found.
But if the Host header is missing:
POST / HTTP/1.1\r\n
Content-Length: 0\r\n
\r\n
Aiohttp accepted with 200 OK:
$ echo -ne "POST / HTTP/1.1\r\nContent-Length: 0\r\n\r\n" | nc 172.18.0.8 80
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 105
Date: Wed, 19 Mar 2025 14:08:55 GMT
Server: Python/3.10 aiohttp/4.0.0a2.dev0
{"headers":[["Q29udGVudC1MZW5ndGg=","MA=="]],"body":"","method":"UE9TVA==","uri":"Lw==","version":"MS4x"}
Expected behavior
Both cases should be rejected by the HTTP server with 400 (Bad Request), as RFC 9112 says:
A server MUST respond with a
400 (Bad Request)status code to any HTTP/1.1 request message that lacks aHostheader field and to any request message that contains more than oneHostheader field line or aHostheader field with an invalid field value.
Logs/tracebacks
/
Python Version
$ python --version
Python/3.10
aiohttp Version
$ python -m pip show aiohttp
aiohttp/4.0.0a2.dev0
multidict Version
$ python -m pip show multidict
/
propcache Version
$ python -m pip show propcache
/
yarl Version
$ python -m pip show yarl
/
OS
Ubuntu 11.4.0-1ubuntu1~22.04
Related component
Server
Additional context
I simply sent the requests above to the server directly.
Code of Conduct
- [x] I agree to follow the aio-libs Code of Conduct
Feel free to create a PR. We probably should reject it, but realistically, this will already have been handled by a reverse proxy or similar, otherwise it'd have no idea where to send the request.
If this is changed to match the python parser, should that go
- in llhttp (like the
header_value_content_length_oncecounter) or - in the pyx extra validation (like the
Sec-WebSocket-Key1refusal)?
I guess the latter, as other HTTP parser usage might permit this (or at least disregard equivalent duplicates).
Might be worth opening an issue with llhttp, they might be interested in it. They have strictness flags that could allow users to disable the check if needed.