Shai-Hulud 테마 악성코드가 PyTorch Lightning AI 학습 라이브러리에서 발견됨 | Semgrep

1 week ago 17
  • PyPI lightning 2.6.22.6.3 버전이 2026년 4월 30일 게시된 뒤 공급망 공격에 악용됐고, pip install lightning만으로 숨겨진 _runtime 디렉터리와 난독화된 JavaScript 페이로드가 실행될 수 있음
  • 악성 페이로드는 모듈 임포트 시 자동 실행돼 자격 증명, 인증 토큰, 환경 변수, 클라우드 비밀값을 탈취하며 GitHub 저장소 오염도 시도함
  • 이번 공격은 진입점이 PyPI이지만 웜 전파는 npm을 통해 이뤄지며, npm 게시 자격 증명을 찾으면 게시 가능한 패키지에 setup.mjs 드로퍼와 router_runtime.js를 주입하고 패치 버전을 다시 게시함
  • 데이터 유출은 HTTPS POST, GitHub 커밋 검색 데드드롭, 공격자 제어 공개 GitHub 저장소, 피해자 저장소 직접 푸시 등 4개 병렬 채널을 사용하며 EveryBoiWeBuildIsAWormyBoi 커밋 접두사와 "A Mini Shai-Hulud has Appeared" 저장소 설명이 지표로 남음
  • 악성코드는 Claude Code의 .claude/settings.json SessionStart 훅과 VS Code의 .vscode/tasks.json runOn: folderOpen 작업을 심어 저장소를 열 때마다 드로퍼를 실행하며, 영향 기간에 악성 패키지를 임포트한 머신은 완전 침해된 것으로 취급해야 함

영향받는 패키지와 확인 절차

  • lightning은 이미지 분류기 구축, LLM 파인튜닝, 확산 모델 실행, 시계열 예측기 개발 팀의 의존성 트리에 자주 포함되는 딥러닝 프레임워크임
  • 영향받는 패키지

    • lightning 버전 2.6.2
    • lightning 버전 2.6.3
  • Semgrep 고객 확인 절차

    • 최근 프로젝트 스캔을 수행하지 않았다면 새 스캔을 실행해야 함
    • advisories 페이지에서 프로젝트가 해당 패키지 버전을 최근 설치했는지 확인 가능함
    • dependency filter에서 일치 항목을 확인할 수 있으며, “No matching dependencies”가 표시되면 프로젝트에서 악성 의존성을 활발히 사용 중이지 않다는 뜻임
    • 일치 항목이 있으면 아래 침해 지표에 있는 .claude/와 .vscode/ 디렉터리의 예상치 못한 파일을 저장소에서 감사해야 함
    • 영향을 받은 환경에 있었을 수 있는 GitHub 토큰, 클라우드 자격 증명, API 키를 교체해야 함
    • 공급망 공격 대응과 대기 기간 관련 일반 조언은 $foo compromised in $packagemanagerAttackers are Still Coming for Security Companies에서 다룸

PyPI에서 npm으로 확산되는 구조

  • mini Shai-Hulud가 npm을 직접 겨냥한 것과 달리 이번 공격의 진입점은 PyPI
  • 악성 페이로드는 여전히 JavaScript이며, 웜 전파는 npm을 통해 발생함
  • 실행된 악성코드가 npm 게시 자격 증명을 찾으면 해당 토큰으로 게시 가능한 모든 패키지에 setup.mjs 드로퍼와 router_runtime.js를 주입함
  • 이후 scripts.preinstall을 드로퍼 실행으로 설정하고, 패치 버전을 올린 뒤 다시 게시함
  • 해당 패키지를 설치한 하위 개발자는 전체 악성코드를 자신의 머신에서 실행하게 되며, 토큰 탈취와 패키지 웜 감염이 이어짐

데이터 유출 방식

  • 탈취 기능은 이전 캠페인의 Mini Shai-Hulud 메커니즘과 설계를 공유하며, 개별 경로가 차단돼도 데이터가 빠져나가도록 4개의 병렬 채널을 사용함
  • 공격에는 EveryBoiWeBuildIsaWormBoi라는 공개 저장소 생성 등 Shai-Hulud 테마가 포함됨
  • 공격 지표 구조는 이전 mini Shai-Hulud 캠페인과 일치하며, 악성 커밋 메시지는 EveryBoiWeBuildIsAWormyBoi 접두사를 사용해 원래 Mini Shai-Hulud 공격과 구분됨
  • HTTPS POST를 통한 C2 전송

    • 탈취 데이터는 포트 443을 통해 공격자 제어 서버로 즉시 POST됨
    • 도메인과 경로는 페이로드 안에 암호화된 문자열로 저장돼 정적 분석을 어렵게 만듦
  • GitHub 커밋 검색 데드드롭

    • 악성코드는 GitHub 커밋 검색 API를 폴링해 EveryBoiWeBuildIsAWormyBoi 접두사가 붙은 커밋 메시지를 찾음
    • 커밋 메시지는 EveryBoiWeBuildIsAWormyBoi:<base64(base64(token))> 형식의 이중 Base64 인코딩 토큰을 운반함
    • 디코딩된 토큰은 이후 작업을 위한 Octokit 클라이언트 인증에 사용됨
  • 공격자 제어 공개 GitHub 저장소

    • 무작위로 선택된 Dune 단어 이름과 "A Mini Shai-Hulud has Appeared" 설명을 가진 새 공개 저장소가 생성됨
    • 이 설명은 GitHub에서 직접 검색 가능함
    • 탈취 자격 증명은 results/results-<timestamp>-<n>.json으로 커밋되며, API를 통해 Base64 인코딩되지만 내부는 일반 JSON임
    • 30MB를 넘는 파일은 번호가 붙은 청크로 나뉨
    • 커밋 메시지는 위장용으로 chore: update dependencies를 사용함
  • 피해자 저장소로 직접 푸시

    • 악성코드가 ghs_ GitHub 서버 토큰을 얻으면 피해자의 GITHUB_REPOSITORY 모든 브랜치에 탈취 데이터를 직접 푸시함

탈취 대상

  • 악성코드는 로컬 파일, 환경, CI/CD 파이프라인, 클라우드 제공자 전반의 자격 증명을 겨냥함
  • 파일 시스템

    • 80개 이상의 자격 증명 파일 경로를 스캔해 ghp_, gho_, npm_ 토큰을 찾음
    • 파일당 최대 5MB까지 처리함
  • 셸과 환경 변수

    • gh auth token을 실행함
    • process.env의 모든 환경 변수를 덤프함
  • GitHub Actions

    • Linux 러너에서 내장 Python으로 Runner.Worker 프로세스 메모리를 덤프함
    • "isSecret":true로 표시된 모든 비밀값과 GITHUB_REPOSITORY, GITHUB_WORKFLOW를 추출함
  • GitHub 조직

    • 토큰 범위인 repo, workflow를 확인함
    • GitHub Actions 조직 비밀값을 순회함
  • AWS

    • 환경 변수, ~/.aws/credentials 프로필, IMDSv2 169.254.169.254, ECS 169.254.170.2를 시도해 sts:GetCallerIdentity를 호출함
    • Secrets Manager의 모든 값과 SSM 파라미터를 열거하고 가져옴
  • Azure

    • DefaultAzureCredential을 사용해 구독을 열거하고 Key Vault 비밀값에 접근함
  • GCP

    • GoogleAuth로 인증함
    • Secret Manager의 모든 비밀값을 열거하고 가져옴
    • 대상 범위는 로컬 개발 환경, CI 러너, 3대 주요 클라우드 제공자를 모두 포함함
    • 영향을 받은 기간에 악성 패키지를 임포트한 모든 머신은 완전 침해된 것으로 취급해야 함

개발 도구를 통한 지속성 확보

  • 저장소 내부에 들어온 뒤 악성코드는 흔히 사용되는 개발 도구인 Claude CodeVS Code를 겨냥해 지속성 훅을 심음
  • Claude Code

    • 악성코드는 저장소의 Claude Code 설정 파일인 .claude/settings.json에 matcher: "*"인 SessionStart 훅을 작성함
    • 훅은 node .vscode/setup.mjs를 가리킴
    • 개발자가 감염된 저장소에서 Claude Code를 열 때마다 실행되며, 세션 시작 외의 도구 사용이나 사용자 동작은 필요하지 않음
  • VS Code

    • VS Code 사용자를 겨냥해 .vscode/tasks.json에 runOn: folderOpen 작업을 심음
    • 프로젝트 폴더가 열릴 때마다 node .claude/setup.mjs가 실행됨
  • 드로퍼 setup.mjs

    • 두 훅은 모두 자체 포함형 Bun 런타임 부트스트래퍼인 setup.mjs를 호출함
    • Bun이 설치돼 있지 않으면 GitHub releases에서 bun-v1.3.13을 조용히 다운로드함
    • Linux x64, Linux arm64, Linux musl, macOS x64, macOS arm64, Windows x64, Windows arm64를 처리함
    • 이후 전체 14.8MB 페이로드인 .claude/router_runtime.js를 실행하고 /tmp에서 정리함
  • 악성 GitHub Actions 워크플로

    • 악성코드가 쓰기 권한이 있는 GitHub 토큰을 보유하면 피해자 저장소에 Formatter라는 워크플로를 푸시함
    • 모든 push에서 ${{ toJSON(secrets) }}를 통해 모든 저장소 비밀값을 덤프함
    • 결과를 format-results라는 다운로드 가능한 Actions 아티팩트로 업로드함
    • Actions는 정상처럼 보이도록 특정 커밋 SHA에 고정됨
    • CI에서 감염된 lightning 패키지를 받았고 쓰기 권한 토큰을 가진 저장소는 해당 파일들을 감사해야 함

침해 지표

  • 검색 가능한 지표

    • EveryBoiWeBuildIsAWormyBoi 접두사가 붙은 커밋 메시지는 데드드롭 토큰 운반자로 사용되며 GitHub 커밋 검색으로 찾을 수 있음
    • "A Mini Shai-Hulud has Appeared" 설명을 가진 GitHub 저장소는 공격자 유출 저장소이며 직접 검색 가능함
  • 패키지

    • lightning@2.6.2
    • lightning@2.6.3
  • 파일과 시스템 아티팩트

    • _runtime/start.py: 임포트 시 페이로드를 초기화하는 Python 로더
    • _runtime/router_runtime.js: 난독화된 JavaScript 페이로드이며 14.8MB Bun 런타임임
    • _runtime/: 악성 패키지 버전에 추가된 디렉터리
    • .claude/router_runtime.js: 피해자 저장소에 주입된 악성코드 복사본
    • .claude/settings.json: 피해자 저장소에 주입된 Claude Code 훅 설정
    • .claude/setup.mjs: 피해자 저장소에 주입된 드로퍼
    • .vscode/tasks.json: 피해자 저장소에 주입된 VS Code 자동 실행 작업
    • .vscode/setup.mjs: 피해자 저장소에 주입된 드로퍼
Read Entire Article