
자동 결함 검증 도구 for 6 dnsmasq CVEs (CVE-2026-2291, 4890, 4891, 4892, 4893, 5172)
6가지 dnsmasq 취약점(2026년 5월)을 검증하는 자동화 블랙박스 도구입니다. 공격 패킷을 실제 DUT에 전송하고 PASS/FAIL을 보고합니다. 소스 코드 접근이 필요하지 않습니다.
# 먼저 GUI를 통해 DUT DNS를 노트북의 WAN IP로 설정한 후:
sudo python3 dnsmasq_cve_verify.py --laptop <YOUR_WAN_IP> --dut <DUT_LAN_IP> --dut-pass <SSH_PASS>
# 예시:
sudo python3 dnsmasq_cve_verify.py --laptop 10.0.0.211 --dut 192.168.1.1 --dut-pass '12345Asdf@'
| CVE | CVSS | 유형 | 공격 벡터 | 영향을 받는 기능 |
|---|---|---|---|---|
| CVE-2026-2291 | 9.2 | 힙 버퍼 오버플로우 | 원격 | extract_name() — 항상 활성 |
| CVE-2026-5172 | 7.5 | 범위 밖 읽기 / 크래시 | 원격 | extract_addresses() — 항상 활성 |
| CVE-2026-4890 | 7.5 | 무한 루프 DoS | 원격 | NSEC 비트맵 파싱 (--dnssec) |
| CVE-2026-4891 | 5.3 | 힙 범위 밖 읽기 | 원격 | RRSIG 검증 (--dnssec) |
| CVE-2026-4892 | 8.4 | 힙 오버플로우 → 루트 권한 획득 | 로컬/인접 | DHCPv6 CLID (--dhcp-script + DHCPv6) |
| CVE-2026-4893 | 5.3 | 검증 우회 | 원격 | ECS 소스 확인 (--add-subnet) |
원인: union bigname은 char name[MAXDNAME]을 선언하지만, 이스케이프된 문자는 이름을 2*MAXDNAME+1 바이트로 확장할 수 있어 힙 오버플로우가 발생합니다.
테스트 방법: 내부적으로 \DDD 이스케이프(입력 바이트당 4바이트)되는 상위 비트 문자(0x80+)가 포함된 도메인 이름을 포함한 DNS 쿼리를 전송합니다. dnsmasq가 크래시되거나 응답을 멈추면 취약한 것입니다.
패치된 동작: 과도하게 큰 이름을 정상적으로 거부(FORMERR/REFUSED)하거나 버퍼를 확장합니다.
원인: 위조된 rdlen 필드로 인해 extract_name()이 포인터를 레코드 끝 이상으로 진행시킬 수 있습니다. 남은 바이트 언더플로우로 인해 큰 값이 생성되어 대규모 범위 밖 읽기 → 크래시가 발생합니다.
테스트 방법: rdlen이 실제 인코딩된 이름보다 작은 CNAME 레코드가 포함된 DNS 응답을 전송합니다. dnsmasq가 크래시되면 취약한 것입니다.
패치된 동작: extract_name() 후 포인터가 선언된 rdlen 경계 내에 머무르는지 검증합니다.
원인: NSEC 유형 비트맵 파싱이 p[1]+2 대신 p[1]만큼 진행됩니다(윈도우 헤더 크기 누락). bitmap_length=0이면 포인터가 절대 진행되지 않아 무한 루프가 발생합니다.
테스트 방법: window=0, bitmap_length=0으로 조작된 NSEC 레코드를 전송합니다. dnsmasq가 모든 쿼리에 응답하지 않으면(크래시가 아닌 멈춤) 취약한 것입니다. RRSIG 검증 전에 악용 가능합니다.
패치된 동작: p[1]+2만큼 진행하고 길이가 0인 비트맵을 건너뜁니다.
원인: RRSIG의 rdlen이 최소 크기(18 + 서명자 이름)에 대해 검증되지 않습니다. 계산된 서명 길이가 음수로 언더플로우되어 큰 값으로 처리 → 범위 밖 읽기가 발생합니다.
테스트 방법: rdlen=10(최소 31+ 바이트보다 훨씬 낮음)인 RRSIG 레코드를 전송합니다. 크래시 = 취약함.
패치된 동작: 서명 길이를 계산하기 전에 rdlen >= 고정_필드 + 서명자_이름_길이를 검증합니다.
원인: DHCPv6 CLID(최대 65535바이트)가 sprintf("%.2x")를 통해 daemon->packet(5131바이트)으로 16진수 인코딩됩니다. 3000바이트 CLID → 6000바이트 16진수 문자열 → 오버플로우. 헬퍼 프로세스가 root로 실행됩니다.
테스트 방법: 3000바이트 클라이언트 식별자가 포함된 DHCPv6 SOLICIT을 전송합니다. IPv6 인접성과 --dhcp-script 구성이 필요합니다. 헬퍼 크래시 = 취약함.
패치된 동작: 16진수 인코딩 전에 CLID 길이를 자르거나 검증합니다.
참고: 일부 빌드는 -DNO_DHCP6로 컴파일되며 이 CVE의 영향을 받지 않습니다.
원인: process_reply()가 전체 패킷 길이 대신 OPT 레코드 길이(~23바이트)를 check_source()에 전달합니다. 모든 경계 검사가 실패하여 함수가 항상 1(유효)을 반환합니다.
테스트 방법: 위조된 소스 프리픽스가 포함된 EDNS 클라이언트 서브넷 옵션이 있는 DNS 쿼리를 전송합니다. dnsmasq가 검증 없이 ECS를 다시 에코하면 취약한 것입니다.
패치된 동작: 전체 패킷 길이를 check_source()에 전달하여 RFC 7871 섹션 9.2에 따른 적절한 경계 검사를 활성화합니다.
dnsmasq 2.92rel2로 업그레이드 (권장)
dnsmasq_cve_verify.py)주요 QA 도구입니다. 테스트 노트북에서 실행되며 DUT에 공격 패킷을 전송하고 각 CVE에 대해 명확한 PASS/FAIL을 보고합니다. 상태 검사를 위한 읽기 전용 SSH 액세스 외에는 DUT 수정이 필요하지 않습니다.
┌─────────────────────────────────────────────────────────────────────┐
│ 테스트 노트북 │
│ │
│ LAN 인터페이스 WAN 인터페이스 │
│ <LAPTOP_LAN_IP> <LAPTOP_WAN_IP> │
│ │ │ │
│ │ ┌────┴──────────────┐ │
│ │ │ 악성 DNS │ │
│ │ │ 서버 (포트 53) │ │
│ │ └────┬──────────────┘ │
│ │ │ │
└────────┼───────────────────────────────┼────────────────────────────┘
│ LAN 서브넷 │ WAN 서브넷
│ │
┌────────┼───────────────────────────────┼────────────────────────────┐
│ │ │ │
│ LAN: <DUT_LAN_IP> WAN: <DUT_WAN_IP> │
│ (LAN 게이트웨이) (WAN 업링크) │
│ │
│ DUT (Linksys 라우터) │
│ dnsmasq (2.92rel2 미만 모든 버전) │
│ │
│ resolv-file=/etc/resolv.conf │
│ → nameserver <LAPTOP_WAN_IP> ← GUI로 설정, 우리에게 전달 │
│ │
└─────────────────────────────────────────────────────────────────────┘
데이터 흐름:
1. 도구가 DUT LAN IP(포트 53)로 DNS 쿼리 전송
2. DUT의 dnsmasq가 로컬에서 해결 불가 → 업스트림(LAPTOP_WAN_IP)으로 전달
3. WAN 인터페이스의 악성 서버가 익스플로잇 페이로드로 응답
4. DUT의 dnsmasq가 악성 응답 처리 → 크래시/멈춤/생존
5. 도구가 SSH(읽기 전용)로 DUT 상태 확인
예시 설정 (실제 IP는 다를 수 있음):
| 역할 | IP (예시) |
|---|---|
| 노트북 LAN | 192.168.1.254 |
| 노트북 WAN | 10.0.0.211 |
| DUT LAN | 192.168.1.1 |
| DUT WAN | 10.0.0.214 |
핵심 요구 사항: 노트북 WAN IP와 DUT WAN IP가 동일한 서브넷에 있어야 DUT가 업스트림 DNS 서버로 노트북에 연결할 수 있습니다.
┌──────────┐ ┌───────────┐ ┌──────────────────┐ ┌──────────┐
│ SETUP │ ──► │ TRIGGER │ ──► │ STATE INSPECT │ ──► │ VERDICT │
│ │ │ │ │ │ │ │
│ 시작 │ │ DNS 쿼리 │ │ SSH로 DUT 확인: │ │ PASS: │
│ 악성 DNS │ │ DUT→DUT │ │ - pidof dnsmasq │ │ 생존 │
│ 서버를 │ │ 전달→우리 │ │ - PID 변경? │ │ │
│ WAN │ │ 익스플로잇│ │ - dmesg 크래시? │ │ FAIL: │
│ 인터페이 │ │ 페이로드 │ │ - /var/log/msg │ │ 크래시/ │
│ 스에 시작│ │ 로 응답 │ │ │ │ 멈춤 │
│ (10.0.0. │ │ │ │ 생존 쿼리 │ │ │
│ 211:53) │ │ │ │ (version.bind) │ │ │
└──────────┘ └───────────┘ └──────────────────┘ └──────────┘
참고: 도구는 DUT 설정을 수정하지 않습니다. 사용자는 GUI를 통해 DNS를 10.0.0.211로 설정해야 합니다.
paramiko가 설치된 Python 3.6+ (pip install paramiko)테스트 노트북을 DUT에 두 개의 케이블로 연결합니다:
| 노트북 포트 | 연결 대상 | 목적 |
|---|---|---|
| LAN 포트 | DUT LAN 포트 | SSH 접속 + DUT로 DNS 쿼리 전송 |
| WAN 포트 | DUT WAN 서브넷 (예: 업스트림 스위치/모뎀 포트) | 업스트림 DNS 서버 역할 |
연결 후 노트북의 IP를 확인합니다:
# IP 확인
ip addr show | grep "inet "
# 예시 출력:
# inet 192.168.1.254/24 ... ← LAN IP
# inet 10.0.0.211/24 ... ← WAN IP (--laptop에 사용)
http://192.168.1.1 또는 http://myrouter.local10.0.0.211)로 설정cd /path/to/dnsmasq-cve-2026/
# 6개 CVE 테스트 모두 실행:
sudo python3 dnsmasq_cve_verify.py --laptop <YOUR_WAN_IP> --dut <DUT_LAN_IP> --dut-pass <SSH_PASSWORD>
# 예시:
sudo python3 dnsmasq_cve_verify.py --laptop 10.0.0.211 --dut 192.168.1.1 --dut-pass '12345Asdf@'
도구는 다음을 수행합니다: