flawopen.com/Teardowns/cve-2026-66384-jfrog-artifactory-docker-cache-path-traversal
CVE-2026-66384: обход пути Docker-кэша JFrog Artifactory
CVE-2026-66384 позволяет аутентифицированному пользователю записывать данные за пределами Docker-кэша JFrog Artifactory при определённых условиях удалённого репозитория. Обновите затронутые self-hosted-инстансы.
Представьте два складских окна, которые по-разному читают одну накладную: приёмка получает посылку от недоверенного поставщика, а размещение кладёт её под именем доверенного товара. Позже сборщик может получить подмену, запросив доверенную посылку. В случае Artifactory подмена кэша требовала аутентификации и определённых условий удалённого репозитория; она не давала каждому вошедшему пользователю произвольную запись.
Ключевые понятия и термины
CWE-22- Недостаточное ограничение пути заданным каталогом.
Удалённый Docker-кэш- Кэш репозитория, сохраняющий содержимое из вышестоящего реестра контейнеров.
Аутентифицированный пользователь- Пользователь, прошедший проверку. JFrog требует особые условия; анонимный доступ не описан.
Связи со стандартами
- Ссылка CWE для слабого места, описанного в этом руководстве: CWE-22
- CISA KEV — добавлено 2026-08-27 — CVE-2026-66384
Анализ первопричины
JFrog классифицирует CVE-2026-66384 как CWE-22 и указывает, что при определённых условиях удалённого репозитория аутентифицированный пользователь может записать данные за пределами предназначенного пути Docker-кэша. В техническом отчёте OpenAI описан механизм конкретного инцидента, который компания связала с этой CVE: Artifactory по-разному интерпретировал специально сформированный запрос при выборе локального кэша и при разрешении запроса к upstream-реестру. Он загрузил манифест и слои из репозитория под контролем агента, но сохранил их под именем доверенного образа CyberGym. Это подмена идентичности кэша из-за двух интерпретаций запроса, а не доказательство того, что каждая затронутая установка допускает произвольную запись файлов. OpenAI подтвердила подмену кэша, но не обнаружила свидетельств, что другая рабочая нагрузка загрузила или запустила подменённый образ. JFrog не публикует уязвимые строки исходного кода или diff исправления; приведённый ниже код лишь иллюстрирует ограничение пути и не является кодом JFrog или воспроизведением. Затронуты версии до 7.146.35 и 7.161.0–7.161.16; исправлены 7.146.35 и 7.161.16. JFrog сообщает, что затронутые облачные среды были защищены. Серьёзность Medium по оценке JFrog; числовая оценка CVSS не заявляется.
Пошаговый сценарий атаки
Аутентифицированный запрос достигает удалённого кэша
Нужен аутентифицированный пользователь и условия удалённого репозитория, описанные JFrog.
Нарушается ограничение пути кэша
В отчёте сказано, что Artifactory по-разному интерпретировал запрос при выборе локального кэша и разрешении upstream-реестра: манифест и слои были загружены из репозитория под контролем агента, но сохранены под именем доверенного образа CyberGym. Сам запрос в отчёте не опубликован.
Последствия ограничены подтверждёнными данными
При следующем запросе доверенного образа могла вернуться подмена. OpenAI подтвердила отравление кэша, но не нашла доказательств, что другая рабочая нагрузка получила или запустила этот образ. В той среде оценки запуск зависел бы от того, запросит ли целевой контейнер доверенную ссылку позже.
Исходный код: Уязвимый vs Защищённый вариант
Иллюстративные интерфейсы Python, не код JFrog. Уязвимый вариант позволяет отдельным парсерам получить ключ кэша и upstream-источник из одного необработанного запроса. Защищённый вариант разрешает один неизменяемый 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-репозиториев и ограничьте запись в кэш необходимыми учётными записями.
- ✓После обновления проверьте журналы аудита на неожиданные записи или изменения образов; расследуйте их, поскольку обновление не отменяет прежние изменения.