分配的 CVE ID: CVE-2026-67183
TinyWeb 在解析每个请求时会分配若干对象,但从不释放它们。请求和头部结构没有析构函数,响应发送后也没有任何代码删除它们。工作进程的内存随每个请求增长且从不下降,因此持续的普通请求流会使工作进程内存耗尽。
TnyWeb/0.0.8e48f15d(2018-11-20),即这些结构和解析器引入之时,直至 a381da2(2023-11-22,master 分支最新提交)。CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N未认证的远程攻击者,能够访问服务器监听的 TCP 端口(随附配置中为 9090),并以适度的持续速率发送请求。无需任何凭据或用户交互。请求可以是格式良好的;不需要特殊载荷。
对于每个请求,HttpParser::execute() 会分配一个 Url、一个 HttpHeaders,以及每个头行一个 HttpHeader:
// src/tiny_http/http_parser.cc:1692
request->url = new Url;
// src/tiny_http/http_parser.cc:1709
request->headers = new HttpHeaders;
// src/tiny_http/http_parser.cc:1033-1040
HttpHeader* header = new HttpHeader;
return_val = parseHeader(stream, offset, len, header);
...
result->generals.push_back(header); // stored in a raw-pointer list
HttpRequest 和 HttpHeaders 是没有析构函数的普通结构体,因此销毁 HttpRequest 不会释放其中任何一个:
// src/tiny_http/http_parser.h:442
typedef struct HttpRequest {
...
Url* url;
HttpHeaders* headers;
HttpBody* body;
} HttpRequest;
// src/tiny_http/http_parser.h:279-304
typedef struct HttpHeaders {
...
std::list<HttpHeader*> generals; // raw pointers, never deleted
...
} HttpHeaders;
WebProtocol 通过 std::shared_ptr<HttpRequest>(src/tiny_http/http_protocol.h:49)持有请求。当下一个请求替换它时,默认的 ~HttpRequest 运行并泄漏 url、headers、body 以及 generals 中的每个 HttpHeader。代码中唯一的 delete request 位于 getDataFromProxy()(src/tiny_http/http_protocol.cc:194),而该函数在请求路径中永远不会被调用。来自内存池的每请求响应缓冲区进一步加剧了增长。
使用随附配置启动服务器(监听 9090 端口)。记下某个工作进程的进程 ID。
记录工作进程的常驻内存:
grep VmRSS /proc/<worker_pid>/status
for i in $(seq 1 2000); do
curl -s -o /dev/null http://TARGET:9090/
done
VmRSS。它已经增长,并且之后不会回落。一次代表性运行:VmRSS before: 3704 kB
after 500 requests: 4788 kB
after 1000 requests: 22068 kB
after 1500 requests: 41652 kB
after 2000 requests: 59956 kB
增长是单调的,每个请求约 20 至 28 kB,且没有平台期。重复该批次会继续攀升,直到工作进程因内存耗尽而被终止。
未认证攻击者可以通过持续的请求流耗尽工作进程的内存,并强制触发内存不足状态,从而拒绝服务。正常流量也会随时间泄漏,因此这种情况也可能在没有攻击者的情况下发生。
为 HttpRequest 和 HttpHeaders 提供析构函数,释放 url、headers、body 以及 generals 中的每个 HttpHeader,或者用智能指针持有它们以便自动释放。此外,在响应发送完成后,释放每请求的内存池缓冲区。