관리형 서비스 제공업체(MSP) 하나의 계정이 털리면서 하위 고객사 28곳이 동시에 뚫린 코리안 리크스 사고의 구조를 재현했습니다. 공유 크리덴셜 대신 고객사별로 분리된 전용 접근권한을 쓰면 피해가 어떻게 한 곳에 갇히는지 보여줍니다. 지금은 전부 무료로 공개합니다.
진짜 MSP 운영자용이 아닙니다 — 멀티테넌트 SaaS나 여러 고객사를 관리하는 백엔드/인프라 개발자가 '벤더 하나가 뚫리면 왜 전부 뚫리는가'를 코드로 직접 확인하기 위한 학습 자료입니다.
하나의 크리덴셜로 모든 고객사에 접근 가능한 취약한 MSP 접근 모델.
// 취약한 MSP 접근 모델 — "코리안 리크스" 캠페인 모델링:
// GJTec 크리덴셜 하나가 털리면서 하위 금융기관 28곳이 연쇄로
// 뚫림. 결함: 크리덴셜 하나가 MSP가 관리하는 모든 고객사에
// 상시 접근 권한을 가짐.
class SharedCredentialAccess {
constructor(tenants) {
this.tenants = tenants; // [{id, name}, ...]
this.credential = { id: 'msp-admin', scope: 'all-tenants' };
}
// 탈취된 크리덴셜로 어떤 고객사든 접근 가능 — 격리 없음.
accessTenant(credential, tenantId) {
if (credential.scope !== 'all-tenants') return { status: 'denied' };
const tenant = this.tenants.find(t => t.id === tenantId);
return { status: 'breached', tenant };
}
simulateCompromise() {
// 공격자가 크리덴셜 하나를 확보 — 모든 고객사가 노출됨.
return this.tenants.map(t => this.accessTenant(this.credential, t.id));
}
}
module.exports = { SharedCredentialAccess };
고객사별로 분리된 전용 크리덴셜로 피해를 한 곳에 가두는 방어 코드.
// 해결책: 크리덴셜을 고객사 1곳으로 한정하고 빠르게 만료시킨다.
// 한 고객사에 쓰인 크리덴셜이 털려도 다른 고객사는 절대 접근
// 불가 — GJTec식 MSP 피해를 한 곳에 가뒀을 방법.
class TenantIsolatedAccess {
constructor(tenants) {
this.tenants = tenants;
this.credentials = new Map(
tenants.map(t => [t.id, { tenantId: t.id, issuedAt: Date.now(), ttlMs: 15 * 60 * 1000 }])
);
this.blockedAttempts = [];
}
accessTenant(credential, tenantId) {
if (credential.tenantId !== tenantId) {
this.blockedAttempts.push({ credentialTenant: credential.tenantId, attemptedTenant: tenantId });
return { status: 'denied', reason: 'scope_mismatch' };
}
const tenant = this.tenants.find(t => t.id === tenantId);
return { status: 'breached', tenant }; // 표적 고객사만 노출
}
simulateCompromise(compromisedTenantId) {
const stolenCredential = this.credentials.get(compromisedTenantId);
return this.tenants.map(t => this.accessTenant(stolenCredential, t.id));
}
}
module.exports = { TenantIsolatedAccess };
이 상품에 대한 피드백이나 질문이 있으면 알려주세요. 회신을 원하시면 이메일을 남겨주세요(선택).