Dev 환경 해킹 시나리오 — Nginx + Docker + Cloudflare 구조에서 실제 침투 경로 분석
dev.example.com 도메인을 운영할 때 해커가 실제로 백엔드까지 도달하는 경로를 트래픽 흐름 기준으로 분석합니다. UFW 방화벽이 왜 무력화되는지, 각 레이어별 차단 방법은 무엇인지 구체적으로 다룹니다.
아키텍처 구조도
위험한 구조: dev 도메인이 프로덕션 서버를 공유할 때
소규모 프로젝트에서는 비용 절감을 위해 하나의 서버에서 prod, staging, dev 환경을 모두 운영하는 경우가 많습니다. dev.example.com 도메인을 Cloudflare DNS에 등록하고, nginx-proxy가 해당 도메인을 호스트의 dev 서버로 포워딩하는 구조입니다.
이 구조에서 가장 위험한 점은, dev 환경으로 들어온 트래픽이 UFW 방화벽을 완전히 우회하여 호스트의 백엔드 서버까지 도달할 수 있다는 것입니다. 아래에서 해커가 실제로 사용할 수 있는 공격 경로를 하나씩 분석합니다.
공격 경로 1: dev 도메인을 통한 정면 침투
위험도: 높음 — 가장 현실적인 공격
해커가 dev.example.com 주소를 알아내면 (서브도메인 스캔, DNS 열거, Certificate Transparency 로그 등), 별도의 취약점 없이도 dev 환경의 전체 API에 접근할 수 있습니다.
서브도메인 발견
subfinder, amass 같은 도구로 dev.example.com을 발견합니다. Certificate Transparency(CT) 로그에서 SSL 인증서에 포함된 서브도메인을 조회할 수도 있습니다.
Cloudflare DNS 통과
dev 도메인이 Cloudflare에 등록되어 있으므로, 요청은 Cloudflare를 거쳐 서버의 공인 IP:443으로 도달합니다.
nginx-proxy가 dev 서버 블록 매칭
nginx-proxy는 Host 헤더를 보고 dev.example.com 서버 블록을 찾아 172.18.0.1:3000 (호스트의 Next.js dev 서버)으로 프록시합니다.
Next.js rewrites로 백엔드 도달
Next.js의 next.config.ts에 설정된 rewrites 규칙에 의해 /api/* 요청이 127.0.0.1:8000 (Python 백엔드)으로 프록시됩니다.
백엔드 API 자유롭게 호출
해커는 이제 dev 환경의 모든 API 엔드포인트에 접근할 수 있습니다. dev DB에 실제 데이터가 복제되어 있다면 데이터 유출로 이어집니다.
UFW 방화벽이 3000, 8000 포트를 차단하고 있는데 왜 뚫리나요?
UFW는 이 경로를 전혀 차단하지 못합니다. 외부 트래픽은 443 포트(UFW 허용)로 nginx-proxy에 도달하고, 이후 Docker 브릿지 네트워크(172.18.0.1)를 통해 호스트의 3000 포트에 접근합니다. Docker 브릿지 네트워크 내부 통신은 UFW의 INPUT 체인을 거치지 않습니다. Next.js에서 Python 백엔드(127.0.0.1:8000)로의 요청 역시 localhost 내부 통신이므로 UFW가 관여하지 않습니다.
# 1. 서브도메인 열거
subfinder -d example.com -silent
# 출력: dev.example.com, staging.example.com, ...
# 2. CT 로그에서 인증서 조회 (와일드카드 인증서 사용 시 노출)
curl -s "https://crt.sh/?q=%.example.com&output=json" | jq '.[].name_value' | sort -u
# 3. dev 환경 API 직접 호출
curl https://dev.example.com/api/users
curl https://dev.example.com/api/admin/reports
# 4. 디버그 정보 수집 (dev 모드는 상세 에러를 반환)
curl https://dev.example.com/api/nonexistent
# → 스택 트레이스, DB 스키마, 파일 경로 등 노출 가능공격 경로 2: 서버 공인 IP 직접 접근
위험도: 중간 — Cloudflare를 우회하는 공격
해커가 Cloudflare 뒤에 숨겨진 서버의 실제 공인 IP를 알아내면, Cloudflare의 WAF/IP 제한을 완전히 우회할 수 있습니다.
서버의 공인 IP를 알아내는 방법은 다양합니다. 과거 DNS 기록(SecurityTrails), 이메일 헤더, 잘못 설정된 서브도메인(Cloudflare 프록시를 거치지 않는 DNS Only 레코드), 또는 서버에서 외부로 나가는 요청의 IP를 역추적하는 방법 등이 있습니다.
# 공인 IP를 알아낸 경우 Cloudflare를 우회하여 직접 접근
curl -k -H "Host: dev.example.com" https://실제공인IP/api/users
# nginx-proxy의 Cloudflare IP 화이트리스트가 없으면 이 요청이 통과됨Cloudflare IP 화이트리스트 미적용
- •공인 IP로 직접 접근 시 nginx-proxy가 정상 응답
- •Host 헤더를 조작하면 원하는 서버 블록 매칭 가능
- •Cloudflare의 모든 보안 규칙이 무력화됨
Cloudflare IP 화이트리스트 적용
추천- ✓nginx-proxy가 Cloudflare IP 대역이 아닌 요청을 즉시 차단 (deny all)
- ✓공인 IP 직접 접근 시 403 Forbidden 반환
- ✓Cloudflare를 반드시 거쳐야만 서버에 도달 가능
http {
# Cloudflare IPv4 대역만 허용
allow 173.245.48.0/20;
allow 103.21.244.0/22;
allow 103.22.200.0/22;
allow 103.31.4.0/22;
allow 141.101.64.0/18;
allow 108.162.192.0/18;
allow 190.93.240.0/20;
allow 188.114.96.0/20;
allow 197.234.240.0/22;
allow 198.41.128.0/17;
allow 162.158.0.0/15;
allow 104.16.0.0/13;
allow 104.24.0.0/14;
allow 172.64.0.0/13;
allow 131.0.72.0/22;
# Docker/로컬 내부 통신 허용
allow 127.0.0.1;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
# 나머지 전부 차단
deny all;
}공격 경로 3: Docker 브릿지 네트워크를 이용한 UFW 우회
핵심 개념: Docker와 UFW의 충돌
Docker는 컨테이너의 포트를 외부에 노출할 때 iptables NAT 규칙을 직접 추가합니다. 이 규칙은 UFW의 INPUT 체인보다 먼저 평가되므로, UFW에서 해당 포트를 DROP으로 설정해도 Docker가 열어놓은 포트는 차단되지 않습니다.
예를 들어, docker-compose에서 ports: "0.0.0.0:8000:8000"으로 설정하면 UFW 규칙과 무관하게 외부에서 8000 포트에 접근할 수 있습니다. 이것이 Docker 환경에서 발생하는 대표적인 보안 함정입니다.
✕위험한 Docker 포트 설정
- ✕ports: "8000:8000" (모든 인터페이스에 바인딩)
- ✕ports: "0.0.0.0:5432:5432" (DB를 외부에 노출)
- ✕UFW에서 차단했으니 안전하다고 착각
✓안전한 Docker 포트 설정
- ✓ports: "127.0.0.1:8000:8000" (로컬에서만 접근)
- ✓ports: "127.0.0.1:5434:5432" (DB는 반드시 로컬만)
- ✓Docker 네트워크 내부에서만 컨테이너 간 통신
# Docker가 추가한 NAT 규칙 확인
sudo iptables -t nat -L DOCKER -n
# 출력 예시: 8000 포트가 UFW와 무관하게 열려있음
# DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8000 to:172.19.0.3:8000
# 해결: docker-compose에서 반드시 127.0.0.1 바인딩
# ports:
# - "127.0.0.1:8000:8000" # 안전
# - "8000:8000" # 위험!공격 경로 4: dev 환경의 디버그 정보를 이용한 정찰
dev 환경은 디버그 모드로 실행되기 때문에, 프로덕션에서는 숨겨지는 상세한 에러 메시지, 스택 트레이스, DB 쿼리, 환경변수 등이 그대로 노출됩니다. 해커는 이 정보를 수집하여 프로덕션 환경을 공격하는 데 활용할 수 있습니다.
Q.스택 트레이스에서 어떤 정보가 노출되나요?
서버의 파일 시스템 경로(/home/user/projects/...), 사용 중인 라이브러리 버전, DB 테이블/컬럼 이름, 내부 API 엔드포인트 구조 등이 노출됩니다. 이 정보만으로도 프로덕션 환경의 취약점을 유추할 수 있습니다.
Q.dev DB에 프로덕션 데이터를 복제해서 쓰면 얼마나 위험한가요?
dev 환경이 뚫리면 프로덕션 데이터가 그대로 유출됩니다. 이메일, 비밀번호 해시, 결제 정보 등을 모두 탈취할 수 있습니다. 반드시 개인정보를 마스킹한 더미 데이터를 사용하거나, dev 환경의 외부 접근을 완전히 차단해야 합니다.
방어 전략: 레이어별 차단 체크리스트
보안은 단일 레이어가 아닌 다중 레이어(Defense in Depth)로 구성해야 합니다. 하나의 레이어가 뚫려도 다음 레이어에서 막을 수 있어야 합니다.
Cloudflare 레이어
dev/staging 도메인에 WAF 규칙 또는 Cloudflare Access(Zero Trust)를 적용하여 특정 IP만 허용합니다. 또는 dev DNS 레코드 자체를 삭제합니다.
nginx-proxy 레이어
Cloudflare IP 대역만 allow하고 나머지는 deny all로 설정합니다. dev 서버 블록을 주석 처리하거나 제거하여 dev 도메인으로의 라우팅 자체를 없앱니다.
Docker 포트 바인딩 레이어
모든 docker-compose의 ports 설정에서 127.0.0.1을 명시합니다. 0.0.0.0 바인딩을 절대 사용하지 않습니다.
UFW 방화벽 레이어
기본 정책을 DROP으로 유지하고, 필요한 포트(22, 80, 443)만 명시적으로 허용합니다. 사설 네트워크(192.168.x.x)에서만 dev 포트 접근을 허용합니다.
애플리케이션 레이어
dev 환경에서도 인증을 적용합니다. DB에 프로덕션 데이터를 복제할 때는 개인정보를 마스킹합니다. 디버그 모드의 에러 상세 노출 수준을 제한합니다.
# 같은 공유기(LAN) 안의 모바일 기기에서만 dev 서버 접근 허용
sudo ufw allow from 192.168.45.0/24 to any port 3000 # Next.js (ijm)
sudo ufw allow from 192.168.45.0/24 to any port 3001 # Next.js (quizzer)
# 사설 IP(192.168.x.x)는 인터넷에서 라우팅되지 않으므로
# 해커가 이 IP를 알아내도 외부에서 접근할 수 없습니다.공격 경로별 차단 여부 총정리
차단에 실패하는 경우
- •dev DNS가 살아있고 nginx-proxy에 서버 블록이 존재
- •Cloudflare WAF/Access 미적용 상태
- •nginx-proxy에 Cloudflare IP 화이트리스트 미적용
- •Docker ports가 0.0.0.0으로 바인딩됨
- •dev DB에 마스킹 없이 프로덕션 데이터 복제
정상 차단되는 경우
추천- ✓dev DNS 레코드 삭제 또는 nginx-proxy에서 dev 서버 블록 제거
- ✓Cloudflare에서 dev/staging에 IP 제한 적용
- ✓nginx-proxy에 Cloudflare IP 대역 화이트리스트 + deny all
- ✓Docker ports를 127.0.0.1로 바인딩
- ✓UFW 기본 정책 DROP + 필요 포트만 명시적 허용
더 알아보기
이정마 보안팀
인프라 보안 분석
이 가이드는 실제 운영 중인 서버에서 발견한 보안 문제를 기반으로 작성되었습니다. dev 환경의 편의성과 보안 사이의 균형을 맞추되, 최소한 외부 접근은 완전히 차단하는 것을 권장합니다.
