python / python/cpython

http.server: BaseHTTPRequestHandler does not enforce RFC 7230 framing validation (3 defects, hardening for keep-alive)

Đang mở
#150,499 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

stdlib type-bug
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:

  1. In parse_request, after http.client.parse_headers returns,
    reject requests with conflicting Content-Length values (defect A).
  2. In parse_request, after the same point, reject any
    Transfer-Encoding header (defect B) — once http.server grows a
    chunked decoder this check can be narrowed to "anything other than
    identity".
  3. In handle_one_request, wrap self.rfile with 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

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. 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

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.