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 のパーサーで callback を確認した後、未正規化の文字列をブラウザーへ渡しており、ブラウザーは別の意味に解釈する可能性がありました。[1]

主要な概念と専門用語

Native-app callback
デスクトップアプリがブラウザーでのログイン結果を受け取るために使う、一時的なローカル Web アドレス。[1], [4]
URL parser differential
二つの URL パーサーが同じ入力を異なる意味に解釈すること。一方の解釈を検証して別の解釈を使うと、セキュリティ境界が崩れます。[1], [3]
PKCE
PKCE は認可コードを、ログインを開始したアプリが保持する秘密の verifier に結び付けます。[1], [4]
Loopback address
127.0.0.1 や ::1 のように同じ端末へ戻る IP アドレス。Hermes の修正は正規形式の IP リテラルのみを許可し、任意のリモートホストは認めません。[3], [4]

フレームワーク対応

  • FlawOpenによるMITRE CWEへの概念上の対応付けです。ベンダーやCVEレコードがこの研究デモに弱点IDを割り当てたものではありません。 CWE-601

ステップ・バイ・ステップの攻撃フロー

ステップ 1

公開されたネイティブログイン経路が callback を受け取る

ユーザー認証の前に認可エンドポイントが redirect URI と PKCE challenge を受け取ります。[1]

ステップ 2

サーバーとブラウザーで callback の解釈が異なる

脆弱な検証処理は Python が解析した hostname を確認しながら、元の文字列を保持していました。曖昧な URL 構文によりブラウザーが別の authority へ移動することがあります。[1], [3]

ステップ 3

ログイン完了後に認可コードが漏れる可能性

被害者が通常のログインを完了すると、ブラウザーが解釈した authority にコードを送る場合があります。対応する PKCE challenge を選んだ攻撃者はコード交換を試みられます。被害者のログインが必要であり、無操作のアカウント乗っ取りではありません。[1]

ステップ 4

URI を正規化し、配布済みビルドを更新する

修正は曖昧な構文と非正規 authority を拒否し、正規 URI を返してその値を保存します。対応チャネルでは 0.21.7 以降へ更新してください。確認した 0.21.7 のリリースノートに Termux は記載されていないため、別途確認してください。[2], [5], [3]

ソースコード比較:脆弱 vs 堅牢化

これはセキュリティ境界を示す例であり、Hermes Agent の完全なソースコードや exploit ではありません。脆弱例は「解析後の host を確認して元入力を使う」流れを要約しています。修正版は、曖昧な入力を拒否し、正規の loopback authority のみ許可して正規 URI を返し、その値を保存・使用するという merged fix の性質を示します。実際の実装とテストはリンク先の 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 →