어느 날 애플리케이션이 뜨지 않았다

M3 작업 중 bootRun 이 Unknown database 'music_pipeline' 으로 실패했다. 어제까지 멀쩡했던 로컬 DB 다. 안에 들어가 보니:

SHOW DATABASES;
-- RECOVER_YOUR_DATA      ← ???
-- information_schema
-- mysql
-- ...
-- music_pipeline 이 없다

RECOVER_YOUR_DATA 데이터베이스 안에는 이런 내용이 있었다.

All your data was backed up by us. You must pay 0.013 bitcoin to … or in 48 hours, your data will be publicly disclosed and deleted.

노출된 MySQL/MariaDB 를 스캔해서 약한 비밀번호로 로그인하고, DB 를 삭제한 뒤 랜섬 노트를 남기는 자동화 봇의 전형적인 수법이다.

뚫린 경로

거창한 취약점이 아니다. 조합이 문제였다.

# docker-compose.yml (사고 전)
ports:
  - "3307:3306"          # = 0.0.0.0:3307, IPv6 [::]:3307 까지 바인딩
environment:
  MARIADB_ROOT_PASSWORD: root   # 개발용 "임시" 비밀번호

Docker 포트 매핑의 기본값은 모든 인터페이스(0.0.0.0)다. “로컬 개발용이니까”라고 쓴 root/root 와 만나는 순간, 네트워크에서 접근 가능한 무인 DB 가 된다. 공유기 NAT 뒤라고 안심할 수 없다 — IPv6 는 공인 주소가 직접 붙는 경우가 많고, 같은 네트워크의 감염된 기기가 경유지가 되기도 한다.

대응

  1. 몸값은 지불하지 않는다. 이런 봇은 대부분 백업 없이 삭제만 한다. 그리고 잃은 것은 전부 재생성 가능한 로컬 샘플 데이터였다 — 결과적으로 피해 0.
  2. 모든 포트를 루프백 바인딩으로 변경. MariaDB 만이 아니라 Kafka(무인증 PLAINTEXT), Redis(무인증 — 노출 시 RCE 까지 가능) 전부.
ports:
  - "127.0.0.1:3307:3306"
  - "127.0.0.1:9092:9092"
  - "127.0.0.1:16379:6379"
  1. 오염된 볼륨은 폐기하고 재생성 — 공격자가 root 로 만졌던 데이터는 신뢰하지 않는다. Flyway 가 스키마를, 샘플 생성기 + 입수 API 가 데이터를 복원했다 (5,000 릴리스 / 50,000 트랙).
  2. mysql.user 에 주입된 계정이 없는지 확인.

복원이 몇 분에 끝난 것은 우연이 아니라 “파일이 아니라 재현 방법을 버전관리한다” 원칙 덕이었다. 데이터가 전부 스크립트에서 재생성되는 구조면 랜섬은 협박이 못 된다.

배운 것

  • 로컬 개발 환경도 공격 표면이다. “어차피 로컬”이라는 가정이 0.0.0.0 바인딩과 기본 비밀번호를 정당화하는 순간 뚫린다.
  • compose 로 띄우는 개발용 인프라의 기본기: 루프백 바인딩, 그리고 그걸 하는지 주석으로 남기기 (다음 사람이 편의로 되돌리지 않게).
  • 사고를 숨기지 않기로 했다 — 프로젝트 README 에 경위를 그대로 적었다. “무엇을 만들었나”만큼 “무엇이 뚫렸고 어떻게 복구했나”도 기록할 가치가 있다.