"서버가 이상하게 느린데, 뚫린 걸까?" — 방어는 나중, 지금은 30초 진단
로드 애버리지가 갑자기 튀고, 모르는 프로세스가 CPU를 먹고, 대시보드에 낯선 아웃바운드 트래픽이 잡힌다. 이럴 때 가장 급한 일은 fail2ban을 설치하는 게 아니라 "이미 뚫렸는지"를 먼저 판별하는 것이다. 방어 설정은 침해 여부를 확인한 다음의 이야기다.
2026년 현재 SSH를 노리는 자동화 봇넷과 크리덴셜 스터핑은 계속 증가 추세로 보고되며, 클라우드에 노출된 키를 악용한 암호화폐 채굴 악성코드는 특유의 아웃바운드 연결 패턴을 남긴다. 다행히 이런 흔적 대부분은 로그인 · 인증 로그 · 네트워크 · 프로세스 4개 축만 훑으면 30초 안에 드러난다.
이 글은 개념 설명을 최소화하고, 지금 터미널에 그대로 붙여넣을 명령 시퀀스와 정상/이상 판별 기준표, 그리고 발견 시 즉시 실행할 격리 명령까지 원스톱으로 정리했다.
적용 범위: Debian/Ubuntu 계열(인증 로그
/var/log/auth.log)과 RHEL/CentOS/Rocky/Alma 계열(/var/log/secure). systemd 저널만 쓰는 최신 배포판은journalctl대안을 각 절에 병기했다.
점검 시퀀스 ① 누가 접속했나 — last / lastb / who / w
가장 먼저 "누가 로그인에 성공했고, 누가 실패를 퍼부었으며, 지금 누가 붙어 있는가"를 본다.
# 성공 로그인 이력 (IP까지 표시)
last -a | head -20
# 실패 로그인 이력 — 무차별 대입의 1차 신호
sudo lastb | head -30
# 현재 로그인 중인 사용자와 원격지 IP
who -a
# 현재 세션 + 각 세션이 실행 중인 명령
w예상 정상 결과: last -a에는 익숙한 관리자 IP(회사 대역, VPN 대역)만 보인다. lastb는 몇 건~수십 건 정도로 짧거나 비어 있다. who/w에는 지금 작업 중인 본인 세션만 뜬다.
예상과 다를 때(분기):
lastb가 수백~수천 줄로 화면을 뒤덮으면 → 무차별 대입 폭주. 아래 ②로 이동해 성공 여부를 반드시 확인한다.last -a에 낯선 국가/호스팅 대역 IP가 성공 이력으로 찍혀 있으면 → 침해 강한 의심. 즉시 ⑤ 조치로.w에 내가 실행하지 않은 세션(특히root나 서비스 계정)이 원격지에서 붙어 있으면 → 활성 침입 가능성.
lastb는/var/log/btmp를 읽으며 root 권한이 필요하다. 파일이 없으면 실패 로그 기록이 꺼진 것이니,sudo touch /var/log/btmp후에도 향후 기록을 남기도록 확인한다.
점검 시퀀스 ② 인증 로그 — 실패 폭주와 수상한 성공 IP
lastb가 요약이라면, 인증 로그는 원본 증거다. 실패가 아무리 많아도 "성공(Accepted)"이 없으면 방어선은 아직 유지되고 있다는 뜻이다.
# ── Debian / Ubuntu ──
# 실패한 비밀번호 시도 (최근순)
sudo grep "Failed password" /var/log/auth.log | tail -30
# 공격 IP별 실패 횟수 집계 (많은 순)
sudo grep "Failed password" /var/log/auth.log \
| awk '{for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}' \
| sort | uniq -c | sort -rn | head
# ★ 가장 중요: 로그인 성공 기록 (여기 낯선 IP가 있으면 비상)
sudo grep "Accepted" /var/log/auth.log | tail -20
# 새 사용자/그룹 추가, sudo 권한 획득 흔적
sudo grep -E "useradd|new user|new group|sudo:.*COMMAND" /var/log/auth.log | tailRHEL/CentOS 계열은 경로만 /var/log/secure로 바꾸면 된다.
# ── RHEL / CentOS / Rocky / Alma ──
sudo grep "Failed password" /var/log/secure | tail -30
sudo grep "Accepted" /var/log/secure | tail -20로그 파일이 로테이션됐거나 systemd 저널만 쓴다면:
# 저널 기반 인증 로그 조회
sudo journalctl -u ssh -u sshd --no-pager | grep -E "Failed|Accepted" | tail -40판별 포인트:
Failed password가 특정 IP 하나에서 수천 건 → 단순 봇 스캔. 성공만 없으면 즉시 침해는 아니지만 차단 대상.Accepted password/Accepted publickey에 모르는 IP + root 계정 조합 → 최우선 대응.- 실패가 폭주하던 IP가 어느 순간
Accepted로 바뀌었다면 → 크리덴셜 스터핑 성공 의심. 해당 계정 즉시 잠금.
점검 시퀀스 ③ 밖으로 새는 연결과 숨은 작업 — ss / netstat / ps / crontab
침입 성공 후 악성코드는 보통 밖으로 나가는 연결(C2 접속, 채굴 풀 연결)을 만들고, 재부팅 후에도 살아남기 위해 크론이나 서비스에 자신을 심는다.
# 현재 맺어진 연결 + 프로세스명 (established만)
sudo ss -tnp state established
# LISTEN 중인 모든 포트 + 프로세스 (낯선 고포트 주의)
sudo ss -tlnp
# netstat 선호 시 (net-tools 필요)
sudo netstat -antp
# 프로세스 트리 — 부모 없는/난독화된 프로세스 찾기
ps auxf | less
# CPU 점유 상위 프로세스
ps aux --sort=-%cpu | head
# 예약 작업 점검 (여러 계정 + 시스템 크론 전체)
sudo crontab -l
for u in $(cut -f1 -d: /etc/passwd); do echo "== $u =="; sudo crontab -l -u $u 2>/dev/null; done
ls -la /etc/cron.* /etc/cron.d/ 2>/dev/null
cat /etc/crontab예상 정상 결과: ss established 목록은 알려진 서비스(80/443 웹, 5432 DB, 모니터링 에이전트) 위주. 크론에는 백업·로그 로테이션 등 익숙한 라인만.
위험 신호 분기:
ss에4444,3333,14444같은 고포트로 나가는 established 연결 → 채굴 풀/C2 의심.ps auxf에서/tmp,/dev/shm,/var/tmp경로에서 실행되는 바이너리, 랜덤 문자열 이름(kdevtmpfsi,xmrig유사) → 즉시 격리.crontab에curl ... | bash,wget ... | sh, base64 인코딩된 라인 → 재감염 루틴. 삭제 전 반드시 기록.
한 줄 통합 점검 스니펫
시간이 없다면 아래 블록 하나로 4개 축을 한 번에 훑는다. 배포판 경로는 자동 감지한다.
LOG=$( [ -f /var/log/auth.log ] && echo /var/log/auth.log || echo /var/log/secure ); \
echo "== 성공 로그인 =="; last -a | head -5; \
echo "== 실패 폭주 IP TOP =="; sudo grep "Failed password" "$LOG" 2>/dev/null \
| awk '{for(i=1;i<=NF;i++) if($i=="from") print $(i+1)}' | sort | uniq -c | sort -rn | head -5; \
echo "== 수상한 성공(Accepted) =="; sudo grep "Accepted" "$LOG" 2>/dev/null | tail -5; \
echo "== 아웃바운드 established =="; sudo ss -tnp state established | head -10; \
echo "== CPU TOP =="; ps aux --sort=-%cpu | head -5; \
echo "== root 크론 =="; sudo crontab -l 2>/dev/null결론: 정상 vs 이상 판별 기준표
훑고 나서 헷갈릴 때를 위한 기준표다. 위험 신호가 2개 이상 겹치면 침해로 간주하고 조치로 넘어간다.
| 점검 항목 | 정상 신호 | 위험 신호 |
|---|---|---|
lastb 실패 건수 | 수 건~수십 건/시간 | 수백~수천 건, 특정 IP 집중 |
Accepted 성공 IP | 익숙한 관리자/VPN 대역 | 낯선 국가·호스팅 대역, 특히 root |
| 실패→성공 전환 | 없음 | 폭주하던 IP가 Accepted로 전환 |
| LISTEN 포트 | 알려진 서비스 포트 | 정체불명 고포트, /tmp 실행 바이너리 |
| established 아웃바운드 | 웹·DB·모니터링 에이전트 | 4444/3333 등 채굴풀·C2 의심 포트 |
| 프로세스명 | 정규 서비스명, 명확한 부모 | 랜덤 문자열, /tmp·/dev/shm 실행 |
| root crontab | 백업·로테이션 등 익숙한 라인 | curl|bash, base64, 추가된 낯선 라인 |
오탐 주의 — 이건 정상입니다
- 정상 백업 크론:
rsync,pg_dump,tar야간 작업은 CPU·네트워크를 순간 튀게 한다. - 모니터링 에이전트 아웃바운드: Datadog·NewRelic·Prometheus 원격 write, CloudWatch 에이전트는 established 아웃바운드를 상시 유지한다.
- 패키지 미러 접속:
apt/yum자동 업데이트 시점의 외부 연결. - 클라우드 메타데이터:
169.254.169.254로의 연결은 정상.
핵심은 "낯선 것"이 아니라 **"설명되지 않는 것"**을 찾는 것이다. 프로세스·연결·크론을 봤을 때 "왜 이게 여기 있지?"에 답할 수 없다면 위험 신호로 다룬다.
발견 시 즉시 조치 — 복붙 순서
침해가 확실하면 네트워크 격리 → 세션 종료 → 계정 잠금 → 키 회수 → 증거 보존 순으로 진행한다. 서두르다 로그를 지우지 않도록 순서를 지킨다.
# 1) 공격/의심 IP 인바운드·아웃바운드 차단
sudo iptables -A INPUT -s <공격IP> -j DROP
sudo iptables -A OUTPUT -d <C2/채굴풀IP> -j DROP
# 2) 침입자 세션 강제 종료
sudo pkill -KILL -u <의심계정> # 특정 사용자 세션 전체
sudo kill -9 <악성PID> # 특정 프로세스
# 3) 계정 잠금 (로그인·셸 모두 차단)
sudo passwd -l <의심계정>
sudo usermod -s /usr/sbin/nologin <의심계정>
# 4) 침해 의심 SSH 키 회수 — 삭제 전 반드시 백업(증거)
sudo cp /home/<user>/.ssh/authorized_keys /root/ir_authorized_keys.$(date +%s).bak
sudo cat /home/<user>/.ssh/authorized_keys # 낯선 키 확인
# 낯선 키 확인 후 해당 라인 제거 또는 파일 비우기
sudo : > /home/<user>/.ssh/authorized_keys # 전체 회수가 필요할 때
# 5) 크론 백도어 제거 (내용 기록 후)
sudo crontab -l -u <user> > /root/ir_cron_<user>.bak
sudo crontab -r -u <user>완전한 네트워크 격리가 필요하면 클라우드 콘솔에서 보안 그룹/방화벽을 "전체 차단"으로 바꾸는 편이 확실하다. 다만 SSH 관리 접근까지 끊기지 않도록 본인 IP만 허용 규칙을 남긴다.
재발 방지 방향(별도 대응): 근본 원인 제거 후 비밀번호 인증 비활성화·키 기반 인증·fail2ban 도입을 검토한다. 이 글은 탐지·진단 전용이므로 방어 설정은 별도 가이드로 이어가면 된다. 침해가 확인된 서버는 완전한 신뢰 회복이 어려우므로, 중요 자산이면 스냅샷 보존 후 재프로비저닝을 권한다.
자주 묻는 질문 (FAQ)
Q. lastb가 수천 건인데 서버가 이미 뚫린 건가요?
A. 실패(lastb)가 많은 것 자체는 봇 스캔의 일상적 소음일 수 있습니다. 판단 기준은 성공 여부입니다. grep "Accepted" /var/log/auth.log(또는 /var/log/secure)에 낯선 IP의 성공이 없다면 아직 방어선은 유지된 상태입니다. 다만 해당 IP는 iptables로 차단하는 것이 좋습니다.
Q. auth.log가 없고 secure도 없어요. 어디를 봐야 하나요?
A. 최신 배포판은 텍스트 로그 대신 systemd 저널만 쓰는 경우가 있습니다. sudo journalctl -u sshd --no-pager | grep -E "Failed|Accepted"로 동일한 정보를 조회할 수 있습니다. 저널 보존 기간은 /etc/systemd/journald.conf의 설정에 따라 달라집니다.
Q. /tmp에서 실행되는 프로세스는 무조건 악성인가요?
A. 대부분의 정규 서비스는 /usr/bin, /opt 등에서 실행됩니다. /tmp·/dev/shm·/var/tmp에서 실행되는 바이너리, 특히 랜덤 문자열 이름은 채굴·백도어의 전형적 패턴이므로 강한 의심 대상입니다. 다만 일부 CI 러너나 빌드 도구가 임시 경로를 쓰기도 하므로, 프로세스의 부모와 실행 경로(ls -l /proc/<pid>/exe)를 함께 확인해 판단하세요.
이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
댓글
첫 번째 댓글을 남겨보세요.