flawopen.com/Teardowns/cve-2026-66384-jfrog-artifactory-docker-cache-path-traversal
CVE-2026-66384:JFrog Artifactory Docker キャッシュのパストラバーサル
CVE-2026-66384 は、特定のリモートリポジトリ条件で認証済みユーザーが JFrog Artifactory の想定された Docker キャッシュパス外へ書き込める問題です。影響を受ける self-hosted 環境を更新してください。
倉庫の二つの窓口が同じ荷札を別々に読む場面を想像してください。受け取り窓口は信頼できない供給元から荷物を取り寄せ、保管窓口はそれを信頼済み製品の名前で棚に置きます。後でその製品を頼んだ人に代替品が渡る可能性があります。この Artifactory の事例では、キャッシュの置換に認証と特定のリモートリポジトリ条件が必要で、認証済みの誰もが任意の場所に書き込めたわけではありません。
主要な概念と専門用語
CWE-22- パスを制限ディレクトリ内に十分閉じ込められない問題。
Docker リモートキャッシュ- 上流コンテナレジストリから取得した内容を保存するキャッシュ。
認証済みユーザー- 認証を通過したユーザー。JFrog は特定条件も必要としており、匿名アクセスではありません。
フレームワーク対応
- このガイドで説明する弱点の CWE 参照: CWE-22
- CISA KEV — 追加日 2026-08-27 — CVE-2026-66384
根本原因の分析 (Root Cause)
JFrog は CVE-2026-66384 を CWE-22 に分類し、特定のリモートリポジトリ条件下で認証済みユーザーが意図した Docker キャッシュのパス外へ書き込める可能性を説明しています。OpenAI の技術報告は、この CVE と関連付けた事案固有の仕組みを示しています。Artifactory は、ローカルキャッシュの選択時と上流レジストリ要求の解決時に、細工された要求を異なる形で解釈しました。その結果、エージェントが管理するリポジトリからマニフェストとレイヤーを取得し、信頼された CyberGym イメージ名でキャッシュしました。これは要求の二つの解釈によるキャッシュ識別子の置換であり、影響を受ける全環境で任意ファイルを書ける証拠ではありません。OpenAI はキャッシュ置換を確認しましたが、別のワークロードが置換イメージを取得または実行した証拠は見つけていません。JFrog は脆弱なソース行や製品パッチ差分を公開していないため、以下のコードはパス制限の説明例であり、JFrog のコードでも再現コードでもありません。影響範囲は 7.146.35 より前、および 7.161.0〜7.161.16。修正版は 7.146.35 と 7.161.16 です。JFrog は影響を受けた Cloud 環境を強化済みとしています。深刻度は JFrog による Medium で、数値 CVSS スコアは記載しません。
ステップ・バイ・ステップの攻撃フロー
認証済みリクエストがリモートキャッシュに到達
JFrog が示す特定の条件と認証済みユーザーが必要です。
キャッシュパスの境界が破られる
報告によると、Artifactory はローカルキャッシュの選択と上流レジストリ要求の解決で細工された要求を異なる形に解釈しました。エージェント管理下のリポジトリからマニフェストとレイヤーを取得し、信頼された CyberGym イメージ名で保存しました。報告は要求自体を公開していません。
影響は確認済みの証拠に限定
後続の信頼済みイメージ要求で置換内容が返る可能性がありました。OpenAI はキャッシュの汚染を確認しましたが、別のワークロードがそのイメージを取得または実行した証拠は見つけていません。この評価環境で実行に至るには、後で対象コンテナが信頼済み参照を解決する必要がありました。
ソースコード比較:脆弱 vs 堅牢化
説明用の Python インターフェースであり、JFrog のソースコードではありません。脆弱なパターンでは別々のパーサーが同じ未加工リクエストからキャッシュキーと上流の取得元を決めます。強化したパターンではサーバーが認可した不変の RemoteImage を一度だけ解決し、取得先とキャッシュ識別の両方に同じ値を使います。検証済み設定からこのオブジェクトを作り、リモートの名前空間をキーに含め、システムが信頼できる不変 digest を提供する場合はコンテンツと結び付けてください。これは報告が示す防御上の不変条件であり、完全なパーサー、製品パッチ、再現コードではありません。JFrog の修正版へ更新してください。
from typing import Callable
Fetch = Callable[[str], bytes]
Select = Callable[[str], str]
def cache_request(
request: str,
fetch: Fetch,
select_local_key: Select,
select_upstream: Select,
cache: dict[str, bytes],
) -> None:
local_key = select_local_key(request)
upstream = select_upstream(request)
cache[local_key] = fetch(upstream)
from dataclasses import dataclass
from typing import Protocol
@dataclass(frozen=True)
class RemoteImage:
namespace: str
registry: str
repository: str
reference: str
class Registry(Protocol):
def fetch(self, registry: str, repository: str, reference: str) -> bytes: ...
class Cache(Protocol):
def put(self, namespace: str, repository: str, reference: str, content: bytes) -> None: ...
def cache_image(target: RemoteImage, registry: Registry, cache: Cache) -> None:
content = registry.fetch(target.registry, target.repository, target.reference)
cache.put(target.namespace, target.repository, target.reference, content)
エンジニアリング&システム堅牢化チェックリスト
- ✓self-hosted Artifactory を確認し、影響ブランチを 7.146.35 または 7.161.16 に更新します。対象は JFrog の情報で確認してください。
- ✓リモート Docker リポジトリを見直し、必要な ID にだけキャッシュ書き込み権限を付与します。
- ✓更新後、想定外のキャッシュ書き込みやイメージ参照変更を監査ログで調査します。更新は過去の変更を元に戻しません。