flawopen.com/AI Security/wazuh-mcp-server-webshell-check-ssrf-ghsa-pw2j-pj4h-f5vg
●
CWE-918 · 高危
安全研究 · FlawOpen
Wazuh MCP Webshell 检查工具的 SSRF 漏洞 — GHSA-pw2j-pj4h-f5vg
取证工具访问主机名之前,该主机名绕过了 Wazuh-MCP-Server 对字面 IP 的检查。GHSA 标为高危并关联 CWE-918;尚未分配 CVE。修复提交为 2bbfe12。[1], [2], [3]
💡 通俗易懂的原理解析 (ELI5)
前台先检查访客证件是否在黑名单上,再让访客选择办公室。只检查印在证件上的号码,无法发现这个名字实际指向受限房间。修复会检查名称解析到哪里,并让连接固定使用已检查的地址。[1], [3]
核心概念与专有名词
目标地址固定- 将已验证的 IP 绑定到出站连接,同时保留主机名用于 HTTP 和 TLS 校验,从而消除检查与连接之间的差异。[3]
框架映射
- 本指南所述弱点的 CWE 参考: CWE-918
- D3-OTF 出站流量过滤 — 本指南中的出站网络限制会限制服务或主机可访问的目标。
攻击执行流程分解
步骤 1
取证数据中的 URL 进入检查工具
受影响的 blueteam_check_webshell 工具接收 URL,供分析人员检查疑似托管 webshell 的地址。公告将问题限定在该工具,并未称 Wazuh-MCP-Server 的所有功能都受影响。[1]
步骤 2
步骤 3
源代码对比:漏洞与安全实现
有缺陷的片段复现了提交 6bd1fa8 中 WebshellCheckInput.validate_url 的相关逻辑;省略了 imports 和类字段。它会拒绝非公网字面 IP,但把主机名触发的 ValueError 当作域名并直接放行,不进行 DNS 解析。修复片段复现了修复提交 2bbfe12 中的 _pinned_curl_args。修复后的验证器还会调用 _host_pins;请求循环将这些 --resolve 参数传给 curl,并在每次跳转前验证重定向目标。只有显式配置的域名才允许访问内部地址。这些是聚焦的源码片段,并非完整 URL 请求流程的替代实现。[2], [3]
源码摘录: mcp_server/tools/webshell_check.py — 验证 6bd1fa8;修复 2bbfe12
存在漏洞的实现
@field_validator("url")
@classmethod
def validate_url(cls, v: str) -> str:
v = v.strip()
if not v.startswith(("http://", "https://")):
raise ValueError(f"URL must start with http:// or https://, got: {v!r}")
# 拒绝 URL 主机中的私有/保留 IP 地址。
from urllib.parse import urlparse
import ipaddress
host = urlparse(v).hostname
if host:
try:
ip = ipaddress.ip_address(host)
except ValueError:
ip = None # not an IP - domain name, allowed
if ip is not None and (ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local):
raise ValueError(f"URL host is a private/reserved IP: {host}. This tool only accepts public hosts.")
return v
加固后的安全修复
def _pinned_curl_args(url: str) -> tuple[list[str], str | None]:
"""Build curl --resolve args for an already-validated destination."""
import ipaddress
u = urlparse(url)
host = u.hostname
if not host:
return [], "URL has no host."
try:
ipaddress.ip_address(host)
return [], None # Literal IP: no DNS lookup to rebind.
except ValueError:
pass
port = u.port or (443 if u.scheme == "https" else 80)
ips, err = _host_pins(host, _allowed_internal_domains())
if err:
return [], err
args: list[str] = []
for ip in ips:
addr = f"[{ip}]" if ":" in ip else ip
args += ["--resolve", f"{host}:{port}:{addr}"]
return args, None
工程与系统安全加固清单
- ✓盘点包含受影响
blueteam_check_webshell实现的部署;公告给出提交版本,没有软件包版本范围。[1], [2] - ✓升级到
2bbfe12或包含修复的后续版本,并确认实际部署的源码已更新。[3] - ✓解析所有 A 和 AAAA 记录,拒绝任何非公网地址;仅在确有需要时配置范围严格的内部域名例外。[3]
- ✓将已验证 IP 绑定到实际连接,避免后续 DNS 查询改变目标地址。[3]
- ✓关闭自动重定向,或逐跳验证并固定每个重定向目标。[1], [3]
- ✓限制工具调用权限和服务器出站网络,并确认不可信内容是否能控制 URL 参数。[1]
- ✓在不访问真实内部服务的情况下测试混合 DNS 响应、IPv4、IPv6、内部例外、重定向和地址固定。[3]