FlawOpen

flawopen.com/AI Security/hermes-agent-native-signin-redirect-parser-confusion

● FlawOpen 映射: CWE-601 · CVSS v4 8.6[1] · 高危
安全研究 · FlawOpen

Hermes Agent 原生登录重定向解析缺陷

Tenable 披露了 Hermes Agent 原生登录中的高危重定向验证缺陷。0.21.6 之前的版本受影响。0.21.6 的修复已提供给 Docker/Hermes Cloud 和 CLI/git 安装;10 月 11 日发布的 0.21.7 还列出了 Desktop 应用、Microsoft Store/MSIX、安装程序、Docker 和 Hermes Cloud。查阅的发布说明没有提到 Termux,请单独核实该渠道。Tenable 将其评为 High(CVSS 4.0 8.6);未分配 CVE。CWE-601 是针对重定向后果的编辑分类。[1], [2], [5]

💡 通俗易懂的原理解析 (ELI5)

想象前台用一张地图核对地址,却把原始地址照抄后交给使用另一张地图的快递员。快递员可能去往前台未批准的地点。Hermes 使用 Python 解析器检查回调地址,却把未经规范化的原字符串交给浏览器;浏览器可能有不同解释。[1]

核心概念与专有名词

Native-app callback
桌面应用用来接收浏览器登录结果的临时本地 Web 地址。[1], [4]
URL parser differential
两个 URL 解析器对同一输入得出不同含义;验证一种解释后再使用另一种解释会破坏安全边界。[1], [3]
PKCE
PKCE 将授权码绑定到由发起登录的应用保存的秘密验证值。[1], [4]
Loopback address
回环 IP 地址会返回本机,例如 127.0.0.1 或 ::1。Hermes 修复只接受规范的 IP 字面量,不接受任意远程主机。[3], [4]

框架映射

  • FlawOpen 对 MITRE CWE 的概念性映射;没有厂商或 CVE 记录将此弱点编号分配给本研究演示。 CWE-601

攻击执行流程分解

步骤 1

公开的原生登录路由接收回调地址

用户完成身份验证之前,授权端点会接收重定向 URI 和 PKCE challenge。[1]

步骤 2

服务器和浏览器对回调地址的解释不同

有缺陷的验证器检查 Python 解析出的 hostname,却保留原始字符串。含糊的 URL 语法可能让浏览器访问另一个 authority。[1], [3]

步骤 3

用户完成登录后授权码可能泄露

受害者完成正常登录后,浏览器可能把授权码发送到它解析出的 authority。持有相应 PKCE challenge 的攻击者可能尝试兑换该码。需要受害者登录;这不是无需交互的账户接管。[1]

步骤 4

规范化 URI 并更新已发布渠道

修复会拒绝含糊语法和非规范 authority,返回规范 URI,并保存该值。请通过受支持的渠道升级到 0.21.7 或更高版本;发布说明未列出 Termux,请单独核实该渠道。[2], [5], [3]

源代码对比:漏洞与安全实现

这是安全边界的示意模型,并非 Hermes Agent 完整源码或利用代码。易受影响示例概括了“检查解析后的主机、随后使用原始输入”的模式。加固示例展示已合并修复的安全属性:拒绝含糊输入,只允许规范的 loopback authority,返回规范 URI,并持久化/使用该值。实际实现和测试见链接 PR。[1], [3]

存在漏洞的实现
from urllib.parse import urlparse

def validate_callback(raw):
    parsed = urlparse(raw)
    # 原始流程检查了解析后的主机名,却继续保留并使用原始 URL。
    if parsed.scheme == "http" and parsed.hostname in {"127.0.0.1", "::1"}:
        return raw
    raise ValueError("callback must be loopback")
加固后的安全修复
from urllib.parse import urlparse, urlunparse

LOOPBACK_HOSTS = {"127.0.0.1": "127.0.0.1", "::1": "[::1]"}

def validate_callback(raw):
    if not raw.isascii() or "\\" in raw or any(ord(c) <= 0x20 or ord(c) == 0x7f for c in raw):
        raise ValueError("callback must use canonical loopback syntax")
    try:
        parsed = urlparse(raw)
        host, port = parsed.hostname, parsed.port
    except ValueError as exc:
        raise ValueError("invalid callback") from exc
    if parsed.scheme != "http" or "#" in raw or parsed.username is not None or parsed.password is not None:
        raise ValueError("callback must be a plain loopback URL")
    authority_host = LOOPBACK_HOSTS.get(host)
    if authority_host is None:
        raise ValueError("callback host must be a loopback IP literal")
    authority = authority_host + (f":{port}" if port is not None else "")
    if parsed.netloc != authority:
        raise ValueError("callback authority is not canonical")
    return urlunparse(parsed._replace(path=parsed.path or "/"))

工程与系统安全加固清单

参考来源

← AI Security Directory →