| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
- prometheus
- selector
- netty
- docker
- NIO
- webflux
- CloudNative
- DevOps
- 백엔드개발
- helm
- RDBMS
- SpringBoot
- grafana
- mysql
- 성능 최적화
- spring boot
- Java
- 성능최적화
- 동시성제어
- monitoring
- 트랜잭션
- jvm
- Kubernetes
- 데이터베이스
- GitOps
- 백엔드
- JPA
- kafka
- Kotlin
- redis
- Today
- Total
유성
랜섬웨어 공격 경험 기록: Docker 설정이 만든 보안 구멍 본문
자고 일어났더니 서비스 로그인이 되지 않는다..
아침에 일어나 내 서비스의 카카오톡 로그인을 시도했는데 멈춘다. 확인해 보니 데이터베이스가 말끔하게 지워져 있었고, RECOVER DB라는 이름의 데이터베이스 하나만 남아 있었다.
그 안에는 "0.74BTC를 보내면 복구해주겠다." 는 메시지가 들어 있었다.
서버 앞에 라우터를 두지 않았고, OS 방화벽은 active 상태였다. 나름 보안에 신경 쓴다고 생각했는데 실제 공격을 당해보니 허술한 부분이 바로 드러났다.
이 글은 그 과정과, Docker의 잘못된 세팅이 만든 보안 취약점을 정리한 기록이다.
공격 증상
- 카카오톡 로그인 API를 통과한 뒤 모든 기능이 동작하지 않음
- 서버 로그에는 table이 존재하지 않아 Error 발생 (내가 테이블을 생성 안했다고?)
- DB에 접속해 보니 테이블 전체 삭제
- 새로 생긴 DB에 "0.74BTC"를 요구하는 메시지 확인
- 라우터 없음, OS 방화벽은 active 상태
대응과 복구
다행히 이번 환경은 운영 서비스가 아니라 배포 전 테스트 환경이었다. 지워진 데이터는 다시 만들어도 큰 문제는 없었다. 하지만 향후 운영 환경에서도 같은 상황이 발생할 수 있기에 복구 절차를 밟았다.
- MySQL binlog 확인 → DROP 명령이 실행된 시점 확인
- 해당 시점 이전부터 binlog를 순차적으로 replay -> 데이터 복구 완료
백업 스냅샷은 없었지만 binlog를 활용해 비교적 빠르게 데이터를 되살릴 수 있었다.
원인 분석
복구 직후 서버를 점검했다. 침투 경로는 Docker의 포트 노출이었다.
- OS 방화벽은 닫혀 있었지만, Docker는 컨테이너를 띄울 때 iptables 규칙을 직접 수정한다.
- docker-compose.yml 에 다음과 같은 설정이 있었다.
ports:
- "3306:3306"
왼쪽 3306(호스트 포트)이 외부에 그대로 열리면서 iptables도 수정되었고, 결국 DB가 외부에 노출됐다.
공격자는 무작위 IP를 스캔해 3306 포트가 열린 서버를 찾은 뒤, root 계정으로 무차별 대입(brute-force)을 시도했을 것으로 추정된다.
단순히 ufw status만 보고 안심했던 게 문제였다.
피해 범위
운영 환경은 아니었기에 피해는 테스트 데이터 삭제에 그쳤다. 하지만 같은 구조가 운영 서비스에도 있었다면 피해는 상상 이상이었을 것이다.
보안 강화
보안 이전 상태
- 80, 443 포트 : 오픈
- 22 포트 : iptime 공유기 public ip 기준으로만 접근 가능
- 3306 포트 : Docker 설정으로 인해 외부 전체 오픈
- 그 외 포트 : 접근 불가
- 서버 백신 없음
- SSH 로그인 : ID/PW 로그인
SSH 보안 강화
- SSH 포트는 기본 22번 대신 2222번 포트로 변경 (무작위 공격으로 인해 로그가 쌓이는 것을 방지)
- ID/PW 인증 제거, 비대칭키 기반(pem) 로그인만 허용
- 문작위 대입 공격을 사실상 불가능하게 만들고, 로그 노이즈도 줄임
비 대칭키의 private key는 네트워크에 절대 노출하지 않도록 하기 위해 Local PC에서 생성한 후 pub 키 만 서버에 올렸다.
MySQL 보안 강화
- ports: / expose: 설정 제거 (외부 노출 차단)
- DB 컨테이너는 고정 subnet IP만 할당 (이유는 아래에 더 설명)
- root 패스워드는 openssl rand -base64 24 로 랜덤 생성
- 매일 00시에 DB 스냅샷 생성, 원드라이브 또는 NAS에 업로드 예정 (백업 서버와 분리)
OS 보안 강화
- 우분투 환경에서 ClamAV 설치 후 전체 검사 실행
- cron 으로 매일 새벽 3시 백신 검사 자동화, 결과 로그만 보관
- 향후 Slack 알림까지 붙일 예정
보안 이후 상태
- 80, 443 포트 : 오픈
- 2222 포트 : SSH 통신용, 오픈
- 그 외 포트 : 접근 불가
- 일 1회 03시 서버 백신 기동
- SSH : 비대칭키 로그인만 허용
- Docker 네트워크 분리 : 리버스 프록시(NGINX)를 DMZ로 두고 다른 서비스는 내부 Docker 네트워크만 사용
- 00시 서버 내 데이터 스냅샷 생성(백업)
Docker가 iptables을 직접 만지는 걸 고려하지 못한 게 문제였다. 운영 중인 서비스가 아니었기에 망정이지, 실제 서비스 DB였다면 결과는 참혹했을 것이다.
서비스를 시작하게되면 꼭 라우터를 추가로 붙여, 2단계 보안을 구성해야겠다.
IDE로 DB 접근하기
3306 포트를 열지 않아도, SSH 로컬 포트 포워딩을 통해 접근할 수 있다.
ssh -i key.pem -p 2222 -L 13306:172.30.0.10:3306 user@serverIP
로컬에서 localhost:13306 으로 접속하면, 서버 내부 DB와 안전하게 연결된다. (세션은 유지해야 함)
이를 위해 Docker network를 고정 IP로 설정해 두었다.
맺음말
랜섬웨어 공격은 남 얘기가 아니었다. 직접 겪고 나니 보안은 서비스 설계 단계부터 가장 먼저 고려해야 할 요소라는 걸 뼈저리게 느꼈다.
결론은 간단하다.
포트 하나, 진입점 하나가 서비스 전체를 무너뜨릴 수 있다.
'DevOps' 카테고리의 다른 글
| Kubernetes에서 MySQL 마스터-슬레이브(Replication) 구축하기 (0) | 2026.01.21 |
|---|---|
| Linux Page Cache와 Linux OS의 메모리 사용 전략 (0) | 2025.12.09 |
| ELK 스택으로 실시간 로그 수집 및 분석하기: Logback, Filebeat, Elasticsearch, Kibana Quick Starter (1) | 2024.08.18 |
| Grafana로 실시간 서비스 모니터링: Actuator와 Prometheus Quick Starter (0) | 2024.08.15 |
| Kafka의 EOS 보장 및 성능 최적화 전략: Producer, Consumer, Broker 설정 (1) | 2023.08.08 |