Open
Description
漏洞概述 / Summary
在 libhv 的 HTTP 反向代理实现中,HttpService::GetProxyUrl() 通过直接拼接配置的后端 URL 字符串与剥离前缀后的客户端请求路径来构造代理目标 URL:
url = iter->second + (path + iter->first.length());当反向代理后端 URL 配置为不含尾部 / 的 authority 形式(如 service.Proxy("/api/", "http://127.0.0.1:18081"))时,未认证攻击者可发送以 @ 开头的子路径请求(例如 GET /api/@169.254.169.254:80/latest/meta-data/)。拼接后的 URL 变为 http://127.0.0.1:18081@169.254.169.254:80/latest/meta-data/。随后 HUrl::parse() 将 @ 之前的 127.0.0.1:18081 解析为 URL 的 userinfo 字段,而将攻击者控制的 169.254.169.254:80 解析为真正的目标 host 与 port。
此外,HttpHandler::connectProxy() 仅在 forward_proxy(正向代理)模式下检查 service->IsTrustProxy(url.host.c_str()),对 reverse_proxy(反向代理)不做任何目标主机一致性校验,导致反向代理服务器直接向攻击者指定的任意内网或外部 host:port 发起 HTTP 请求并返回响应内容。
2. 漏洞成因与源码分析 / Root Cause & Vulnerable Code
std::string HttpService::GetProxyUrl(const char* path) {
std::string url;
for (auto iter = proxies.begin(); iter != proxies.end(); ++iter) {
if (hv_strstartswith(path, iter->first.c_str())) {
// 直接进行字符串拼接,未保证路径分隔符 '/',也未校验拼接后解析出的 host 是否改变
url = iter->second + (path + iter->first.length());
break;
}
}
return url;
}int HttpHandler::connectProxy(const std::string& raw_url) {
HUrl url;
if (!url.parse(raw_url)) {
return SendHttpStatusResponse(HTTP_STATUS_BAD_REQUEST);
}
if (forward_proxy && !service->IsTrustProxy(url.host.c_str())) { // 反向代理跳过主机校验
hlogw("[%s:%d] Forbidden to proxy %s", ip, port, url.host.c_str());
return SendHttpStatusResponse(HTTP_STATUS_FORBIDDEN);
}影响范围与危害评估 / Impact
确认影响:未认证远程攻击者可将 libhv 反向代理作为跳板,向云环境元数据服务、内网未授权 HTTP 管理服务或任意内部端口发起全回显 SSRF 请求。
修复建议 / Remediation
- 在
HttpService::Proxy()注册或GetProxyUrl()拼接时,若配置的目标 URL 路径为空且不以/结尾,自动补齐/;并确保拼接的子路径始终以/开头。 - 在
HttpService::GetProxyUrl()或HttpHandler::connectProxy()中解析拼接前后的HUrl,强制校验最终请求的url.host与url.port必须与反向代理配置的目标host和port完全一致。
Reacted by Copilot
Activity