
이스트시큐리티 대응센터(ESRC)는 공급망 위협 모니터링을 통해 유명 테스트 프레임워크인 Chai.js의 플러그인으로 위장한 악성 npm 패키지(chai-as-init)가 v1.4.5 ~ v1.4.7까지 총 3개 버전으로 배포된 사실을 확인했습니다.
이 패키지는 설치 또는 코드에서 불러오는 즉시 외부 서버에서 악성 코드를 내려받아 실행하며, 시스템의 자격증명을 탈취하는 인포스틸러와 원격 명령을 수행하는 RAT 기능을 함께 갖추고 있습니다.


chai-as-init 패키지는 인기 패키지인 chai 및 chai-as-promised 패키지명을 모방한 타이포스쿼팅 (Typosquatting)기법을 사용했으나 내부 코드는 전혀 다릅니다.
공격자는 정상 Node.js 로깅 라이브러리인 pino의 전체 소스와 문서, 타입 정의를 통째로 복사해 정상 패키지처럼 보이도록 위장했으며, 총 42개 파일 가운데 실제 악성 코드는 단 2개 파일(index.js, lib/initializeCaller.js)에만 존재합니다.
또한 함수명을 initializeCaller, runBackgroundTask처럼 일반적인 초기화 코드처럼 짓고, 탈취한 데이터를 cookie나 level, timestamp 같은 로그,쿠키 형식의 필드명에 담아 정상 데이터로 오인하도록 만들었습니다.
README는 chai를 설명하지만 실제 코드는 pino인 점, 내부 버전 정보가 선언된 1.4.7과 달리 pino의 9.6.0으로 남아 있는 점 등 곳곳에서 위장 흔적이 확인되었습니다.

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

이후 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단계 로더를 통해 탈취되는 환경변수 유형
chai-as-init은 세가지 버전에 걸쳐 공격 기법이 체계적으로 고도화되었으며, 핵심 로더인 initializeCaller.js를 비교하면 그 변화가 뚜렷하게 드러납니다.
초기 버전인 v1.4.5는 C2 서버 주소가 평문으로 하드코딩되어 있고, 단순히 외부에서 코드를 내려받아 실행하는 단방향 다운로더에 가까웠습니다.

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

세가지 버전의 핵심 차이를 정리하면 다음과 같습니다.
| 항목 | 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 로 인프라를 옮기면서, 보안 분석가나 샌드박스가 접근할 때는 정상 응답을 돌려주어 탐지를 회피하는 것도 가능해졌습니다.
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) 환경 여부도 자체 함수로 탐지합니다.

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)에 전송합니다.

자체 조사 결과, 이미 이전에 보고된 다수의 '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) 비교
해당 패키지가 설치된 환경은 모든 비밀 값이 침해된 것으로 간주하고 즉시 대응해야 합니다.
감염 확인 시 즉시 조치
예방 조치
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 |
| Solana SDK로 위장한 npm 패키지, Telegram Bot API 기반 RAT 유포 (0) | 2026.06.11 |
|---|---|
| 개인정보 유출 의심 문의로 위장한 Kimsuky 스피어피싱 사례 분석 (0) | 2026.06.09 |
| 디지털 서명 검증을 우회하는 MS Word 변조 악성코드 주의 (CVE-2013-3900) (0) | 2026.06.01 |
| 오픈소스 공급망을 넘어 로컬 AI 인프라를 겨냥한 위협 분석 (0) | 2026.05.26 |
| 스틸러·RAT·랜섬웨어를 결합한 복합 위협 악성코드 분석 - MoscowTeam_Steal (0) | 2026.05.20 |
댓글 영역