SK쉴더스 로고
ADT캡스캡스홈
SK쉴더스

LMCache 다중 프로세스 모드 미인증 역직렬화 RCE 취약점 (CVE-2026-105192)

EQST Now | 2026.10.12

NOW Briefing

Brief 1 LLM 추론 서버의 KV 캐시를 공유하는 LMCache의 다중 프로세스 모드에서 인증 없이 원격 코드 실행(RCE)이 가능한 취약점(CVE-2026-105192)이 공개됐습니다.
Brief 2 공격자가 도달할 수 있는 주소에 캐시 서버가 열려 있으면 메시지 한 통으로 LMCache 프로세스 권한의 임의 코드 실행이 가능합니다.
Brief 3 패치된 정식 릴리스가 없으므로 캐시 서버의 바인딩 주소와 방화벽 정책을 즉시 점검해 외부 노출을 끊어야 합니다.

‍

LMCache 다중 프로세스 모드 미인증 역직렬화 RCE 취약점 (CVE-2026-105192)

‍

■ 개요

‍

LMCache는 vLLM 같은 LLM 추론 서버가 계산해 둔 KV 캐시(모델이 토큰마다 미리 계산한 중간 결과)를 GPU·CPU 메모리와 디스크에 걸쳐 보관해 여러 추론 인스턴스가 재사용할 수 있게 해 주는 오픈소스 도구입니다. 이 가운데 다중 프로세스 모드(LMCache를 독립 서버 프로세스로 띄워 여러 vLLM 프로세스가 같은 캐시를 공유하는 배포 방식)의 통신 경로에서 CVE-2026-105192가 확인됐습니다. JFrog Security Research가 2026년 10월 7일 공개한 권고에 따르면 0.3.9 이상 모든 버전이 영향을 받습니다.

이 통신 경로에는 인증 절차가 없습니다. 서버는 들어온 메시지를 요청 핸들러로 넘기기 전에 먼저 해독하는데, 보낸 쪽이 지정한 형식대로 파이썬 객체를 복원하는 역직렬화가 이 해독 단계에서 일어납니다. 공격자가 해당 포트에 도달할 수 있으면 메시지 한 통으로 LMCache 프로세스 권한의 임의 코드 실행이 가능하고, 공식 컨테이너 이미지는 이 프로세스를 root로 실행합니다. 공식 문서 기준으로 서버는 기본적으로 루프백 주소에만 바인딩되므로, 원격에서 도달하려면 운영자가 외부에서 접근 가능한 주소로 바인딩한 상태여야 합니다. 다만 여러 노드가 캐시를 공유하려면 외부 주소 바인딩이 필요하고, 공식 문서도 인증 없는 HTTP 관리 API를 두고 루프백 외 주소 바인딩은 신뢰된 네트워크에서만 쓰라고 안내합니다.

권고 공개 시점에 수정 릴리스는 없었고, 2026년 10월 10일 공개된 최신 선행 배포판 0.5.6rc4.dev27 이후로도 2026년 10월 12일 기준 패치된 정식 릴리스는 나오지 않아 개발 브랜치 코드에는 문제가 된 역직렬화가 그대로 남아 있습니다. 이런 상태에서 PoC 코드는 권고에 전문이 포함된 채 공개돼 있어 외부에 노출된 인스턴스는 지금 바로 점검해야 합니다. 국내에서도 ML 인프라 기술 블로그가 vLLM에 LMCache를 붙여 첫 토큰 지연 변화를 실측했고, 파이토치 커뮤니티에도 운용 논의 글이 올라와 있습니다.

‍

■ 요약

‍

항목 내용
CVE ID CVE-2026-105192 (다중 프로세스 전송 경로의 인증이 필요 없는 역직렬화)
CVSS 점수 CVSS v3.1 9.8
취약점 유형 CWE-502 신뢰할 수 없는 데이터 역직렬화
CWE-306 핵심 기능의 인증 누락
영향/위험 • 인증 절차 없이 메시지 한 통으로 임의 코드 실행 가능
• LMCache 프로세스 권한 획득, 공식 컨테이너 이미지는 root 실행
• 권고 기준 근본 수정 수단 없음, 네트워크 차단만 가능
취약 버전 • 0.3.9 이상 모든 릴리스(v0.5.5, v0.5.6rc3 포함)
• 최신 선행 배포판 0.5.6rc4.dev27(2026-10-10 공개) 이후에도 수정 릴리스 없음
• 개발 브랜치(dev) 기준 취약 코드 유지 확인
패치 버전 • 패치 릴리스 없음(2026-10-12 기준)
• 수정 풀 리퀘스트(#5644) 공개, 병합 전

‍

■ 기술 분석

‍

다중 프로세스 모드의 LMCache 서버는 메시지 통신 라이브러리 ZeroMQ의 ROUTER 소켓을 열고 vLLM 워커 쪽 DEALER 소켓과 메시지를 주고받습니다. 공식 문서 기준 --host 기본값은 localhost, --port 기본값은 5555이고 전송 방식 기본값은 ZeroMQ입니다. 이 소켓에는 ZeroMQ가 제공하는 CURVE 암호 인증이나 ZAP 접근 제어가 걸려 있지 않아, 포트에 TCP 연결이 되는 상대는 모두 유효한 피어로 취급됩니다.

메시지 본문은 msgspec 라이브러리의 MessagePack 디코더로 해독합니다. 디코더는 표준 타입으로 표현할 수 없는 값을 만나면 확장 코드(ext code)와 바이트열을 인자로 받는 훅을 호출합니다. 아래는 v0.5.5 태그의 lmcache/v1/multiprocess/custom_types.py에서 디코더를 만드는 get_customized_decoder() 내부의 ext_hook()입니다.

    def ext_hook(code: int, data: bytes) -> Any:
        for cfg in _CUSTOMERIZED_SERIALIZERS.values():
            if cfg.code == code:
                return cfg.deserializer(data)
        raise TypeError(f"Unsupported ext code for deserialization: {code}")

훅은 코드 번호만 비교하고 보낸 쪽이 누구인지, 그 메시지가 허용된 요청인지는 확인하지 않습니다. 같은 파일의 등록 표는 코드 1을 KV 캐시 핸들 래퍼의 역직렬화 함수 DeviceIPCWrapper.Deserialize에 연결해 두었으므로, 확장 코드 1을 단 프레임은 요청 핸들러가 실행되기 전에 해독 단계에서 그 함수로 들어갑니다.

DeviceIPCWrapper는 GPU 공유 메모리 등 장치별 KV 캐시 핸들을 감싸는 기반 클래스입니다. 아래는 v0.5.5 태그의 lmcache/v1/platform/base/ipc_wrapper.py에 있는 DeviceIPCWrapper.Deserialize()입니다.

    @staticmethod
    def Deserialize(data: bytes) -> "DeviceIPCWrapper":
        """Inverse of :meth:`Serialize`.

        Args:
            data: The pickled bytes payload produced by :meth:`Serialize`.

        Returns:
            The reconstructed wrapper instance, with its concrete
            subclass identity preserved.
        """
        return pickle.loads(data)

파이썬 표준 직렬화 모듈의 pickle.loads()는 바이트열에 적힌 복원 지시를 그대로 실행하므로, 네트워크에서 받은 바이트열을 넣으면 객체를 만드는 과정 자체가 코드 실행이 됩니다. 프로젝트가 pickle을 쓴 이유는 Serialize() 함수 주석에 적혀 있습니다. 모든 래퍼가 확장 코드 1 하나를 공유하므로, 받는 쪽에서 구체적인 하위 클래스 정보가 남아 있어야 텐서 복원 함수가 올바르게 호출되기 때문입니다. 프로세스 사이의 편의를 위해 고른 직렬화 방식을 인증 없는 네트워크 입력에도 그대로 적용했습니다.

업스트림 수정은 2026년 10월 11일 올라온 풀 리퀘스트 #5644에서 진행 중이고, 작성 시점까지 병합되지 않았습니다. 확장 코드 1의 직렬화를 pickle에서 버전이 붙은 타입 기반 MessagePack 서술자로 바꾸고, 객체를 만들기 전에 고정된 허용 목록으로 서술자를 검증하는 방식입니다. 이 변경은 ZeroMQ와 gRPC 전송 코덱 양쪽에 적용되므로, 전송 방식을 gRPC로 바꾸는 것은 이 역직렬화 경로의 대응책이 아닙니다.

‍

■ PoC

‍

JFrog 권고에는 명령 실행까지 확인하는 PoC 코드가 전문 그대로 실려 있습니다. 외부에서 접근 가능한 주소로 띄운 다중 프로세스 서버에 조작된 ZeroMQ 메시지를 한 통 보내고, 해독 단계의 역직렬화에서 운영체제 명령이 실행되는 것을 확인하는 흐름입니다. 권고 기준 유효한 자격증명은 필요하지 않습니다. 다만 이 코드는 서버가 공격자가 도달할 수 있는 주소에 바인딩돼 있고 영향 버전이 설치된 상태를 전제합니다. 수정 풀 리퀘스트에도 취약 버전에서는 이 코드가 성공하고 수정 후에는 해당 페이로드가 역직렬화 검증 오류로 거부된다는 검증 기록이 함께 올라와 있습니다. PoC 코드는 본문에 싣지 않았으며, 원문은 참고 자료의 링크에서 확인할 수 있습니다.

⚠️ WARN

패치된 정식 릴리스가 없는 상태에서 명령 실행까지 확인된 PoC가 공개돼 있습니다.
외부 접근이 가능했던 LMCache 다중 프로세스 서버는 침해 가능성을 전제로 우선 점검해야 합니다.

‍

■ 탐지 및 점검

‍

• lmcache server 실행 인자와 배포 매니페스트의 --host 값을 확인해 루프백이 아닌 주소에 바인딩된 인스턴스를 모두 찾아야 합니다.

• 캐시 서버 노드의 5555/tcp(또는 변경한 포트)가 클러스터 밖이나 다른 네임스페이스에서 연결되는지 외부에서 직접 연결을 시도해 확인해야 합니다.

• 설치된 패키지 버전을 pip show lmcache로 확인하고, 0.3.9 이상이면 영향 대상으로 분류해야 합니다. 선행 배포판 lmcache@0.5.6rc4.dev27도 수정 전 코드가 포함된 버전입니다.

• LMCache 컨테이너에서 셸이나 네트워크 유틸리티 같은 예상 밖의 자식 프로세스가 생성된 이력이 있는지 프로세스 실행 로그로 점검해야 합니다. 단독 신호만으로는 침해를 확정할 수 없으므로 발생 시각과 해당 구간의 외부 연결 기록을 함께 대조해야 합니다.

• ZeroMQ 트래픽은 암호화되지 않지만 방화벽·NetFlow 로그에는 연결 메타데이터만 남으므로, 메시지 본문까지 확인하려면 해당 구간의 전체 패킷 캡처가 필요합니다.

‍

■ 대응 방안

‍

• 다중 프로세스 서버는 루프백 바인딩을 유지하고, 노드 간 공유가 필요하면 방화벽·네트워크 정책으로 지정한 워커 주소에서만 해당 포트에 접근하도록 제한해야 합니다.

• 네트워크 차단은 노출만 줄이고 취약 코드는 그대로 남으므로, 수정이 반영된 릴리스가 공개되면 즉시 업그레이드해야 합니다.

• 공식 문서에 ZeroMQ 인증·암호화 설정이 없으므로, 전송 포트를 노드 밖으로 열어야 한다면 전용 네트워크 구간이나 별도 인증 터널 안에만 두고 직접 노출은 피해야 합니다.

• 공식 컨테이너 이미지는 비특권 사용자를 지정하지 않으므로, 실행 사용자를 비root로 바꾸고 불필요한 권한을 제거해 침해 시 영향 범위를 줄여야 합니다.

• 외부 노출 이력이 있는 인스턴스는 해당 노드에 저장된 자격증명과 토큰을 폐기·재발급하고, 캐시 서버를 재배포해야 합니다.

💡 TIP

수정 풀 리퀘스트는 통신 형식 자체를 바꾸기 때문에 하위 호환되지 않습니다.
패치 릴리스가 나오면 워커와 캐시 서버를 같은 버전으로 함께 올리는 작업 계획을 미리 준비해야 합니다.

‍

[참고 자료]

• JFrog Security Research — LMCache 다중 프로세스 ZMQ 전송 인증 없는 RCE 권고

• OpenCVE — CVE-2026-105192 상세 정보

• LMCache v0.5.5 — custom_types.py (MessagePack 인코더·디코더 훅)

• LMCache v0.5.5 — ipc_wrapper.py (DeviceIPCWrapper.Deserialize)

• LMCache 수정 풀 리퀘스트 — 장치 IPC 서술자에서 pickle 제거

• LMCache 공식 문서 — 다중 프로세스 서버 설정 기본값

• PyPI — lmcache 릴리스 이력

• GBHackers — 미패치 상태와 공개 PoC 보도

• PyTorchKR — LMCache 운용 논의

• TT Lab — vLLM·SGLang 비교에서 LMCache 적용 실측

  • #EQST_NOW
  • #취약점

관련 서비스

더 많은 보안 인사이트

SK쉴더스 유튜브 채널에서 확인하세요.

SK쉴더스 유튜브 채널에서 확인하세요.
보안 트렌드와 대응방법

매월 뉴스레터로 확인하세요.

매월 뉴스레터로 확인하세요.