사고가 났을 때 첫 한 시간: 대응 런북과 신고

아프로·2026년 11월 2일

사고가 났을 때 첫 한 시간 표지

SK텔레콤은 2022년 2월에 감염 서버를 발견하고도 신고하지 않았습니다. 악성코드는 2025년 4월 이상 트래픽이 탐지될 때까지 약 3년을 더 숨어 있었습니다. KT도 2024년에 찾은 감염 서버를 신고하지 않았고, 개인정보보호위원회(이하 개인정보위)는 2026년 7월 미신고·로그 삭제·허위자료 제출을 이유로 KT를 고발했습니다. 두 사고는 국내편 SK텔레콤 유심 해킹과 KT 무단 소액결제에서 다뤘습니다.

이 시리즈의 공격을 실제 쇼핑몰 코드에서 직접 재연하고 막아 보는 인프런 강의가 열렸습니다. 실제 해킹 사고로 배우는 Spring Boot·Express 시큐어 코딩 · 이 글과 이어지는 수업: 14-4, 14-5, 14-6

한눈에 보기

위험어디서 생기나막는 법 한 줄OWASP
격리가 늦거나 지나침무엇을 끊을지 정해 두지 않음토큰·키 폐기와 구역별 차단을 런북에 미리 적는다—
증거가 사라짐로그 자동 삭제, 서버 재설치사고 중에는 삭제 작업을 멈추고, 로그는 앱과 다른 저장소에 둔다—
숨기거나 늦은 신고누가 판단하고 누가 신고할지 없음발견 시각부터 기록하고, 신고 담당에게 바로 알린다—

첫 한 시간의 순서인 격리, 증거 보존, 연락과, 하지 말아야 할 서버 재설치·로그 삭제·발견 덮기를 사례와 함께 정리한 그림

1. 격리: 끊되 지우지 않는다

무엇이 문제인가

침해를 발견한 순간에는 판단할 시간이 없습니다. 무엇을 끊을지 정해 두지 않으면 두 방향으로 틀립니다. 하나는 늦은 격리입니다. 서버를 들여다보는 사이 탈취된 토큰과 키가 계속 쓰입니다. 다른 하나는 지나친 격리입니다. 끌 수 있는 단위가 전체뿐이면 서비스 전체를 내려야 합니다. 해외편 재규어 랜드로버는 첫 조치로 전 세계 시스템을 내렸고, 영국 공장이 약 5주 멈췄습니다.

런북(사고 때 할 일을 순서대로 적은 문서)에는 끊는 대상을 미리 적습니다. 의심 계정의 세션과 토큰, 노출된 API 키, 감염이 의심되는 구역의 네트워크입니다. 그리고 감염 서버는 끄거나 다시 설치하지 않고 연결만 끊습니다. 다시 설치하면 증거도 함께 사라집니다.

막는 코드

특정 회사 코드가 아니라 개념을 보여 주는 예시입니다. 값은 가짜입니다. 토큰을 하나씩 찾지 않고 "이 시각 이전에 발급된 토큰은 모두 무효"로 처리하는 대응 도구입니다.

@Service
class TokenRevoker {
    private final StringRedisTemplate redis;

    // 런북 1단계: 의심 계정의 기존 토큰을 한 번에 무효로
    void revokeAll(List<String> userIds, String incidentId) {
        String now = String.valueOf(Instant.now().getEpochSecond());
        userIds.forEach(id -> redis.opsForValue().set("revoked-before:" + id, now));
        audit.log("incident.tokens-revoked", incidentId, userIds.size());
    }

    // 인증 필터에서 호출: 발급 시각이 폐기 시각보다 앞이면 거부
    boolean isRevoked(String userId, Instant issuedAt) {
        String cut = redis.opsForValue().get("revoked-before:" + userId);
        return cut != null && issuedAt.getEpochSecond() <= Long.parseLong(cut);
    }
}

Redis에 연결할 수 없을 때는 토큰을 통과시키지 않고 막는 쪽으로 둡니다. 서명키가 의심되면 계정 단위 폐기로는 부족하므로, 키를 교체해 그 키로 서명한 토큰 전체를 무효로 만듭니다.

확인하는 법

훈련 환경에서 alice 계정으로 로그인해 토큰을 받고 revokeAll을 호출합니다. 같은 토큰으로 보낸 요청이 401을 받고, 다시 로그인한 새 토큰은 통과하면 됩니다.

2. 증거 보존: 사고 중에는 지우지 않는다

무엇이 문제인가

사고 범위는 로그로 정합니다. 누가, 언제, 어떤 데이터에 접근했는지 남아 있어야 유출 인원과 기간을 말할 수 있습니다. SK텔레콤은 로그가 남아 있지 않은 기간이 있어 유출 범위를 끝내 확정하지 못했고, 자료보전 명령 뒤 서버 2대를 포렌식이 불가능한 상태로 제출했습니다. 쿠팡은 자료보존 명령 뒤에도 웹 접속로그가 자동으로 삭제됐고, 이 일은 수사의뢰로 이어졌습니다.

평소에 돌던 로그 정리 작업이 사고 중에도 그대로 돌면 증거가 사라집니다. 그래서 사고가 열리면 삭제 작업을 멈추는 보존 상태(legal hold)를 둡니다. 감사 로그는 앱과 다른 저장소에 보내, 앱 서버가 장악돼도 기록을 고칠 수 없게 합니다.

막는 코드

특정 회사 코드가 아니라 개념을 보여 주는 예시입니다. 값은 가짜입니다.

위험한 모양: 무조건 지우는 정리 작업

@Scheduled(cron = "0 0 4 * * *")
void purgeOldLogs() {
    accessLogRepository.deleteOlderThan(Instant.now().minus(Duration.ofDays(7)));
}

고친 모양: 보존 상태면 멈추고 기록한다

@Scheduled(cron = "0 0 4 * * *")
void purgeOldLogs() {
    if (incidentState.hasOpenHold()) {          // 사고가 열려 있으면 지우지 않는다
        audit.log("log.purge-skipped", incidentState.openIncidentId());
        return;
    }
    accessLogRepository.deleteOlderThan(Instant.now().minus(Duration.ofDays(180)));
}
# application.yml: 감사 로그는 앱과 다른 저장소로 (강의에서 만드는 방어 부품 예시)
secure-kit:
  audit: { sink: loki }

secure-kit은 강의에서 만드는 방어 부품 예시입니다. 보존 기간 180일은 예시 값입니다. 국내편 SK텔레콤 글은 방화벽 로그를 6개월 이상 보관해야 사고 뒤에 범위를 가릴 수 있다고 정리합니다.

확인하는 법

보존 상태를 켠 뒤 정리 작업을 수동으로 실행합니다. 로그 건수가 그대로이고 log.purge-skipped 기록이 남으면 통과입니다.

3. 연락과 신고: 숨기면 커진다

무엇이 문제인가

사례를 보면 숨긴 대가는 사고 자체보다 컸습니다. SK텔레콤은 2022년에 신고하지 않은 감염이 약 3년의 잠복으로 이어졌습니다. 2025년 탐지 뒤에도 한국인터넷진흥원(KISA) 신고는 신고 기한인 24시간을 넘겼습니다. KT는 미신고와 로그 삭제가 과징금과 별도로 고발로 이어졌습니다. GS리테일은 추가로 확인된 피해자 1,599명에게 법정 기한 72시간을 넘겨 알렸다고 개인정보위가 밝혔고, 쿠팡에 대해서는 개인정보위가 유출 통지 의무 위반을 판단했습니다.

신고 기한과 항목은 법과 업종, 사고 종류에 따라 다릅니다. 이 글은 법률 자문이 아니고, 위 숫자도 각 사례에서 공개된 내용입니다. 개발팀이 미리 할 일은 기한을 외우는 것보다 누가 판단하고 누가 신고할지, 그리고 언제 무엇을 알았는지를 남기는 것입니다. 공지 문안은 "확인된 것"과 "확인 중인 것"을 나눠 쓰는 틀을 미리 만들어 둡니다. 재규어 랜드로버는 보도에 따르면 처음에 데이터 유출 증거가 없다고 했다가 나중에 정정했습니다.

막는 코드

특정 회사 코드가 아니라 개념을 보여 주는 예시입니다. 값은 가짜입니다. 발견부터 신고까지의 시각을 한 곳에 쌓는 기록입니다.

public record IncidentEvent(String incidentId, Instant at, String actor,
                            String type, String note) {}

@Service
class IncidentTimeline {
    void open(String incidentId, String actor, String note) {
        repo.append(new IncidentEvent(incidentId, Instant.now(), actor, "DISCOVERED", note));
        incidentState.openHold(incidentId);                 // 2절의 로그 삭제 멈춤
        notifier.page("privacy-officer", incidentId);       // 신고 담당에게 바로 알림
    }
    void mark(String incidentId, String actor, String type, String note) {
        repo.append(new IncidentEvent(incidentId, Instant.now(), actor, type, note));
    }   // 예: ISOLATED, TOKENS_REVOKED, REPORTED, NOTIFIED
}

기록은 덧붙이기만 하고 고치지 않습니다. 사고 뒤 "언제 알았고 언제 신고했나"라는 질문에 이 기록으로 답합니다.

확인하는 법

모의 침해 훈련에서 open을 호출하고, 신고 담당에게 알림이 가는지, 보존 상태가 켜지는지, 타임라인에 순서대로 남는지 봅니다.

점검 체크리스트

  • □ 런북에 끊을 대상(세션·토큰, API 키, 구역 네트워크)과 실행할 사람이 적혀 있다
  • □ 감염 서버는 재설치하지 않고 연결만 끊는다는 원칙이 런북에 있다
  • □ 사고가 열리면 로그 정리 작업이 멈추고, 감사 로그는 앱과 다른 저장소에 있다
  • □ 발견 시각부터 신고·통지까지 덧붙이기만 되는 타임라인에 남긴다
  • □ 누가 신고 여부를 판단하고 누가 신고하는지, 법무·개인정보 담당과 미리 정했다
  • □ 공지 문안은 "확인된 것 / 확인 중인 것"으로 나누는 틀이 있다

정리

사고 대응의 첫 한 시간은 격리, 증거 보존, 연락 순서로 움직입니다. 탈취된 토큰은 한 번에 무효로 만들고, 감염 서버는 지우지 않고 끊고, 로그 정리 작업은 멈춥니다. 사례에서는 발견을 숨긴 결정이 긴 잠복과 고발로 이어졌습니다. 신고 기한과 항목은 법무 담당과 확인하고, 개발팀은 언제 무엇을 알았는지를 남겨 둡니다.

함께 보면 좋은 글

보안을 더 알고 싶다면

오늘 본 사고의 원인은 대부분 서버와 코드의 기본기에서 막을 수 있는 것들이었습니다. 직접 손으로 막아 보고 싶다면 이런 강의가 있습니다.

실제 해킹 사고로 배우는 Spring Boot·Express 시큐어 코딩

  • AI로 자동화된 해킹 시대, 오픈소스 쇼핑몰 코드에서 실제 사고 22건을 재연하고 직접 취약점을 찾아 한 건씩 막습니다.
  • OWASP Top 10:2025 전체와 목록 밖 위험 20가지를 102개 수업으로 다루고, 취약점 37개를 모두 막은 최종 소스를 가져갑니다. Spring Boot와 Express 모두 다룹니다.
  • 이 글과 이어지는 수업: 14-4, 14-5, 14-6

서버 보안 기초부터 차근차근 시작하고 싶다면 이 강의도 있습니다.

백엔드 개발자를 위한 서버 보안 실무: 해킹 방어부터 정보유출 방지까지

  • 서버를 공격하는 흐름을 로그로 보고, 내 서버와 Spring·Express 코드에서 직접 막아 봅니다.
  • SSH 하드닝, 인젝션, 인증·인가, 시크릿과 개인정보 보호, 침해사고 첫 한 시간까지 8개 섹션 54개 수업입니다.
  • 이 글과 이어지는 수업: 6-9 침해사고, 첫 한 시간: 격리, 증거 보존, 타임라인, 6-10 사고 이후: 유출 신고 의무와 재발 방지

출처와 확인 날짜

수치와 사실은 2026년 11월 2일 기준 공개 자료입니다. 이 글은 법률 자문이 아닙니다. 소송과 수사가 진행 중인 사건(2026년 10월 말 기준)은 내용이 바뀔 수 있습니다.


이전 글 백업이 같이 잠기지 않게: 불변 백업과 복구 훈련
시리즈 전체 목록 해킹 방식 VS 방어 방법 전체 목록

profile
고인물 개발자(OG)

0개의 댓글