오픈소스 패키지 하나가 전체 CI/CD를 무너뜨린다 — 공급망 공격의 실제 구조 (socket.dev)
목차(5)
한줄 요약
오픈소스 의존성 하나가 감염되면 전체 빌드 환경과 클라이언트 시크릿이 동시에 노출된다.
본문
소프트웨어 공급망 공격(Supply Chain Attack)이란, 개발자가 직접 작성한 코드가 아니라 그 코드가 의존하는 외부 패키지나 빌드 파이프라인을 침투 경로로 활용하는 사이버 공격 방식이다. 최근 들어 이 방식은 단순한 악성 패키지 배포를 넘어, CI/CD 파이프라인과 GitHub Actions 같은 자동화 인프라를 직접 노리는 수준으로 정교해지고 있다.
IT 개발 에이전시 입장에서 이 문제는 남의 일이 아니다. 다수의 클라이언트 프로젝트를 동시에 진행하는 에이전시 환경에서는 패키지 하나의 감염이 여러 프로젝트의 빌드 환경, 시크릿, 클라우드 자격증명으로 동시에 번질 수 있다.
실제 공격은 어떤 방식으로 작동하나?
최근 보안 커뮤니티에서 주목받은 사례들을 보면 공격 패턴이 놀랍도록 일관적이다. 공격자는 합법적인 오픈소스 패키지의 빌드 파이프라인에 침투해 악성 파일을 함께 배포한다. 이 파일은 패키지 설치 시점에 실행되는 훅(hook)을 이용해 조용히 동작한다.
감염된 패키지가 실행되면 다음과 같은 행동이 순차적으로 일어난다.
- 자격증명 수집: GitHub 토큰, npm 토큰, AWS/Azure/GCP 클라우드 자격증명, SSH 키, 환경변수 전체를 탈취한다.
- 외부 서버로 전송: 공격자가 운영하는 C2(Command & Control) 서버로 수집한 데이터를 암호화해 전송한다.
- 지속성 확보: 셸 프로파일 파일을 수정해 재부팅 후에도 악성 코드가 다시 실행되도록 설정한다.
- 공급망 전파: 탈취한 npm 토큰으로 피해자가 관리하는 다른 패키지에 악성 코드를 심어 재배포한다.
핵심은 속도다. 개발자가 이상 징후를 인지하기 전에 이미 모든 시크릿이 빠져나간 상태가 된다.
에이전시 환경이 특히 위험한 이유
일반 기업의 내부 개발팀과 달리, 개발 에이전시는 구조적으로 공격 표면이 넓다.
단일 빌드 환경에 복수의 클라이언트 시크릿이 공존한다. 같은 CI/CD 러너에서 A 클라이언트의 배포 파이프라인과 B 클라이언트의 테스트 파이프라인이 함께 돌아가는 경우, 하나의 패키지 감염이 두 클라이언트의 자격증명을 동시에 노출시킨다.
의존성 관리가 분산되어 있다. 프로젝트마다 담당 개발자가 다르고, 패키지 버전 고정이나 잠금 파일(lock file) 관리가 일관되지 않으면 악성 버전이 조용히 설치될 가능성이 높아진다.
외부 패키지 신뢰도를 검증할 체계가 없다. 유명 패키지라도 특정 버전이 감염될 수 있다. 패키지 이름이 동일하고 배포자도 같더라도, 특정 버전만 악성 코드를 포함하는 경우가 실제로 발생하고 있다.
에이전시가 지금 당장 해야 할 것
기술적 대응보다 먼저 필요한 건 구조적 분리다.
클라이언트별 빌드 환경 격리가 가장 중요하다. 동일한 러너나 서버에서 여러 클라이언트의 파이프라인을 실행하지 않는다. 클라우드 환경이라면 프로젝트별로 별도의 서비스 계정과 권한 범위를 설정하고, 각 파이프라인이 접근할 수 있는 시크릿을 최소화한다.
패키지 버전을 고정하고 변경을 추적한다. package-lock.json 또는 yarn.lock 파일을 반드시 커밋하고, PR에서 의존성 변경이 발생할 때 자동 알림을 받도록 설정한다. 버전이 자동으로 올라가는 환경은 공격자에게 열린 문과 같다.
GitHub Actions 권한을 최소화한다. 워크플로우 파일에 필요 이상의 권한을 부여하지 않는다. GITHUB_TOKEN의 기본 권한을 read-only로 설정하고, 필요한 경우에만 명시적으로 write 권한을 추가한다. 외부 액션을 사용할 때는 태그 대신 커밋 해시로 버전을 고정한다.
자격증명은 주기적으로 교체한다. 장기 유효 토큰은 공격자에게 장기간 접근권을 제공한다. GitHub PAT, npm 토큰, 클라우드 서비스 키는 90일 이내 주기로 교체하고, 사용하지 않는 토큰은 즉시 폐기한다.
자주 묻는 질문
Q.오픈소스 패키지를 사용하는 것 자체가 위험한가?
오픈소스 패키지 사용 자체가 문제는 아니다. 문제는 검증 없이 신뢰하는 태도다. 패키지 다운로드 수나 인지도와 관계없이 특정 버전이 악성 코드를 포함할 수 있다. 버전 고정, 의존성 변경 추적, 설치 훅 모니터링을 병행하면 위험을 크게 줄일 수 있다. 완전한 제거보다는 관리된 신뢰가 현실적인 접근이다.
Q.이미 감염된 패키지를 설치했다면 어떻게 해야 하나?
우선 해당 패키지를 즉시 제거하고 빌드 환경을 초기화한다. 그 환경에 있던 모든 자격증명, 즉 GitHub 토큰, npm 토큰, 클라우드 키, SSH 키를 전부 폐기하고 새로 발급한다. 클라우드 환경이라면 비정상적인 API 호출 이력과 새로 생성된 리소스를 감사한다. GitHub에서는 최근 생성된 저장소, 워크플로우 파일, 아티팩트를 점검한다. 빠른 대응이 피해 범위를 결정한다.
Q.클라이언트에게 이런 보안 리스크를 어떻게 설명해야 하나?
기술적 용어보다 비즈니스 영향으로 설명하는 것이 효과적이다. "개발 환경이 감염되면 클라이언트의 데이터베이스 접근 키, 결제 API 키, 내부 시스템 접근 권한이 외부로 유출될 수 있다"고 직접적으로 전달한다. 에이전시가 어떤 격리 구조와 모니터링 체계를 갖추고 있는지를 함께 제시하면 신뢰를 유지하면서 협의를 진행할 수 있다.