네트워크

외부 보안 공격 이후 EC2 네트워크가 끊긴 이야기 — DHCP 갱신 실패 장애 분석

clay.kim 2026. 2. 23. 21:18

외부 보안 공격 이후 EC2 네트워크가 끊긴 이야기 — DHCP 갱신 실패 장애 분석

안녕하세요, Tasteam에서 Cloud 팀을 맡고 있는 clay입니다.

"dev 서버 접속이 안 돼요."

장애의 시작은 백엔드 팀의 제보였습니다. dev 서버에 접속이 안 된다는 이야기를 듣고 확인하려고 SSH로 들어가려 했는데, SSH도 붙지 않았습니다.

처음에는 인스턴스가 꺼졌나 싶었습니다. 그런데 EC2 콘솔에서는 인스턴스가 running 상태였어요. 서버는 실행 중인데, 웹도 안 되고 SSH도 안 되는 이상한 상황이었습니다.

조금 더 확인해 보니 EC2 상태 검사가 2/3으로 떨어져 있었습니다. 이후 로그를 따라가 보니, 실제 원인은 애플리케이션보다 더 아래 계층에 있었습니다. EC2 인스턴스의 DHCP 갱신이 실패하면서 네트워크 인터페이스가 Failed 상태가 되었고, 그 결과 웹 서비스와 SSH 접근이 모두 끊긴 장애였습니다.

오늘은 외부 SSH 브루트포스 공격 이후 EC2 네트워크가 어떻게 끊겼는지, 어떤 로그를 근거로 원인을 좁혔는지, 그리고 이후 어떤 재발 방지 대책을 세웠는지 공유해 보겠습니다.


환경

항목 내용

장애 일시 2026-02-10 17:56 KST ~ 21:05 KST
장애 시간 약 3시간 9분
영향 범위 dev.tasteam.kr, grafana-dev.tasteam.kr 접근 불가
서비스 단계 MVP 출시 전 Development 환경
인스턴스 development-ec2-mvp
인스턴스 타입 c7i-flex.large
리전 / AZ ap-northeast-2b
외부 진입점 Cloudflare + Caddy
네트워크 관리 systemd-networkd, DHCPv4
직접 증상 웹 서비스 접근 불가, SSH 접속 불가, EC2 Instance Status Check 2/3
핵심 원인 SSH 브루트포스 공격 이후 시스템/네트워크 부하 증가, DHCP 갱신 실패

처음엔 인스턴스가 꺼진 줄 알았습니다

백엔드 팀에서 dev 서버 접속이 안 된다고 알려준 뒤, 저는 먼저 SSH 접속을 시도했습니다.

ssh ubuntu@dev.tasteam.kr

하지만 SSH 접속도 되지 않았습니다.

이때까지만 해도 가장 단순한 가능성은 "인스턴스가 꺼졌나?"였습니다. 그런데 AWS 콘솔에서 확인해 보니 인스턴스 상태는 running이었습니다.

문제는 상태 검사였습니다.

Instance status checks: 2/3 checks passed

인스턴스는 실행 중이지만, 네트워크나 인스턴스 내부 상태 중 일부가 정상은 아니었습니다. 이때부터 애플리케이션 문제가 아니라 EC2 레벨 문제로 보고 확인하기 시작했습니다.


Cloudflare 522와 앱 타임아웃은 결과였습니다

웹 쪽 증상으로는 Cloudflare 522가 보였습니다.

Cloudflare 522는 Cloudflare가 오리진 서버와 TCP 연결을 맺지 못할 때 발생합니다. 그래서 처음에는 Caddy 설정이나 애플리케이션 프로세스 상태도 의심했습니다.

장애 전에는 Caddy reload도 있었습니다.

17:00 KST - sudo systemctl reload caddy.service
17:00 KST - Caddy admin_limit rate limit 초과 로그 대량 발생

그리고 Spring Boot 쪽에서도 이런 로그가 확인됐습니다.

dial tcp 127.0.0.1:8081: i/o timeout

이 로그만 보면 Caddy가 로컬 애플리케이션으로 프록시하지 못한 문제처럼 보입니다. 하지만 EC2 콘솔에서 상태 검사가 2/3으로 떨어진 것을 확인하면서 관점을 바꿔야 했습니다.

애플리케이션이 죽은 것이 아니라, 인스턴스 네트워크 자체가 흔들리고 있었습니다.


결정적 단서는 DHCP 갱신 실패였습니다

복구 후 이전 부팅 세션의 로그를 확인했습니다.

journalctl -b -2

장애 시점 직전에 아래 로그가 남아 있었습니다.

Feb 10 08:55:33 systemd-networkd[386]: enp39s0: Could not set DHCPv4 address: Connection timed out
Feb 10 08:55:33 systemd-networkd[386]: enp39s0: Failed

EC2 인스턴스는 VPC 안에서 DHCP를 통해 네트워크 설정을 받습니다. 이 갱신이 타임아웃되면서 enp39s0 인터페이스가 Failed 상태가 되었고, 그 뒤로 인스턴스는 외부 요청을 받을 수 없었습니다.

그래서 Cloudflare 522도, SSH 접속 실패도, 애플리케이션 프록시 타임아웃도 모두 같은 방향을 가리키고 있었습니다.

오리진 서버의 네트워크가 정상적으로 동작하지 않았던 것입니다.


타임라인으로 다시 보면

장애 로그를 시간 순서로 정리하면 흐름이 더 명확해집니다.

시각 (KST) 이벤트

17:00 Caddy 설정 reload 수행
17:00 Caddy rate limit 관련 로그 대량 발생
17:45 홍콩 IP 156.236.75.178에서 SSH 브루트포스 공격 시작
17:46 Spring Boot 앱 dial tcp 127.0.0.1:8081: i/o timeout 발생
17:55 enp39s0: Could not set DHCPv4 address: Connection timed out 발생
17:55 enp39s0: Failed 로그 발생
17:56 EC2 Instance Status Check 실패 감지
18:00 이후 SSH 접속 불가, 웹 서비스 접근 불가, Cloudflare 522 발생
저녁 시간대 식사 시간과 겹치며 즉시 확인이 지연됨
20:59 1차 리부팅, 37초 만에 다시 종료
21:05 2차 리부팅 후 정상 부팅, 서비스 복구

여기서 중요한 건 장애가 단일 애플리케이션 프로세스의 문제가 아니었다는 점입니다. SSH, Caddy, Spring Boot가 각각 따로 실패한 게 아니라, 네트워크 인터페이스 실패 이후 모두 함께 영향을 받은 상황에 가까웠습니다.

다행히 이 서버는 MVP 출시 전 dev 환경이었습니다. 운영 사용자 전체가 영향을 받은 것은 아니었지만, 팀 내부 개발과 테스트는 완전히 막혔고 dev 환경을 확인하던 사람에게는 서비스가 죽은 것처럼 보였을 겁니다.


SSH 브루트포스 공격이 있었습니다

DHCP 실패 직전, SSH 로그에서 반복적인 접속 시도가 확인됐습니다.

홍콩 소재 IP 156.236.75.178에서 존재하지 않는 admin 계정으로 SSH 접속을 반복했습니다. 확인된 시도는 총 638회였습니다.

Feb 10 08:45:07 sshd[138845]: Invalid user admin from 156.236.75.178 port 49628
Feb 10 08:45:10 sshd[138850]: Invalid user admin from 156.236.75.178 port 55706
... (638회 반복)

이 공격이 DHCP 서버를 직접 공격했다는 뜻은 아닙니다. 더 정확히 말하면, 퍼블릭 SSH가 열려 있었고, 자동화된 브루트포스 요청이 들어오면서 인스턴스 내부에서 sshd 프로세스가 반복 생성되는 상황이었습니다.

여기에 Caddy rate limit 관련 로그와 기존 웹 트래픽까지 겹치면서 시스템 리소스와 네트워크 계층에 압박이 생겼고, 그 상태에서 DHCP 갱신이 타임아웃된 것으로 판단했습니다.

정리하면 아래와 같습니다.

SSH 브루트포스 공격
    ↓
sshd 프로세스 반복 생성, 시스템/네트워크 부하 증가
    ↓
Caddy rate limit 관련 로그 및 웹 트래픽과 부하 중첩
    ↓
DHCP 갱신 타임아웃
    ↓
네트워크 인터페이스 Failed
    ↓
Instance Status Check 실패
    ↓
웹 서비스와 SSH 접근 모두 불가

이 중에서 로그로 확정할 수 있었던 것은 SSH 브루트포스, DHCP 갱신 실패, 네트워크 인터페이스 Failed, Instance Status Check 실패입니다. 반면 c7i-flex 인스턴스의 네트워크 allowance나 시스템 부하가 DHCP 패킷에 어느 정도 영향을 줬는지는 가능성이 높은 연결고리로 봤습니다.

장애 분석에서는 이 구분이 중요했습니다. 확인된 사실과 추정한 인과관계를 섞어버리면, 이후 재발 방지 대책도 엉뚱한 곳으로 갈 수 있기 때문입니다.


복구는 리부팅이었지만, 원인 파악은 로그였습니다

복구 자체는 복잡하지 않았습니다.

순서 조치 결과

1 AWS 콘솔에서 시스템 로그 확인 부팅 단계 문제는 아닌 것으로 판단
2 EC2 콘솔에서 인스턴스 리부팅 1차 리부팅 후 37초 만에 다시 종료
3 Stop → Start 방식으로 재기동 정상 부팅, 서비스 복구
4 journalctl -b -2로 이전 부팅 로그 확인 DHCP 실패와 SSH 공격 확인
5 네트워크, 디스크, 메모리 상태 확인 리부팅 후 정상

장애 상황에서는 리부팅으로 서비스가 살아나는 것만으로는 충분하지 않습니다. 왜 네트워크가 끊겼는지 확인하지 않으면 같은 문제가 다시 반복될 수 있습니다.

이번에도 복구 후 이전 부팅 세션의 로그를 확인했기 때문에 DHCP 실패와 SSH 브루트포스 공격을 연결해서 볼 수 있었습니다.


바로 막아야 했던 것들

1. SSH를 전체 공개로 열어두지 않기

가장 먼저 봐야 할 것은 보안 그룹이었습니다. 당시 SSH(22) 접근이 0.0.0.0/0로 열려 있었다면, 인터넷 전체에서 접속 시도가 들어올 수 있습니다.

변경 방향은 단순합니다.

유형 포트 변경 전 변경 후

SSH 22 0.0.0.0/0 관리자 IP /32

고정 IP를 쓰기 어렵다면, SSH를 직접 열어두는 대신 AWS Systems Manager Session Manager를 검토하는 편이 낫습니다.

2. fail2ban으로 반복 시도 차단하기

SSH를 완전히 닫기 어렵다면 최소한 반복 로그인 실패를 자동으로 차단해야 합니다.

sudo apt install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

기본 설정만으로도 SSH 로그인 실패가 반복되는 IP를 일정 시간 차단할 수 있습니다.

3. Session Manager로 접속 경로를 바꾸기

이 장애 이후 SSH 접근 방식을 다시 봤습니다. 퍼블릭 SSH를 열어두면 로그인 성공 여부와 관계없이 공격 표면이 계속 남습니다.

그래서 이후에는 AWS Systems Manager Session Manager를 도입했습니다.

Session Manager를 쓰면 인스턴스에 SSH 포트를 직접 열지 않고도 IAM 권한을 통해 쉘에 접근할 수 있습니다. 보안 그룹에서 22번 포트를 닫을 수 있고, 접속 권한도 IAM 기준으로 관리할 수 있습니다.

이 장애의 직접 복구 수단은 리부팅이었지만, 재발 방지 관점에서는 SSH 자체를 운영 접근의 기본 경로로 두지 않는 것이 더 중요했습니다.

4. Instance Status Check 알람 설정하기

이번 장애는 애플리케이션 알람보다 EC2 상태 검사 알람이 더 빨리 알려줬어야 하는 유형입니다.

알람 지표 조건

Instance Status StatusCheckFailed_Instance >= 1
System Status StatusCheckFailed_System >= 1
네트워크 allowance NetworkBandwidthInAllowanceExceeded > 0

웹 헬스체크만 보고 있으면, 원인이 애플리케이션인지 인스턴스인지 구분하는 데 시간이 걸립니다. EC2 상태 검사 알람은 인프라 레이어 문제를 더 빠르게 알려줄 수 있습니다.

특히 이번 장애는 저녁 식사 시간과 겹치면서 확인이 늦어졌습니다. 사람이 바로 콘솔을 보고 있지 않아도 상태 검사 실패를 알 수 있도록, 이런 알람은 초기에 잡아두는 편이 맞았습니다.

5. Caddy Admin API는 localhost로 제한하기

로그에서 Caddy admin 관련 경고도 확인됐습니다.

admin endpoint on open interface; host checking disabled

Caddy Admin API는 외부에 열어둘 이유가 거의 없습니다. Caddyfile에서 admin endpoint를 localhost로 제한합니다.

{
    admin localhost:2019
}

이 조치는 이번 장애의 직접 원인이라기보다, 장애 분석 중 함께 발견한 보안 개선 지점이었습니다.


돌아보며

1. 퍼블릭 SSH는 보안 문제이자 가용성 문제입니다

SSH를 전체 공개로 열어두면 로그인 성공 여부와 별개로 계속 공격 트래픽을 받습니다. 이번 장애에서 보듯이, 실패한 로그인 시도도 충분히 시스템에 부담이 될 수 있습니다.

2. dev 환경도 인터넷에 열리면 공격받습니다

이번 장애가 MVP 출시 전 dev 서버에서 발생한 것은 그나마 다행이었습니다. 하지만 dev 환경이라고 해서 공격자가 봐주는 것은 아니었습니다. 퍼블릭 IP와 SSH 포트가 열려 있으면 자동화된 봇은 그대로 들어옵니다.

3. Cloudflare 522는 출발점일 뿐입니다

Cloudflare 522를 보면 프록시나 애플리케이션 설정을 먼저 의심하게 됩니다. 하지만 522는 결국 "Cloudflare가 오리진에 연결하지 못했다"는 뜻입니다. 오리진 내부의 네트워크 인터페이스나 EC2 상태 검사까지 함께 봐야 합니다.

4. 장애 분석에서는 사실과 추정을 나눠야 합니다

이번에 확정할 수 있었던 건 SSH 브루트포스 공격, DHCP 갱신 실패, 인터페이스 Failed, Instance Status Check 실패였습니다.

반대로 "왜 DHCP 갱신 패킷이 타임아웃됐는가"는 시스템/네트워크 부하와 인스턴스 특성이 겹친 결과로 추정했습니다. 이 차이를 구분해야 재발 방지 대책도 정확해집니다.


마치며

이번 장애는 겉으로는 Cloudflare 522와 SSH 접속 불가로 보였지만, 실제로는 EC2 내부 네트워크 서비스가 실패한 장애였습니다.

가장 큰 교훈은 단순했습니다.

외부에 열린 관리 포트는 작은 서비스에서도 반드시 공격받고, 그 영향은 보안 사고를 넘어 가용성 장애로 이어질 수 있다는 점입니다.

서비스 규모가 작을수록 "아직 괜찮겠지"라고 생각하기 쉽습니다. 하지만 인터넷에 공개된 순간부터 봇은 들어옵니다. 운영 환경이 아니더라도, 퍼블릭 SSH와 상태 검사 알람은 초기에 정리해두는 편이 훨씬 안전했습니다.