gitlab

샤이훌루드 모방 캠페인, PyPI 타이포스쿼팅으로 파이썬 개발자 노려 (새 탭에서 열림)

2026년 6월, GitLab은 PyPI를 대상으로 한 Shai-Hulud 악성코드 모방 공급망 공격을 발견했다. 공격자는 Flask·Requests·NumPy의 오타 패키지와 정상 프로젝트를 변조한 패키지에 설치 시 자동 실행되는 자격 증명 탈취 웜을 삽입했다. 이 웜은 CI/CD와 클라우드 자격 증명을 훔칠 뿐 아니라 GitHub 저장소와 패키지 레지스트리에 악성 코드를 퍼뜨려 추가 감염을 시도한다.

공격 대상 패키지와 위장 방식

  • 공격 패키지 5개는 모두 PyPI 계정 elitexp에서 배포됐다.
  • 오타 패키지:
    • rlask, tlask: Flask 사칭
    • rsquests: Requests 사칭
    • nhmpy: NumPy 사칭
  • mflux-streamlit은 원래 정상적으로 사용되던 프로젝트였지만, 공격자가 버전 0.0.3, 0.0.4를 악성 버전으로 변조했다.
  • 공격자는 먼저 실제 최신 버전과 동일한 “탐색용” 정상 패키지를 등록한 뒤, 악성 페이로드가 포함된 후속 버전을 배포했다.
  • 따라서 단순 오타 입력뿐 아니라, 기존 프로젝트의 정상적인 의존성 업데이트도 감염 경로가 될 수 있다.

.pth 파일을 이용한 설치 시 자동 실행

  • npm 기반 Shai-Hulud 변종이 preinstall 스크립트를 사용한 것과 달리, 이번 공격은 Python의 .pth 메커니즘을 악용했다.
  • Python은 시작 시 패키지에 포함된 .pth 파일을 자동 처리하므로, 사용자가 악성 모듈을 직접 import하거나 함수를 호출하지 않아도 코드가 실행될 수 있다.
  • 예시 파일은 rlask-setup.pth이며, 임시 디렉터리의 .bun_ran 마커 파일을 확인한 뒤 다음 작업을 수행한다.
    • GitHub에서 Bun JavaScript 런타임 다운로드
    • 패키지에 포함된 약 5MB 크기의 난독화 JavaScript 실행
    • 마커 파일을 이용해 반복 실행 방지
  • 초기 rlask 버전에는 Python 시작 시 자동 import되는 sitecustomize.py도 포함되어 _index.js를 실행하는 보조 경로가 있었다.
  • 이후 버전에서는 .pth 실행 방식만 남겨 공격 구조를 단순화했다.

다층 난독화와 페이로드 구성

  • JavaScript 페이로드는 세 단계로 난독화됐다.
    • 정수 배열에 ROT-N 문자 치환 적용
    • AES-128-GCM으로 두 개의 데이터 블록 암호화
    • _0x 접두사의 변수명 난독화
  • 패키지마다 ROT 값이 달랐다.
    • rlask: ROT-13
    • rsquests: ROT-17
    • tlask: ROT-25
  • 분석 결과:
    • 첫 번째 블록 약 907바이트: Bun 런타임 다운로드 코드
    • 두 번째 블록 약 772KB: Shai-Hulud 자격 증명 탈취 웜 전체
  • 두 번째 페이로드에는 2,538개의 하드코딩된 문자열이 포함돼 있었다.
  • GitLab 연구팀은 실제 실행 없이 정적 분석만으로 페이로드를 복호화했다.

광범위한 자격 증명 탈취

웜은 주요 클라우드, CI/CD, 패키지 저장소와 개발 환경을 폭넓게 탐색한다.

  • GitHub:
    • GITHUB_TOKEN, 개인·Fine-grained 토큰
    • OIDC 토큰, 조직·저장소 시크릿
    • Actions 아티팩트와 러너 프로세스 메모리
  • AWS:
    • IAM 액세스 키와 시크릿 키
    • 세션 토큰, STS 토큰
    • IMDS 주소 169.254.169.254의 인스턴스 자격 증명
    • Secrets Manager와 SSM 파라미터
  • Azure 및 GCP:
    • 클라이언트 시크릿, 관리형 ID 토큰, Key Vault 시크릿
    • 서비스 계정 키와 Application Default Credentials
  • HashiCorp Vault:
    • /var/run/secrets/vault-token, /etc/vault/token, /root/.vault-token 등 알려진 경로
    • API 및 Kubernetes 인증 정보
  • 패키지 저장소:
    • npm, JFrog/Artifactory, PyPI, RubyGems 토큰
    • OIDC 토큰 교환 정보
  • 기타:
    • SSH 개인 키
    • Kubernetes 서비스 계정 토큰과 kubeconfig
    • Sigstore OIDC 토큰 및 Fulcio 서명 인증서
    • MongoDB, MySQL, PostgreSQL, Redis 접속 문자열과 비밀번호

자격 증명 탈취를 넘어선 자기 전파

  • 이 악성코드는 단순한 정보 탈취기가 아니라 훔친 권한을 이용해 다른 환경으로 확산하는 웜이다.
  • 접근 가능한 GitHub 저장소에 다음 파일을 커밋한다.
    • .github/setup.js
    • GitHub Actions 워크플로 파일
  • 이를 통해 다른 CI 파이프라인에서 악성 코드가 다시 실행되도록 만든다.
  • .github/copilot-instructions.md를 삽입해 AI 코딩 도구의 동작을 오염시키려 한다.
  • 훔친 레지스트리 토큰으로 PyPI, npm, RubyGems에 추가 악성 패키지를 배포한다.
  • 자체 호스팅 CI 러너에서는 sudoers 규칙을 삽입해 권한 상승을 시도한다.
  • StepSecurity의 harden-runner가 존재하는지 확인하고 탐지되면 동작을 조정한다.

공격자와 정상 프로젝트 변조

  • PyPI 계정 elitexp는 2024년 11월 생성됐으며, 원래 정상 패키지 mflux-streamlit을 보유하고 있었다.
  • 연결된 GitHub 계정은 13년 이상 된 계정으로, 대학 과제와 Laravel 프로젝트 등 다수의 공개 저장소가 있어 신뢰성을 높이는 데 활용됐을 가능성이 있다.
  • 모든 패키지의 업로드에는 Bun/1.3.14 사용자 에이전트가 사용됐다.
  • 공격자는 악성코드 실행 과정에서도 동일한 Bun 런타임을 다운로드한다.
  • mflux-streamlit0.0.1, 0.0.2는 정상 버전이지만, 이후 0.0.3, 0.0.4에는 동일한 .pth 드로퍼와 난독화 페이로드가 포함됐다.
  • 정상 프로젝트의 기존 사용자까지 일반적인 업데이트 과정에서 감염될 수 있다는 점이 전형적인 typosquatting보다 위험하다.

실용적인 대응 권고

  • rlask, tlask, rsquests, nhmpy, mflux-streamlit의 악성 버전 설치 여부를 확인하고 즉시 제거한다.
  • 해당 패키지가 설치된 환경에서는 CI/CD, 클라우드, 패키지 저장소, SSH 관련 자격 증명을 모두 폐기하고 재발급한다.
  • GitHub 저장소의 워크플로와 .github/setup.js, .github/copilot-instructions.md 변경 이력을 점검한다.
  • Python 패키지 설치 시 패키지명뿐 아니라 유지보수자, 배포 이력, 버전 변화, 해시를 검증한다.
  • CI 환경에서는 최소 권한 토큰, 짧은 수명의 자격 증명, 네트워크 제한, 패키지 허용 목록을 적용하는 것이 안전하다.