http.server: BaseHTTPRequestHandler does not enforce RFC 7230 framing validation (3 defects, hardening for keep-alive)
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Python
- Star
- 77.2k
- Fork
- 35.9k
- Chỉ số merge pull request
- Chỉ số pull request đang chờ
Mô tả
Feature or enhancement
Summary
http.server.BaseHTTPRequestHandler does not enforce three RFC 7230
framing-validation rules. The module's docstring already states it is
not intended for production, and PSRT (via Stan Ulbrych and Seth
Larson, 2026-05-26) suggested filing these as a public RFC-compliance
improvement rather than as security issues, so this issue collects them
in one place for a single hardening PR. The three defects are
independently reproducible on main (commit 776573c) and on the
3.13 / 3.14 maintenance branches; each one is a single-rule deviation
that can be fixed in BaseHTTPRequestHandler without changing the
public API.
Defect A — duplicate Content-Length with conflicting values is accepted
RFC 7230 §3.3.3 rule 4 requires the recipient to reject a message that
has multiple Content-Length field-values that disagree (MUST close
the connection and respond 400 Bad Request). BaseHTTPRequestHandler
calls http.client.parse_headers and never inspects
headers.get_all("Content-Length"), so a request such as
POST / HTTP/1.1
Host: 127.0.0.1
Content-Length: 4
Content-Length: 0
ABCD
is dispatched to the handler. A handler that reads
int(self.headers.get("Content-Length")) silently sees only the first
value (4), while a different handler that takes get_all and picks
the last value sees 0. The behaviour is implementation-dependent
even within the standard library, which is the exact situation RFC
7230 §3.3.3 was written to prevent.
- file:
Lib/http/server.py - function:
BaseHTTPRequestHandler.parse_request - observed: request accepted; handler dispatched
- expected:
400 Bad Request+Connection: close
Defect B — Transfer-Encoding header is accepted but never decoded
RFC 7230 §3.3.3 rule 3 states that if a Transfer-Encoding header is
present, it overrides Content-Length and the recipient must dechunk
the body. BaseHTTPRequestHandler does not implement a chunked
decoder, but it also does not reject the header, so a request such as
POST / HTTP/1.1
Host: 127.0.0.1
Transfer-Encoding: chunked
Content-Length: 5
0
GET /pwn HTTP/1.1
Host: 127.0.0.1
is accepted and a handler that reads int(Content-Length) bytes
leaves the rest of the chunked frame (and the trailing request) in the
socket buffer, so the keep-alive loop parses GET /pwn HTTP/1.1 as a
new top-level request line. Per RFC 7230 §3.3.3 the correct response
is 400 Bad Request + close, because the server cannot honour the
Transfer-Encoding semantics it received.
- file:
Lib/http/server.py - function:
BaseHTTPRequestHandler.parse_request - observed: request accepted; handler dispatched; framing desynchronised
- expected:
400 Bad Request+Connection: close
Defect C — request body is not drained between requests on a persistent connection
RFC 7230 §6.3 requires the server to either consume the entire
request body or close the connection before reading the next request
on a persistent connection. BaseHTTPRequestHandler.handle_one_request
does neither: after do_<method>() returns, the next iteration of
handle() calls rfile.readline(65537) directly. A request such as
GET / HTTP/1.1
Host: 127.0.0.1
Content-Length: 100
AAAAAAAAAAAAAAAA...AAAGET /pwn HTTP/1.1
Host: 127.0.0.1
results in SimpleHTTPRequestHandler (which does not read the body of
a GET) leaving 100 bytes plus the smuggled request line in the
buffer. The keep-alive loop then reads
AAAA...AAAAGET /pwn HTTP/1.1 as a malformed request line and replies
501 Unsupported method ('AAAA...GET'), generated entirely from
attacker-controlled input.
- file:
Lib/http/server.py - function:
BaseHTTPRequestHandler.handle_one_request/handle - observed: leftover body parsed as next request line
- expected: leftover body drained (within a sane cap) or connection closed
Reachability
http.server is documented as not for production, but
BaseHTTPRequestHandler is the basis for wsgiref.simple_server,
which is used in many quick-start guides, development servers
(manage.py runserver style flows, Flask app.run in some
configurations, plenty of python -m http.server tutorials), CI
fixtures, internal admin tools and embedded devices. RFC 7230 framing
rules are the foundation of HTTP request-boundary integrity; honouring
them does not change semantics for any well-formed client and only
costs three header checks plus a bounded body drain.
Reproduction
Three self-contained reproductions (one per defect) are available on
request; each starts a ThreadingHTTPServer on 127.0.0.1, sends the
raw bytes shown above over socket.socket, and prints the observed
response.
Suggested fix
A single PR can address all three defects in BaseHTTPRequestHandler:
- In
parse_request, afterhttp.client.parse_headersreturns,
reject requests with conflictingContent-Lengthvalues (defect A). - In
parse_request, after the same point, reject any
Transfer-Encodingheader (defect B) — oncehttp.servergrows a
chunked decoder this check can be narrowed to "anything other than
identity". - In
handle_one_request, wrapself.rfilewith a small
byte-counting reader for the duration of the request, then after
do_<method>()returns drain up to a bounded number of bytes of
any unread declared body (defect C). If the declared body exceeds
the drain cap, close the connection instead.
All three checks are conservative: well-formed clients are unaffected.
Linked PR
A pull request implementing the fix and adding regression tests in
Lib/test/test_httpservers.py plus a Misc/NEWS.d entry is being
prepared.
Acknowledgement
Per PSRT (Stan Ulbrych, 2026-05-26 14:51 UTC and Seth Larson,
2026-05-26 21:55 UTC) these defects are out of scope for the security
process and were invited to be filed as a public issue plus PR.
Reported by tonghuaroot.
Linked PRs
- gh-150500
- gh-150508
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu với Lib/http/server.py, đặc biệt là BaseHTTPRequestHandler.parse_request và handle_one_request/handle, sau đó xem lại phần kiểm thử hồi quy liên quan trong Lib/test/test_httpservers.py. Sử dụng ba bản tái hiện trong issue để hiểu hành vi mong đợi khi trả về 400 rồi đóng kết nối hoặc khi xử lý body, và kiểm tra rằng các PRs được liên kết cùng mục trong Misc/NEWS.d phản ánh công việc đã hoàn tất.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- python
- Lĩnh vực
- backend, networking
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Đình trệ
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức phù hợp với người mới
- 25/100