[서버 침해 대응기 시리즈] 1인 개발자가 크립토재킹 당하고, 서버를 통째로 새로 만든 5일간의 기록
- 1편. 내 서버가 6주째 몰래 채굴당하고 있었습니다 ← 지금 글
- 2편. docker.sock을 Jenkins한테도 AI한테도 주지 마세요 (socket-proxy)
- 3편. 7개 도메인 무중단 HTTPS 이전기 (nginx + certbot)
- 4편. Jenkins 자동배포, 보안이랑 403 싸운 이야기
- 5편. 그래서 보안과 편의 사이, 어디에 서야 할까
지난 글에서 이런 얘기를 했어요.
AI한테 권한을 어디까지 줄 거냐.
자동화를 할수록 권한 분리가 중요하다고.
근데 솔직히 그땐 좀 추상적인 얘기였거든요.
"중요하다" 정도였죠.
이번에 제대로 당했습니다.
제 서버가 6주 동안 누군가의 채굴기로 돌아가고 있었어요.
처음엔 그냥 서버가 느렸어요
어느 날부터 서버가 좀 이상했습니다.
CPU가 계속 높아요.
배포할 때도 평소보다 굼뜨고.
처음엔 그냥 트래픽 늘었나 했어요.
서비스 여러 개 돌리고 있으니까.
근데 새벽에도 CPU가 안 떨어지더라고요.
새벽엔 아무도 안 들어오는데요?
어? 이상하죠?
그래서 top을 쳤습니다.
그리고 봤어요.
처음 보는 프로세스가 CPU를 다 먹고 있는 걸.
채굴기였습니다
이름도 안 숨겼더라고요.
XMRig.
이거 모네로(Monero) 채굴 프로그램입니다.
암호화폐 채굴기요.
C3Pool이라는 채굴 풀에 연결돼 있었고.
외부의 어떤 IP랑 계속 통신하고 있었어요.
명령을 받는 서버죠.
이걸 C2(Command and Control)라고 부릅니다.
쉽게 말하면.
해커가 내 서버한테 "이거 해" 하고 시키는 원격 조종 채널이에요.
제 서버가 남의 지갑을 채워주고 있었던 겁니다.
전기세도 제가 내고요.

어떻게 들어온 걸까
여기서부터가 진짜예요.
저는 처음에 SSH가 뚫린 줄 알았어요.
비밀번호 털렸나.
근데 로그를 까보니까 아니었어요.
SSH 접속 기록은 다 제 IP였거든요.
그럼 대체 어디로 들어온 거야?
범인은 Jenkins였습니다.
Jenkins가 문을 활짝 열어놨더라고요
제가 CI/CD 돌리려고 Jenkins를 띄워놨거든요.
코드 push하면 자동으로 빌드하고 배포하는 그거요.
근데 두 가지 실수가 겹쳤어요.
하나.
Jenkins 포트를 0.0.0.0으로 열어놨어요.
이게 무슨 뜻이냐면.
"전 세계 누구나 들어오세요" 입니다.
내 IP만 열었어야 했는데.
둘.
Jenkins한테 docker.sock을 물려놨어요.
이게 핵심입니다.
docker.sock이 뭐길래
잠깐 설명할게요.
docker.sock은 도커를 조종하는 리모컨이에요.
이걸 가진 쪽은 컨테이너를 만들고, 지우고, 마음대로 할 수 있어요.
그래서 편합니다.
Jenkins가 이걸로 빌드하고 배포하니까요.
근데 여기 함정이 있어요.
docker.sock을 쥐면.
사실상 호스트 서버 전체를 쥐는 거랑 같습니다.
왜냐고요?
컨테이너 하나로 서버 전체를 먹는 법
공격자는 이렇게 했어요.
먼저 열려있는 Jenkins로 들어옵니다.
그리고 docker.sock으로 컨테이너를 하나 만들어요.
평범한 alpine 리눅스 컨테이너요.
근데 만들 때 호스트의 최상위 폴더(/)를 컨테이너 안에 통째로 마운트합니다.
그다음 이 명령 한 줄.
chroot /mnt sh
이게 마법입니다.
chroot는 "여기를 루트로 쳐" 하는 명령이에요.
호스트의 /를 컨테이너 안에서 루트로 잡아버린 거죠.
이 순간.
컨테이너 안에 있지만.
실제로는 호스트 서버의 root가 됩니다.
격리됐다던 컨테이너 벽이.
그냥 사라진 거예요.

그리고 뿌리를 내렸습니다
호스트 root를 먹은 다음은 쉬웠어요.
채굴기를 /tmp에 숨겨놓고.
crontab에 등록했어요.
서버가 재부팅돼도 다시 살아나게요.
이런 걸 지속성(persistence)이라고 합니다.
한 번 들어온 집에 열쇠 복사해두는 거예요.
쫓아내도 다시 들어오게.
그래서 6주를 버틴 거죠.
저는 그것도 모르고 그냥 "서버 느리네" 하고 있었고요.
솔직히 식은땀 났습니다
처음 이거 발견했을 때.
진짜 등골이 서늘했어요.
6주면 짧은 시간이 아니거든요.
그동안 뭘 더 했을까.
DB는 안 털렸을까.
다른 서버로 번지진 않았을까.
머릿속이 복잡해졌습니다.
근데 한편으론.
이게 채굴기라 차라리 다행이라는 생각도 들었어요.
채굴기는 "돈"이 목적이라 시끄럽거든요.
CPU를 다 먹으니까 티가 나요.
만약 조용히 데이터만 빼가는 공격이었으면?
지금도 몰랐을 수도 있어요.
결국 핵심은 지난 글이랑 똑같았어요
기억나세요?
지난 글에서 제가 그랬잖아요.
"AI에게 어디까지 권한을 줄 건가, 이걸 먼저 정해야 한다."
이번 일이 정확히 그거였어요.
AI 얘기였지만.
Jenkins도 똑같습니다.
docker.sock을 준다는 건.
"너 서버 전체 권한 가져" 랑 같은 말이거든요.
저는 그걸 0.0.0.0에 열어놓기까지 했고요.
권한을 줄 거면.
어디까지 줄지, 어디서 막을지를 먼저 정했어야 했어요.
안 정했더니.
6주간 남 좋은 일만 시켰습니다.
다음 편 예고
일단 급한 불은 껐어요.
채굴기 죽이고, crontab 지우고, C2 IP 차단하고.
근데 이미 root까지 먹힌 서버는.
솔직히 못 믿어요.
어디에 뭘 더 심어놨을지 모르거든요.
그래서 결정했습니다.
서버를 통째로 새로 만들기로.
다음 편에서는 새 서버로 옮기면서.
docker.sock을 Jenkins한테도, AI한테도 안 주는 방법을 풀어볼게요.
socket-proxy라는 녀석이 주인공입니다.
이번에 제대로 데였으니.
이번엔 제대로 막아야죠.
'서버, 인프라' 카테고리의 다른 글
| 7개 도메인을 무중단으로 HTTPS 이전한 이야기 (0) | 2026.06.19 |
|---|---|
| docker.sock을 Jenkins한테도 AI한테도 주지 마세요 (0) | 2026.06.19 |
| Docker 안에서 Claude Code, Codex 쓰다가 git push 막힌 분들 보세요 (0) | 2026.06.03 |
| Ubuntu 서버에 Claude Code + Codex 올려봤더니, 개발 방식이 달라졌어요 (0) | 2026.06.02 |
| [Claude 블로그 자동화] 블로그 글감 자동화 완성 — 수집부터 티스토리 발행까지 5편 (0) | 2026.05.31 |