Python's binary wheels and supply chain attacks
Python 바이너리 휠과 공급망 공격
PyPI는 설치 때 소스 배포판보다 미리 컴파일한 바이너리 휠을 우선 사용합니다. 글쓴이는 휠의 바이너리가 함께 올라온 소스 코드와 일치하는지 보장하는 절차가 부족해 악성 코드가 검사망을 피할 수 있다고 지적하고, 의존성을 직접 빌드하자고 제안합니다.
- 주제
에디터 노트
글의 우려는 휠을 올린 계정이 자기 패키지를 배포하더라도 바이너리와 공개 소스가 다를 수 있다는 점입니다. 댓글에서도 다른 사람의 패키지에 무단으로 바이너리를 올리는 상황이 아니라는 점을 짚습니다. 출처 증명은 게시 경로를 확인하지만 소스와 결과물의 일치까지 보장하지 않는다는 반론도 글의 문제 제기와 맞닿습니다. 의존성을 전부 직접 빌드하자는 해법에는 비용과 운영 부담이 따릅니다. 댓글은 빌드 스크립트 취약점과 전이 의존성 관리 문제도 제기합니다. 저장소가 직접 빌드하면 소스와 바이너리의 연결을 확인하기 쉬워지지만, 모든 패키지를 대상으로 할 인프라 비용은 별도 쟁점으로 남습니다. 글은 직접 빌드를 유일한 해법으로 제시하지만, 댓글 논의는 출처 증명만으로는 부족하고 빌드 주체와 검증 비용까지 정해야 한다는 점을 보여줍니다.
AI 요약
Python 패키지의 빠른 실행 속도는 C, C++ 또는 Rust로 작성한 컴파일 코드에 기대는 경우가 많습니다. PyPI는 설치 과정에서 미리 컴파일한 바이너리 배포 형식인 휠(wheel)을 소스 배포판보다 우선 설치합니다. 글쓴이의 패키지는 Python 버전과 운영체제, 아키텍처 조합에 맞춰 한 릴리스에 최대 90개의 휠을 배포합니다. 휠이 없으면 사용자가 설치할 때 직접 컴파일해야 하며, 컴파일러와 시스템 의존성도 준비해야 합니다.
소스 검사와 바이너리 검사
PyPI에는 악성 패키지가 자주 올라옵니다. 글쓴이는 스캐너들이 주로 소스 코드를 분석하며 AST 분석, 샌드박스, LLM 등을 이용한다고 설명합니다. 하지만 패키지에 깨끗한 소스 코드와 악성 바이너리를 함께 올릴 수 있고, PyPI에는 바이너리가 그 소스 코드로 빌드됐는지 확인하는 절차가 제대로 마련돼 있지 않습니다. 바이너리 증명(binary attestation)도 의무가 아닙니다.
바이너리 악성 코드는 자동 분석이 어렵고 역공학이 필요합니다. 공격자가 특정 시점에만 작동하는 코드를 심거나 보안 소프트웨어가 드문 플랫폼을 노리면 탐지까지 시간이 걸릴 수 있다고 글쓴이는 말합니다. 글쓴이가 제안하는 대응은 Python 의존성을 처음부터 직접 빌드하고 최신 버전 설치를 피하는 것입니다.
Reddit 반응
- @u/lurch303 — 저장소를 복제하고 신뢰성을 증명하는 업체가 이미 몇 곳 있습니다. 이 사업은 수익성이 계속 있을 테고 모든 바이너리를 직접 빌드하고 신뢰성을 증명하는 일도 포함하리라 생각합니다.
- @u/Somepotato — 그중 하나인 Anaconda는 모르는 사람이 거기서 뭔가 내려받으면 회사에 소송을 걸겠다고 위협할 겁니다.
- @u/the_poope — PyPI가 바이너리 업로드를 막고, 몇 가지 대상 환경용으로 소스 코드를 컴파일하는 빌드 파이프라인을 제공하면 쉽게 대응할 수 있습니다.
- @u/f311a — 모든 패키지에 그렇게 하려면 비용이 많이 듭니다. 인기 패키지에는 적용할 수 있겠지만요. LLM 때문에 PyPI에 엉성한 패키지가 많아졌고 거의 매일 새 버전이 나옵니다.
- @u/PaintItPurple — 그렇게 하기는 쉽지만 모든 Python 패키지를 받는 저장소라는 PyPI의 목표를 유지하면서 실행하기는 어렵습니다.
- @u/darkslide3000 — PyPI는 아무나 유명 패키지의 바이너리를 올리도록 허용하나요? 원 작성자의 승인도 없이요? 대체 어떤 패키지 관리자입니까? 아니면 원 작성자가 악성 코드를 몰래 넣을 수 있다는 우려인가요? 그건 다른 오픈소스 프로젝트의 바이너리 배포보다 나쁠 것도 없습니다.
- @u/Solumin — 후자입니다. 소스 코드는 무해해 보이지만 완전히 다른 악성 코드를 담은 휠을 올릴 수 있습니다. 다른 사람의 패키지에 휠을 올리는 상황은 아닙니다.
- @u/f311a — 아무나 올리는 건 아닙니다. 하지만 계정이 해킹돼 악성 버전을 올린 사건이 적어도 다섯 번 있었습니다. 악성 코드가 잘 숨겨지면 하루 수만 번 설치되는 패키지가 일주일간 침해될 수도 있습니다.
- @u/fredisa4letterword — 이미 pip install --no-binary 옵션이 있습니다. 다만 모든 전이 의존성을 직접 빌드할 수 있어야 합니다. 설치가 훨씬 느리고 설정이 번거로우며 의존성 충돌도 생길 수 있습니다.
- @u/PaintItPurple — 그러면 글쓴이가 우려한 공격보다 훨씬 흔한 패키지 빌드 스크립트 취약점에 노출됩니다.
- @u/-Redstoneboi- — 재현 가능한 빌드(Reproducible Builds)를 위해 모든 사전 컴파일 휠이 같은 빌드 설정을 쓰도록 요구하면 어떨까요? 가능하면 바이트 단위로 똑같은 결과를 내는 방식으로요.
- @u/chucker23n — 그러려면 빌드 결과가 같은지 검증할 인프라가 필요합니다. 그럴 바엔 왜 작성자가 빌드를 올리게 두고 저장소가 직접 빌드하지 않나요?
- @u/-Redstoneboi- — 맞습니다. 작성자는 소스와 빌드 설정을 올리고 결과물을 검증하는 방법을 제공하면 됩니다. 사용자는 저장소에서 미리 컴파일한 바이너리를 내려받고 해시로 확인할 수 있습니다. 신중한 사용자는 소스를 내려받아 직접 빌드할 수도 있습니다.
- @u/chucker23n — 저장소가 해시를 제공한다면 작성자가 빌드 결과를 올릴 이유가 없습니다. 저장소가 직접 빌드해야 해시를 만들 수 있기 때문입니다. 상당한 인프라 비용이 들고 저장소는 그 비용을 원하지 않을 수 있습니다. 코드 서명이 더 현실적인 대안일 수 있지만 그것에도 단점이 있습니다.
- @u/bzbub2 — CI/CD에서 신뢰할 수 있는 게시자(trusted publishing)를 이용한 출처 증명(attested provenance)을 적용하면 됩니다. npm에는 있고 PyPI에도 있는 것 같습니다.
- @u/PaintItPurple — 그 방식은 어떤 시스템이 PyPI에 게시했는지 증명할 뿐, 결과물이 무해한지 또는 해당 버전의 Git 태그 소스와 일치하는지는 증명하지 않습니다. 공격 대상을 PyPI 토큰에서 GitHub Actions 같은 서비스의 토큰으로 옮깁니다. 그래도 막는 공격이 있겠지만 실제 공격 중 어느 정도를 예방할지는 궁금합니다.
- @u/teerre — 실제 공격 경로인 건 분명하지만 지금보다 훨씬 나빠질 가능성은 낮습니다. 아무도 몰랐던 비밀도 아니고 실행에 엄청난 컴퓨팅 자원이 필요한 일도 아닙니다.
원문: Rushter / 번역·요약: Trawling