소프트웨어 공급망 공격이 개발자의 신뢰를 어떻게 악용하는가

지원 종료(EOL)된 PHP용 php2shell 패치(CVE-2026-109618 및 CVE-2026-109619)가 출시되었습니다. 수요가 많아 평소보다 영업팀의 답변이 늦어지고 있으므로, 패치된 패키지를 가장 빠르게 확보하려면 온라인에서 Runtimes용 ELS를 구매하시기 바랍니다 (새 탭에서 열림). 패치 FAQ 읽어보기

이 블로그 글의 AI 요약으로 바로 가기

  • Perplexity를 사용하여 이 게시물을 요약해 주세요
  • Gemini로 이 게시물을 요약해 주세요

주요 내용

  • 오타와 AI 환각은 모두 공격자에게 활용 가능한 패키지 이름을 제공하며, 둘 다 익스플로잇이 필요하지 않습니다.
  • 출처는 유물이 어디서 유래했는지를 증명할 뿐, 그 유물의 출처가 된 커밋이나 계정이 정당한 것인지는 증명하지 않습니다.
  • 주의를 기울이는 것만으로는 확장성이 떨어지므로, 작업을 고정된 종속성, 결정론적 라우팅 및 스크립트 허용 목록으로 이전하십시오.

패키지를 설치할 때마다 여러분이 신뢰하고 있는 사람들의 연결 고리가 얼마나 긴지 생각해 본 적이 있나요? 코드를 작성한 사람. 코드를 검토한 사람. 코드를 호스팅하는 레지스트리. 새벽 3시에 아무도 지켜보지 않는 사이 코드를 가져오는 CI 실행기. 공격자들은 패키지 생태계를 샅샅이 뒤지는 데 능숙하며, 그 사슬에서 가장 취약한 고리를 찾아내는 데도 매우 능숙해졌습니다. 누구도 검토할 틈도 없이 코드를 너무 빨리 설치하면 수많은 문제가 발생합니다.

대부분의 소프트웨어 공급망 공격은 이런 식으로 이루어집니다. 공격자들은 보안 경계를 뚫을 필요가 없으며, 신뢰할 수 있는 경로를 원래 의도된 대로 정확히 활용합니다.

다음은 신뢰가 어떻게 악용되는지, 그리고 실제로 도움이 되는 방법은 무엇인지에 대한 간략한 개요입니다.

패키지의 오타

익스플로잇이 필요 없는 가장 오래된 수법입니다. 그냥 빨리 입력하고 자세히 보지 않기만 하면 됩니다. 공격자는 글자 하나나 하이픈 하나만 틀린 패키지를 공개한 뒤, 누군가가 실수로 이를 설치하기를 기다립니다.

그리고 crossenv 패키지가 대표적인 예입니다. 2017년 7월, ‘hacktask’라는 닉네임을 사용하는 한 npm 사용자가 약 40개의 유사 패키지를 공개했는데, 그중에서도 특히 눈에 띄었던 것은 cross-env 하이픈이 빠진 상태였습니다. 그 한 글자만 놓쳤다면, 설치한 프로그램이 환경 변수를 읽어 공격자가 제어하는 서버로 전송했을 것입니다.

{
  "name": "crossenv",
  "scripts": {
    "postinstall": "node package-setup.js"
  }
}

npm의 사고 보고서 페이로드 스크립트의 이름을 지정합니다 package-setup.js. 간단히 말해, 다음과 같은 역할을 했습니다:

// Simplified illustration of what the script did.
// Reads the environment variables available to the
// install, then sends them to the attacker's server.
const vars = process.env;
sendToAttackerServer(vars);

개발자 컴퓨터나 CI 러너에서 이러한 변수에는 종종 레지스트리 토큰, 클라우드 인증 정보, 데이터베이스 비밀번호가 저장되어 있기 때문에, 이를 탈취할 가치가 있습니다. npm은 679건의 다운로드를 기록했는데, crossenv 하지만 미러가 아닌 실제 설치 사례는 50개 정도에 불과할 것으로 추정되었다.

타이포스쿼팅이 통하는 이유는 해결책이 단순한 ‘패치’가 아니기 때문입니다. 진정한 ‘해결책’은 주의를 기울이는 것이지만, 주의를 기울이는 일은 확장성이 없다는 사실을 우리 모두 잘 알고 있습니다. 그러니 확장성이 있는 곳으로 작업을 옮겨야 합니다. 의존성을 고정하고, 코드 변경 사항만큼이나 꼼꼼하게 락파일 변경 사항을 검토하며, 승인된 내부 소스에서 해결책을 찾아야 합니다.

AI를 활용한 스쿼트 (슬롭스쿼트)

슬롭스쿼팅은 다른 실수를 악용하는 동일한 수법입니다. 오타 대신, 코딩 도우미가 존재하지 않는 패키지를 추천하는 점을 악용하여, 공격자가 실제 프로젝트가 해당 이름을 등록하기 전에 먼저 그 이름을 등록할 기회를 얻게 만듭니다.

USENIX Security 2025의 패키지 환각 현상에 대한 연구에서, 16개의 모델이 223만 개의 패키지를 참조하는 576,000개의 코드 샘플을 생성했습니다. 이 테스트 전반에 걸쳐 참조의 19.7%(440,445건)는 존재하지 않는 패키지를 지칭했으며, 이는 205,474개의 고유한 가상 이름을 포함했습니다. 연구진이 이미 환각 현상을 일으켰던 프롬프트를 다시 실행했을 때, 가상 이름의 43%가 10회 실행 모두에서 다시 나타났는데, 이는 이러한 이름을 사전에 등록해 두는 것이 유용할 수 있음을 시사합니다.

huggingface-cli 사례가 좋은 예입니다. Lasso Security의 보안 연구원 바르 라냐도 (Bar Lanyado ) 는 PyPI에 해당 패키지가 존재하지 않음에도 불구하고 모델들이 그 이름을 추천하는 것을 발견했고, 이에 2023년 12월 빈 패키지를 등록했습니다. 이 패키지는 3개월 동안 3만 건 이상의 실제 다운로드를 기록했으며, 알리바바의 공개 연구 저장소 README 파일에는 잘못된 설치 명령어가 포함되어 있었습니다.

# What an AI assistant might confidently suggest
pip install huggingface-cli

# What actually installs the official Hugging Face CLI
pip install -U "huggingface_hub[cli]"

두 번째는 공식 버전을 설치합니다. huggingface_hub CLI 추가 기능이 포함된 패키지입니다. 전자는 모델이 임의로 지정한 이름 아래 등록된 배포판을 무조건 설치합니다. Lanyado의 경우 의도적으로 무해하게 설계되었지만, 다른 누군가가 만든 것이라면 반드시 그렇지는 않을 수 있습니다. 따라서 프로젝트가 실제로 존재하는지 확인하고, 게시자와 릴리스 이력을 검토한 뒤, 락파일에 반영되기 전에 승인을 받아야 합니다.

의존성 혼동

의존성 혼동은 해결 실패를 의미합니다. 이는 기업이 공개 패키지와 비공개 패키지를 함께 사용하며, 빌드 과정에서 하나의 이름이 여러 출처에서 충족될 수 있는 경우 어디서나 발생합니다.

세부 사항은 생태계에 따라 다릅니다. pip가 가장 대표적인 예입니다. 다음 명령어를 통해 추가 인덱스를 지정하면 --extra-index-url 그리고 npm은 구성된 모든 인덱스의 후보를 검토한 후, 그중에서 가장 잘 맞는 것을 선택하는데, 이로 인해 버전이 더 높은 공개 패키지가 선정될 수도 있습니다. npm은 피드를 병합하는 대신 범위와 구성된 레지스트리에 따라 해결하므로, 이러한 노출은 대개 범위가 지정되지 않은 내부 이름이나, 네임스페이스나 우선순위 정책 없이 공개 및 비공개 리소스를 하나의 뷰로 통합하는 프록시에서 비롯됩니다.

알렉스 버산 (Alex Birsan )은 2020년과 2021년에 내부 패키지 이름이 공개 저장소 및 오류 메시지에 유출되는 것을 발견한 후 이 문제를 입증했습니다. 그는 해당 이름으로 버전 번호를 매우 높게 설정한 무해한 패키지들을 npm, PyPI, RubyGems에 공개했고, 이 패키지들이 애플, 마이크로소프트, 페이팔을 포함한 35개 이상의 기업에서 내부 빌드에 포함되는 것을 지켜보며 13만 달러 이상의 버그 바운티를 받았습니다.

{
  "name": "internal-auth-tool",
  "version": "99.0.0"
}

위험한 부분은 JSON 자체가 아닙니다. 그 이름에 대해 신뢰할 수 있는 단일 출처를 절대 지정하지 않는 구성의 빌드 과정입니다. npm의 경우, 내부 패키지의 범위를 제한하고 해당 범위를 비공개 레지스트리에 고정해야 합니다.

# .npmrc
registry=https://registry.npmjs.org/
@your-org:registry=https://your-private-registry.example.com/

이러한 패턴은 npm에 특정한 것이며, 레지스트리 라우팅, 네임스페이스 소유권, 게시 권한이 모두 일치하는 경우에만 성립합니다. 스코프가 없는 생태계는 자체적인 소스 고정 메커니즘이 필요합니다. pip의 경우, 이는 다음을 처리하지 않는 것을 의미합니다. --extra-index-url 'private-first' 방식의 대체 방안으로서.

납치된 관리자

이번 사례에서는 허울뿐인 속임수가 사라집니다. 공격자는 사용자가 이미 신뢰하는 패키지 뒤에 숨어 계정이나 릴리스 경로를 장악한 뒤, 프로젝트의 정상적인 릴리스 인프라를 통해 악성 업데이트를 배포합니다. 이는 코드 취약점을 악용한 것이 아니라, 신뢰 관계를 악용한 공격입니다.

2026년 8월 4일, 바로 그런 일이 일어났습니다. keyv 그리고 이 프로젝트와 관련된 다른 캐싱 관련 프로젝트들. 공격자들은 유지보수자의 GitHub 계정을 해킹하여 메인 브랜치에 악성 커밋을 푸시했고, 프로젝트 자체의 Actions 워크플로우를 통해 이를 빌드하고 공개하도록 했다. ‘ChainDrop’이라는 별명을 가진 이 웜은 해킹당한 환경에서 노출된 모든 인증 정보를 수집한 뒤, 훔친 npm 토큰을 이용해 다른 유지보수자들의 패키지에 자신을 다시 배포했다. StepSecurity는 444개의 패키지와 2,212개의 악성 버전을 집계했습니다. 4시간 이내에; 다른 이들은 그보다 늦게 더 높은 수치를 기록했다.

유해한 릴리스에는 유효한 출처 정보가 포함되어 있었으며, 이는 출처 정보의 오류가 아니었습니다. 이는 해당 아티팩트가 선언된 입력값을 바탕으로 프로젝트 자체의 워크플로를 통해 생성되었음을 정확하게 입증한 것이었습니다. 다만, 해당 커밋이 정당한 것이었는지, 또는 그 커밋을 수행한 계정이 여전히 소유자의 것이었는지는 알 수 없습니다.

많은 팀은 그런 선택을 할 필요가 전혀 없었다 keyv 전혀. flat-cache 그리고 file-entry-cache ESLint의 하위 범주에 속하며, keyv 그 아래에 자리 잡고 있다.

설치 전 및 설치 후 스크립트

설치 스크립트는 별도의 공격 유형이라기보다는 다른 여러 공격 유형이 활용하는 실행 경로에 가깝습니다. 패키지는 다음을 선언할 수 있습니다. preinstall, install또는 postinstall 작동했고, 역사적으로 보면 설치 과정에서, 아무도 그 내용을 한 줄도 읽기 전에 실행되었습니다.

이러한 타이밍이 매우 중요합니다. 설치 과정은 겉보기에는 평범해 보이지만, 스크립트는 개발자나 CI 실행자의 권한으로 실행됩니다. ChainDrop도 같은 방식으로 악성 설치 후크를 활용했습니다.

2021년 ua-parser-js ‘compromise’가 대표적인 예입니다. 유지보수자의 npm 계정이 해킹당한 후, 세 개의 악성 버전이 preinstall 리눅스에서는 암호화폐 채굴 프로그램을, 윈도우에서는 채굴 프로그램과 함께 인증 정보를 탈취하는 악성코드를 설치하는 후크가 발견되었습니다. 이 프로그램들은 사고 대응 기준으로는 빠른 편인 약 4시간 만에 삭제되었습니다. 해당 패키지는 주당 수백만 건의 다운로드를 기록하고 있었기 때문에, 사용자가 이름을 직접 입력하지 않아도 자동 빌드 과정을 통해 다운로드되었습니다.

{
  "scripts": {
    "preinstall": "node setup.js"
  }
}

드디어 디폴트들이 움직이기 시작했다. pnpm v10 이후로는 명시적인 승인 없이는 종속성 빌드 스크립트를 실행하지 않았으며, 현재 릴리스에서는 이러한 결정 사항을 allowBuilds 지도, 그리고 npm 12개 블록의 종속성 설치 스크립트는 다음에서 다루지 않습니다. allowScripts. 이 기본값이 어떻게 동작하는지 파악해야 합니다. 스크립트는 경고와 함께 건너뛰어지지만 설치는 여전히 성공하므로, 네이티브 종속성이 해당 바이너리가 빌드되지 않은 상태에서도 패키지에 포함될 수 있습니다. 패키지를 승인하면, npm rebuild 건너뛴 설치 작업을 실행하며, CI 환경에서는 --strict-allow-scripts 검토되지 않은 스크립트가 조용히 반쯤 구축된 상태로 남는 대신 설치 실패로 이어지게 합니다.

구버전 npm 릴리스가 무방비 상태인 것은 아닙니다. 단지 기본 설정이 반대일 뿐입니다.

npm install --ignore-scripts

그 방법은 너무 무뚝뚝합니다. 설치 시점에 정상적으로 컴파일되는 패키지조차 작동하지 않게 만들 뿐만 아니라, 애플리케이션이 해당 코드를 불러올 때까지 기다리는 악성 코드에 대해서는 아무런 조치도 취하지 않습니다. 명시적인 허용 목록을 진정한 해결책으로 삼아야 합니다.

왜 이 모든 것이 계속 잘 돌아가는 걸까

이러한 공격들은 어느 하나도 한 사람이나 하나의 도구만으로는 해결할 수 없습니다. 팀이 프로세스에서 모호성을 제거하면 이러한 공격은 더욱 어려워집니다. 즉, 범위 지정된 비공개 패키지, 결정론적 레지스트리 라우팅, 검토를 거친 잠금 파일(lockfile)에 따른 고정된 설치, 기본적으로 허용되는 대신 예외적으로만 허용되는 설치 스크립트, 그리고 누군가가 확인하기 전까지는 검증되지 않은 것으로 간주되는 AI 의존성 제안 등이 그 예입니다.

하지만 모든 엔지니어가 모든 설치 과정에서 모든 문제 있는 패키지를 잡아내기를 기대하는 것은 보안 정책이 아닙니다. 그 패키지는 어딘가에서 들어와야 하며, 그 ‘어딘가’는 여러분의 보안 경계의 일부입니다.

바로 이것이 SecureChain이 구축된 목적입니다. 공개 레지스트리에서 아티팩트를 직접 가져오는 대신, 패키지 관리자를 선별된 소스 저장소로 지정하면, 해당 저장소에서는 검증된 소스 코드를 기반으로 격리된 환경에서 패키지를 재빌드하고, 악성코드를 검사하며, 새로운 CVE가 발견될 때마다 패치를 적용합니다. 또한 감사 목적으로 서명된 빌드와 출처 정보도 제공됩니다. 이 서비스는 JavaScript, Python, Java, Go, Rust, PHP를 지원하며, 개발자는 익숙한 패키지 관리자를 계속 사용할 수 있습니다.

문제가 악성 패키지가 아닌 유지보수가 중단된 패키지인 경우, Endless Lifecycle Support(ELS) 는 사용자가 이미 실행 중인 버전으로 보안 수정 사항을 역이식하므로, 업스트림 지원이 종료된 후에도 패치가 계속 제공됩니다.

마지막으로 드리고 싶은 말씀

신뢰는 현대 소프트웨어 개발의 핵심 요소이지만, 악용되는 것은 바로 맹목적인 신뢰입니다. 공격자들은 오타, AI, 해킹당한 계정, 혹은 익숙한 패키지가 여전히 자신이 생각했던 그대로일 것이라는 가정 등을 통해 침투할 방법을 모색합니다. 우리는 공개 웹에서 일어나는 모든 일을 통제할 수는 없습니다.

여러분이 통제할 수 있는 한 가지는 바로 환경에 배포되는 내용입니다. 이러한 공격이 어떻게 작동하는지 이해하고, 빌드 파이프라인을 철저히 보호하며, 레지스트리와 스크립트 권한을 관리하고, 공개 레지스트리와 프로덕션 환경 사이에 검증된 소스를 배치하십시오. 배포하는 코드는 여러분이 실행하고자 했던 코드여야 합니다.

요약
소프트웨어 공급망 공격이 개발자의 신뢰를 어떻게 악용하는가
기사 이름
소프트웨어 공급망 공격이 개발자의 신뢰를 어떻게 악용하는가
설명
타이포스쿼팅, AI가 생성한 패키지, 의존성 혼동, 관리자 계정 탈취, 설치 스크립트 등을 실제 사례를 통해 설명합니다.
작성자
게시자 이름
TuxCare
게시자 로고

4,500명 이상의 Linux 및 오픈 소스 전문가와 함께하세요!

한 달에 두 번. 스팸이 없습니다.

목차
필요한 오픈 소스 보안에 대한 답변 얻기

4,500명 이상의 Linux 및 오픈 소스 전문가와 함께하세요!

한 달에 두 번. 스팸이 없습니다.