상세 컨텐츠

본문 제목

Chai.js 플러그인으로 위장한 북한발 npm 악성 패키지 'chai-as-init' 분석

악성코드 분석 리포트

by 알약4 2026. 6. 16. 18:16

본문

[이미지] 생성형 AI 제작

 

 

이스트시큐리티 대응센터(ESRC)는 공급망 위협 모니터링을 통해 유명 테스트 프레임워크인 Chai.js의 플러그인으로 위장한 악성 npm 패키지(chai-as-init)가 v1.4.5 ~  v1.4.7까지 총 3개 버전으로 배포된 사실을 확인했습니다.

이 패키지는 설치 또는 코드에서 불러오는 즉시 외부 서버에서 악성 코드를 내려받아 실행하며, 시스템의 자격증명을 탈취하는 인포스틸러와 원격 명령을 수행하는 RAT 기능을 함께 갖추고 있습니다.

 

[그림 1] npm에 배포된 Chai.js 플러그인 위장 악성 패키지

 

 

패키지 개요

 

chai-as-init 패키지는  인기 패키지인 chaichai-as-promised 패키지명을 모방한 타이포스쿼팅 (Typosquatting)기법을 사용했으나 내부 코드는 전혀 다릅니다. 


공격자는 정상 Node.js 로깅 라이브러리인 pino의 전체 소스와 문서, 타입 정의를 통째로 복사해 정상 패키지처럼 보이도록 위장했으며, 총 42개 파일 가운데 실제 악성 코드는 단 2개 파일(index.js, lib/initializeCaller.js)에만 존재합니다. 

또한 함수명을 initializeCaller, runBackgroundTask처럼 일반적인 초기화 코드처럼 짓고, 탈취한 데이터를 cookielevel, timestamp 같은 로그,쿠키 형식의 필드명에 담아 정상 데이터로 오인하도록 만들었습니다. 


README는 chai를 설명하지만 실제 코드는 pino인 점, 내부 버전 정보가 선언된 1.4.7과 달리 pino의 9.6.0으로 남아 있는 점 등 곳곳에서 위장 흔적이 확인되었습니다.

 

[그림 2] 악성 패키지 내부 구조

 

감염 체인

 

모든 버전은 동일한 초기 실행 구조를 사용합니다.
진입점인 index.js는 정상적인 Express 미들웨어처럼 보이지만, 패키지를 불러오는 순간 백그라운드에서 악성 스크립트를 실행합니다. 이때 자식 프로세스를 부모와 분리(detached)하고 출력을 숨기며(stdio: 'ignore'), 부모가 종료되어도 살아남도록(child.unref()) 설정해 추적을 어렵게 만듭니다.

 

[그림 3] index.js 코드

 

이후 2단계 로더(initializeCaller.js)가 C2 서버와 통신하여 최종 페이로드를 받아 실행하고, 이 페이로드가 시스템 정보 수집과 파일 탐색, 데이터 암호화 전송, 원격 명령 실행을 수행합니다.

v1.4.5는 정적 호스팅에서 페이로드만 내려받는 단방향 구조인 반면, v1.4.6 이후는 환경변수를 먼저 전송하고 응답 본문 전체를 코드로 실행하는 양방향 구조라는 점에서 차이가 있습니다.

v1.4.6/v1.4.7에서 2단계 로더가 환경변수와 함께 즉시 탈취하는 정보에는 다음과 같은 고위험 자격증명이 포함됩니다.

 

분류 환경변수 예시
클라우드 인프라 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
소스코드·레지스트리 GITHUB_TOKEN, NPM_TOKEN
데이터베이스 DATABASE_URL, REDIS_URL
결제 STRIPE_SECRET_KEY
CI/CD 시크릿 JENKINS, GITLAB_CI, CIRCLE_CI 관련 변수

[표 1] v1.4.6/v1.4.7의 2단계 로더를 통해 탈취되는 환경변수 유형

 

 버전별 공격 기법의 진화 (v1.4.5 → v1.4.7)

 

chai-as-init은 세가지 버전에 걸쳐 공격 기법이 체계적으로 고도화되었으며, 핵심 로더인 initializeCaller.js를 비교하면 그 변화가 뚜렷하게 드러납니다.

초기 버전인 v1.4.5는 C2 서버 주소가 평문으로 하드코딩되어 있고, 단순히 외부에서 코드를 내려받아 실행하는 단방향 다운로더에 가까웠습니다.

 

[그림 4] v1.4.5의 initializeCaller.js 코드

 

반면 v1.4.6 이후부터는 C2 주소를 Base64로 인코딩해 숨기고, HTTP 요청 방식을 GET에서 POST로 바꾸면서 피해 시스템의 환경변수(process.env) 전체를 함께 전송하고 C2의 응답 본문 전체를 코드로 실행하는 양방향 구조로 변경되었습니다.

 

[그림 5] v1.4.6 이후 initializeCaller.js 코드

 

세가지 버전의 핵심 차이를 정리하면 다음과 같습니다.

 

항목 v1.4.5 v1.4.6 v1.4.7
C2 URL 인코딩 평문 (하드코딩) Base64 Base64
HTTP 메서드 GET POST POST
환경변수 탈취 없음 process.env 전체 전송 process.env 전체 전송
인증 헤더 없음 x-secret-header: secret x-secret-header: secret
코드 실행 방식 new Function.constructor("require", res.data.cookie) new Function("require", response.data) new Function("require", response.data)
페이로드 소스 res.data.cookie (JSON 필드) 응답 본문 전체 응답 본문 전체
C2 호스팅 tiiny.site Vercel Vercel

[표 2] chai-as-init 버전 별 동작 방식

 

v1.4.5에서 v1.4.6으로의 변화는 단순한 코드 정리가 아니라 공격의 질적 도약입니다.
초기 버전이 모든 피해자에게 동일한 코드를 내려주는 단순 다운로더였다면, 이후 버전은 환경변수를 먼저 전송받아 분석한 뒤 피해자별 맞춤형 페이로드를 내려보내는 구조로 바뀌었습니다. 

 

특히 정적 호스팅(tiiny.site)에서 Vercel 로 인프라를 옮기면서, 보안 분석가나 샌드박스가 접근할 때는 정상 응답을 돌려주어 탐지를 회피하는 것도 가능해졌습니다.

 

v1.4.5 페이로드 data.json 분석

 

ESRC는 v1.4.5의 C2 서버에서 최종 페이로드인 data.json을 확보하여 분석했습니다.
이 파일은 약 3.8MB 크기의 JSON 객체로, cookie 필드 안에 난독화된 자바스크립트 코드 전체가 문자열로 담겨 있으며, cookie 라는 이름은 네트워크 모니터링이나 로그에서 정상 쿠키 데이터로 오인하도록 유도하기 위한 위장입니다.

 

한편 v1.4.6/v1.4.7은 분석 시점에 C2의 동적 응답이 확인되지 않아, 해당 버전이 실제로 내려받는 페이로드까지는 파악하지 못했습니다. 다만 로더 구조상 v1.4.5와 동일한 페이로드를 JSON 래핑 없이 응답 본문으로 직접 반환했을 가능성이 높은 것으로 추정됩니다.

 

1.파일 구조
data.json은 단일 JSON 객체로, cookie 필드 안에 난독화된 자바스크립트 코드 전체가 문자열로 존재합니다. v1.4.5 로더( initializeCaller.js)는 이 cookie 필드를 읽어 Function.constructor로 실행합니다.

{

  "cookie": "(function(){var wY=['\\x57\\x50\\x6a\\x55\\x72\\x41', ... ];function V(){return wY;}...})();"

}

 

2. 페이로드 동작 분석
난독화 해제 후 확인된 페이로드는시스템 정보와 파일 탐색 및 명령 실행 결과를 수집한 뒤, 이를 암호화해 C2로 전송하는 순서로 동작합니다.

 

2.1 시스템 정보 수집
os.hostname()으로 호스트명, os.type()os.release()로 OS 종류·버전, os.userInfo()로 사용자명을 수집하며, WSL(Windows Subsystem for Linux) 환경 여부도 자체 함수로 탐지합니다. 

 

[그림 6] 정보 수집 코드

 

2.2 파일 시스템 탐색 및 명령 실행
fs.readdirSync()로 특정 4개 디렉터리를 제외한 채 재귀적으로 파일 목록을 수집하고, child_process.execSync()로 시스템 명령을 동기 실행해 결과를 캡처하며, spawn()으로 비동기 프로세스를 생성합니다.
maxBuffer 옵션과 stdio: 'pipe' 설정이 사용되어 대량의 출력 데이터까지 캡처하는 것으로 확인됩니다. 

 

분류 수집 항목 사용 API 설명
시스템 식별 호스트명 os.hostname() 피해자 시스템 식별에 사용
시스템 식별 OS 종류 및 버전 os.type() + os.release() 예: Windows_NT 10.0.19045
시스템 식별 사용자명 os.userInfo().username 현재 로그인 사용자
시스템 식별 WSL 환경 여부 자체 탐지 함수 Windows/Linux 이중 환경 확인
파일시스템 디렉토리 목록 fs.readdirSync() 특정 4개 디렉토리를 제외한 재귀 탐색
명령 실행 시스템 명령 결과 child_process.execSync() 동기 실행, maxBuffer로 대량 출력 캡처
명령 실행 비동기 프로세스 child_process.spawn() 백그라운드 프로세스 생성

[표 3] 공격자 서버로 유출되는 정보

 

 

2.3 C2 통신 프로토콜

수집 및 캡처한 데이터를 crypto 모듈로 암호화한 뒤 JSON 형태로 구성해 axios.post로 공격자 서버(hxxp://144.172.89[.]180:8086/upload)에 전송합니다.

 

[그림 7] C2 전송 JSON 파일 구조

 

위협 귀속: 북한 Contagious Interview 캠페인

 

자체 조사 결과, 이미 이전에 보고된 다수의 'chai-as-*' 패키지가 북한 연계 'Contagious Interview' 캠페인의 일환으로 확인된 바 있으며, 이번에 분석한 'chai-as-init' 패키지 역시 기존 패키지들과 공격 기법(TTP) 및 인프라 구성 패턴 양면에서 일치하는 양상을 보였습니다.

이러한 근거를 바탕으로, ESRC에서는 이번 'chai-as-init' 악성 npm 패키지 공격을 북한 연계 'Contagious Interview' 캠페인의 연장선으로 판단하고 현재 추가적인 연관성 분석을 진행하고 있습니다.

 

💡 참고하세요

Contagious Interview 캠페인이란?
Contagious Interview는 북한 연계 위협 그룹이 IT 개발자를 표적으로 삼아 진행하는 대규모 공급망 공격 캠페인입니다. 가짜 채용·면접 과정이나 정상 라이브러리로 위장한 악성 패키지를 통해 개발자의 시스템에 침투하며, npm·PyPI 등 여러 생태계에 걸쳐 1,700개 이상의 악성 패키지가 보고된 바 있습니다. 대표 악성코드로는 BeaverTail(정보 탈취), InvisibleFerret(백도어), OtterCookie(인포스틸러·RAT) 등이 알려져 있습니다.

 

 

구체적으로, Base64로 C2 주소를 인코딩하고 process.env 전체를 유출하며 new Function()으로 원격 코드를 실행하는 방식, 인기 패키지를 타이포스쿼팅하고 Vercel 서버리스를 C2로 활용하는 방식 등 공격 기법이 기존 캠페인과 일치합니다. 


또한 ipcheck-hashed.vercel[.]app이라는 도메인 패턴과 ipcheck 키워드, /api/auth/ 형태의 API 경로는 기존에 확인된 북한 OtterCookie 인프라의 패턴과도 부합합니다.

 

기법 chai-as-init Contagious Interview TTP
Base64 C2 URL 인코딩 O (v1.4.6/v1.4.7) O
process.env 전체 유출 O (v1.4.6/v1.4.7) O
new Function(require, code) RCE O (모든 버전) O
axios HTTP 클라이언트 C2 통신 O (모든 버전) O
분리된 자식 프로세스 생성 O (모든 버전) O
Vercel C2 호스팅 O (v1.4.6/v1.4.7) O
정적 호스팅 페이로드 배포 O (v1.4.5, tiiny.site) O
인기 패키지 타이포스쿼팅 O (모든 버전) O

[표 4] 공격 기법 (TTP) 비교

 

대응 권고

 

해당 패키지가 설치된 환경은 모든 비밀 값이 침해된 것으로 간주하고 즉시 대응해야 합니다.

 

감염 확인 시 즉시 조치

  • 모든 자격증명 교체
    이 패키지가 로드된 환경의 시크릿·API 키·토큰·비밀번호를 지체 없이 교체합니다.
  • 패키지 제거 및 점검
    chai-as-init을 제거하고, 모든 프로젝트의 package-lock.json에서 chai-as-init 및 관련 chai-as-* 패키지의 존재 여부를 확인합니다.
  • CI/CD 로그 감사
    가장 민감한 자격증명이 모이는 빌드 환경의 로그에서 이 패키지의 설치 흔적을 확인합니다.
  • 네트워크 차단 및 점검
    방화벽·프록시 로그에서
    ipcheck-hashed.vercel[.]app144.172.89[.]180으로의 연결 이력을 확인하고 해당 IP·URL을 차단합니다.
  • 시스템 정밀 조사
    원격 코드 실행 단계에서 추가 악성코드가 배포되었을 수 있으므로 영향받은 시스템 전반을 조사합니다.

예방 조치

  • 의존성 스캐닝 도구(npm audit, Socket.dev 등)를 활용한 패키지 점검
  • CI/CD 파이프라인에 패키지 허용 목록(allowlist) 적용
  • npm config set ignore-scripts true로 설치 시 라이프사이클 스크립트 실행 차단
  • 빌드 환경에서 사용 가능한 환경변수 최소화
  • 인기 패키지의 타이포스쿼팅 변종(chai-as-* 등)에 대한 지속적인 모니터

 

IoC

Indicator Type Description Detection Name
90708BF06B972FBDE9AE254AA3E0F835 MD5  index.js Trojan.Script.Agent
860DB751FC7700524BD3895B43DAB67C MD5   chai-as-init-1.4.5.tgz
Trojan.Script.Agent
EBED87A47743FA5D81F8E1B2BF6E9058 MD5   initializeCaller.js (v1.4.5) Trojan.Script.Agent
751F36896A5547C4EAECFF9EC80558CD MD5  chai-as-init-1.4.6.tgz Trojan.Script.Agent
02C31A426801C27D65B02D9E04C28FD4 MD5  initializeCaller.js (v1.4.6) Trojan.Script.Agent
078B18EB695DA94B85D29C94462BA64C MD5  chai-as-init-1.4.7.tgz Trojan.Script.Agent
F8F5DF788C5B46E1FA779F01658F94C1 MD5  initializeCaller.js (v1.4.7) Trojan.Script.Agent
hxxps://*****-******-**.tiiny[.]site/data.json URL C2  
hxxps://ipcheck-hashed.vercel[.]app/api/auth/b4dadd6a26d820d085963 URL C2  
hxxps://ipcheck-hashed.vercel[.]app/api/auth/b4dadd6a26d820d08596 URL C2  
144.172.89[.]18 IP C2   

 

관련글 더보기

댓글 영역