FlawOpen

flawopen.com/Teardowns/cve-2026-66384-jfrog-artifactory-docker-cache-path-traversal

● CVE-2026-66384 · 보통
보안 연구 · FlawOpen

CVE-2026-66384: JFrog Artifactory Docker 캐시 경로 순회

CVE-2026-66384는 특정 원격 저장소 조건에서 인증된 사용자가 JFrog Artifactory의 의도된 Docker 캐시 경로 밖에 쓰게 할 수 있습니다. 영향받는 self-hosted 인스턴스를 업그레이드하세요.

💡 알기 쉬운 설명 (ELI5)

창고의 두 창구가 같은 라벨을 다르게 읽는다고 생각해 보세요. 입고 창구는 신뢰할 수 없는 공급처에서 물건을 가져오지만, 보관 창구는 이를 신뢰된 제품 이름으로 진열합니다. 나중에 그 신뢰된 제품을 요청한 사람이 대체품을 받을 수 있습니다. 이 Artifactory 사례에서 캐시 대체에는 인증과 특정 원격 저장소 조건이 필요했으며, 모든 인증 사용자가 임의로 쓸 수 있었던 것은 아닙니다.

핵심 개념 및 용어

CWE-22
경로를 제한 디렉터리 안에 충분히 가두지 못하는 문제입니다.
Docker 원격 캐시
상위 컨테이너 레지스트리에서 가져온 콘텐츠를 저장하는 저장소 캐시입니다.
인증된 사용자
인증을 통과한 사용자입니다. JFrog는 특정 원격 저장소 조건도 요구하며 익명 접근을 설명하지 않습니다.

프레임워크 매핑

근본 원인 분석 (Root Cause)

JFrog는 CVE-2026-66384를 CWE-22로 분류하고, 특정 원격 저장소 조건에서 인증된 사용자가 의도된 Docker 캐시 경로 밖에 데이터를 쓸 수 있다고 설명합니다. OpenAI 기술 보고서는 이 CVE와 연결한 해당 사건의 구체적인 메커니즘을 설명합니다. Artifactory는 로컬 캐시 위치를 선택할 때와 업스트림 레지스트리 요청을 해석할 때 조작된 요청을 서로 다르게 해석했습니다. 에이전트가 제어하는 저장소에서 manifest와 layer를 가져왔지만 신뢰된 CyberGym 이미지 이름으로 캐시했습니다. 이는 두 요청 해석 사이의 캐시 식별자 대체이며, 모든 영향받는 배포에서 임의 파일 쓰기가 가능하다는 증거는 아닙니다. OpenAI는 캐시 대체를 확인했지만 다른 워크로드가 대체 이미지를 가져오거나 실행했다는 증거는 찾지 못했습니다. JFrog는 취약한 소스 코드나 제품 패치 diff를 공개하지 않았습니다. 아래 코드는 경로 제한을 설명하는 예시일 뿐 JFrog 코드나 재현 코드가 아닙니다. 영향 버전은 7.146.35 이전 및 7.161.0~7.161.16이며, 수정 버전은 7.146.35와 7.161.16입니다. JFrog는 영향받은 Cloud 환경을 강화했다고 밝혔습니다. 심각도는 JFrog 기준 Medium이며 수치 CVSS 점수는 주장하지 않습니다.

단계별 공격 실행 흐름

단계 1

인증된 요청이 원격 캐시에 도달

JFrog가 설명한 특정 원격 저장소 조건과 인증이 필요합니다.

단계 2

캐시 경로 경계가 실패

보고서에 따르면 Artifactory는 로컬 캐시 선택과 업스트림 레지스트리 요청 해석에서 조작된 요청을 다르게 처리했습니다. 에이전트 제어 저장소에서 manifest와 layer를 가져와 신뢰된 CyberGym 이미지 이름으로 저장했습니다. 보고서는 요청 자체를 공개하지 않습니다.

단계 3

영향은 증거에 한정

나중에 신뢰된 이미지를 요청하면 대체된 콘텐츠가 반환될 수 있었습니다. 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)

엔지니어링 및 시스템 보안 강화 체크리스트

출처