1. 3-Tier Architecture 설계
작은 이커머스 서비스를 만든다고 가정하고 시작했다. 기능은 단순했다. 로그인, 상품 조회, 상품 주문, 회원 정보 저장, 주문 정보 저장. 이 정도면 그냥 서버 한 대에 다 넣어도 되지 않을까 싶었다.
시작: 서버 한 대에 다 몰아넣기
가장 쉬운 방법부터 시도해봤다. 서버 한 대에 웹 서버, 애플리케이션, DB를 전부 설치하는 것. 개발 초기에는 이게 제일 편하다. 배포도 한번만 하면 되고 서버 관리도 하나만 신경 쓰면 된다.

그런데 이 구성으로 사용자가 늘어난다고 가정해보면, 문제는 거기서부터 시작된다. 서버가 눈에 띄게 느려지는데, 정작 원인을 못 찾겠는 상황이 되어버린다. 웹 요청 처리가 느린 건지, 주문 로직이 느린 건지, DB 조회가 느린 건지 구분할 방법이 없다. CPU와 메모리 지표를 봐도 세 가지가 뒤섞여서 하나로 튀는 것처럼 보였다. 더 답답했던 건, 뭐 하나 고치겠다고 재시작하면 나머지 두 개까지 같이 멈춰버린다는 점이었다.
→ 여기서 그럼 그냥 더 좋은 서버로 바꾸면 되지 않나 하는 생각이 들었다. 그런데 조금만 더 파보니 이건 임시방편일 뿐이었다. 진짜 필요한 건 역할별로 서버를 나누는 것이었다. 웹 처리, 비즈니스 로직 처리, 데이터 저장은 애초에 리소스를 쓰는 패턴이 다르다. 웹은 I/O 위주고, 애플리케이션은 CPU 연산 위주고, DB는 디스크와 메모리 위주다. 이걸 한 서버에 몰아넣으니 어디가 병목인지 구분이 안됐던 거고 장애가 나면 셋 다 같이 죽는 구조였던 거다. 이게 흔히 말하는 3-Tier Architecture(Presentation - Application - Data)의 시작점이라는 걸 여기서 이해했다.
Web / App / DB를 나눴더니 생긴 새로운 문제
서버를 세 대로 나눴다. Web, Application, DB. 이제 원인 분리는 됐다. 그런데 사용자가 100명에서 10,000명으로 늘어나니 이번엔 Application 서버만 CPU 100%를 찍었다. 처음엔 단순하게 생각해서 더 큰 서버로 교체했다. 잠깐은 버텼는데 결국 또 한계가 왔고, 무엇보다 교체할 때마다 서비스를 내려야 한다는 게 계속 걸렸다.
여기서 Scale-up(수직 확장)과 Scale-out(수평 확장)의 차이를 찾아봤다. Scale-up은 서버 한 대의 사양을 올리는 거라 간단하지만 물리적 한계가 있고 교체할 때 다운타임이 생긴다. 반면 Scale-out은 서버를 여러 대로 늘리는 방식이라 이론상 한계가 거의 없고 한 대가 죽어도 나머지가 버텨준다. 다만 여러 대를 하나처럼 다루기 위한 장치가 추가로 필요하다는 게 숙제로 남았다. (= 로드밸런서)
Scale-up vs Scale-out
- Scale-up (수직 확장, Vertical Scaling)
- 장점: 구조가 단순함 (서버 여러 대를 관리할 필요 없음)
- 단점:
- 물리적 한계가 있음 → 아무리 좋은 서버도 최고 사양이라는 게 있음
- 다운타임 발생 → 사양 올리려면 서버를 잠깐이라도 멈추고 교체해야 함
- 그 한 대가 죽으면 서비스 전체가 죽음 (여전히 단일 장애점)
: 서버 한 대의 성능(사양)을 올리는 방식. CPU를 4코어→16코어로, 메모리를 8GB→64GB로 업그레이드하는 식
- Scale-out (수평 확장, Horizontal Scaling)
- 장점:
- 이론상 한계가 거의 없음 (필요하면 계속 서버를 추가하면 됨)
- 한 대가 죽어도 나머지가 버텨줌 (가용성 ↑)
- 단점:
- 여러 대를 하나처럼 다루는 장치가 필요함
: 서버를 여러 대로 늘리는 방식. 한 대 대신 두 대, 세 대로 늘려서 부하를 나눔.
→ 이게 바로 다음에 나오는 로드밸런서(ALB)
여러 대가 있어도 사용자는 그냥 하나의 주소로 접속하고, 뒤에서 로드밸런서가 알아서 분산해주는 구조가 필요함
App 서버를 두 대로 늘렸는데 트래픽이 안 감
Application 서버를 두 대로 늘렸다. 그런데 사용자는 여전히 서버 한 대의 주소로만 접속하고 있었다. 두 번째 서버는 사실상 놀고 있는 셈이었다. 당연한 결과였는데 서버를 늘리는 것과 트래픽을 나누는 건 완전히 다른 문제라는 것을 알게되었다.

→ 여기서 필요한 게 로드밸런서였다.
- 로드밸런서: 사용자 요청을 받아서 여러 서버에 분산해주는 앞단 장치
- AWS로 치면 ALB(Application Load Balancer)가 이 역할을 한다.
- 로드밸런서는 라운드로빈이나 최소 연결 수 같은 기준으로 요청을 나눠주고 뒤에 있는 서버가 몇 대든 사용자 입장에서는 하나의 주소로만 접속하면 되게 만들어준다.
로드밸런서를 붙였더니 로그인이 자꾸 풀림
앞단에 분산 장치를 두고 나니 다음 문제가 등장했다. 로그인한 사용자가 페이지를 넘길 때마다 로그아웃되는 현상이었다. 원인을 찾아보니 단순했다. 요청이 App 1로 갔다가 다음 요청이 App 2로 가면, App 2 입장에서는 이 사용자가 로그인했다는 사실 자체를 모른다. 세션 정보가 각 서버 메모리 안에만 있었기 때문이다.
→ 이건 세션을 서버 밖으로 빼야 한다는 걸 알려주는 문제였다. 서버가 이 사용자를 기억하는 상태를 자기 안에 들고 있으면 서버를 늘릴수록 이런 문제가 계속 생긴다. 그래서 Redis 같은 외부 세션 스토어에 세션을 저장하고 어떤 App 서버가 요청을 받든 그 외부 저장소를 조회해서 로그인 상태를 확인하는 구조로 가야 한다는 걸 정리했다. 이렇게 서버 자체가 상태를 갖지 않게 만드는 걸 Stateless 설계라고 부른다는 것을 알게되었다.
Stateless 설계
- 서버가 상태(사용자 정보)를 자기 안에 기억하지 않는다는 뜻
- 보통 서버는
이 사용자가 로그인했다는 걸 자기 메모리에 저장(Stateful)하는데, 서버가 여러 대면 문제가 생김 - 요청이 다른 서버로 가면 그 서버는 이 사용자를 모름
- 그래서 세션 정보를 서버가 아니라 Redis 같은 외부 저장소에 둠
- 그러면 어떤 서버가 요청을 받든, 그 외부 저장소를 확인해서
아 이 사람 로그인했네라고 알 수 있음
→ 이렇게 서버 자체는 아무 상태도 안 갖고 있는 걸 Stateless!
서버가 죽었는데 트래픽이 계속 감
세션 문제를 해결하고 나니 이번엔 진짜 장애 상황을 가정해봤다. App 1이 죽었다. 그런데 로드밸런서는 이 사실을 모르고 여전히 App 1로도 요청을 보내고 있었다. 사용자 절반이 에러 화면을 보게 되는 상황이다.
이 문제를 풀려면 로드밸런서가 헬스체크를 주기적으로 해야 한다는 걸 알게 됐다. 일정 주기로 서버에 신호를 보내서 응답이 없으면 그 서버를 트래픽 대상에서 자동으로 제외하는 방식이다. 그리고 여기서 자연스럽게 따라오는 질문이
"그럼 한 대가 죽어도 서비스가 유지되려면 최소 몇 대가 필요한가"였는데, 답은 최소 2대 이상의 다중화였다. 한 대만 있으면 그 한 대가 죽는 순간 서비스 전체가 같이 죽어버리기 때문이다.헬스체크 (Health Check)
- 로드밸런서가 뒤에 있는 서버들이 살아있는지 주기적으로 신호를 보내는 것
- 응답이 없으면 그 서버를 죽은 걸로 판단하고 트래픽 대상에서 자동으로 빼줌
다중화 (Redundancy)
- 서버를 최소 2대 이상 두는 것
- 왜?
- 1대만 있으면, 그 1대가 죽는 순간 서비스 전체가 멈추니까 (단일 장애점, Single Point of Failure)
- 2대 이상 있으면 한 대가 죽어도 나머지가 서비스를 계속 처리할 수 있음
DB를 App과 같은 서버에 뒀다가 생긴 일
App 서버를 다중화하고 나서 뒤늦게 발견한 문제가 있었다. DB를 처음에 편하다는 이유로 App 1과 같은 서버에 뒀던 것이다. App 1을 배포하느라 재시작했는데 DB까지 같이 내려가버렸고 결국 App 2까지 전부 멈추는 사태가 벌어졌다.
→ 같은 서버에 있는 것들은 하나가 죽으면 전부 같이 죽는다. DB는 애플리케이션과 완전히 분리된 장애 범위에 둬야 하고 이게 애초에 3-Tier에서 DB를 별도 계층으로 나누는 이유였다. App은 Stateless로 얼마든지 늘리거나 재시작해도 되지만 DB는 상태(데이터)를 갖고 있는 계층이라 더 신중하게, 별도로 관리해야 한다는 걸 다시 확인했다.
최종 구조와 AWS 매핑
여기까지 정리된 구조를 그림으로 그리면 이렇다.

각 구성 요소를 무엇인지 / 왜 필요한지 / 없으면 어떤 문제가 생기는지 / 장애가 나면 어떤 일이 발생하는지로 정리하면 다음과 같다.
구성 요소 | 무엇인가 | 왜 필요한가 | 없으면 생기는 문제 | 장애 시 영향 |
DNS (Route53) | 도메인을 서버 주소로 변환 | 사용자가 IP가 아닌 이름으로 접속 | 접속 자체가 불가능 | 도메인 접속 불가, IP 직접 접근만 가능 |
로드밸런서 (ALB) | 요청을 여러 서버로 분산 | 서버를 늘려도 트래픽을 골고루 받게 함, 헬스체크로 죽은 서버 자동 제외 | 특정 서버로 트래픽 쏠림, 죽은 서버로도 요청 감 | 로드밸런서 자체가 죽으면 전체 접속 불가 (그래서 AWS는 이 부분을 매니지드로 이중화해서 제공) |
Application 서버 | 비즈니스 로직 처리 | 웹/DB와 분리해서 CPU 부하 원인 명확화, 독립적 확장 | 로직 병목을 원인 분리 못 함 | 한 대 죽어도 로드밸런서가 나머지로 우회 (다중화 시) |
세션 저장소 (Redis) | 로그인 상태 등 사용자 상태 저장 | App 서버를 Stateless로 만들어 어떤 서버가 받아도 동일하게 동작 | 서버 바뀔 때마다 로그아웃 | 세션 저장소가 죽으면 전체 로그인 상태 유실 |
DB (RDS) | 회원/주문 데이터 저장 | 데이터의 영속성 보장, App과 별도 장애 범위 | App 재시작 시 데이터까지 같이 내려감 | DB 장애 시 서비스 전체 기능 마비 (읽기/쓰기 모두 불가) |
역할별로 정리하면 AWS 서비스 매핑은 이렇게 된다.
역할 | AWS 서비스 |
가상 네트워크 | VPC |
요청 분산 | ALB (Application Load Balancer) |
컴퓨팅 (Web/App) | EC2 (+ Auto Scaling Group) |
관계형 DB | RDS |
DNS | Route53 |
Route53 (DNS)
- 사람이 기억하기 쉬운 도메인 이름(예: kintoun.com)을 실제 서버의 IP 주소로 바꿔주는 서비스
- 우리가 IP 숫자를 안 외우고 도메인 이름으로 접속할 수 있게 해줌
- AWS의 DNS 서비스 이름이 Route53
RDS
- AWS에서 관리해주는 관계형 데이터베이스 서비스
- MySQL, PostgreSQL 같은 DB를 직접 서버에 설치해서 관리하는 대신, AWS가 대신 운영/백업/장애 복구를 해주는 매니지드 서비스
VPC / Auto Scaling Group
- VPC: AWS 안에 우리만의 가상 네트워크 공간을 만드는 것 (우리 회사 전용 네트워크 구역이라고 생각하면 됨)
- Auto Scaling Group: 트래픽에 따라 EC2 서버 개수를 자동으로 늘리거나 줄여주는 기능 (사용자 많아지면 서버 자동 추가, 적어지면 자동 감소)
2. 네트워크 배치
이번에는 "과제 1에서 만든 서버들이 있는데, 아직 네트워크는 하나도 안 잡혀 있다"는 가상의 상황을 가정하고 이 서버들을 어떻게 배치해야 안전한지를 단계별로 따라가며 정리한 것이다. 지금까지 AWS는 콘솔로 이것저것 만들어봤지만 왜 이렇게 나눠야 하는지를 네트워크 기초부터 짚어본 건 이번이 처음이었다.
전부 인터넷에 그대로 연결했더니
가장 먼저 시도해본 방식은 단순했다. Web, App, DB 세 서버 모두에 공인 IP를 주고 그냥 인터넷에 직접 연결하는 것. 어차피 다 우리 서비스에 필요한 서버니까 문제없을 거라 생각했다.

그런데 하루 만에 DB 서버 3306 포트로 전 세계에서 로그인 시도가 들어왔다는 걸 알게 됐다. DB는 애초에 외부 사용자와 직접 통신할 일이 없는 서버인데, 그냥 인터넷에 노출만 시켜놨다는 이유로 전 세계 스캐너들의 공격 대상이 되어버린 거다.
→ 여기서 최소 권한과 네트워크 분리가 필요하다. 인터넷과 통신해야 하는 서버와 그럴 필요가 없는 서버를 구분하고, 후자는 애초에 인터넷에서 접근 자체가 안 되는 영역에 둬야 한다는 것. 당연한 얘기 같지만 막상 서버를 만들 때는 일단 다 열어놓고 나중에 막자는 생각이 먼저 든다.
2005년 데이터센터로 돌아가서 — DMZ의 등장
이 개념을 클라우드가 아닌 온프레미스(자체 데이터센터) 관점에서 먼저 짚어보기로 했다. 웹 서버, 사내 업무 서버, DB 서버, 관리자 PC가 있는 회사를 가정하고, 전부 사설 IP로 내부망(VLAN)에 몰아넣었다.

안전해지긴 했는데, 이번엔 외부 고객이 웹 서버에 아예 접속을 못 하는 문제가 생겼다. 내부망에 다 넣어버렸으니 외부에서 들어올 길이 없어진 거다.
여기서 DMZ(DeMilitarized Zone)를 찾아봤다. 외부에 노출되어야 하는 서버(웹 서버)만 따로 떼어내는 완충 지대다. DMZ와 내부망 사이에는 방화벽과 라우터가 있어서 DMZ는 인터넷과 통신하되 내부망과는 엄격하게 분리된 규칙으로만 통신하게 만든다. 지금 보면 AWS의 Public Subnet이 사실상 이 DMZ 개념을 그대로 클라우드로 옮겨온 것이라는 걸 나중에 알게 됐다.
VLAN
- 온프레미스(자체 서버실)에서 네트워크를 논리적으로 나누는 기술
- 물리적으로는 같은 네트워크 장비를 쓰더라도, 소프트웨어적으로 여러 구역으로 나눠서 서로 분리된 것처럼 만들어줌
AWS 대응: VPC/Subnet
DMZ (DeMilitarized Zone)
- 네트워크에서는 외부에 노출돼야 하는 서버만 모아두는 중간 지대를 뜻함
- 내부망도 아니고 완전히 열린 인터넷도 아닌, 그 중간에 방화벽으로 격리된 구역
- 웹서버처럼 손님을 맞아야 하는 서버는 여기 두고, DB처럼 안 보여줘도 되는 건 더 안쪽(내부망)에 둠
AWS 대응: DMZ = Public Subnet, 내부망 = Private Subnet
웹은 DMZ에, DB는 내부망에 (그런데 패치를 못 받음)
웹 서버는 DMZ, DB는 내부망에 뒀다. 그런데 이번엔 DB 서버가 OS 보안 패치를 받아야 하는데 내부망이라 인터넷에 나갈 수가 없는 상황이 됐다. 완전히 막아버리니 이번엔 운영이 안 되는 딜레마였다.
→ 이걸 풀어주는 게 NAT(Network Address Translation)이다. 내부망 서버가 인터넷으로 나가기만 할 수 있게 해주고, 바깥에서 들어오는건 여전히 막아주는 장치다. 패치 다운로드처럼 서버가 먼저 요청을 보내는 상황(아웃바운드)은 열어주되, 외부에서 먼저 접속을 시도하는 상황(인바운드)은 차단한다는 게 핵심이었다.
"NAT가 있으면 외부에서도 DB에 접속할 수 있는 거 아니냐"는 질문을 던져봤는데, 답은 명확히 아니다였다. Outbound와 Inbound는 완전히 다른 문제다. NAT는 나가는 연결의 주소를 변환해주는 역할만 하지 외부에서 들어오는 연결을 열어주는 게 아니다. 이 둘을 헷갈리면 NAT 붙였으니 안전하다는 착각을 하기 쉽다는 걸 알게 됐다.NAT (Network Address Translation) + 원리
- 사설 IP를 쓰는 내부 서버가 인터넷에 나가려면, 그 사설 IP로는 인터넷에서 응답을 못 받음 (사설 IP는 인터넷에서 안 통하니까)
→ 그래서 NAT 장치가 내부 서버의 사설 IP를 자기(NAT)의 공인 IP로 바꿔서 대신 요청을 보내주고, 응답이 오면 다시 원래 내부 서버한테 돌려줌
Inbound vs Outbound
- Outbound (나가는 트래픽): 내 서버 → 인터넷으로 나가는 요청 (예: 패치 다운로드, API 호출)
- Inbound (들어오는 트래픽): 인터넷 → 내 서버로 들어오는 요청 (예: 사용자가 우리 웹사이트 접속)
+) NAT는 Outbound만 허용하는 장치
급하게 SSH 포트를 열었다가
관리자가 집에서 내부 서버를 관리해야 하는 상황이 생겼다고 가정했다. 급한 김에 방화벽에서 DB 서버 SSH 포트를 외부에 그냥 열어버렸다. 며칠 뒤 DB 서버에 알 수 없는 로그인 시도가 수천 건 쌓였다는 결과가 나왔다. 이건 앞에서 본 3306 포트 노출과 본질적으로 똑같은 실수였다. 관리 목적이라는 이유로 예외를 만든 것뿐 결국 내부 서버에 외부 접근 경로를 그냥 뚫어버린 거였다.
여기서 필요한 게 배스천 호스트(Bastion Host)였다. 외부에서 내부 서버로 들어와야 할 때 각 서버에 직접 포트를 여는 게 아니라 이 목적을 위해 별도로 만든 중계 서버 하나만 외부에 열어두는 방식이다. 관리자는 배스천 호스트에 먼저 접속하고, 거기서 다시 내부 서버로 접속한다. 이렇게 하면 외부에 노출되는 지점이 딱 하나로 좁혀지고, 그 하나만 집중적으로 모니터링/제한(Least Privilege, ACL)할 수 있다.
배스천 호스트(Bastion Host)
- 사설 IP를 쓰는 내부 서버가 인터넷에 나가려면, 그 사설 IP로는 인터넷에서 응답을 못 받아 (사설 IP는 인터넷에서 안 통하니까). 그래서 NAT 장치가 내부 서버의 사설 IP를 자기(NAT)의 공인 IP로 "바꿔치기"해서 대신 요청을 보내주고, 응답이 오면 다시 원래 내부 서버한테 돌려줌
온프레미스 설계를 AWS로 옮기기
여기까지 정리한 온프레미스 개념들을 AWS 서비스로 옮기면 다음과 같이 대응된다는 걸 확인했다.
온프레미스 개념 | AWS 서비스 |
사내 네트워크 영역 전체 | VPC |
Network Segment | Subnet |
Routing Table | Route Table |
DMZ | Public Subnet |
Internal Network | Private Subnet |
Internet Router | Internet Gateway |
NAT | NAT Gateway |
Firewall Rule (서버 단위) | Security Group |
Firewall Rule (서브넷 단위) | NACL |
Web 서버는 Public Subnet에, App과 DB는 Private Subnet에 두는 걸로 배치했다.

Private Subnet인데 왜 뚫렸을까?
AWS로 옮기고 나서 Web은 Public, App과 DB는 Private에 넣었으니 안전하다고 생각했다. 그런데 공격자가 웹 서버 취약점으로 Web Server 권한을 획득한 상황을 가정해보니, 거기서 DB로 바로 접속이 되는 문제가 나왔다. Private Subnet에 뒀는데 왜 뚫리는지 처음엔 이해가 안 됐다.
찾아보니 답은 간단했다. Private Subnet은 인터넷에서 직접 들어오는 것만 막아줄 뿐, 이미 내부에 들어온 공격자가 다른 서버로 옆으로 이동하는 것까지 막아주지는 않는다. Subnet 배치는외부↔내부 방향의 보호지, 내부↔내부 방향은 별도로 막아야 한다는 걸 알게되었다.
Security Group을 세밀하게 쪼개야 하는 이유
Security Group을 붙이긴 했는데 처음엔 내부망끼리는 다 허용으로 대충 설정했더니 여전히 Web에서 DB로 접속이 가능했다. 이건 애초에 내부니까 신뢰한다는 전제 자체가 틀렸다는 걸 보여주는 결과였다.
그래서 접근 제어를 역할 단위로 다시 쪼갰다.
출발지 | 목적지 | 허용 포트 | 이유 |
Internet | Web (Public Subnet) | 80/443 | 사용자 요청만 받으면 됨 |
Web | App (Private Subnet) | App 포트(예: 8080)만 | Web이 App 로직을 호출하는 데만 필요 |
App | DB (Private Subnet) | DB 포트(예: 3306/5432)만 | App만 DB를 조회/저장하면 됨 |
Web | DB | 없음 (차단) | Web이 DB에 직접 접속할 이유가 전혀 없음 |
관리자 | Bastion | 22 (SSH), 특정 IP만 | 관리 목적 접근을 배스천으로만 한정 |
Bastion | 내부 서버 | 22/SSM | 배스천을 거친 접근만 허용 |
이렇게 출발지-목적지-포트를 서버 역할 단위로 명시적으로 쪼개는 것이 Security Group Chaining이고, 이게 곧 Zero Trust(내부라고 자동으로 신뢰하지 않는다)의 실제 구현 방식이라는 걸 정리하면서 이해했다.
3. CERT 인프라 설계
이번 조사는 서비스와 네트워크는 완성됐다는 가상의 상황을 이어받아서 실제 사고가 났을 때 이걸 탐지하고 대응하는 체계가 어떻게 만들어지는지를 Mission 단위로 따라가며 정리한 것이다. 근두운 팀에서 실제로 Wazuh를 운영해본 경험이 있어서, 앞부분(EDR, 로그 수집)은 익숙하게 읽혔고 뒷부분(위협 인텔, 자동 대응)에서 새로 배운 게 많았다.
Mission 1. 서버에서 무슨 일이 일어났는지 알아내라
새벽 03:12, Application Server에서 알 수 없는 프로세스가 실행됐다는 알림을 아침에 확인한 상황을 가정해봤다. 서버에 들어가 봤더니 프로세스는 이미 종료돼서 없고, ps에도 안 보이고 history는 비어 있었다. 누가, 어떤 명령으로, 어떤 파일을 만들고, 어디와 통신했는지 알아낼 방법이 아예 없었다.
이 상황에서 깨달은 건 사고가 난 뒤에 들어가서 보는 것과 평소에 계속 기록해두는 것은 완전히 다른 차원의 문제라는 점이다. 사후 조사는 흔적이 남아 있어야만 가능한데 공격자가 흔적을 지우면 그걸로 끝이다. 그래서 평소에 서버가 스스로 기록해야 하는 게 있는데 그게 프로세스 실행 기록, 파일 생성/변경 기록, 네트워크 연결 기록이다.
Mission 2. 서버 여러 대의 로그를 한곳에서 보고 싶다
서버가 5대로 늘어난 상황을 가정했다. 사고가 나면 5대에 각각 SSH로 들어가서 로그를 뒤져야 하는데, 이미 공격자가 서버 권한을 가졌다면 로그 자체를 지울 수도 있다는 문제가 있었다.

이걸 풀려면 각 서버의 기록을 실시간으로 서버 밖으로 내보내는 에이전트가 필요했다. 이게 EDR(Endpoint Detection & Response)이고, 근두운에서는 오픈소스 EDR/SIEM 도구인 Wazuh를 이 역할로 쓰고 있다. Wazuh 에이전트를 각 서버에 설치해두면, 서버가 뚫려도 이미 밖으로 나간 로그는 지워지지 않는다는 점이 핵심이었다.
EDR(Endpoint Detection & Response)
- 정의
- 개별 엔드포인트(서버, PC)를 실시간으로 지속 감시해서 위협을 탐지·대응하는 보안 솔루션
- 각 엔드포인트에 에이전트를 설치해서 프로세스 실행, 파일 변경, 네트워크 연결 등을 기록.
- 핵심 특징
- 엔드포인트 단위로 세밀하게 감시하고 실시간 탐지가 강점
- 한계
- 엔드포인트 하나하나는 잘 보지만 조직 전체를 가로지르는 큰 그림(여러 서버에 걸친 공격 패턴)은 EDR 혼자로는 안 보임 → 그래서 SIEM이 필요
SIEM (Security Information and Event Management)
- 정의: 조직 전체의 여러 도구·시스템에서 나오는 보안 데이터를 한곳에 모아 통합 분석하는 중앙 플랫폼
- EDR과의 차이
ㅤ | EDR | SIEM |
범위 | 엔드포인트 단위 | 조직 전체 |
데이터 | 엔드포인트 상세 활동 | 여러 도구의 로그 |
탐지 속도 | 실시간 | 로그 수집 후 분석이라 다소 지연 있을 수 있음 |
- 한계
- SIEM 혼자서는 로그가 늦게 들어오면 탐지도 늦어짐
- EDR과 SIEM은 경쟁 관계가 아니라 같이 써야 서로의 사각지대를 메꿔줌
→ 이게 Wazuh 하나로 EDR+SIEM 기능을 같이 쓰는 이유이기도 함
로그를 한 곳(Wazuh 매니저)으로 모으고 나니, 이번엔 서버마다 로그 형식과 시간대가 다르다는 문제가 나왔다. 공격 하나를 추적하려면 Firewall에서 IP를 찾고, 그 시각의 Web 로그를 보고, 다시 App 로그를 봐야 하는데 시간대가 안 맞으면 순서 자체를 재구성할 수 없다. 그래서 로그 정규화와 NTP로 서버 간 시간 동기화를 맞추는 게 로그 중앙화의 전제 조건이라는 걸 정리했다.

Mission 3. 로그를 모았는데 이상한 걸 못 찾아줌
로그를 한 서버로 모으고 나니, 하루에 수백만 줄이 쌓여서 사람이 읽을 수가 없는 상황이 됐다. 모아두기만 했지, 뭐가 이상한지는 알려주지 않았다.
여기서 로그를 저장하는 것과 보안 이벤트를 탐지하는 것이 다른 문제라는 걸 이해했다. 저장은 그냥 쌓아두는 거고 탐지는 이런 패턴이 나오면 위험하다는 탐지 룰을 정의하고 자동으로 걸러내는 거다. 이 역할을 하는 게 SIEM(Security Information and Event Management)이고, Wazuh도 이 SIEM 기능을 함께 제공한다.
탐지 룰을 넣고 나니 Web에서는 로그인 실패 알림이, App에서는 이상 프로세스 알림이 따로따로 오는 상황이 됐다. 문제는 이 둘이 같은 공격자의 소행인지 알 방법이 없다는 거였다.
→ Firewall에서 막힌 IP와 Web 로그인 실패 IP가 같은지를 사람이 눈으로 맞춰야 한다. 서로 다른 서버의 이벤트를 하나의 공격으로 묶어주는 기능이 상관분석이고, 이것도 SIEM의 핵심 기능 중 하나였다.
Mission 4. 이 IP가 진짜 공격자인지 어떻게 아는가
SIEM에서 특정 Source IP가 반복적으로 탐지되는 상황을 가정했다. 그런데 이 IP가 공격자인지 해외 고객인지 우리 로그만 봐서는 판단이 안 됐다. whois를 돌려봐도 국가 정보 정도만 나올 뿐이었다.
→ 여기서 필요한 게 위협 인텔리전스였다. 다른 보안 기관들이 이미 파악해둔 이 IP/도메인/파일 해시는 악성이다라는 정보를 받아오는 것이다. 이렇게 이게 나오면 악성이라고 판단할 수 있는 단위를 IOC(Indicator of Compromise)라고 부른다는 걸 여기서 알게 됐다.
문제는 위협 인텔 소스를 여러 군데 구독하니 형식도 다르고 같은 IP를 두고 한쪽은 악성이라 하고 다른 쪽은 정보가 없다고 하는 경우가 생긴다는 거였다. 여러 소스의 IOC를 모아 정규화하고 신뢰도를 관리해주는 역할이 TIP(Threat Intelligence Platform)이고, 이 IOC를 SIEM과 연결해두면 로그에 특정 IP/해시가 잡힐 때 자동으로 이거 위협 인텔에 등록된 악성 지표다라고 매칭해줄 수 있다는 흐름을 이해했다.
TIP (Threat Intelligence Platform)
- 정의: 여러 소스(오픈소스 위협 정보, 상업용 벤더, 내부 로그 등)에서 온 위협 데이터를 모아서 정규화하고, 실행 가능한 형태로 정리해주는 플랫폼.
- 핵심 기능 3가지:
- 집계·정규화: STIX/TAXII 같은 표준 포맷으로 형식을 통일하고, 중복 제거
- 신뢰도 점수 부여: 출처의 신뢰성, 여러 소스에서 동시 확인됐는지, 최신 정보인지, 그 출처의 과거 정확도 등을 기준으로 점수를 매김 → 신뢰도 높은 건 자동 대응, 애매한 건 사람이 검토하도록 분리 (이게 SOAR 자동/승인 분기의 기준이 되는 부분!)
- SIEM 연동: 검증된 지표를 SIEM 탐지 룰에 자동으로 반영하고, 반대로 SIEM에서 잡힌 알림에
이 IP는 어떤 공격 그룹이 쓰던 거다같은 맥락 정보를 붙여줌
Mission 5. 탐지는 됐는데 대응이 너무 느림
탐지 → 분석 → 위협 정보 확인까지 거쳐 악성 IP를 확정한 상황을 가정했다. 문제는 이 확정이 새벽 3시에 됐는데, 담당자가 AWS 콘솔에 들어가 수동으로 차단한 건 아침 9시였다는 거였다. 6시간 동안 공격자는 자유롭게 활동할 수 있었던 셈이다.
이 문제를 풀려면 탐지에서 차단까지 사람 개입 없이 자동으로 이어지는 흐름이 필요했다. 그런데 이걸 곧바로 그럼 다 자동 차단해버리면 또 다른 문제가 생긴다는 걸 알게 됐다.
→ 위협 인텔 소스 하나가 사무실 공인 IP를 잘못 악성으로 등록하면, 팀원 전원이 서비스에서 자동으로 차단당하는 사고가 날 수 있다. 즉 자동 차단이 항상 옳은 게 아니라 오탐의 피해를 누가 감당할지를 먼저 정해야 한다는 것이다.
그래서 필요한 게 SOAR(Security Orchestration, Automation and Response)와 Playbook이었다. 신뢰도가 높은 IOC(여러 소스에서 동시에 확인된 경우 등)는 자동 차단하고, 애매한 경우는 사람 승인을 거치도록 분기를 두는 것이다. 이 분기와 절차를 문서로만 남기는 게 아니라 시스템(Playbook)으로 만들어야 새벽에도 일관되게 작동한다는 게 핵심이었다.
SOAR, Playbook
- SOAR (Security Orchestration, Automation and Response)
- 정의: 여러 보안 도구와 사람 사이의 작업을 하나의 플랫폼에서 조율·실행·자동화해주는 기술
- Playbook 정의: 보안 알림이 발생하면 자동으로 트리거되는 워크플로우
- 미리 정해둔 절차대로 몇 가지 조치(일부는 자동, 일부는 사람 승인 필요)를 순서대로 실행함. 예를 들어 피싱 대응 Playbook은
지표 자동 추출 → 정보 보강 → 사용자에게 알림 → 결과 보고까지 자동으로 이어짐 - 한계/기각조건: SOAR는 만능 자동화가 아니고, 신뢰도가 애매한 경우엔 반드시 사람 승인 단계를 거치도록 설계해야 함 — 그렇지 않으면 오탐(False Positive) 하나가 전체 서비스를 마비시킬 수 있음 (근두운 글에서도 이 부분을 우선순위 마지막에 둔 이유).
- Playbook
사고 조사: 흩어진 기록을 하나의 흐름으로
실제 사고를 가정해봤다. 03:12 의심 프로세스 실행 → 03:14 외부 IP 접속 → 03:17 의심 파일 생성 → 03:23 다른 서버 접근 시도. 이 각 사건이 EDR, SIEM, Firewall에 각각 흩어져 있는데, 이걸 보고서로 정리해야 하는 상황이었다.
흩어진 기록을 시간순으로 재구성한 걸 Incident Timeline이라 부르고, 각 단계를 초기 침투인지, 실행인지, 지속성 확보인지, 내부 이동인지처럼 공격 전술 단위로 분류하는 표준 체계가 MITRE ATT&CK라는 걸 찾아봤다. 아래처럼 정리해봤다.
시각 | 단계 (MITRE ATT&CK) | 근거 (도구 / 로그) |
03:12 | Execution (의심 프로세스 실행) | EDR — 프로세스 실행 로그 |
03:14 | Command & Control (외부 IP 접속) | EDR — 네트워크 연결 로그, TIP — IOC 매칭 |
03:17 | Persistence (의심 파일 생성) | EDR — 파일 생성 로그 |
03:23 | Lateral Movement (다른 서버 접근 시도) | SIEM — 상관분석, Firewall — 내부 트래픽 로그 |
MITRE ATT&CK (Adversarial Tactics, Techniques, and Common Knowledge)
- 정의: 공격자들이 실제로 쓰는 전술과 기법을 정리해둔 지식 베이스
- Tactics(전술) vs Techniques(기법) 구분
- 전술 = 공격자가 뭘 이루려고 하는지 (목표). 정찰부터 영향(Impact)까지 총 14개 전술 단계가 있음.
- 기법 = 그 목표를 위해 구체적으로 어떤 방법을 쓰는지. 211개 기법 + 468개 세부 기법이 정리돼 있음.
- 공격 단계 흐름
- 사전 침투(정찰)
- 초기 접근
- 침투 후(실행, 지속성 확보, 권한 상승, 내부 이동, 데이터 수집, 유출, 영향)
- 활용
: 조사 결과를 이 표준 용어로 분류해두면 팀 내부든 외부든 이 공격이 어느 단계까지 왔는지 공통된 언어로 얘기할 수 있음
그리고 이 조사 과정 자체를 다음 사고 때도 재현 가능하게 남겨야 한다는 요구가 있었는데, 이게 DFIR(Digital Forensics and Incident Response)의 Case Management와 Chain of Custody 개념이었다. 조사 과정과 근거를 어떤 도구의 어떤 데이터로 확인했는지까지 기록해둬야 담당자가 바뀌거나 같은 유형의 사고가 다시 나도 같은 절차로 대응할 수 있다는 것이다.
DFIR (Digital Forensics and Incident Response)
- 정의: 사이버 사고를 조사해서 위협을 식별하고 완화하는 활동. 두 가지가 합쳐진 개념:
- 디지털 포렌식: 전자적 증거를 수집·분석하면서 무결성을 보존 (나중에 법적 절차에도 쓸 수 있게)
- 사고 대응: 활성 위협을 억제하고 정상 운영으로 복구
- Case Management(사례 관리): 조사를 증거 수집부터 최종 보고까지 체계적으로 문서화하는 것. 여러 조사 단계를 관리하고, 발견 사항을 추적하고, 팀원/법무팀/외부 관계자와 조율하는 과정 전체를 포함.
- Chain of Custody(증거 보관 연속성): 디지털 증거를 조작되지 않도록 취급·보호하는 절차. 누가, 언제 그 증거에 접근했고, 어떻게 다뤘는지를 계속 기록해야 함. 이게 깨지면 증거의 무결성이 훼손되고, 법적 절차에서 증거로 인정 안 될 수도 있음.
최종 구조
Mission 1~4까지 거치면서 정리된 CERT 인프라 구조는 다음과 같다.

역할 | 도구 후보 | 선택 이유 |
EDR | Wazuh Agent | 오픈소스, 근두운에서 실제 운영 중 |
SIEM | Wazuh Manager | EDR과 통합 운영 가능, 별도 SIEM 구축 비용 절감 |
Threat Intelligence | MISP | 오픈소스 TIP, IOC 정규화·공유 표준(STIX/TAXII) 지원 |
SOAR | Shuffle / TheHive | 오픈소스, Playbook 기반 자동화·승인 워크플로우 지원 |
![[Cloud & CERT] 3-Tier Architecture 설계 (장혜리)](https://www.notion.so/image/https%3A%2F%2Fwww.notion.so%2Fimages%2Fpage-cover%2Fartemis_ii_6.jpg?table=block&id=3e1d23c8-25c0-80b0-8194-c458e512b1d9&cache=v2&width=1600)
