icon

안동민 개발노트

8장 : DNS

DNS 심화와 실무


DNS의 기본 동작과 레코드를 이해했으니, 이제 실무에서 DNS가 어떻게 활용되고 어떤 보안 위협이 존재하는지 살펴보겠습니다.


DNS 라운드 로빈

하나의 도메인에 여러 개의 A 레코드를 설정하면, DNS 서버는 질의마다 IP 주소의 순서를 바꿔서 응답합니다. 이것이 DNS 라운드 로빈입니다.

DNS 라운드 로빈 동작
A 레코드 설정
  example.com  →  10.0.0.1 (서버 A)
  example.com  →  10.0.0.2 (서버 B)
  example.com  →  10.0.0.3 (서버 C)

질의 1 응답 순서: [10.0.0.1, 10.0.0.2, 10.0.0.3]
질의 2 응답 순서: [10.0.0.2, 10.0.0.3, 10.0.0.1]
질의 3 응답 순서: [10.0.0.3, 10.0.0.1, 10.0.0.2]

클라이언트는 보통 첫 번째 IP를 사용
→ 트래픽이 세 서버에 분산

문제점
  서버 B 다운! → DNS는 모른다 (헬스 체크 없음)
  질의 2 → 10.0.0.2 (다운된 서버) → 접속 실패!
  TTL 동안 캐시 → 분산이 불균등

실무: DNS 라운드 로빈 단독 사용 X
      → L4/L7 로드 밸런서와 함께 사용

GeoDNS와 글로벌 서비스 라우팅

GeoDNS는 클라이언트의 위치에 따라 다른 IP 주소를 응답하는 DNS 서비스입니다.

GeoDNS 동작
                    API 서버
                  example.com

        ┌─────────────┼─────────────┐
      한국에서         미국에서       유럽에서
      질의 시          질의 시        질의 시
        │             │             │
  10.0.1.1          10.0.2.1       10.0.3.1
  (도쿄 서버)       (버지니아 서버)  (프랑크푸르트 서버)

위치 판단 기준
  1. ECS (EDNS Client Subnet): 리졸버가 클라이언트 서브넷 전달
  2. 리졸버 IP: 한국 ISP 리졸버 → 한국 사용자로 추정
  3. GeoIP 데이터베이스: IP → 위치 매핑 DB

한계
  8.8.8.8 같은 글로벌 리졸버 사용 시 위치 추정 부정확
  → ECS 확장이 이 문제를 상당 부분 해결
DNS 라우팅 정책설명사용 사례
단순 (Simple)하나의 레코드 응답단일 서버
라운드 로빈여러 레코드 순환기본 부하 분산
가중치 (Weighted)비율에 따라 분배 (70:30)A/B 테스트, 점진적 이전
지리적 (Geolocation)클라이언트 위치 기반지역별 콘텐츠 제공
지연 시간 (Latency)가장 빠른 리전으로글로벌 서비스
장애 조치 (Failover)주 서버 다운 시 백업고가용성
다중 값 (Multi-value)헬스 체크된 여러 IP향상된 라운드 로빈
DNS 기반 장애 조치 (Failover)
평상시:
  example.com → 10.0.1.1 (Primary, 서울)
                헬스 체크: ✓ 정상

장애 발생:
  example.com → 10.0.1.1 (Primary) 헬스 체크: ✗ 실패!
             → 10.0.2.1 (Secondary, 도쿄)로 자동 전환

복구 후:
  example.com → 10.0.1.1 (Primary) 헬스 체크: ✓ 복구
             → 다시 Primary로 자동 복귀

Route 53, Cloudflare 등이 이 기능 제공
TTL을 짧게 설정해야 빠른 전환 가능

DNS over HTTPS와 DNS over TLS

전통적인 DNS 질의는 평문 UDP로 전송됩니다. 이것은 누구든 네트워크 트래픽을 감청하면 이 사용자가 어떤 사이트에 접속하려는지 알 수 있다는 의미입니다.

DNS 프라이버시 문제
기존 DNS (평문 UDP):
  사용자 ──UDP 53──→ DNS 리졸버
         "www.비밀사이트.com의 IP?"

         네트워크 관찰자 (ISP, 공유기, 공격자)
         → 사용자의 모든 방문 사이트 목록 노출!

HTTPS로 암호화해도:
  DNS 질의: 평문 → 방문지 노출 ✗
  HTTP 내용: 암호화 → 내용 보호 ✓
  SNI 필드: 평문 → 도메인 노출 ✗ (ECH로 해결 중)
프로토콜포트암호화식별 가능장점단점
DNS (전통)53/UDP없음DNS 트래픽빠름, 단순프라이버시 없음
DoT853/TCPTLSDNS 전용 포트표준 암호화차단 쉬움
DoH443/TCPHTTPSHTTPS와 구분 불가차단 어려움디버깅 어려움

DNS over TLS(DoT)는 DNS 질의를 TLS로 암호화합니다. 853번 포트를 사용합니다. DNS 트래픽을 식별하기가 비교적 쉬워 네트워크 관리자가 차단할 수 있습니다.

DNS over HTTPS(DoH)는 DNS 질의를 HTTPS 프로토콜로 캡슐화합니다. 443번 포트를 사용하므로, 일반 HTTPS 트래픽과 구분되지 않습니다.

dns_privacy_check.sh
# 현재 시스템의 DNS 서버 확인
# Windows
ipconfig /all | findstr "DNS Servers"

# Linux
cat /etc/resolv.conf

# DoH 지원 DNS 서버로 직접 질의
curl -s "https://cloudflare-dns.com/dns-query?name=example.com&type=A" \
  -H "Accept: application/dns-json" | python3 -m json.tool

# DoH 응답 예시:
# {
#   "Status": 0,
#   "Answer": [
#     {
#       "name": "example.com",
#       "type": 1,
#       "TTL": 86400,
#       "data": "93.184.216.34"
#     }
#   ]
# }

DNS 캐시 포이즈닝과 DNSSEC

DNS는 인터넷의 핵심 인프라이기 때문에, 공격 대상이 되기도 합니다.

DNS 캐시 포이즈닝 공격
정상 흐름
  클라이언트 → 리졸버 → 권한 서버
                         "bank.com = 10.0.0.1" (정상)

공격
  1. 공격자가 리졸버에 bank.com 질의를 발생시킴
  2. 권한 서버 응답보다 먼저 위조 응답을 전송
     "bank.com = 200.0.0.1" (공격자 서버)
  3. 리졸버가 위조 응답을 캐시에 저장
  4. TTL 동안 모든 클라이언트가 공격자 서버로 연결!

  클라이언트 → 리졸버 (캐시: bank.com = 200.0.0.1)

         200.0.0.1 (피싱 사이트)
         → 로그인 정보 탈취!
         → URL은 정상처럼 보임!

Kaminsky Attack (2008)
  Transaction ID(16비트)를 추측하여 대규모 위조 가능
  → DNS 보안의 근본적 취약점 노출

DNSSEC(DNS Security Extensions)는 이 문제를 해결하기 위한 보안 확장입니다.

DNSSEC 신뢰 체인
         . (Root)
         │  KSK: 루트 키 (Trust Anchor)
         │  서명: .com의 DS 레코드에 서명

       .com (TLD)
         │  ZSK: .com 영역 서명 키
         │  서명: example.com의 DS 레코드에 서명

    example.com
         │  ZSK: example.com 영역 서명 키
         │  서명: A 레코드에 서명

    A: 93.184.216.34  +  RRSIG (디지털 서명)

리졸버 검증 과정
  1. 루트 키(Trust Anchor)는 미리 알고 있음
  2. 루트의 서명으로 .com 키 검증
  3. .com의 서명으로 example.com 키 검증
  4. example.com의 서명으로 A 레코드 검증
  → 체인이 끊기지 않으면 "이 레코드는 진짜다"

DNSSEC 관련 레코드
  RRSIG: 레코드의 디지털 서명
  DNSKEY: 영역의 공개 키
  DS: 하위 영역의 키 해시 (위임 서명)
  NSEC/NSEC3: "이 이름은 존재하지 않음" 증명
dnssec_check.sh
# DNSSEC 서명 확인
dig +dnssec example.com

# DNSSEC 검증 (AD 플래그 확인)
dig +dnssec +adflag example.com | grep "flags"
# ;; flags: qr rd ra ad; → ad = Authenticated Data (검증 성공)

# DNSKEY 레코드 조회
dig DNSKEY example.com +short

# DS 레코드 확인 (상위 영역에 저장)
dig DS example.com +short

DNSSEC은 데이터의 무결성인증을 보장하지만, 기밀성은 보장하지 않습니다. 기밀성까지 보호하려면 DoT나 DoH를 함께 사용해야 합니다.


DNS 실무 문제 해결

DNS 문제 진단 플로우차트
사이트 접속 불가?

  ├── ping IP주소 → 성공? → DNS 문제!
  │                           │
  │   nslookup domain         │
  │     ├── 응답 없음 → DNS 서버 장애 / 방화벽
  │     ├── NXDOMAIN → 도메인 미등록 / 만료
  │     ├── SERVFAIL → 권한 서버 장애 / DNSSEC 실패
  │     └── 잘못된 IP → 캐시 포이즈닝 / 레코드 오류

  └── ping IP주소 → 실패? → 네트워크 문제 (DNS 아님)
문제증상원인해결
DNS 캐시 오래됨서버 이전 후 옛 IP 접속TTL 내 캐시 유지캐시 플러시, TTL 미리 낮추기
NXDOMAIN사이트를 찾을 수 없음도메인 미등록/만료도메인 등록/갱신 확인
SERVFAILDNS 해석 실패권한 서버 장애, DNSSEC 오류권한 서버 상태 확인
느린 DNS페이지 로딩 지연ISP DNS 느림1.1.1.1 또는 8.8.8.8 사용
DNS 하이재킹엉뚱한 사이트 접속악성 DNS, 라우터 해킹DNS 서버 직접 지정, DoH 사용
dns_health_check.py
import socket
import time

def dns_health_check(domains, dns_servers=None):
    """여러 도메인의 DNS 해석 상태 확인"""
    results = []
    
    for domain in domains:
        start = time.time()
        try:
            ip = socket.gethostbyname(domain)
            elapsed = (time.time() - start) * 1000
            status = "OK"
            results.append((domain, ip, f"{elapsed:.1f}ms", status))
        except socket.gaierror as e:
            elapsed = (time.time() - start) * 1000
            results.append((domain, "-", f"{elapsed:.1f}ms", str(e)))
    
    # 결과 출력
    print(f"{'도메인':<25} {'IP':<18} {'시간':<10} {'상태'}")
    print("-" * 70)
    for domain, ip, time_str, status in results:
        print(f"{domain:<25} {ip:<18} {time_str:<10} {status}")

domains = [
    "google.com",
    "github.com",
    "naver.com",
    "example.com",
    "nonexistent-domain-test.xyz",
]
dns_health_check(domains)

다음 장에서는 웹 개발자가 매일 사용하지만 깊이 이해하는 경우가 드문 프로토콜, HTTP를 자세히 살펴보겠습니다.

목차