logo
홈블로그소개
4,743

Built with Next.js, Bun, Tailwind CSS and Shadcn/UI

·개인정보처리방침
AIPythonDocker

LiteLLM 공급망 침해: 서명된 배포도 안전을 보장하지 않는 이유

Toma
2026년 10월 23일
약 12분
목차
🛡️ LiteLLM 공급망 침해: 서명된 배포도 안전을 보장하지 않는 이유
🧩 먼저 구분할 것: 소스와 배포물은 같은 대상이 아닙니다
📦 Git tag를 읽는 것과 wheel을 확인하는 것
🔐 digest·서명·provenance가 답하는 질문
⚙️ 정상 배포 경로는 어떻게 공격에 사용됐나
🏗️ 빌드 도구보다 상위 권한이 문제였습니다
🧾 AI 응답 로그도 인증정보 경계에 포함됩니다
🐍 import하지 않아도 실행되는 이유
🚪 Python 시작 시점의 hook
🧯 확인 명령이 다시 실행을 촉발할 수 있습니다
🔬 공개 조사에서 확인된 사례와 한계
🧪 Socket의 아티팩트 분석
🌍 수집 대상과 수집 성공은 다릅니다
🧰 우리 개발·배포 흐름에 적용하기
🚑 이미 노출이 의심된다면
🧱 다음 배포에서는 검증 단위를 작게 나눕니다
⚖️ 더 단순한 대안은 언제 충분할까
🚧 현재 상태와 검증 범위
🎯 정리
Sources
이전 포스트PHP2Shell: 요청이 끝나도 남는 상태와 AI 시대의 패치 경계

목차

🛡️ LiteLLM 공급망 침해: 서명된 배포도 안전을 보장하지 않는 이유
🧩 먼저 구분할 것: 소스와 배포물은 같은 대상이 아닙니다
📦 Git tag를 읽는 것과 wheel을 확인하는 것
🔐 digest·서명·provenance가 답하는 질문
⚙️ 정상 배포 경로는 어떻게 공격에 사용됐나
🏗️ 빌드 도구보다 상위 권한이 문제였습니다
🧾 AI 응답 로그도 인증정보 경계에 포함됩니다
🐍 import하지 않아도 실행되는 이유
🚪 Python 시작 시점의 hook
🧯 확인 명령이 다시 실행을 촉발할 수 있습니다
🔬 공개 조사에서 확인된 사례와 한계
🧪 Socket의 아티팩트 분석
🌍 수집 대상과 수집 성공은 다릅니다
🧰 우리 개발·배포 흐름에 적용하기
🚑 이미 노출이 의심된다면
🧱 다음 배포에서는 검증 단위를 작게 나눕니다
⚖️ 더 단순한 대안은 언제 충분할까
🚧 현재 상태와 검증 범위
🎯 정리
Sources

🛡️ LiteLLM 공급망 침해: 서명된 배포도 안전을 보장하지 않는 이유

2026년 10월 21일, LiteLLM의 정상 릴리스 경로를 통해 악성 1.107.0 wheel과 Docker 이미지가 배포됐습니다.[29] 공식 GitHub source tag에는 악성 파일이 없었지만, 실제 배포 아티팩트는 공격자 저장소에서 만들어졌습니다.[29]

이 사건에서 배울 것은 단순히 “최신 버전을 조심하자”가 아닙니다. 내용의 동일성, 배포 주체, 빌드 입력, 실행 권한을 서로 다른 검증 항목으로 나누어야 한다는 점입니다.

💡 💡 핵심: digest는 같은 바이트를 가져오는지 확인하고, provenance는 어떤 빌드 입력이 기록됐는지 보여줍니다.[35][30] 이번에는 정상 workflow가 잘못된 소스로 빌드한 결과를 서명·배포했다는 것이 유지관리자의 설명입니다.[29]

🧩 먼저 구분할 것: 소스와 배포물은 같은 대상이 아닙니다

📦 Git tag를 읽는 것과 wheel을 확인하는 것

wheel은 .whl 확장자를 가진 ZIP 형식의 Python 배포물입니다.[36] 설치 과정에서는 내부의 .data 하위 파일도 지정된 설치 경로로 옮겨지므로, 애플리케이션의 주 패키지 디렉터리만 살펴보는 것으로 설치되는 파일 전체를 확인할 수는 없습니다.[36]

Socket이 분석한 악성 wheel에는 litellm/ 바깥으로 설치되는 cook.pth, cook.py, sitecustomize.py가 있었습니다.[30] 공식 tag에 해당 파일이 없다는 사실은, 사용자에게 전달된 wheel이나 이미지까지 깨끗하다는 증거가 아니었던 셈입니다.[29][30]

🔐 digest·서명·provenance가 답하는 질문

  • digest — 내용이 같은가? Docker 문서에 따르면 tag는 변경될 수 있지만 digest는 이미지 내용에 연결되는 불변 식별자입니다.[35]
  • 서명 — 어느 신뢰 경로가 승인했는가? 이번 사건에서는 침해된 workflow가 악성 아티팩트를 서명하고 배포했다고 유지관리자가 밝혔습니다.[29]
  • provenance — 어떤 입력으로 빌드했다고 기록됐는가? Socket이 별도로 조회한 이미지 provenance에는 정상 BerriAI/litellm이 아니라 하이픈이 들어간 Berri-AI/litellm이 기록돼 있었습니다.[30]

설계 관점에서는 “검증 성공” 하나로 묶지 않는 편이 좋습니다. 같은 바이트를 반복해서 받는다는 것과, 그 바이트가 허용한 저장소·commit에서 만들어졌다는 것은 다른 조건입니다.[35][30]

⚙️ 정상 배포 경로는 어떻게 공격에 사용됐나

🏗️ 빌드 도구보다 상위 권한이 문제였습니다

유지관리자는 탈취된 개발자 GitHub CLI 자격 증명 중 조직 Owner 권한을 가진 것이 사용됐다고 설명합니다.[29] 처음에는 branch protection이 workflow 변경을 막았지만, 공격자가 보호를 비활성화하고 변경을 force push한 뒤, 유사 이름의 저장소를 입력으로 지정해 정상 릴리스 workflow를 실행했다는 것입니다.[29]

권한 탈취 → 보호 정책 변경 → 빌드 소스 변경 → 정상 workflow의 서명·배포가 공식 조사에서 설명한 흐름입니다.[29] private release repo나 승인 단계가 존재하더라도, 그것들을 바꾸거나 통과시킬 권한까지 함께 침해되면 같은 방어 효과를 기대하기 어렵습니다. 이는 이번 사건을 바탕으로 한 설계 해석입니다.

🧾 AI 응답 로그도 인증정보 경계에 포함됩니다

유지관리자는 특정 상황에서 Claude Code 응답에 GitHub CLI OAuth token이 포함되고, 응답 본문을 기록하는 LiteLLM 로그에 그 token이 남을 수 있음을 관찰했다고 밝혔습니다.[29][23] 또한 외부에 노출된 내부 LiteLLM 배포에 공격자가 접근해 로그에서 token을 얻었다는 공격 경로를 추정하고 있습니다.[29]

다만 그 내부 배포에 처음 접근한 방법은 아직 확정하지 못했다고 같은 공지에 명시돼 있습니다.[29] 이를 “모든 Claude Code 사용자의 token이 유출된다”거나 “특정 취약점이 최초 침입 원인으로 확정됐다”는 결론으로 확대하면 안 됩니다.

실무 적용 제안은 단순합니다. AI gateway의 prompt·response 로그에도 비밀정보가 들어갈 수 있다는 전제로 접근권한, 수집 범위, 보존 기간, redaction을 점검하세요. 개발자 인증정보와 릴리스 정책을 변경하는 권한도 가능한 한 분리해 검토하는 것이 좋습니다.

🐍 import하지 않아도 실행되는 이유

🚪 Python 시작 시점의 hook

Python 3.13 공식 문서는 site 초기화 과정에서 .pth 파일의 import 행을 실행하며, 그 행은 해당 모듈을 실제로 사용할지와 관계없이 Python 시작 때 실행된다고 설명합니다.[32] 이어서 sitecustomize 모듈도 import하려고 시도합니다.[32]

이번 악성 배포는 이 동작을 이용했습니다. Socket 분석에 따르면 cook.pth가 자격 증명 수집기를 불러오고, sitecustomize.py가 별도의 원격 코드 로더 진입점이 됩니다.[30] 따라서 애플리케이션에서 import litellm을 제거하는 것만으로는 충분하지 않습니다.[30]

악성 배포물 설치 → 시작 hook 배치 → 영향받은 환경의 Python 시작 → 수집기·로더 실행 경로로 이해하면 됩니다.[30][32] 이는 Python 전체가 원격으로 감염된다는 뜻이 아니라, 해당 파일이 설치된 환경의 시작 동작을 악용했다는 의미입니다.

🧯 확인 명령이 다시 실행을 촉발할 수 있습니다

공식 공지에는 영향 확인 항목으로 pip show litellm이 등장하지만, 같은 문서의 복구 절차는 영향받은 환경에서 Python이나 pip를 시작하면 악성 hook이 다시 실행될 수 있다고 경고합니다.[29] 이 글에서는 의심 환경에서 복사해 실행할 확인 명령을 제시하지 않습니다.

Socket도 wheel을 데이터로 내려받아 ZIP 내용을 보는 것과, 그 환경에서 Python을 시작하는 것을 구분합니다.[30] 조사할 때는 파일 보유·설치·실행을 같은 사건으로 취급하지 말고, 어느 단계까지 진행됐는지 증거를 나누어야 합니다.

🔬 공개 조사에서 확인된 사례와 한계

🧪 Socket의 아티팩트 분석

Socket은 직접 조회한 이미지 provenance에서 빌드 소스와 revision을 확인하고, 그 revision의 세 payload 파일이 검사한 PyPI wheel과 바이트 단위로 일치한다고 보고했습니다.[30] 이는 tag와 배포물 사이의 차이를 실제 아티팩트 수준에서 조사한 사례입니다.[30]

반면 Socket은 private workflow 로그에 접근하지 못했으며, 그 자료만으로 소스 변경 권한을 어떻게 얻었는지나 같은 workflow가 PyPI wheel까지 업로드했는지를 확정하지 않았습니다.[30] 계정·권한 침해 경로는 유지관리자의 후속 설명과 구분해서 읽어야 합니다.[29][30]

🌍 수집 대상과 수집 성공은 다릅니다

분석된 payload는 LiteLLM proxy의 저장된 자격 증명과 모델 설정을 조회하도록 작성돼 있었습니다.[30] 그러나 Socket은 해당 쿼리의 존재만으로 특정 배포에서 접근·복호화가 성공했다고 단정할 수 없다고 명시합니다.[30]

플랫폼 차이도 있습니다. 검사한 macOS 아티팩트의 DB 수집 경로에는 Linux /proc 의존성이 있고, 일부 Chromium 비밀번호 복호화에도 제약이 있었지만, AI 도구 파일과 현재 프로세스 환경을 노리는 다른 모듈은 여전히 적용될 수 있다고 설명합니다.[30] “macOS는 안전하다” 또는 “모든 OS에서 모든 비밀정보를 똑같이 탈취했다”는 표현은 모두 근거 범위를 벗어납니다.

원격 코드 로더가 존재한다는 것과 실제 2단계 공격이 성공했다는 것도 다릅니다. Socket은 관찰 시점의 주소 포인터가 비어 있었고, 매시간 반복 루프를 매시간 확정적인 정보 유출로 해석해서는 안 된다고 설명합니다.[30]

🧰 우리 개발·배포 흐름에 적용하기

🚑 이미 노출이 의심된다면

아래는 공개 대응 지침을 업무 순서로 정리한 제안입니다. 실제 사용자 환경을 조사하거나 변경한 기록은 아닙니다.

  1. 배포 이력을 확보합니다. 설치한 버전뿐 아니라 이미지 digest, CI 실행, 배포·실행 시각을 함께 확인합니다. 공식 공지는 문제 버전을 실행한 환경을 침해된 것으로 간주하도록 안내합니다.[29]
  2. 의심 환경에서 Python·pip부터 실행하지 않습니다. 공식 복구 지침은 시작 hook과 실행 중인 daemon을 처리하고, 이미지·venv를 처음부터 재구축하는 편이 낫다고 설명합니다.[29]
  3. 깨끗한 환경에서 복구와 비밀정보 교체를 진행합니다. 단순 downgrade로 끝내지 말고, 접근 가능했던 provider key·DB 비밀번호·관련 인증정보의 폐기와 교체를 별도 작업으로 다룹니다.[29]
  4. 침해 흔적 부재를 무침해 판정으로 바꾸지 않습니다. 공식 공지와 Socket 모두 흔적 파일이 없다고 안전을 증명할 수는 없다고 경고합니다.[29][30]

유지관리자는 임시로 1.106.2 등 사건 이전의 명시적 버전으로 고정하도록 안내합니다.[29] 이는 이번 악성 릴리스에 대한 대응 지침이지, 그 버전에 다른 취약점이 전혀 없다는 보장은 아닙니다.

🧱 다음 배포에서는 검증 단위를 작게 나눕니다

다음은 예방을 위한 설계 제안입니다. 이번 사건을 완전히 막는 검증된 만능 구성이라는 뜻은 아닙니다.

  • 내용 고정: 운영 배포의 이미지 digest를 기록하고, 새 내용으로의 승격을 별도 변경으로 관리합니다. digest는 같은 이미지 재현을 돕지만, 처음부터 악성인 digest를 승인하는 실수까지 막지는 않습니다.[35][30]
  • 소스 정책: 빌드 입력의 repository owner·repository·commit을 허용 목록과 대조합니다. 사람이 비슷한 이름을 알아보는 것만으로 끝내지 않습니다.
  • 권한 정책: 릴리스 실행 권한, 승인 권한, branch protection 변경 권한의 조합을 점검합니다. 공격자가 빌드 설정과 검증 정책을 함께 바꿀 수 있는지 살펴봅니다.
  • 실행·로그 경계: AI gateway에 저장되는 응답과 실행 환경이 접근할 수 있는 비밀정보를 최소화합니다. 수집하지 않아도 되는 원문 로그는 줄이는 방향부터 검토합니다.

⚖️ 더 단순한 대안은 언제 충분할까

검증한 digest로 고정하고 수동으로 승격하는 방식은 “어제와 같은 이미지를 오늘도 실행한다”는 목표에 유용합니다.[35] 작은 팀에서는 이 방식에 배포 이력과 변경 검토를 붙이는 것부터 시작하는 편이 운영 부담을 줄일 수 있습니다. 이는 편집 의견입니다.

하지만 이번처럼 처음 승인하려는 아티팩트의 출처 자체가 문제라면 내용 고정만으로는 부족합니다.[29][30] 여러 저장소와 자동 릴리스를 운영한다면 source policy 검증을 자동화할 가치가 있습니다. 그 대신 허용 목록 유지, 예외 승인, 검증 실패 시 배포 중단을 운영할 책임도 생깁니다.

판단 기준은 “보안 도구를 얼마나 많이 붙였는가”보다 각 도구가 어떤 질문에 답하며, 공격자가 그 답의 기준까지 바꿀 수 있는가입니다.

🚧 현재 상태와 검증 범위

공식 공지는 악성 PyPI 배포물과 이미지가 제거되거나 정상 대상으로 전환됐다고 안내합니다.[29] 10월 22일 02:19 UTC 유지관리자 업데이트는 조사가 계속되고 정규 릴리스가 여전히 중단돼 있다고 밝혔습니다.[23] 이는 이미 실행한 환경의 복구·인증정보 교체가 자동으로 끝났다는 뜻이 아닙니다.[29]

💡 🔎 검증 노트: 2026년 10월 23일 KST에 공식 공지의 live HTML, 유지관리자 업데이트, Python·Docker·wheel 문서와 Socket의 독립 분석을 대조했습니다. 악성 wheel 다운로드·설치·실행, 바이너리 재분석, 사내 자산 점검, 인증정보 교체는 수행하지 않았습니다. 독립 자료가 존재해도 이 글 자체가 실험 재현이나 사용자 환경의 무침해를 검증한 것은 아닙니다.

초기 추출본과 live 공지의 수정 시각·공격 경로 설명이 달랐으므로 현재 공지 설명은 직접 받은 live 본문을 기준으로 했습니다. 최초 침입과 token 유출 경로에는 유지관리자가 남겨 둔 미확정 부분이 있으며, 독립 분석으로 그 전체를 재구성했다고 주장하지 않습니다.[29][30]

🎯 정리

  • Git tag 검토와 배포물 검토는 다릅니다. 이번에는 정상 tag에 없던 파일이 wheel과 이미지로 전달됐습니다.[29][30]
  • 내용 동일성과 안전성은 다릅니다. digest 고정에 더해 빌드 소스와 정책을 바꿀 수 있는 권한을 따로 확인해야 합니다.[35][29]
  • 설치와 실행, 수집 시도와 성공도 다릅니다. 시작 hook·플랫폼 제약·로그를 구분하고, 이미 노출된 인증정보는 패키지 복구와 별도로 다룹니다.[30][29]

가져갈 원칙은 하나입니다. “정상 배포 경로에서 받았다”는 이유로 검증을 끝내지 말고, 허용한 소스에서 만들어진 바로 그 아티팩트를 제한된 권한으로 실행하는가를 확인해야 합니다.

Sources

[23] https://github.com/BerriAI/litellm/issues/47826 — LiteLLM issue #47826 — maintainer investigation updates

[29] https://docs.litellm.ai/blog/security-incident-oct-21-2026 — Contained: Oct 21, 2026 LiteLLM release v1.107.0 Compromised — live 20:30 UTC revision

[30] https://socket.dev/blog/litellm-compromise — LiteLLM PyPI Package and Docker Images Compromised to Steal Cloud and AI Credentials — Socket

[32] https://docs.python.org/3.13/library/site.html — Python 3.13 site — Site-specific configuration hook

[35] https://docs.docker.com/dhi/core-concepts/digests — Image digests — Docker Docs

[36] https://packaging.python.org/en/latest/specifications/binary-distribution-format — Binary distribution format — Python Packaging User Guide