Hacker News

Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor

PyPI의 LiteLLM 1.107.0에 자격 증명 탈취기와 백도어가 삽입됐습니다

PyPI에 배포된 LiteLLM 1.107.0에는 Python 프로세스가 시작될 때 실행되는 자격 증명 탈취기와 원격 코드 실행 백도어가 들어 있었습니다. SafeDep은 공개 저장소에 없는 파일이 비공개 릴리스 파이프라인에서 공격자가 수정한 소스로 빌드됐을 가능성을 중간에서 높은 신뢰도로 판단했습니다. PyPI는 해당 버전을 제거했으며, 1.106.2와 두 릴리스 후보 버전은 깨끗한 것으로 확인됐습니다.

에디터 노트

이번 침해의 직접 원인은 공개 저장소의 커밋보다 배포 경로에서 악성 소스가 빌드에 들어간 데 있습니다. 휠이 정상 파이프라인에서 만들어진 흔적과 공개 GitHub Actions 기록의 부재를 바탕으로 SafeDep은 비공개 릴리스 파이프라인을 의심하지만, 원인 확정에는 BerriAI의 로그가 필요합니다. 출처 증명만으로는 워크플로가 어떤 소스를 빌드했는지 보장하지 않는다는 댓글의 지적도 이 사례의 범위와 맞닿아 있습니다. 댓글에서는 새 패키지 설치를 늦추는 조치가 감염을 줄일 수 있다는 의견과, 장기간 숨어 있는 공격에는 부족하다는 반론이 맞섭니다. 대기 시간은 악성 릴리스를 검토할 시간을 주지만, 이 사례처럼 이미 수정된 소스를 정상 파이프라인이 빌드하는 문제까지 해결하지는 않습니다. 샌드박싱과 자격 증명 격리는 탈취 가능한 정보를 줄이는 별도의 방어책으로 언급됐습니다. 사건의 원인 조사와 함께 빌드 소스 검증, 실행 권한 제한을 각기 다른 통제로 다뤄야 합니다.

AI 요약

SafeDep의 분석에 따르면 PyPI의 LiteLLM 1.107.0 배포물에는 자격 증명 탈취기와 원격 코드 실행 백도어가 포함됐습니다. 리눅스, macOS, Windows용 휠 7개 모두에 세 파일이 들어 있었으며, Python 프로세스가 LiteLLM을 직접 가져오지 않아도 실행됐습니다. PyPI는 해당 릴리스를 제거했습니다. 1.106.2와 1.107.0rc1, 1.107.0rc2, 그리고 1.107.0이 고정한 두 동반 패키지는 분석 당시 깨끗한 것으로 확인됐습니다.

설치와 실행 방식

cook.pth에는 import cook가 들어 있습니다. Python은 site-packages의 .pth 파일에서 import로 시작하는 줄을 실행하므로, cook.py에 담긴 탈취기가 애플리케이션뿐 아니라 pip, IDE 언어 서버, CI 작업 등 호스트의 Python 프로세스마다 실행됩니다. 이 코드는 SSH 키, 클라우드와 CI/CD 자격 증명, 브라우저 비밀번호, 개발 도구 토큰, 환경 변수, GPG와 TLS 개인 키 등을 수집합니다. LiteLLM 프록시 데이터베이스가 있으면 공급자 API 키가 담긴 테이블도 읽습니다.

수집한 데이터는 AES-256-CBC로 암호화하고, 암호화 키는 코드에 내장된 RSA 공개 키로 처리합니다. 결과를 tar.gz로 묶어 www.litellm[.]dev/healthcheck에 전송합니다. 업로드에 성공하면 호스트별 표식 파일을 남겨 같은 방식의 수집을 한 번으로 제한합니다.

sitecustomize.py도 Python 시작 시 실행됩니다. 백그라운드 데몬은 매시간 공격자 GitHub 저장소에서 서버 주소를 읽고 탈취기를 다시 실행한 뒤, 지정된 서버에서 코드를 내려받아 실행합니다. 공격자 계정과 저장소는 2026년 10월 21일 13:40 UTC에 404 상태가 됐습니다. 그 전에 데몬이 두 번째 단계 코드를 받아 실행했는지는 SafeDep이 확인하지 못했습니다.

공개 소스와 배포물의 차이

공개 GitHub 저장소에는 data = "cook" 설정이나 cook/ 폴더가 없습니다. 반면 1.107.0의 소스 배포본에는 Maturin 설정에 이 줄이 추가돼 있었습니다. 이 설정이 cook/ 폴더를 휠에 복사하고, pip가 해당 파일을 site-packages에 설치하도록 합니다. 휠의 RECORD에도 추가 파일 세 개가 기록돼 있어 빌드 뒤에 파일을 덧붙인 흔적은 없다고 SafeDep은 설명합니다.

릴리스 태그는 1.108.0을 가리키는 커밋을 기준으로 하지 않았습니다. SafeDep의 비교 결과, 소스 배포본은 이후 main 커밋을 바탕으로 하되 다섯 파일을 수정하고 cook/ 폴더를 추가했습니다. pyproject.toml에서는 버전을 1.107.0으로 낮추고 동반 패키지 버전을 1.107.0rc2와 같은 값으로 되돌렸습니다. 그래서 RC2와 의존성만 비교하면 이상이 드러나지 않습니다.

빌드 경로와 조사 한계

악성 휠은 정상 BerriAI 릴리스 파이프라인의 산출물과 일치하는 특징을 보였습니다. 정상 릴리스와 같은 Maturin 1.9.4를 사용했고, 파일별 빌드 시각과 Windows 휠의 CRLF 줄바꿈도 각각의 네이티브 빌드 및 Windows 체크아웃에서 생기는 형태와 맞았습니다. 공개 GitHub Actions의 539개 실행 기록에는 해당 릴리스를 빌드하거나 게시한 작업이 없었습니다. PyPI 업로드 약 10분 뒤 GitHub 릴리스가 게시됐습니다.

SafeDep은 비공개 릴리스 파이프라인이 공격자가 수정한 Git 소스로 휠을 만들었을 가능성을 중간에서 높은 신뢰도로 평가했습니다. 유지관리자나 봇의 자격 증명 탈취, 비공개 브랜치나 미러, 파이프라인이 참조하는 소스의 변조 등이 가능한 경로로 제시됐습니다. 공격자가 자신의 장비에서 탈취한 PyPI 토큰으로 직접 올렸다는 설명은 정황에 덜 맞는다고 봤습니다. 원인을 확정하려면 BerriAI의 파이프라인 로그가 필요합니다. 1.107.0과 앞선 정상 버전들에는 PyPI 출처 증명이 없었습니다.

Hacker News 반응

  • @madamelic — 릴리스에 파일이 어떻게 들어갔는지 이해하지 못했습니다. cook.pth가 PyPI에서 자동으로 내려받는 의존성이었고 빌드 시스템이 의존성에 넣은 건가요?
    • @madamelic — 공격자가 유지관리자나 비공개 파이프라인을 침해해 로컬 복사본에 악성 변경을 넣었다는 뜻이라면 이해가 됩니다. 모두에게 영향을 주는 의존성을 가로챈 건 아니었군요. 영리한 방식입니다.
    • @aakashpawar — 그 설명이 맞다면 출처 증명만으로는 막지 못했을 겁니다. 출처 증명은 어떤 워크플로가 휠을 게시했는지 보여줄 뿐, 그 워크플로가 어떤 소스를 체크아웃했는지는 보장하지 않습니다. 공개 태그에서 다시 빌드해 결과물을 비교해야 잡을 수 있습니다.
  • @stbenjam — 제 생각에는 uv나 npm 같은 도구가 기본으로 24~48시간의 대기 기간을 둬야 합니다. 더 새 버전을 설치하려면 사용자가 일부러 설정을 바꾸게 해야 합니다.
    • @robinhoodexe — 내부 PyPI 프록시에서 7일 대기를 쓰지만 보안 업데이트도 막혀서 따로 제외해야 합니다. 이걸 어떻게 자동화할지 잘 모르겠습니다.
    • @mattstir — 공격자가 악성 릴리스를 중대한 보안 업데이트라고 표시하면 우회할 수 있습니다. 정말 복잡한 문제입니다.
    • @0c3ca83 — 제가 마지막으로 본 자료에서는 공급망 공격이 발견되기까지 중간값으로 수개월에서 수년이 걸렸습니다. 그보다 짧은 대기 시간은 효과가 없는 척하는 조치에 그칠 수 있습니다.
  • @petesergeant — 자격 증명 탈취기가 들어간 빌드에서 벗어나려면 기업용 비공개 저장소에 비용을 내야 한다는 인상을 줍니다. 공개되지 않은 두 번째 취약점도 기업용으로만 배포한다는 점이 우려됩니다.
    • @chias — LiteLLM 직원입니다. 두 번째 취약점은 기업용 배포에만 영향을 주고 OSS 배포에는 영향을 주지 않습니다. 고객에게 패치 버전을 먼저 제공하고 공개 저장소에 수정 사항을 내기 전까지 업데이트 시간을 주고 있습니다. 공개 오픈소스 제품에 패치를 올리면 취약점이 사실상 공개되기 때문입니다.
  • @crossroadsguy — 샌드박스 규칙도 이런 공격을 막지 못합니다. 권한 팝업에 지치면 사용자는 승인에 익숙해지고, 패키지 설치도 생각 없이 하게 됩니다.
    • @abhisek — 이 공격은 새 도메인과 이미 알려진 PTH 기법을 사용했습니다. 24시간 의존성 대기만으로도 소비자를 보호할 수 있습니다.
    • @lrvick — 일부 공격에는 효과가 있겠지만 수년간 발견되지 않는 공격도 많습니다. 대기 시간을 공급망 공격의 해결책처럼 취급하면 실제 문제를 놓칠 수 있습니다.
    • @isityettime — 샌드박싱은 여전히 위험을 줄입니다. LiteLLM이 실제 자격 증명에 직접 접근하지 않게 하고, 프록시를 거쳐 제한된 네트워크로만 연결하도록 구성할 수 있습니다.
  • @hmokiguess — LiteLLM의 공지에 따르면 OSS 사용자는 1.106.2나 그 이전 버전에 고정하고, 비공개 저장소를 쓰는 기업 고객은 영향을 받지 않았습니다.
    • @wongarsu — 다음 주 공개될 취약점을 고친 버전을 유료로 먼저 제공한다면, 다른 LiteLLM 인스턴스를 공격하는 데 필요한 정보까지 미리 파는 셈 아닌가요?
    • @chias — 해당 취약점은 기업용 배포에만 영향을 줍니다. 고객이 업데이트할 시간을 준 뒤 공개 저장소에 수정 사항과 취약점 정보를 공개할 예정입니다.

원문: SafeDep / 번역·요약: Trawling