본문으로 건너뛰기
NEW · Blast Radius: 유출된 API 키 대응 플레이북 (AWS·GCP·Azure)
블로그로 돌아가기

LiteLLM 1.107.0 공급망 공격은 5월에 지운 키에서 시작됐습니다

10월 21일 LiteLLM 정식 릴리스 파이프라인이 자격증명 탈취 코드가 든 1.107.0을 PyPI와 Docker에 배포했습니다. 시작점은 5월 27일 공개 레포에 커밋됐다가 세 시간 뒤 지워졌지만 폐기되지 않은 관리자 키였습니다. 공격 경로와 악성 버전이 가져간 것, 사용자가 할 일, 다른 팀이 점검할 것을 정리했습니다.

김동현
작성자
김동현
발행
13 분1,351 어절
Share:
5월 공개 레포에서 지운 파일은 점선으로 사라졌지만 그 안의 관리자 키는 노란 블록으로 남아, 10월 GitHub 토큰 세 개를 거쳐 LiteLLM 1.107.0 배포로 이어지는 장면
LiteLLM 1.107.0 공격에 쓰인 자격증명 4개: 5월 27일 공개 레포에 커밋됐다가 3시간 뒤 삭제됐지만 폐기되지 않은 Agent Platform 관리자 키, 10월 10일 그 플랫폼 API가 돌려준 조직 소유자의 GitHub CLI 토큰, 임시 워크플로로 빼낸 GitHub Actions 시크릿 속 메인테이너 토큰 2개(릴리스 승인과 실행 기록 삭제에 쓰임).

LiteLLM 1.107.0에 무슨 일이 있었나

2026년 10월 21일 11:39(UTC), 자격증명을 훔치는 코드가 든 LiteLLM 1.107.0 휠 7개가 PyPI에 올라왔습니다. 7분 뒤에는 같은 빌드가 Docker 이미지로 v1.107.0, latest, main-latest, main-stable 태그를 달고 배포됐습니다. 누가 몰래 올린 패키지가 아니었습니다. LiteLLM의 정식 릴리스 파이프라인이 빌드하고 서명까지 해서 내보낸 버전이었습니다.

12:06에 외부 개발자가 GitHub 이슈로 신고했습니다(PyPI 업로드 27분 뒤, Docker 이미지 배포 20분 뒤). PyPI는 13:23에 해당 버전을 내렸습니다. Docker 태그는 14:20까지 지우거나 정상 이미지로 되돌렸습니다. 악성 버전이 열려 있던 시간은 두 시간 남짓입니다. 짧아 보여도 LiteLLM은 PyPI에서 일주일에 2천만 번 넘게 내려받는 패키지이고(pypistats 기준 11월 3일까지 최근 7일 약 2,308만 회), latest 태그를 따라가는 서버라면 그사이 새 이미지를 받았을 수 있습니다.

LiteLLM이 공급망 공격을 당한 건 올해 두 번째입니다. 3월에는 1.82.7과 1.82.8이 약 40분 동안 PyPI에 올라 있었습니다. LiteLLM은 당시 공지에서 CI/CD 보안 스캔에 쓰던 Trivy 의존성에서 사고가 시작된 것으로 보고, 메인테이너의 PyPI 계정이 침해됐을 수 있다고 밝혔습니다. 10월 사고는 경로가 다릅니다. LiteLLM이 10월 23일 공개한 공격 경로 보고서를 보면 출발점은 다섯 달 전 공개 레포에 올라간 키 하나였습니다.

출발점은 5월에 지운 관리자 키였습니다

2026년 5월 27일 18:53(UTC), 사내 알파 서비스인 LiteLLM Agent Platform의 관리자 키가 공개 레포 LiteLLM-Labs/lite-harness에 커밋됐습니다. 세 시간쯤 뒤 후속 커밋으로 그 파일을 지웠습니다.

그런데 커밋 기록은 10월 22일까지 그대로 남아 있었고, 키도 폐기하지 않았습니다. LiteLLM은 보고서에 "사람의 실수로 아무도 관리자 키 폐기를 마무리하지 않았다"고 적었습니다. 공격자는 네 달 넘게 살아 있던 이 키를 10월 10일에 꺼내 썼습니다.

LiteLLM Agent Platform 관리자 키의 상태: 현재 파일에서는 5월 27일 커밋 약 3시간 뒤 삭제됐지만, 공개 커밋 기록에는 10월 22일까지 남아 있었고, 발급처에서는 폐기되지 않아 10월 10일 공격자가 사용했습니다.

이번 사고에서 가장 중요한 대목입니다. 파일을 지우는 것과 키를 폐기하는 것은 다릅니다. 공개 레포에 한 번 올라간 키는 커밋 기록, 포크, 미러, 수집 봇에 남습니다. 파일을 지웠더라도 발급처에서 그 키가 더는 인증되지 않는 것을 확인해야 노출이 끝납니다.

키 하나로 릴리스 파이프라인까지 간 경로

보고서에 나온 순서대로 정리하면 이렇습니다.

  1. 10월 10일 21:13~21:28(UTC), 공격자는 관리자 키로 Agent Platform API를 호출했습니다. 이 플랫폼은 클라우드 에이전트가 GitHub에 PR을 올리게 하는 실험 서비스였는데, 여기에 설정된 GitHub 토큰은 권한을 좁힌 PAT가 아니라 GitHub 조직 소유자 개인의 GitHub CLI OAuth 토큰이었습니다. API 응답에 그 토큰이 그대로 담겨 있었습니다.
  2. 공격자는 같은 날 조직 소유자 토큰으로 임시 워크플로를 돌려 GitHub Actions 시크릿 두 개를 빼냈습니다. BerriAI/litellm의 GH_TOKEN과 BerriAI/litellm-auto의 RELEASE_PLEASE_TOKEN으로, 둘 다 다른 메인테이너의 토큰이었습니다. 10월 12일에는 비공개 레포 여러 곳에서 두 번째 유출이 있었습니다.
  3. 10월 21일 10:08, 릴리스 워크플로를 바꾸는 푸시가 브랜치 보호 규칙에 막혔습니다. 공격자는 몇 분 뒤 조직 소유자 토큰으로 보호 규칙을 무시하고 변경을 밀어 넣었습니다. 바뀐 워크플로는 원래 저장소(BerriAI)가 아니라 하이픈 하나가 더 붙은 Berri-AI/litellm에서 코드를 받아 빌드했습니다.
  4. 프로덕션 릴리스는 한 사람이 실행하고 다른 사람이 승인해야 나갑니다. 원래는 승인자가 물리 기기에 묶여 있었는데, 그 주에 '엔지니어 아무나 두 명'으로 조건이 느슨해져 있었습니다. 공격자는 두 번째 메인테이너의 GH_TOKEN으로 승인했습니다.
  5. 배포가 끝나자 RELEASE_PLEASE_TOKEN으로 staging과 production 실행 기록을 지웠습니다.
2026년 10월 21일 UTC 타임라인: 10:08 브랜치 보호 무시, 10:46 Berri-AI/litellm에서 staging 빌드, 11:11 프로덕션 승인, 11:39 PyPI 업로드, 11:46 Docker 이미지 배포, 11:53 실행 기록 삭제, 12:06 이슈 보고, 13:23 PyPI 제거, 14:20 Docker 태그 정리.

LiteLLM은 첫 공지에서 내부 배포 서버의 로그로 토큰이 새어 나갔다고 추정했다가, 최종 보고서에서 이를 바로잡았습니다. 첫 토큰은 로그가 아니라 Agent Platform API 응답에서 나왔습니다.

악성 버전은 무엇을 가져갔나

악성 휠은 litellm 폴더 바깥인 site-packages 맨 위에 cook.pth, cook.py, sitecustomize.py 세 파일을 심었습니다. cook.pth에는 import cook 한 줄만 들어 있습니다. .pth 파일은 Python이 시작될 때마다 읽히므로, LiteLLM을 import하지 않았더라도 그 환경에서 python이나 pip를 한 번 실행하면 cook.py가 돌았습니다. GitHub의 tag v1.107.0 소스에는 이 파일들이 없습니다. 빌드 단계에서 끼워 넣은 것입니다.

아래 목록은 LiteLLM 공지와, 이슈 #47826에 공개된 cook.py 디코딩 원문(실행하지 않고 정적으로 푼 1,237줄)에서 직접 확인한 수집 모듈입니다.

  • 환경 변수: 자기 프로세스의 환경 변수, 리눅스에서는 /proc으로 읽을 수 있는 다른 모든 프로세스의 환경 변수
  • 클라우드: ~/.aws의 credentials와 config, Azure CLI 프로필과 토큰 캐시, gcloud 기본 자격증명과 토큰 DB, Kubernetes 설정, 그리고 AWS·GCP·Azure 메타데이터 서비스에서 받는 임시 자격증명
  • 컨테이너: Kubernetes 서비스 계정 토큰, /run/secrets 아래 Docker 시크릿, Docker 레지스트리 설정
  • 개발 도구: Git 자격증명과 .gitconfig, npm·PyPI·pip·Maven·Gradle 등 패키지 레지스트리 설정, 홈 디렉터리와 현재 디렉터리의 .env 파일, .pem·.key·.p12 같은 키 파일
  • CI/CD: GitLab Runner 설정, Jenkins 관리자 비밀번호와 마스터 키, Vault 토큰(~/.vault-token), Terraform·Packer 자격증명
  • AI 개발 도구: Claude(~/.claude, Claude Desktop 설정), Cursor의 mcp.json, Codex CLI, Gemini CLI, GitHub Copilot, Windsurf, Kiro, Zed, Amazon Q 등의 설정·인증 파일
  • 브라우저와 셸: Chromium 계열·Firefox의 저장된 비밀번호와 방문 기록, 셸 기록
  • 키: SSH 키, GPG 키, TLS 개인 키
  • LiteLLM 프록시 DB: DATABASE_URL로 접속해 LiteLLM_CredentialsTable(프록시가 대신 쓰는 AI 제공사 키)과 LiteLLM_ProxyModelTable(모델 설정)을 읽음

모은 정보는 코드에 박힌 공개 키로 암호화해 http://www.litellm[.]dev/healthcheck로 보냈습니다. 전송에 성공하면 /tmp/.pc_once_72129118c59f5636c0bb 파일을 남깁니다.

sitecustomize.py는 별도 역할을 합니다. 백그라운드 프로세스를 띄워 /tmp/remote_code_loader.lock을 잡고, 한 시간마다 GitHub 계정 letters9813의 pythonclient-test 레포에 있는 domain.txt를 읽어 거기 적힌 서버에서 Python 코드를 받아 실행합니다. 이슈 작성 시점에는 domain.txt가 비어 있어 이 단계는 멈춰 있었지만, 공격자가 커밋 하나로 언제든 켤 수 있는 구조입니다. LiteLLM이 공지에서 이 버전을 "자격증명을 훔치고 원격 코드 실행 도구를 내려받는" 악성 코드라고 적은 이유입니다.

LiteLLM 프록시는 여러 AI 제공사의 키를 DB 한 곳에 모아 두는 게이트웨이이고, 수집 코드에는 그 DB의 표 이름까지 적혀 있었습니다. LLM 게이트웨이를 운영하는 팀이라면 게이트웨이 DB에 어떤 키가 얼마나 있는지 따로 확인해 둘 필요가 있습니다.

LiteLLM 사용자는 지금 무엇을 해야 하나

LiteLLM 공지와 이슈 #47826의 안내를 합치면 다음과 같습니다.

  1. 1.107.0이 설치됐던 곳을 모두 찾으세요. 가상환경, uv·pip 캐시, 내부 미러, lock 파일, 빌드 산출물에 더해 10월 21일 11:46~14:20(UTC)에 latest·main-stable 태그로 받은 이미지도 확인해야 합니다. 이미지는 태그가 아니라 다이제스트로 대조하세요.
  2. 의심되는 환경에서 pip install부터 하지 마세요. Python을 실행하면 악성 코드도 다시 실행됩니다. 깨끗한 시스템에서 site-packages를 열어 cook.pth, cook.py, sitecustomize.py와 같은 이름의 .pyc 파일, litellm-1.107.0.dist-info가 있는지 보고, /tmp/remote_code_loader.lock을 잡고 있는 Python 프로세스를 종료하세요. 이 파일들이 없다고 해서 깨끗하다고 단정할 수는 없습니다.
  3. 1.106.2처럼 사고 이전 버전으로 고정해 다시 설치하거나, 이미지와 가상환경을 처음부터 다시 만드세요.
  4. 외부 인터넷에 나갈 수 있는 호스트에서 1.107.0이 실행됐다면 정보가 이미 나갔다고 보세요. 인스턴스·태스크 IAM 역할, 프록시 DB의 모든 제공사 키, 마스터 키와 가상 키, DATABASE_URL 비밀번호, 그 호스트 프로세스 환경 변수에 있던 시크릿을 모두 교체해야 합니다. 개발자 PC였다면 클라우드 CLI, GitHub, SSH, AI 도구 토큰과 브라우저 세션도 포함합니다. 교체 작업은 깨끗한 기기에서 하세요.
  5. CloudTrail에서 설치 시점 전후로 인스턴스 역할의 GetCallerIdentity 호출이 있었는지, 그 자격증명이 우리 인프라 밖에서 쓰였는지 확인하세요.
  6. 교체한 옛 키가 정말 죽었는지 발급처에서 확인하세요. 새 키를 만든다고 옛 키가 폐기되지는 않습니다.

LiteLLM을 쓰지 않는 팀이 점검할 것

공격자가 넘은 통제는 하나씩 떼어 보면 어느 회사에나 있는 것들입니다. 우리 팀에도 다음 같은 곳이 없는지 확인해 보세요.

  • 지우기만 하고 폐기하지 않은 키. 공개 레포에서 키 때문에 커밋을 되돌리거나 파일을 지운 적이 있다면, 그 키가 지금도 인증되는지 확인하세요.
  • 서비스 안에 들어간 개인 토큰. 봇이나 에이전트가 권한을 좁힌 토큰 대신 직원 개인의 gh CLI 토큰이나 PAT로 돌고 있지 않은지 보세요. LiteLLM 보고서에 따르면 GitHub CLI OAuth 토큰은 권한 범위를 좁힐 수 없고, 2FA를 마친 뒤 발급되며, 조직 소유자의 민감한 작업에도 2FA를 다시 묻지 않습니다.
  • 저장된 시크릿을 그대로 돌려주는 내부 API. 설정 조회 API가 토큰 원문을 응답에 담지 않는지 확인하세요.
  • 개인 토큰이 든 Actions 시크릿. 워크플로를 푸시할 수 있는 사람은 그 시크릿을 꺼낼 수 있습니다. 시크릿마다 누구의 토큰이고 어디까지 열 수 있는지 정리돼 있어야 합니다.
  • 느슨해진 승인 조건. 릴리스 승인이 '아무나 두 명'이면 토큰 두 개를 쥔 공격자도 두 명으로 칩니다.
  • 키가 모여 있는 시스템. LLM 게이트웨이, CI 러너, 공용 빌드 서버가 뚫리면 그 안에 있던 키 전부가 교체 대상입니다.

Cremit으로 확인할 수 있는 것

LiteLLM 내부 시스템은 저희가 볼 수 없습니다. Agent Platform 관리자 키가 어떤 형식인지도 공개되지 않았으니, 저희가 그 키를 찾아냈을 거라고 말할 수도 없습니다.

Cremit Platform은 우리 회사 쪽을 봅니다. 연결한 GitHub 저장소를 지금 있는 파일만이 아니라 커밋 기록까지 스캔하기 때문에, 파일을 지운 뒤에도 기록에 남은 자격증명을 찾아냅니다. 찾은 키 가운데 검증을 지원하는 유형은 발급처에 읽기 전용으로 요청을 보내 지금도 인증되는지 확인합니다. GitHub OAuth 토큰(gho_)과 PAT, AWS 키가 여기에 들어갑니다. "지웠으니 끝났다"며 넘어간 키도 인벤토리에서는 아직 활성 상태로 표시됩니다.

같은 인벤토리에서 AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault에 보관한 시크릿도 함께 볼 수 있습니다. 사고가 터졌을 때 "그 시스템에 어떤 키가 있었고, 교체한 키는 정말 꺼졌는가"에 답하려면 이 목록부터 있어야 합니다.

지운 키는 폐기한 키가 아닙니다

LiteLLM은 5월에 실수를 알아채고 세 시간 만에 파일을 지웠습니다. 빠뜨린 건 키 폐기 하나였습니다. 그 키가 다섯 달 뒤 조직 소유자 토큰, Actions 시크릿 두 개를 거쳐 서명된 릴리스로 이어졌습니다.

키가 노출됐다면 파일을 지우는 데서 멈추지 마세요. 발급처에서 키를 폐기하고, 그 키로 닿을 수 있던 다른 키까지 확인해야 대응이 끝납니다.

출처

함께 읽기

네트워크에 공유해보세요LinkedInX

이어서 읽기

다음 글을 메일로 받아보세요

Cremit의 월간 자격증명 보안 브리프. 한 통에 핵심만 담습니다.

이메일을 판매하거나 외부에 넘기지 않으며, 언제든 수신거부할 수 있습니다.