한줄 요약
오픈소스 의존성 하나가 감염되면 전체 빌드 환경과 클라이언트 시크릿이 동시에 노출된다.
본문
소프트웨어 공급망 공격(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일 이내 주기로 교체하고, 사용하지 않는 토큰은 즉시 폐기한다.