📚 네트워크 · 패킷 분석 › 03. Wireshark 패킷 분석 — 144편
이전 글: 143. SYN Scan Packet 분석 · 다음 글: 145. HTTP 메서드와 상태코드로 보는 이상징후
DNS는 거의 모든 네트워크에서 외부로 나가는 것이 허용된 프로토콜입니다. 방화벽이 웹 외 통신을 모두 막아도, 내부 단말이 도메인 이름을 해석할 수 있어야 하므로 DNS 질의는 내부 리졸버를 거쳐 결국 외부 권한 서버까지 전달됩니다.
이 특성 때문에 DNS는 다음과 같은 목적으로 악용됩니다.
이번 글은 공격 도구 사용법이 아니라 각 유형의 원리와 DNS 로그에 남는 흔적, 탐지 지표를 정리합니다. 레코드와 응답 코드의 기본은 07. DNS 레코드와 질의 유형을 참고하세요.
| 유형 | 원리 | 대표 흔적 |
|---|---|---|
| DNS 터널링 | 데이터를 인코딩해 하위 도메인 라벨에 싣고, 공격자가 운영하는 권한 서버가 응답(TXT 등)에 데이터를 실어 돌려줌 | 한 도메인 아래 매번 다른 긴 하위 이름, TXT·NULL 등 드문 타입 질의 증가 |
| DGA (Domain Generation Algorithm) | 악성코드가 날짜 등을 입력으로 도메인을 대량 생성해 순서대로 질의, 공격자는 그중 일부만 등록 | 무작위 형태 도메인, NXDOMAIN 다수 |
| Fast Flux | 짧은 TTL로 도메인의 A 레코드를 빠르게 교체 | 매우 짧은 TTL, 질의마다 다른 IP 다수 |
| 리졸버 우회 | 내부 DNS 대신 외부 DNS(53) 또는 DoH(443)·DoT(853) 직접 사용 | 내부 리졸버가 아닌 목적지로 53번 통신, 알려진 DoH 서버로 443 통신 |
| DNS 증폭(반사) | 출발지를 위조한 작은 질의로 큰 응답을 피해자에게 보내게 함 | 개방 리졸버의 외부 질의 응답 급증 (05. 네트워크 스캔 · 공격 징후 분석 시리즈에서 다룸) |
DNS 이름은 점(.)으로 나뉜 라벨로 구성되며, 라벨 하나는 최대 63바이트, 전체 이름은 최대 255바이트(표기상 약 253자)입니다. 일반 도메인은 이 한도에 한참 못 미치지만, 터널링은 데이터를 많이 싣기 위해 한도에 가까운 긴 라벨을 반복 사용하는 경향이 있습니다.
| 지표 | 계산 방법 | 의미 |
|---|---|---|
| 질의 이름 길이 | 전체 길이, 가장 긴 라벨 길이 | 터널링은 길고 일정한 길이의 라벨이 반복 |
| 문자 무작위성(엔트로피) | 라벨의 문자 분포로 섀넌 엔트로피 계산 | 인코딩 데이터·DGA는 높게 나오는 경향 |
| 고유 하위 이름 수 | 도메인(상위 2단계)별 서로 다른 하위 이름 개수 | 터널링은 한 도메인에 고유 이름이 매우 많음 |
| NXDOMAIN 비율 | 단말별 NXDOMAIN / 전체 질의 | DGA 감염 단말은 높게 나옴 |
| 질의 타입 비율 | TXT·NULL·ANY 등의 비중 | 일반 단말은 A·AAAA·HTTPS 위주 |
| 목적지 DNS 서버 | 단말이 질의를 보낸 IP | 내부 리졸버 외 목적지는 정책 위반 가능성 |
DNS 터널링이 방화벽을 통과하는 경로를 개념적으로 보면 다음과 같습니다.

그림 1. 터널링·DGA는 여러 지표를 결합해 후보를 좁힌 뒤 확인합니다
[감염 단말 192.168.10.30]
데이터 "abc..." → 인코딩 → <인코딩값>.t.attacker.example 질의 (TXT?)
↓ 단말은 외부로 직접 못 나감 → 내부 리졸버에 질의
[내부 DNS 리졸버 192.168.10.1]
↓ 캐시에 없음(매번 다른 이름) → 외부 권한 서버로 재귀 질의
[attacker.example 의 권한 DNS 서버 (공격자 운영)]
↓ 질의 이름에서 데이터 복원, TXT 응답에 명령·데이터를 실어 회신
[내부 리졸버] → [감염 단말]
→ 방화벽 로그에는 "리졸버 → 외부 DNS 53" 만 남음
→ 내용은 DNS 질의 로그에서만 확인 가능
DGA는 반대로 데이터 전송이 아니라 "접속할 곳 찾기" 가 목적입니다.
[감염 단말] 오늘 날짜 기반으로 도메인 N개 생성
qx7vkt2m.example-tld → NXDOMAIN
m2zr9pwa.example-tld → NXDOMAIN
... (반복)
h8dk3lq0.example-tld → NOERROR, 203.0.113.77 ← 공격자가 등록해 둔 하나
↓
203.0.113.77 로 C2 통신 시작
두 경우 모두 개별 질의 하나는 정상 DNS와 형식이 같습니다. 탐지는 한 단말·한 도메인 단위의 통계적 패턴으로 해야 합니다.
실습 환경: 본인 소유 Linux VM (Rocky Linux 또는 Ubuntu). 인터페이스 이름은 ens33을 예시로 사용합니다. 이 실습은 정상 DNS 질의를 수집해 탐지 지표를 계산하는 것이며, 터널링·DGA 트래픽을 생성하지 않습니다.
도구 설치:
# Rocky Linux
sudo dnf install -y bind-utils tcpdump python3
# Ubuntu
sudo apt install -y dnsutils tcpdump python3
# 터미널 1: 질의를 텍스트로 저장
sudo tcpdump -ni ens33 -l 'udp port 53' | tee ~/dns_capture.txt
# 터미널 2: 평소처럼 몇 가지 질의·접속
for d in example.com www.example.com example.net no-such-name.example.com; do dig $d +short; done
# 질의 이름만 추출
grep -oE '(A|AAAA|TXT|MX|CNAME|PTR|HTTPS)\? [^ ]+' ~/dns_capture.txt | awk '{print $2}' > ~/qnames.txt
# 질의 타입 분포
grep -oE ' [A-Z]+\? ' ~/dns_capture.txt | sort | uniq -c | sort -rn
# NXDOMAIN 응답 수
grep -c 'NXDomain' ~/dns_capture.txt
cat > ~/dns_metrics.py << 'EOF'
import math, sys
from collections import Counter
def entropy(s):
if not s:
return 0.0
c = Counter(s)
e = -sum(n/len(s) * math.log2(n/len(s)) for n in c.values())
return e if e > 0 else 0.0
for line in open(sys.argv[1]):
name = line.strip().rstrip('.')
if not name:
continue
labels = name.split('.')
sub = '.'.join(labels[:-2]) if len(labels) > 2 else ''
longest = max(len(l) for l in labels)
print(f"{len(name):4d} {longest:3d} {entropy(sub.replace('.', '')):5.2f} {name}")
EOF
python3 ~/dns_metrics.py ~/qnames.txt | sort -rn | head
출력 열은 전체 길이, 가장 긴 라벨 길이, 하위 이름 엔트로피, 질의 이름 순입니다.
📷 [실습 화면 삽입] 정상 질의에 대한 길이·엔트로피 계산 결과 화면
# 설정된 리졸버(예: 192.168.10.1)가 아닌 목적지로 가는 DNS 질의
sudo tcpdump -ni ens33 'port 53 and not host 192.168.10.1'
# DNS 질의를 보내는 프로세스 확인 (UDP 질의는 순간적이라 질의 중에만 보일 수 있음)
sudo ss -uanp 'dport = :53'
형식 예시(값은 환경마다 다름):
$ python3 ~/dns_metrics.py ~/qnames.txt | sort -rn | head
24 12 3.25 no-such-name.example.com
15 7 0.00 www.example.com
11 7 0.00 example.net
11 7 0.00 example.com
# 비교용 가상의 터널링 형태 이름 (형식 예시, 실제 트래픽 아님)
98 63 (높음) <63자 무작위 문자열>.<짧은 라벨>.t.attacker.example
no-such-name처럼 사람이 만든 이름도 문자 종류가 많으면 엔트로피가 3 이상 나옵니다. 엔트로피 하나만으로 판단하면 오탐이 많으므로 길이·고유 이름 수·응답 코드와 함께 봐야 합니다.
| 지표 | 정상 질의 (실습 결과) | 주의가 필요한 형태 |
|---|---|---|
| 전체 길이 | 대부분 수십 자 이내 | 100자 가까이 또는 그 이상이 반복 |
| 가장 긴 라벨 | 단어 수준 길이 | 60자 안팎의 라벨이 반복 |
| 하위 이름 엔트로피 | 낮음 (www, mail 등) | 높음 (무작위 문자열) |
| 도메인별 고유 하위 이름 수 | 소수 | 한 도메인에 수백~수천 개 |
📷 [실습 화면 삽입] 질의 타입 분포와 NXDOMAIN 집계 결과 화면
| 관찰 | 정상일 수 있는 경우 | 의심해야 하는 경우 |
|---|---|---|
| 길고 무작위한 하위 이름 | CDN·클라우드 서비스의 자동 생성 호스트명, 보안 제품의 평판 조회 질의(해시값을 이름에 담음) | 평판 정보 없는 도메인 아래 고유 하위 이름이 짧은 시간에 대량 발생 |
| TXT 질의 증가 | 메일 서버의 SPF·DKIM 확인 | 일반 PC가 한 도메인에 TXT를 반복 질의 |
| 단말의 NXDOMAIN 다수 | 설정 오류, 삭제된 내부 도메인 참조 | 무작위 형태 도메인 다수가 NXDOMAIN, 그중 하나만 NOERROR 후 그 IP로 연결 |
| 매우 짧은 TTL + IP 다수 | CDN, 로드밸런싱 | 평판 낮은 도메인, IP가 여러 국가·가정용 대역에 분산 |
| 외부 DNS(53) 직접 질의 | 정책상 허용된 장비 | 일반 단말이 내부 리졸버를 우회 |
| 알려진 DoH 서버로 443 통신 | 브라우저 기본 설정 | 조직 정책상 DoH를 막았는데 특정 프로세스가 사용 |
정상 서비스 오탐 주의: 일부 보안 제품·CDN·클라우드 서비스는 정상적으로 길고 무작위한 이름을 질의합니다. 탐지 규칙에는 조직에서 확인된 정상 도메인 예외 목록(허용 목록)이 필요합니다.
도메인별 고유 하위 이름 수를 세는 예시입니다.
# 상위 2개 라벨 기준으로 묶어 고유 이름 수 집계
awk -F. '{n=NF; if(n>=2) print $(n-1)"."$n, $0}' ~/qnames.txt | sort -u | awk '{print $1}' | uniq -c | sort -rn | head
(co.kr처럼 2단계 최상위 도메인은 3개 라벨 기준으로 묶어야 정확합니다.)
| 위치 | 확인 가능한 흔적 | 한계 |
|---|---|---|
| 내부 DNS 리졸버 질의 로그 | 단말별 질의 이름·타입·RCODE | 로그 활성화·보관 용량 필요 |
| 네트워크 센서 (Zeek, Suricata) | 질의·응답 전체, 통계 분석 가능 | 센서 위치 밖 구간 누락 |
| 방화벽 | 외부 53·853 직접 통신, DoH 서버 IP 통신 | 질의 내용 없음 |
| 위협 인텔리전스·도메인 평판 | 신규 등록·악성 도메인 여부 | 신규 DGA 도메인은 아직 목록에 없을 수 있음 |
| 단말 (EDR 등) | 질의한 프로세스 | 수집 설정 필요 |
대응·예방 관점 설정
한계: 개별 질의 하나로는 판단할 수 없습니다. 길이·엔트로피 임계값은 환경마다 다르므로 조직의 평소 DNS 패턴(베이스라인) 을 먼저 측정해야 합니다. 암호화 DNS(DoH·DoT)는 질의 내용이 보이지 않으므로 통신 목적지와 단말 정책으로 관리합니다.