diff --git a/docs/security/libhttp_SECURITY_AUDIT_20260803.md b/docs/security/libhttp_SECURITY_AUDIT_20260803.md index 6868107..706b7d1 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 | 3 | 0 | 3 | +| P1 | 6 | 4 | 0 | 2 | | P2 | 4 | 0 | 0 | 4 | -| **合计** | **10** | **5** | **0** | **5** | +| **合计** | **10** | **6** | **0** | **4** | --- @@ -23,7 +23,7 @@ |---|------|------|------| | 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.cpp` 新增 `WriteCtx`(流式回调 + 累计字节 + 上限),`write_callback` 超限返回 0 触发 `CURLE_WRITE_ERROR`,`request_stream_sync` 抛异常;`request_stream_sync` 改为**真流式**(callback 非空时每块即回调,不二次累积),`HttpClient` 新增 `set_max_response_size()`/`get_max_response_size()`(默认 10MB,对齐 WAF,0 表示不限制) | -| P1-3 | WebSocket 消息无大小限制 | ❌ 未修复 | `Session::onWsRead`(`HttpServer_WsSession.cpp` L88-111)把整条 WS 消息读入内存,依赖 Beast 默认 `read_message_max`(16MB)不可配置;超大消息可被恶意客户端推送导致 OOM | +| P1-3 | WebSocket 消息无大小限制 | ✅ 已修复 | `HttpServer` 新增 `max_ws_message_size_`(默认 16MB,与 Beast 默认一致)与 `setMaxWsMessageSize()`/`getMaxWsMessageSize()`。`doWsUpgrade`/`doWssUpgrade` 及代理流 `onProxyConnect`/`onProxySslHandshake` 创建后调用 `read_message_max()`,超限消息由 Beast 在分配内存前拒绝并关闭连接,防 OOM | | P1-4 | 服务器无读写超时(Slowloris) | ❌ 未修复 | 入站 `stream_`(`beast::tcp_stream`)**从未设置 `expires_after`**(仅代理后端设了超时)。攻击者建立连接后缓慢发送 HTTP 头,可无限占用连接与线程,Slowloris/连接耗尽(对应全局审计 P2-4) | | P1-5 | 静态文件目录路径遍历 | ✅ 已修复 | 新增 `isSafeStaticPath()`(`HttpServer_Request.cpp` L139-156),静态文件服务前校验 target_path(拒绝 `..`、空字节、非 `/` 开头、URL 编码 `%2e`/`%2f`/`%5c`),逻辑与 gateway `is_safe_target()` 对齐(`HttpServer.cpp` L94) | | P1-6 | 发送队列无界(慢消费者 OOM) | ❌ 未修复 | `ws_send_queue_`、`proxy_to_backend_queue_`、`proxy_to_client_queue_`(`HttpServer_WsSession.cpp` L8-16/L362-367/L438-443)均为无界 `std::deque`。若客户端消费慢,服务端持续入队可导致内存耗尽 | @@ -72,7 +72,7 @@ - [ ] P1-4 服务器读超时:入站 `stream_` 设置 `expires_after`(首字节/头+体超时),防 Slowloris ### 短期(72小时) -- [ ] P1-3 WS 消息大小限制:设置 `read_message_max`(可配置) +- [x] P1-3 WS 消息大小限制:设置 `read_message_max`(可配置)→ ✅ 已实现(`setMaxWsMessageSize()`,默认 16MB,服务端与代理流均生效) - [x] P1-5 静态文件路径遍历:新增 `isSafeStaticPath()` 拼接前校验 `target_path`,拒绝 `..`/空字节/编码遍历 → ✅ 已实现(`HttpServer_Request.cpp` L139-157) - [ ] P1-6 发送队列上限:`ws_send_queue_`/代理队列设最大值,超限断开慢消费者 - [ ] P2-1 CORS 白名单:origin 白名单校验,替代反射任意 Origin diff --git a/libhttp/README.md b/libhttp/README.md index 086e4cd..166caf3 100644 --- a/libhttp/README.md +++ b/libhttp/README.md @@ -13,6 +13,7 @@ C++20 network library for the UniServe microservice framework, providing unified - **Static Files**: `setStaticDirectory()` for serving static files - **Thread Pool**: Multi-threaded I/O with `asio::strand` for thread safety - **Client Management**: WebSocket client tracking, broadcast, and targeted send +- **WS Message Size Limit**: `setMaxWsMessageSize()` caps single WebSocket message (default 16MB, aligned with Beast; applies to server and proxy streams) to prevent OOM ### HttpClient - **HTTP Client**: Synchronous HTTP requests via libcurl (GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS) @@ -167,6 +168,10 @@ void use(MiddlewareHandler middleware); void setStaticDirectory(const std::string& path); ``` +// WebSocket 单条消息大小上限(字节)。默认 16MB,与 Beast 默认一致;超限由 Beast 拒绝并关闭连接(防 OOM)。 +void setMaxWsMessageSize(std::size_t bytes); +std::size_t getMaxWsMessageSize() const; + **WebSocket Routes** ```cpp void ws(const std::string& path, WebSocketMessageHandler handler); @@ -283,6 +288,7 @@ using WebSocketClientBase = HttpClient; ## Version History +- **v3.4.1**: WebSocket 单条消息大小限制(P1-3)。新增 `setMaxWsMessageSize()`/`getMaxWsMessageSize()`(默认 16MB,与 Beast 默认一致),服务端 `doWsUpgrade`/`doWssUpgrade` 与代理 `onProxyConnect`/`onProxySslHandshake` 创建流后调用 `read_message_max()`,超限消息由 Beast 在分配内存前拒绝并关闭连接,防 OOM。 - **v3.4.0**: HttpClient 响应大小限制(防 OOM)。`request_stream_sync` 改为真流式(按块回调,不二次缓冲);新增 `set_max_response_size()`/`get_max_response_size()`(默认 10MB,对齐 WAF,0 表示不限制),超限时 `write_callback` 返回 0 中止传输并抛异常。 - **v3.3.0**: Split HttpServer into 6 modular translation units by functional area (core/lifecycle, listener, routes, request dispatch, WS management, WS session/proxy) for dramatically faster incremental builds. Added internal detail header `inc/us/detail/HttpServerImpl.hpp` for shared definitions across TU boundaries. - **v3.2.0**: Split into two shared libraries — `libus_http.so` (server only, no curl) and `libus_httpclient.so` (HttpClient, depends on curl). HttpClient implementation moved to `src/HttpClient.cpp`; `HttpClient.hpp` is declaration-only. diff --git a/libhttp/inc/us/HttpServer.hpp b/libhttp/inc/us/HttpServer.hpp index f132896..de4bd8c 100644 --- a/libhttp/inc/us/HttpServer.hpp +++ b/libhttp/inc/us/HttpServer.hpp @@ -119,6 +119,11 @@ public: void setMaxBodySize(std::size_t bytes) { max_body_size_ = bytes; } std::size_t getMaxBodySize() const { return max_body_size_; } + // WebSocket 单条消息大小上限(字节)。默认 16MB,与 Beast 默认一致(防 OOM)。 + // 超限时 Beast 拒绝该消息并关闭连接(P1-3)。 + void setMaxWsMessageSize(std::size_t bytes) { max_ws_message_size_ = bytes; } + std::size_t getMaxWsMessageSize() const { return max_ws_message_size_; } + class debug { public: static void print_all_header_fields(const http::request& req); @@ -134,6 +139,7 @@ private: std::atomic running_{false}; std::size_t max_body_size_ = 10 * 1024 * 1024; // 默认 10MB,与 WAF 对齐 + std::size_t max_ws_message_size_ = 16 * 1024 * 1024; // 默认 16MB,与 Beast 默认一致 bool ssl_enabled_{false}; std::string cert_file_; diff --git a/libhttp/src/HttpServer_WsSession.cpp b/libhttp/src/HttpServer_WsSession.cpp index 25eb7c9..f49a11d 100644 --- a/libhttp/src/HttpServer_WsSession.cpp +++ b/libhttp/src/HttpServer_WsSession.cpp @@ -19,6 +19,8 @@ void HttpServer::Session::doWsUpgrade() { auto self = shared_from_this(); ws_stream_ = std::make_unique(stream_); ws_stream_->text(true); + // P1-3:限制单条 WS 消息大小,超限由 Beast 拒绝并关闭连接(防 OOM) + ws_stream_->read_message_max(server_->getMaxWsMessageSize()); ws_stream_->async_accept( request_, @@ -31,6 +33,8 @@ void HttpServer::Session::doWssUpgrade() { auto self = shared_from_this(); wss_stream_ = std::make_unique(*ssl_stream_); wss_stream_->text(true); + // P1-3:限制单条 WS 消息大小,超限由 Beast 拒绝并关闭连接(防 OOM) + wss_stream_->read_message_max(server_->getMaxWsMessageSize()); wss_stream_->async_accept( request_, @@ -261,6 +265,9 @@ void HttpServer::Session::onProxyConnect(beast::error_code ec, tcp::endpoint /*e proxy_ws_stream_ = std::make_unique(proxy_tcp_stream_->socket()); proxy_tcp_stream_->expires_never(); + // P1-3:限制代理方向单条 WS 消息大小(防 OOM) + proxy_ws_stream_->read_message_max(server_->getMaxWsMessageSize()); + proxy_ws_stream_->set_option(websocket::stream_base::decorator( [](websocket::request_type& req) { req.set(http::field::user_agent, "UniServe-WsProxy"); @@ -286,6 +293,9 @@ void HttpServer::Session::onProxySslHandshake(beast::error_code ec) { proxy_wss_stream_ = std::make_unique(*proxy_ssl_stream_); proxy_tcp_stream_->expires_never(); + // P1-3:限制代理方向单条 WS 消息大小(防 OOM) + proxy_wss_stream_->read_message_max(server_->getMaxWsMessageSize()); + proxy_wss_stream_->set_option(websocket::stream_base::decorator( [](websocket::request_type& req) { req.set(http::field::user_agent, "UniServe-WsProxy");