From fb485ebac4c732104cf43ba887262f22464ebc89 Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Tue, 4 Aug 2026 00:04:55 +0800 Subject: [PATCH] =?UTF-8?q?fix(libhttp):=20=E4=BF=AE=E5=A4=8DP1-5=E9=9D=99?= =?UTF-8?q?=E6=80=81=E6=96=87=E4=BB=B6=E8=B7=AF=E5=BE=84=E9=81=8D=E5=8E=86?= =?UTF-8?q?=20+=20=E5=90=8C=E6=AD=A5=E5=AE=A1=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 isSafeStaticPath() 校验静态文件路径,拒绝 ..、空字节、非/开头及URL编码遍历(%2e/%2f/%5c) - handleRequest 静态文件服务前校验 target_path - 审计文档标记 P1-5 已修复 --- .../libhttp_SECURITY_AUDIT_20260803.md | 8 +++---- libhttp/inc/us/HttpServer.hpp | 1 + libhttp/src/HttpServer_Request.cpp | 22 ++++++++++++++++++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/docs/security/libhttp_SECURITY_AUDIT_20260803.md b/docs/security/libhttp_SECURITY_AUDIT_20260803.md index 22e02d5..999caed 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 | 1 | 0 | 5 | +| P1 | 6 | 2 | 0 | 4 | | P2 | 4 | 0 | 0 | 4 | -| **合计** | **10** | **3** | **0** | **7** | +| **合计** | **10** | **4** | **0** | **6** | --- @@ -25,7 +25,7 @@ | 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) | -| P1-5 | 静态文件目录路径遍历 | ❌ 未修复 | `handleRequest`(`HttpServer_Request.cpp` L94-119)直接 `static_directory_ + target_path` 拼接文件路径,未校验 `..`/空字节/编码遍历。设置 `setStaticDirectory` 的服务可被 `GET /../../etc/passwd` 越权读取 | +| 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`。若客户端消费慢,服务端持续入队可导致内存耗尽 | --- @@ -73,7 +73,7 @@ ### 短期(72小时) - [ ] P1-3 WS 消息大小限制:设置 `read_message_max`(可配置) -- [ ] P1-5 静态文件路径遍历:拼接前校验 `target_path` 拒绝 `..`/空字节/编码遍历 +- [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/inc/us/HttpServer.hpp b/libhttp/inc/us/HttpServer.hpp index ef1ae47..f132896 100644 --- a/libhttp/inc/us/HttpServer.hpp +++ b/libhttp/inc/us/HttpServer.hpp @@ -167,6 +167,7 @@ private: std::mutex sessions_mutex_; std::unordered_map parseQueryParams(const std::string& query); + bool isSafeStaticPath(const std::string& path) const; std::regex wildcardToRegex(const std::string& pattern); bool matchPath(const std::string& path, const std::regex& pattern); http::response handleRequest(const http::request& req); diff --git a/libhttp/src/HttpServer_Request.cpp b/libhttp/src/HttpServer_Request.cpp index 2aeb3a7..dfbbab2 100644 --- a/libhttp/src/HttpServer_Request.cpp +++ b/libhttp/src/HttpServer_Request.cpp @@ -1,4 +1,5 @@ #include "us/detail/HttpServerImpl.hpp" +#include namespace pmc { namespace net { @@ -91,7 +92,7 @@ http::response HttpServer::handleRequest(const http::request< auto handler = findRoute(method, target_path); - if (!handler && !static_directory_.empty()) { + if (!handler && !static_directory_.empty() && isSafeStaticPath(target_path)) { std::string file_path = static_directory_ + target_path; std::ifstream file(file_path, std::ios::binary); if (file.is_open()) { @@ -136,6 +137,25 @@ http::response HttpServer::handleRequest(const http::request< return res; } +// ---- 静态文件路径安全校验(P1-5)---- +bool HttpServer::isSafeStaticPath(const std::string& path) const { + // 防御:路径遍历、空字节、绝对 URI 形式、URL 编码遍历 + if (path.empty()) return false; + if (path[0] != '/') return false; // 拒绝 "http://..."/相对形式 + if (path.find('\0') != std::string::npos) return false; // 拒绝空字节截断 + if (path.find("..") != std::string::npos) return false; // 拒绝明文 ".." + + // 拒绝 URL 编码遍历/斜杠变体(%2e/./%2f/%5c) + std::string lower; + lower.reserve(path.size()); + for (char c : path) + lower.push_back(static_cast(std::tolower(static_cast(c)))); + if (lower.find("%2e") != std::string::npos) return false; + if (lower.find("%2f") != std::string::npos) return false; + if (lower.find("%5c") != std::string::npos) return false; + return true; +} + // ---- Session HTTP 处理 ---- void HttpServer::Session::setRemoteInfo(const std::string& ip, unsigned short port) { remote_ip_ = ip;