
클라우드 보안기업 Wiz의 14일 보안 연구에 따르면, 오픈소스 이벤트 기반 API 도구인 AsyncAPI의 GitHub Actions 워크플로가 악용돼 공식 npm 패키지 4종에 악성코드가 삽입됐다. 공격자는 풀리퀘스트 코드를 고권한 환경에서 실행하는 설정을 노려 프로젝트 서비스 계정의 개인 액세스 토큰을 탈취한 것으로 분석됐다.
Wiz는 공격자가 이날 AsyncAPI Generator 저장소에 37개의 풀리퀘스트를 열었다고 밝혔다. 이 가운데 PR 2155에는 약 1천 바이트의 공백 뒤에 난독화한 자바스크립트가 숨겨져 있었다. 해당 풀리퀘스트는 병합되지 않았지만, 워크플로가 풀리퀘스트 코드를 실행하면서 환경에 있던 비밀값이 외부로 유출됐다는 설명이다.
문제의 워크플로는 pull_request_target 이벤트로 실행되면서도 기준 브랜치가 아닌 외부 기여자의 풀리퀘스트 코드를 체크아웃했다. pull_request_target은 기본 저장소의 권한과 비밀값을 사용할 수 있어, 신뢰하지 않은 코드를 함께 실행하면 이른바 'pwn request' 취약점으로 이어질 수 있다. 공개 GitHub 기록에는 이 위험을 다룬 보완 PR 2092가 5월 17일 제출됐지만 공격 시점까지 병합되지 않은 상태로 남아 있다.
탈취된 토큰은 AsyncAPI 조직 전반에 접근할 수 있는 asyncapi-bot 계정의 토큰으로 추정됐다. GitHub 공개 기록을 보면 공격자는 오전 6시 58분(UTC) Generator 저장소의 next 브랜치에 악성 커밋을 직접 푸시했다. 이어 릴리스 워크플로가 동작하면서 오전 7시 10분께 악성 패키지 3종이 npm에 배포됐다. 이후 spec-json-schemas 저장소에서도 추가 커밋이 이뤄져 @asyncapi/specs의 악성 버전 2개가 게시됐다.
영향받은 버전은 @asyncapi/generator 3.3.1, @asyncapi/generator-helpers 1.1.1, @asyncapi/generator-components 0.7.1, @asyncapi/specs 6.11.2와 6.11.2-alpha.1이다. 14일 npm 레지스트리 메타데이터에서도 이들 버전의 게시 시각이 확인됐다. Wiz는 네 패키지의 평소 다운로드가 합계 하루 14만 건을 넘는다고 설명했다.
악성코드는 설치 스크립트가 아니라 패키지를 import하거나 require할 때 실행된다. 첫 단계가 IPFS에서 암호화된 후속 페이로드를 내려받고, 리눅스에서는 사용자 systemd 서비스로 지속성을 확보하는 다단계 구조다. Wiz 분석에 따르면 페이로드는 브라우저 저장 비밀번호와 쿠키, SSH 키, npm·GitHub 토큰, AWS 자격 증명, macOS 키체인, 암호화폐 지갑을 수집할 수 있다. 파일 조회와 반출을 위한 원격 명령 기능도 포함된 것으로 분석됐다.
개발 조직은 해당 버전을 사용했거나 CI/CD에서 불러온 이력이 있는지 즉시 확인해야 한다. 감염 가능성이 있다면 패키지 교체에 그치지 않고 GitHub·npm 토큰, SSH 키, 클라우드 자격 증명, CI/CD 비밀값을 노출된 것으로 간주해 교체할 필요가 있다. 개발자 PC와 빌드 러너에 남은 후속 페이로드와 지속성 항목도 함께 점검해야 한다.
이번 사고는 오픈소스 프로젝트의 CI 설정이 배포 권한과 결합될 때 위험이 패키지 사용자에게 곧바로 전파될 수 있음을 보여준다. 특히 pull_request_target을 쓰는 워크플로는 외부 기여자의 코드를 실행하지 않도록 작업을 분리하고, 비밀값에 접근하는 단계와 신뢰하지 않은 코드 실행 단계를 격리해야 한다. 릴리스 토큰의 권한과 유효기간을 최소화하고, 변경할 수 없는 커밋 기준 의존성 고정과 패키지 검증도 병행할 필요가 있다.
Wiz는 현재 공격자를 특정하지 않았으며, 과거 Miasma 계열 공격과 일부 기술적 특징이 겹치지만 직접 연계됐다고 단정하지 않았다. AsyncAPI 프로젝트와 npm의 후속 공지, 악성 버전 폐기 및 안전한 교체 버전 공개 여부는 계속 확인해야 한다.
출처: Wiz Research https://www.wiz.io/blog/m-red-team-asyncapi-supply-chain-compromise-via-github-actions
출처: AsyncAPI Generator GitHub https://github.com/asyncapi/generator/pull/2092
출처: AsyncAPI Generator GitHub https://github.com/asyncapi/generator/pull/2155
출처: AsyncAPI Generator GitHub https://github.com/asyncapi/generator/commit/3eab3ec9304aa26081358330491d3cfeb55cc245
출처: npm Registry https://registry.npmjs.org/%40asyncapi%2Fgenerator
출처: npm Registry https://registry.npmjs.org/%40asyncapi%2Fspecs