
자동 결함 검증 도구 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@'
도구는 다음을 수행합니다:
| 문제 | 해결 방법 |
|---|---|
| "DUT가 우리에게 쿼리를 전달하지 않음" |
테스트한 모든 Linksys 라우터는 프로덕션 빌드 구성에서 6개 CVE 모두에 대해 실질적으로 악용 가능하지 않습니다. 위험한 기능(DNSSEC, DHCPv6-통한-dnsmasq)이 컴파일되지 않았거나 구성되지 않았습니다. 심층 방어를 위해 패치를 적용하는 것이 여전히 권장됩니다.
패치 전 (dnsmasq 2.78, DNSSEC 미컴파일 빌드):
패치 전 (dnsmasq 2.90, DNSSEC 활성화):
패치 후 (dnsmasq 2.92rel2 또는 백포트 패치 적용): 모든 6개 CVE → PASS
paramiko (pip install paramiko)test_dnsmasq_cve_remote.py)경량 버전 확인 전용 — version.bind를 쿼리하여 dnsmasq 버전이 수정 버전 미만인지 확인합니다. SSH, 설정, 익스플로잇 페이로드가 필요 없습니다.
python3 test_dnsmasq_cve_remote.py 192.168.1.1
test_dnsmasq_cve_on_device.sh)SSH/시리얼을 통해 DUT에서 직접 실행합니다. 바이너리 버전과 컴파일 옵션을 확인합니다.
scp test_dnsmasq_cve_on_device.sh [email protected]:/tmp/
ssh [email protected] "sh /tmp/test_dnsmasq_cve_on_device.sh"
malicious_dns_server.py)수동 테스트를 위한 독립형 익스플로잇 서버입니다. 실행하고 DUT의 업스트림 DNS를 가리킨 다음 crash-5172.evil.test, crash-2291.evil.test 등의 쿼리를 트리거합니다.
sudo python3 malicious_dns_server.py --port 53
# 그런 다음 DUT에서: 업스트림 → 이 호스트로 구성
# 그런 다음 트리거: dig @192.168.1.1 crash-5172.evil.test
패치를 적용하고 새 펌웨어를 플래시한 후 다시 실행:
sudo python3 dnsmasq_cve_verify.py --laptop <YOUR_WAN_IP> --dut <DUT_LAN_IP> --dut-pass <SSH_PASS>
# 예상 결과: 모든 6개 PASS
| 2단계가 올바르게 수행되었는지 확인. GUI에 입력한 노트북 WAN IP를 확인하세요. |
| "DUT에 연결할 수 없음" | SSH 자격 증명 확인. 수동으로 시도: ssh [email protected] |
| "포트 53을 바인딩할 수 없음" | sudo로 실행. 또는 --dns-port 5353 사용 (수동 DUT 설정 필요). |
| "버전: 알 수 없음" | DUT에 dnsmasq가 표준 경로에 없을 수 있음. 도구는 여전히 올바르게 테스트합니다. |
| CVE | 결과 | 이유 |
|---|
| CVE-2026-2291 | PASS | DNSSEC 미컴파일 |
| CVE-2026-4890 | PASS | DNSSEC 미컴파일 |
| CVE-2026-4891 | PASS | DNSSEC 미컴파일 |
| CVE-2026-4892 | PASS | dnsmasq가 DHCPv6 제공 안 함 (별도 DHCPv6 서버 사용) |
| CVE-2026-4893 | PASS | 논리 버그만 — 크래시 없음 |
| CVE-2026-5172 | PASS | 익스플로잇에서 생존 (취약 코드 경로가 2.78에 없음) |
| CVE | 결과 | 이유 |
|---|
| CVE-2026-2291 | PASS | DNSSEC 미컴파일 |
| CVE-2026-4890 | PASS | DNSSEC 미컴파일 |
| CVE-2026-4891 | PASS | DNSSEC 미컴파일 |
| CVE-2026-4892 | PASS | DHCPv6 미컴파일 |
| CVE-2026-4893 | PASS | 논리 버그만 — 크래시 없음 |
| CVE-2026-5172 | PASS | 익스플로잇 변종에서 생존 |
| CVE | 결과 | 이유 |
|---|
| CVE-2026-2291 | PASS | DNSSEC 미컴파일 — 악용 불가 |
| CVE-2026-4890 | PASS | DNSSEC 미컴파일 — 악용 불가 |
| CVE-2026-4891 | PASS | DNSSEC 미컴파일 — 악용 불가 |
| CVE-2026-4892 | PASS/FAIL | DHCPv6 컴파일 + dhcp-script 활성화 여부 |
| CVE-2026-4893 | PASS | 논리 버그 — 크래시 없음 (버전 기반만) |
| CVE-2026-5172 | PASS | blockdata_expand 경로가 2.78에 없음 |
| CVE | 결과 | 이유 |
|---|
| CVE-2026-2291 | FAIL | 이스케이프된 이름을 통한 힙 오버플로우 |
| CVE-2026-4890 | FAIL | 무한 루프 (멈춤) |
| CVE-2026-4891 | FAIL | RRSIG 범위 밖 읽기 크래시 |
| CVE-2026-4892 | PASS/FAIL | DHCPv6 + 스크립트 구성에 따라 다름 |
| CVE-2026-4893 | PASS | 논리 버그 — 크래시 없음 |
| CVE-2026-5172 | FAIL | 위조된 rdlen을 통한 범위 밖 읽기 |
| 플래그 | 기본값 | 설명 |
|---|
--laptop | (필수) | 노트북의 WAN IP (악성 DNS 서버를 바인딩) |
--dut | 192.168.1.1 | DUT의 LAN IP (SSH + DNS 쿼리 전송) |
--dut-user | root | DUT SSH 사용자 이름 |
--dut-pass | (프롬프트) | DUT SSH 비밀번호 |
--dns-port | 53 | 악성 DNS 서버 포트 |
--cve | 6개 모두 | 테스트할 특정 CVE (반복 가능) |
| 도구 | Python | Root | SSH | 네트워크 |
|---|
dnsmasq_cve_verify.py | 3.6+ paramiko | 필요 (포트 53) | 필요 (읽기 전용) | DUT에 LAN + WAN |
test_dnsmasq_cve_remote.py | 3.6+ 표준 라이브러리 | 불필요 | 불필요 | DUT로 UDP 53 |
test_dnsmasq_cve_on_device.sh | N/A (셸) | 불필요 | DUT에서 실행 | N/A |
malicious_dns_server.py | 3.6+ 표준 라이브러리 | 필요 (포트 53) | 불필요 | DUT가 우리로 전달 |