[Cloud & CERT] 조사 2. 네트워크 배치 (양혜민)[Cloud & CERT] 조사 2. 네트워크 배치 (양혜민)

[Cloud & CERT] 조사 2. 네트워크 배치 (양혜민)

생성 일시
Sep 29, 2026 10:09 AM
최종 편집 일시
Last updated October 1, 2026
태그
Infra
AWS
작성자
@양혜민
Date

1. 들어가며

서버를 어디에 두고, 누구에게 얼마나 열어줄지 잘못 정하면 서비스가 멀쩡해 보여도 전 세계에서 공격이 들어올 수 있다. 이 글은 Web, App, DB 세 서버를 배치하면서 흔한 실수를 하나씩 겪고 고쳐나가보는 것이 핵심이다. 온프레미스에서 구조를 설계한 뒤 AWS로 옮기고, 마지막으로 "내부라서 안전하다"는 생각의 한계를 살펴본다. 핵심 원칙은 하나로, 꼭 필요한 대상에게, 꼭 필요한 만큼만 연다.
 

2. 아무 서버나 인터넷에 열어두면 안 되는 이유

Web, App, DB 세 서버에 모두 공인 IP를 주고 인터넷에 직접 연결했더니, 하루 만에 DB 서버 3306 포트로 전 세계에서 로그인 시도가 들어왔다. DB는 외부와 통신할 일이 전혀 없는데도 인터넷에 그대로 노출되어 있었던 것이다.
공인 IP(Public IP)는 인터넷에서 직접 접근 가능한 주소다. 공인 IP를 받는 순간 누구나 그 서버로 패킷을 보낼 수 있게 되고, 인터넷에서 접근 가능한 공격 표면(Attack Surface)이 생긴다. 공인 IP라고 무조건 해킹되는 건 아니지만, 공개된 IP 대역을 자동으로 스캔하는 프로그램들이 있어서 공격자가 우리 서버를 특정하지 않아도 하루 만에 로그인 시도가 쏟아질 수 있다.
그렇다면 어떤 서버를 인터넷에 공개해야 할까. 원칙은 간단하다. 인터넷에서 직접 접근할 필요가 있는 서버만 공개한다. 외부에서 먼저 연결을 요청받는 서버는 Web뿐이다. App은 Web이 요청을 전달해주고, DB는 App만 접근하면 되니 외부에 공개할 이유가 없다. 그래서 Web은 Public, App과 DB는 Private으로 분리한다.
여기서 "Public은 위험하고 Private은 안전하다"는 이분법적 생각을 갖기보다, 인터넷과 직접 연결되는가를 기준으로 생각해야 오해가 없다. 또 Private이라고 아무도 못 들어가는 건 아니다. 필요한 통신은 허용해야 하고, 그 범위를 딱 필요한 만큼으로 제한하는 것이 최소 권한 원칙(Least Privilege)이다. 예를 들어, DB는 App하고만 통신하면 되니, App에게만 3306 포트를 열고 나머지는 전부 막으면 되는 것이다.
notion imagenotion image
notion image

3. 사설 IP와 DMZ

2005년의 기업 데이터센터로 돌아가 보자. AWS는 없고, 웹 서버, 사내 업무 서버, DB 서버, 관리자 PC가 있다. 이번에는 전부 사설 IP로 내부망에 넣었다.
사설 IP(Private IP)는 인터넷에서 직접 라우팅되지 않는 내부용 주소다. 그래서 외부에서는 내부망 서버에 직접 접근할 수 없고, 보안 측면에서는 확실히 안전해진 것처럼 느껴진다. 그런데 이번엔 외부 고객이 웹 서버에 접속을 못 한다. 생각해보면 당연하다. 웹 서버까지 내부망에 들어가 있으니, 인터넷에서 웹 서버로 닿을 방법이 없는 것이다.
그렇다면 앞선 방법처럼 웹 서버만 밖으로 빼고 나머지를 내부망에 두면 될까? 웹 서버는 인터넷에 노출되어 있어 가장 먼저 공격받는 서버인데, 이 웹 서버가 내부망과 바로 연결되어 있다면 웹 서버 하나가 뚫리는 순간 내부망 전체가 위험해진다.
그래서 필요한 것이 DMZ(Demilitarized Zone, 비무장지대)다. DMZ는 인터넷과 내부망 사이에 만들어 두는 별도의 네트워크 영역으로, 웹 서버처럼 외부에서 접근해야 하는 서버를 이곳에 배치한다. 즉, 인터넷에 공개되어야 하지만 내부망에 직접 두고 싶지는 않은 서버를 따로 격리하는 것이다.
그리고 DMZ와 내부망 사이에는 방화벽(Firewall)이 있어야 한다. 방화벽은 사전에 정해진 보안 규칙에 따라 트래픽을 모니터링하고 제어하는 보안 시스템이다. 인터넷에서 DMZ로는 웹 접속만, DMZ에서 내부망으로는 꼭 필요한 통신만 허용하고 나머지는 막는다. 이렇게 하면 웹 서버가 뚫리더라도 공격자는 내부망으로 가기 위해 방화벽을 한 번 더 넘어야 한다.
notion imagenotion image
notion image

4. NAT 구조

웹 서버는 DMZ에, DB는 내부망에 두면서 구조는 깔끔해졌다. 그런데 이번엔 DB 서버의 OS 패치를 받아야 하는데, 내부망에 있다 보니 인터넷으로 나갈 수가 없다.
*OS패치: 운영체제의 보안 취약점 수정, 버그 해결, 성능 개선을 위한 업데이트
정확히 말하면 요청을 보내는 것 자체는 가능하다. 문제는 응답이다. DB가 사설 IP로 요청을 보내면 업데이트 서버는 그 사설 IP로 답장을 보내야 하는데, 인터넷에는 사설 IP로 가는 경로가 없으니 응답이 돌아오지 못한다. 그렇다고 DB에 공인 IP를 주면 DB가 인터넷에 그대로 노출되는 문제가 발생한다.
그래서 필요한 것이 NAT(Network Address Translation)다. NAT는 사설 IP 주소를 공인 IP 주소로 변환해주는 기술이다. DB가 밖으로 나갈 때 NAT가 출발지 주소를 공인 IP로 바꿔서 내보내고, 응답이 돌아오면 다시 DB에게 전달해준다. 덕분에 DB는 공인 IP 없이도 OS 패치를 받을 수 있다. 또 내부망의 여러 서버가 각자 공인 IP를 받을 필요 없이, NAT를 통해 하나의 공인 IP를 공유해서 나갈 수 있다. 즉, NAT는 Private 서버의 인터넷 Outbound 통신을 가능하게 해준다.
다만 NAT가 있어도 DB가 그 존재를 모르면 소용이 없다. 그래서 DB에게 “내부 주소가 아니면 전부 NAT로 보내라"는 정보를 알려줘야 하는데, 이 역할을 라우팅(Routing)이 한다. 내부 서버의 라우팅 테이블에 0.0.0.0/0(그 외 전부)→ NAT라는 기본 경로를 설정해두면, 밖으로 나가는 트래픽이 자연스럽게 NAT를 거치게 된다.
notion imagenotion image
notion image

5. 연결의 방향은 누가 먼저 연결을 시작했는지로 나뉜다.

NAT를 붙이고 나니 한가지 의문이 들 수 있다. NAT가 있으면 외부에서도 DB에 접속할 수 있는 거 아닌가 하는 궁금증이다. 여기서 중요하게 생각할 점은 연결의 방향성이다.
연결의 방향은 누가 먼저 연결을 시작했는가로 나뉜다. DB가 업데이트 서버에 접속하는 것처럼 내부가 먼저 시작하면 Outbound, 외부 사용자가 DB의 3306에 접속을 시도하는 것처럼 외부가 먼저 시작하면 Inbound다. 이 둘은 서로 다른 문제로, 나갈 수 있다고 해서 들어올 수 있어야 하는 건 아니다. 조금 헷갈릴 수 있는 부분은 업데이트 서버가 보내는 응답인데, 밖에서 안으로 들어오긴 해도 DB가 시작한 Outbound 연결의 일부일 뿐 새로운 Inbound가 아니다.
NAT는 주로 내부 서버의 Outbound 통신에 사용된다.  사설 IP를 공인 IP로 바꿔서 내부 서버가 밖과 통신하게 해준다. 일반적인 NAT 환경에서는 외부에서 먼저 시작된 연결을 내부 서버로 전달할 매핑이 없기 때문에 연결이 성립하지 않을 수 있지만, 이것을 NAT의 보안 기능으로 보면 안 된다. 포트 포워딩처럼 외부에서 내부로 들어오는 연결을 의도적으로 설정할 수도 있기 때문이다.
그러니, 들어오는 연결을 막는 것은 Stateful Firewall의 역할이다. 진행 중인 연결의 상태를 기억해서, DB가 먼저 시작한 연결의 응답은 자동으로 허용하고 기록에 없는 외부의 새 연결은 차단한다. 덕분에 "나가는 건 허용하되 들어오는 건 막는다"는 정책을 안전하게 구현할 수 있다.
notion imagenotion image
notion image

6. Bastion Host

관리자가 집에서 내부 서버를 관리해야 하는 상황이 생겼다. 급한 마음에 방화벽에 "외부 → DB 22번(SSH) 허용" 규칙을 추가했더니, 며칠 뒤 DB 서버에 알 수 없는 로그인 시도가 수천 건 쌓였다.
내부 서버에 직접 포트를 열면 안 되는 이유는 세 가지다.
(1) 관리자 한 명을 위해 연 문이 전 세계 공격자에게도 똑같이 열리게 된다.
(2) SSH 서비스나 서버 소프트웨어에 심각한 취약점이 존재한다면 인증 전에 악용될 수 있다.
(3) 중요한 서버인 DB가 직접 표적이 되고, 뚫리는 순간 중간에 막아줄 것 없이 곧바로 데이터가 유출된다.
그래서 외부에서 관리 목적으로 들어올 때는 Bastion Host(배스천 호스트)를 거치게 한다. Bastion은 DMZ에 위치한, 외부에서 관리 목적으로 접속할 수 있는 중간 관리용 서버다. 관리자는 먼저 Bastion에 접속하고, Bastion을 거쳐서 DB에 들어간다. 관리용 외부 진입점을 Bastion으로 집중하면 관리 경로를 표준화하고, 해당 진입점에 대한 접근제어와 모니터링을 집중적으로 적용할 수 있다.
여기에 ACL(Access Control List, 접근 제어 목록)로 들어올 수 있는 대상을 제한한다. 만약 Bastion은 관리자 IP에서 오는 접속만, DB는 Bastion에서 오는 접속만 허용한다면, 공격자는 관리자 IP가 아니라서 Bastion에서 먼저 막히고, DB에는 애초에 직접 닿을 수도 없다. 꼭 필요한 대상에게만 꼭 필요한 경로를 여는 것, 결국 여기서도 최소 권한 원칙이 빠질 수 없다.
notion imagenotion image
notion image

7. 지금까지 만든 구조, AWS에선 어떻게 부를까?

온프레미스 설계가 끝났고, 이제 이 구조를 AWS로 옮길 차례다. 새로 배울 것이 많아 보이지만, 사실 대부분은 이미 만든 구조에 AWS식 이름을 붙이는 일이다.
*온프레미스(On-Premise)는 기업이나 기관이 자체 서버실이나 데이터센터에 서버, 스토리지, 네트워크 등의 IT 인프라를 직접 구축하고 운영하는 방식이다.
온프레미스
AWS
설명
사내 네트워크 영역
VPC
AWS에서 논리적으로 격리된 네트워크 환경
Network Segment (VLAN)
Subnet
VPC 내부의 IP 주소 범위를 나눈 네트워크 영역
Routing Table
Route Table
서브넷의 트래픽 라우팅 경로를 지정
DMZ
Public Subnet
외부에서 접근이 필요한 리소스를 배치하는 영역
Internal Network
Private Subnet
Internet Gateway로 직접 연결되는 경로가 없는 영역
Firewall Rule
Security Group + NACL
SG는 리소스 수준, NACL은 서브넷 수준의 트래픽 제어
NAT
NAT Gateway
Private 리소스의 인터넷 Outbound 통신을 위한 NAT 제공
Internet Router
Internet Gateway
VPC와 인터넷 간 통신을 가능하게 하는 게이트웨이
## Public Subnet과 Private Subnet은 사용자가 설정으로 지정할 수 있는 것일까, 하는 궁금증이 생겼다. 결론부터 말하면 사용자가 정하는 것이 맞다. 다만 "Public"이라는 스위치를 켜는 방식이 아니라, 서브넷에 연결된 Route Table에 Internet Gateway로 가는 경로를 넣으면 Public, 넣지 않으면 Private이 되는 방식이다. (참고: https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html)
notion imagenotion image
notion image
AWS 공식 문서의 구조도를 보면 이 내용이 한눈에 보인다. 가장 바깥의 Region은 AWS 데이터센터가 모여 있는 지역이고, 그 안에 사용자가 만든 VPC가 있다. VPC는 리전 안의 여러 Availability Zone(가용 영역)에 걸쳐 만들어지고, 각 서브넷은 그중 하나의 가용 영역에 속한다.
그림에서 주황색 아이콘은 서브넷 안의 서버(EC2 인스턴스)이고, 오른쪽 보라색 원은 VPC와 인터넷을 잇는 출입구인 Internet Gateway다. 화살표를 보면 Internet Gateway는 Public Subnet과만 연결되어 있고, Private Subnet으로는 인터넷에서 들어오는 길이 없다. Public과 Private을 가르는 기준은 결국 Internet Gateway로 가는 경로가 있는가를 통해 확인할 수 있다는 것을 그림으로도 확인할 수 있다.
방화벽은 두 가지다. Security Group(Stateful)은 리소스마다 붙는 방화벽으로, 나간 연결을 기억해서 응답을 알아서 통과시킨다. NACL(Stateless)은 서브넷 전체에 붙는 방화벽으로, 연결을 기억하지 않아서 들어오는 응답도 규칙을 따로 열어줘야 한다. 보통은 Security Group으로 세밀하게 막고, NACL은 보조적으로 사용한다.
서버 배치는 인터넷과 직접 연결되어야 하는가로 정한다.
서버
배치
근거
Web
Public Subnet
외부 고객이 먼저 접속해야 함(온프레미스의 DMZ)
Bastion
Public Subnet
외부에서 들어오는 관리용 진입점
NAT Gateway
Public Subnet
인터넷으로 나가려면 Internet Gateway로 가는 경로가 필요함
App
Private Subnet
Web만 접속하면 됨
DB
Private Subnet
App만 접속하면 됨

8. Private Subnet에 있어도 안전하지 않은 이유

AWS로 옮기면서 Web은 Public Subnet에, App과 DB는 Private Subnet에 넣었다. 그런데 공격자가 웹 서버 취약점으로 Web 서버 권한을 획득했고, 거기서 DB로 바로 접속이 됐다. DB는 분명 Private Subnet에 있는데 왜 이런 일이 생겼을까?
Private Subnet은 인터넷에서 직접 들어올 수 있는 경로가 없는 영역이다. 하지만 VPC의 모든 Route Table에는 10.0.0.0/16 → local, 즉 "VPC 안의 주소끼리는 서로 통신한다"는 경로가 기본으로 들어 있고, 이 경로는 지울 수도 없다. 그래서 Web 서버를 장악한 공격자의 접속은 인터넷에서 온 트래픽이 아니라 같은 VPC 내부에서 온 트래픽으로 취급되고, DB까지 그대로 닿는다.
(1) North-South Traffic: 바깥 ↔ 안쪽 트래픽 (인터넷 ↔ VPC)
→ Private Subnet이 막아주는 건 이쪽이다.
East-West Traffic: 안쪽끼리 옆으로 오가는 트래픽 (Web ↔ App ↔ DB)
→  Private Subnet은 이쪽을 전혀 막지 못한다.
공격자가 한 서버를 장악한 뒤 옆 서버로 옮겨가는 걸 Lateral Movement(측면 이동)라고 한다.
notion imagenotion image
notion image
결국 라우팅은 갈 수 있는 길만 정할 뿐, 가도 되는 대상인지는 판단하지 않는다. 이미 들어온 공격자의 이동을 막으려면 Security Group으로 출발지(Source)와 포트(Port)를 제한해야 한다. 예를 들어 DB의 Security Group에 "3306번, 출발지는 App 서버만 허용"이라고 적어두면, Web 서버가 장악되더라도 DB에 접속하려는 순간 App이 아니라는 이유로 막힌다.

9. Zero Trust와 Security Group Chaining

앞선 교훈대로 Security Group을 붙였다. 다만 처음에는 "내부망끼리는 전부 허용", 즉 모든 포트에 대해 출발지를 10.0.0.0/16(VPC 내부 전체)으로 열어두었다. 그런데 Web 서버도 VPC 내부 주소를 쓰기 때문에, 이 규칙은 사실상 아무것도 막지 못했고 여전히 Web에서 DB로 접속이 된다.
Web 서버를 공격자가 장악했다고 생각해보자. 공격자는 이제 Web의 위치와 네트워크 권한을 사용할 수 있게 된다. 따라서 단순히 VPC 내부에서 온 요청이라는 이유로 신뢰하면 내부 침해가 확산될 수 있다.
그래서 등장한 개념이 Zero Trust(제로 트러스트)다. 누구도 기본적으로 신뢰하지 않고(Never Trust, Always Verify), 모든 접근 요청을 검증하는 보안 패러다임이다.
해결 방법은 사용자 → Web → App → DB 흐름에 있는 연결만 허용하는 것이다.
Web → App은 App의 서비스 포트(예: 8080)만 허용한다.
App → DB는 3306(MySQL)만 허용한다.
Web → DB는 허용하지 않는다. Web이 DB에 접근할 이유가 없고, Web은 인터넷에 노출되어 가장 먼저 공격받는 서버라서 이 길이 열려 있으면 가장 약한 서버 하나만 뚫어도 가장 중요한 데이터까지 한 번에 노출될 수도 있다.
AWS에서는 Security Group 규칙의 출발지를 특정 IP 주소뿐만 아니라 다른 Security Group으로도 지정할 수 있다. 이를 Security Group Referencing이라고 하며, 흔히 Security Group Chaining이라고 표현한다. (참고: https://docs.aws.amazon.com/vpc/latest/userguide/security-group-rules.html)
DB-SG: 3306 허용, 출발지 = 10.0.2.15 (App 서버 IP) → X
DB-SG: 3306 허용, 출발지 = App-SG → O
이렇게 하면 App 서버가 늘어나거나 IP가 바뀌어도 App-SG가 연결된 리소스라면 같은 규칙이 그대로 적용된다. 위치(IP)가 아니라 역할(그룹)로 신뢰를 판단하는 것이다.