From da4957c52df1744b75820df2638e5214b8ee1c90 Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Mon, 3 Aug 2026 23:57:00 +0800 Subject: [PATCH] =?UTF-8?q?fix(libhttp):=20=E8=AF=B7=E6=B1=82=E4=BD=93?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F=E9=99=90=E5=88=B6=E5=8F=AF=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=B9=B6=E8=B6=85=E9=99=90=E8=BF=94=E5=9B=9E=20413=20(P1-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../libhttp_SECURITY_AUDIT_20260803.md | 12 ++++---- libhttp/inc/us/HttpServer.hpp | 6 ++++ libhttp/inc/us/detail/HttpServerImpl.hpp | 2 ++ libhttp/src/HttpServer_Request.cpp | 29 ++++++++++++++----- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/docs/security/libhttp_SECURITY_AUDIT_20260803.md b/docs/security/libhttp_SECURITY_AUDIT_20260803.md index f5c2edc..22e02d5 100644 --- a/docs/security/libhttp_SECURITY_AUDIT_20260803.md +++ b/docs/security/libhttp_SECURITY_AUDIT_20260803.md @@ -11,9 +11,9 @@ | 优先级 | 总数 | 已修复 | 部分修复 | 未修复 | |--------|------|--------|----------|--------| | P0 | 0 | 0 | 0 | 0 | -| P1 | 6 | 0 | 0 | 6 | +| P1 | 6 | 1 | 0 | 5 | | P2 | 4 | 0 | 0 | 4 | -| **合计** | **10** | **2** | **0** | **8** | +| **合计** | **10** | **3** | **0** | **7** | --- @@ -21,7 +21,7 @@ | # | 问题 | 状态 | 说明 | |---|------|------|------| -| P1-1 | 请求体大小限制不可配置 + 超限无 413 | ❌ 未修复 | `Session::doRead`(`HttpServer_Request.cpp` L167-183)用 `http::async_read(stream, buffer_, request_, ...)` 直接把整个请求体读入 `http::request`。依赖 Beast 默认 `body_limit`(1MB,硬编码不可配置),与 WAF 的 10MB 不一致;**超限时 `http::error::body_limit` 走错误分支直接断开连接,不返回 413**(对应全局审计 P1-5) | +| P1-1 | 请求体大小限制不可配置 + 超限无 413 | ✅ 已修复 | `Session::doRead`(`HttpServer_Request.cpp` L167-186)改用 `http::request_parser` + `body_limit()`(默认 10MB,与 WAF 对齐);`HttpServer` 新增 `setMaxBodySize()`/`getMaxBodySize()`(`HttpServer.hpp` L118-120)。超限时 `handleRead` 返回 **413 Payload Too Large** 并关闭连接(L256-265),而非静默断连 | | P1-2 | HttpClient 响应无大小限制 | ❌ 未修复 | `HttpClient::write_callback`(`HttpClient.cpp` L329-334)把下游响应 `append` 进 `full_response` 无上限,超大响应可导致 OOM(`request_stream_sync` L63-81) | | P1-3 | WebSocket 消息无大小限制 | ❌ 未修复 | `Session::onWsRead`(`HttpServer_WsSession.cpp` L88-111)把整条 WS 消息读入内存,依赖 Beast 默认 `read_message_max`(16MB)不可配置;超大消息可被恶意客户端推送导致 OOM | | P1-4 | 服务器无读写超时(Slowloris) | ❌ 未修复 | 入站 `stream_`(`beast::tcp_stream`)**从未设置 `expires_after`**(仅代理后端设了超时)。攻击者建立连接后缓慢发送 HTTP 头,可无限占用连接与线程,Slowloris/连接耗尽(对应全局审计 P2-4) | @@ -67,7 +67,7 @@ ## 修复路线图 ### 立即(24小时) -- [ ] P1-1 请求体大小限制:`doRead` 改用 `http::request_parser` + `body_limit()`,`HttpServer` 增加 `setMaxBodySize()`(对齐 WAF 10MB),超限返回 413 而非断开 +- [x] P1-1 请求体大小限制:`doRead` 改用 `http::request_parser` + `body_limit()`,`HttpServer` 增加 `setMaxBodySize()`(对齐 WAF 10MB),超限返回 413 而非断开 → ✅ 已实现(默认 10MB,`setMaxBodySize()` 可覆盖) - [ ] P1-2 HttpClient 响应上限:`write_callback` 累计超过上限即失败,返回错误 - [ ] P1-4 服务器读超时:入站 `stream_` 设置 `expires_after`(首字节/头+体超时),防 Slowloris @@ -92,7 +92,9 @@ | 功能 | 文件 | 行号 | |------|------|------| -| 请求读入(无体限制) | `src/HttpServer_Request.cpp` | L167-183 | +| 请求读入(body_limit 可配置) | `src/HttpServer_Request.cpp` | L167-186 | +| 请求体超限返回 413 | `src/HttpServer_Request.cpp` | L256-265 | +| setMaxBodySize/getMaxBodySize | `inc/us/HttpServer.hpp` | L118-120 | | 请求处理/静态文件 | `src/HttpServer_Request.cpp` | L7-137, L94-119 | | CORS 反射 Origin | `src/HttpServer_Request.cpp` | L73-74 | | WS 读消息(无大小限制) | `src/HttpServer_WsSession.cpp` | L88-111 | diff --git a/libhttp/inc/us/HttpServer.hpp b/libhttp/inc/us/HttpServer.hpp index f2c07e4..ef1ae47 100644 --- a/libhttp/inc/us/HttpServer.hpp +++ b/libhttp/inc/us/HttpServer.hpp @@ -115,6 +115,10 @@ public: void setPrivateKeyPassword(const std::string& password); + // 请求体大小上限(字节)。默认 10MB,与 WAF 对齐;超限返回 413。 + void setMaxBodySize(std::size_t bytes) { max_body_size_ = bytes; } + std::size_t getMaxBodySize() const { return max_body_size_; } + class debug { public: static void print_all_header_fields(const http::request& req); @@ -129,6 +133,8 @@ private: unsigned int threads_; std::atomic running_{false}; + std::size_t max_body_size_ = 10 * 1024 * 1024; // 默认 10MB,与 WAF 对齐 + bool ssl_enabled_{false}; std::string cert_file_; std::string key_file_; diff --git a/libhttp/inc/us/detail/HttpServerImpl.hpp b/libhttp/inc/us/detail/HttpServerImpl.hpp index 16393b0..072a0a5 100644 --- a/libhttp/inc/us/detail/HttpServerImpl.hpp +++ b/libhttp/inc/us/detail/HttpServerImpl.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -112,6 +113,7 @@ private: beast::flat_buffer buffer_; beast::flat_buffer ws_buffer_; http::request request_; + std::optional> parser_; HttpServer* server_; bool ssl_enabled_; diff --git a/libhttp/src/HttpServer_Request.cpp b/libhttp/src/HttpServer_Request.cpp index f5e5aa7..2aeb3a7 100644 --- a/libhttp/src/HttpServer_Request.cpp +++ b/libhttp/src/HttpServer_Request.cpp @@ -169,16 +169,19 @@ void HttpServer::Session::doRead() { request_ = {}; buffer_.consume(buffer_.size()); + // 使用 request_parser 并设置可配置的请求体上限(默认 10MB,超限返回 413) + parser_.emplace(); + parser_->body_limit(server_->max_body_size_); + + auto on_read = [self](beast::error_code ec, std::size_t) { + if (!ec) self->request_ = self->parser_->release(); + self->handleRead(ec); + }; + if (ssl_enabled_ && ssl_stream_) { - http::async_read(*ssl_stream_, buffer_, request_, - [self](beast::error_code ec, std::size_t) { - self->handleRead(ec); - }); + http::async_read(*ssl_stream_, buffer_, *parser_, on_read); } else { - http::async_read(stream_, buffer_, request_, - [self](beast::error_code ec, std::size_t) { - self->handleRead(ec); - }); + http::async_read(stream_, buffer_, *parser_, on_read); } } @@ -250,6 +253,16 @@ void HttpServer::Session::handleRead(beast::error_code ec) { auto req_copy = std::move(request_); processRequest(std::move(req_copy)); + } else if (ec == http::error::body_limit) { + // 请求体超过上限 → 返回 413 并关闭连接 + auto resp = std::make_shared>(); + resp->version(11); // request_ 在超限时未成功解析,固定 HTTP/1.1 + resp->result(http::status::payload_too_large); + resp->set(http::field::content_type, "text/plain"); + resp->set(http::field::connection, "close"); + resp->body() = "413 Payload Too Large\n"; + resp->prepare_payload(); + doWrite(resp); } else if (ec != http::error::end_of_stream) { std::cerr << "[Read] Error from " << remote_ip_ << ":" << remote_port_ << ": " << ec.message() << std::endl;