← 목록으로
● 2026-10-01 신한은행 해킹(고객 2.5만 명 개인정보 유출) 분석 기반

본인확인 우회를 그대로 재현하고,
막는 코드까지 공개합니다

신한은행 대출모집인 서비스에서 실제로 뚫린 패턴 — 본인확인 절차를 우회해 타인의 고객 정보를 조회하는 IDOR(권한 우회) 취약점을 재현하고, 세션 신원에 조회 권한을 강제로 묶는 방어 코드를 구현했습니다. 지금은 전부 무료로 공개합니다.

라이브 데모 보기 → 코드 바로보기

누구를 위한 건가

진짜 은행 시스템 운영자용이 아닙니다 — 백엔드 개발자가 IDOR(Insecure Direct Object Reference)가 실제로 뭘 의미하는지, 왜 OWASP가 매번 최상위권에 올려두는지 코드로 직접 확인하기 위한 학습 자료입니다.

OWASP #1
API 보안 Top 10에서 BOLA(IDOR)가 차지한 순위
25,000
신한은행 사고로 유출된 고객 수(명)
ARTEX
실제 AI 침투테스트 에이전트(github.com/Autumn-27/ARTEX)로 격리 샌드박스에서 직접 검증
0원
지금 공개하는 코드의 가격 — 전부 무료

코드 — vulnerable-lookup-api.js

클라이언트가 보낸 고객 ID를 그대로 신뢰하는 취약한 대출조회 API.

vulnerable-lookup-api.js
// 취약한 대출조회 API — 신한은행 사고(2026-10-01) 모델링:
// 대출모집인 서비스의 본인확인 절차가 우회되어 약 2만5000명의
// 개인정보가 유출됨. 결함: 엔드포인트가 세션이 아니라
// 클라이언트가 보낸 customerId를 그대로 신뢰함.
class VulnerableLoanLookup {
  constructor(customerDb) {
    this.db = customerDb; // { customerId: {name, rrn, ci, phone, income, loanLimit} }
  }

  // session은 로그인한 사람의 실제 신원.
  // requestedId는 클라이언트가 보낸 파라미터 — 예: ?customerId=CUST-1042
  // 버그: requestedId를 그대로 쓰고, session과 비교하지 않음.
  lookup(session, requestedId) {
    const record = this.db[requestedId];
    if (!record) return { status: 'not_found' };
    return { status: 'ok', record }; // 로그인한 아무에게나 타인 정보 유출
  }
}

module.exports = { VulnerableLoanLookup };

코드 — session-bound-guard.js

모든 조회를 호출자 본인의 세션 신원에 강제로 묶는 방어 코드.

session-bound-guard.js
// 해결책: 모든 조회를 호출자 본인의 세션 신원에 묶는다.
// 클라이언트가 뭘 보내든, 다른 고객 id를 요청하면 거부하고
// 로그로 남긴다 — 신한은행식 본인확인 우회를 막는 방법.
class SessionBoundLoanLookup {
  constructor(customerDb) {
    this.db = customerDb;
    this.deniedAttempts = [];
  }

  lookup(session, requestedId) {
    if (!session || !session.customerId) {
      return { status: 'unauthenticated' };
    }
    if (requestedId !== session.customerId) {
      this.deniedAttempts.push({
        sessionOwner: session.customerId,
        attemptedId: requestedId,
        at: Date.now(),
      });
      return { status: 'denied', reason: 'session_id_mismatch' };
    }
    const record = this.db[requestedId];
    if (!record) return { status: 'not_found' };
    return { status: 'ok', record };
  }
}

module.exports = { SessionBoundLoanLookup };

코드 — layer1-extensions.js

별도의 격리 샌드박스(ARTEX로 실제 공격 검증 완료)에서 돌리고 있는 2개 확장 레이어 요약 — 전체 구현은 proxy.js + isolation-forest.js.

layer1-extensions.js
// 확장 레이어 — session-bound-guard.js로는 못 잡는 2가지 회피를 막음
// (1) 출처(IP) 교차신원 상관: "차단되면 다른 신원으로 재로그인"을 무력화
// (2) Isolation Forest 이상탐지: 위반 0건인 비정상 패턴(자기계정 폭주)도 잡음

const ipViolatingOwners = new Map(); // ip -> Set<ownerCustomerId>
const ipBlocked = new Set();

function onAuthorizationViolation(owner, ip) {
  const owners = ipViolatingOwners.get(ip) || new Set();
  owners.add(owner);
  ipViolatingOwners.set(ip, owners);
  // 같은 출처가 서로 다른 신원 2개 이상에서 위반을 내면 그 출처 자체를 차단
  if (owners.size >= 2) ipBlocked.add(ip);
}

function onEveryRequest(owner, ip, requestsPerMinute, distinctIdsAttempted) {
  if (ipBlocked.has(ip)) {
    return { status: 'blocked', reason: 'source_blocked_cross_identity_pattern' };
  }
  // 권한 위반이 전혀 없어도 "평소와 다른 모양"의 요청은 점수로 잡음
  // (모델 전체는 isolation-forest.js — 시드 고정 PRNG로 재시작해도 동일한 점수)
  const anomalyScore = anomalyModel.score([requestsPerMinute, distinctIdsAttempted]);
  if (anomalyScore >= 0.70) {
    // 기본값은 로깅만 — 실제 차단은 운영자가 ML_BLOCK=true로 명시해야 켜짐
    logFlag({ owner, anomalyScore });
  }
  return { status: 'ok' };
}

가격

무료 (지금)
심화 버전(레이트리밋, 이상탐지, 실전 배포 가이드 포함) 출시 예정 — 사업자등록 완료 후 자유가격($0부터)으로 공개
goalsgo7574@gmail.com
구매·결제가 아닙니다 — "심화버전 출시알림 받고 싶어요"라고 메일 한 통만 보내주시면, 출시 시 연락드립니다.
주의: 위 2개 파일은 학습용 축소 구현입니다. 3번째 파일(layer1-extensions.js)이 보여주는 교차신원 상관·이상탐지는 별도의 격리 샌드박스에서 실제 ARTEX로 검증했지만, 그 전체 구현(proxy.js, isolation-forest.js)은 아직 비공개입니다 — 지금 그대로 실제 금융 시스템에 넣으면 안 됩니다.

의견을 남겨주세요

이 상품에 대한 피드백이나 질문이 있으면 알려주세요. 회신을 원하시면 이메일을 남겨주세요(선택).