前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CVE-2017-7184-xfrm_user 分析

CVE-2017-7184-xfrm_user 分析

原创
作者头像
De4dCr0w
修改2020-06-12 18:20:08
7690
修改2020-06-12 18:20:08
举报

前言

IPSEC是一个协议组合,用于提供IP层的安全性。它包含AH、ESP、IKE协议,IPSec提供了两种安全机制:认证(采用ipsec的AH)和加密(采用ipsec的ESP)。

(1) SA(Security Associstion)

SA由spi、ip、安全协议标识(AH或ESP)这三个参数唯一确定。SA定义了ipsec双方的ip地址、ipsec协议、加密算法、密钥、模式、抗重放窗口等。

(2) AH(Authentication Header)

AH为ip包提供数据完整性校验和身份认证功能,提供抗重放能力,验证算法由SA指定。

(3) ESP(Encapsulating security payload)

ESP为ip数据包提供完整性检查、认证和加密

xfrm_user.c中的代码允许我们向内核发送netlink消息来调用相关handler实现对SA和SP的配置,其中涉及处理函数如下:

代码语言:txt
复制
xfrm_dispatch[XFRM_NR_MSGTYPES] = {
[XFRM_MSG_NEWSA       - XFRM_MSG_BASE] = { .doit = xfrm_add_sa        },
[XFRM_MSG_DELSA       - XFRM_MSG_BASE] = { .doit = xfrm_del_sa        },
[XFRM_MSG_GETSA       - XFRM_MSG_BASE] = { .doit = xfrm_get_sa,
	.dump = xfrm_dump_sa,
	.done = xfrm_dump_sa_done  },
[XFRM_MSG_NEWPOLICY   - XFRM_MSG_BASE] = { .doit = xfrm_add_policy    },
[XFRM_MSG_DELPOLICY   - XFRM_MSG_BASE] = { .doit = xfrm_get_policy    },
[XFRM_MSG_GETPOLICY   - XFRM_MSG_BASE] = { .doit = xfrm_get_policy,
	                                   .dump = xfrm_dump_policy,
	                                   .done = xfrm_dump_policy_done },
[XFRM_MSG_ALLOCSPI    - XFRM_MSG_BASE] = { .doit = xfrm_alloc_userspi },
[XFRM_MSG_ACQUIRE     - XFRM_MSG_BASE] = { .doit = xfrm_add_acquire   },
[XFRM_MSG_EXPIRE      - XFRM_MSG_BASE] = { .doit = xfrm_add_sa_expire },
[XFRM_MSG_UPDPOLICY   - XFRM_MSG_BASE] = { .doit = xfrm_add_policy    },
[XFRM_MSG_UPDSA       - XFRM_MSG_BASE] = { .doit = xfrm_add_sa        },
[XFRM_MSG_POLEXPIRE   - XFRM_MSG_BASE] = { .doit = xfrm_add_pol_expire},
[XFRM_MSG_FLUSHSA     - XFRM_MSG_BASE] = { .doit = xfrm_flush_sa      },
[XFRM_MSG_FLUSHPOLICY - XFRM_MSG_BASE] = { .doit = xfrm_flush_policy  },
[XFRM_MSG_NEWAE       - XFRM_MSG_BASE] = { .doit = xfrm_new_ae  },
[XFRM_MSG_GETAE       - XFRM_MSG_BASE] = { .doit = xfrm_get_ae  },
[XFRM_MSG_MIGRATE     - XFRM_MSG_BASE] = { .doit = xfrm_do_migrate    },
[XFRM_MSG_GETSADINFO  - XFRM_MSG_BASE] = { .doit = xfrm_get_sadinfo   },
[XFRM_MSG_NEWSPDINFO  - XFRM_MSG_BASE] = { .doit = xfrm_set_spdinfo,
		                           .nla_pol = xfrma_spd_policy,
				           .nla_max = XFRMA_SPD_MAX },
[XFRM_MSG_GETSPDINFO  - XFRM_MSG_BASE] = { .doit = xfrm_get_spdinfo   },
};

其中几个函数的功能:

xfrm_add_sa

创建一个新的SA,并可以指定相关attr,在内核中,是用一个xfrm_state结构来表示一个SA的。

xfrm_del_sa

删除一个SA,也即删除一个指定的xfrm_state。

xfrm_new_ae

根据传入参数,更新指定xfrm_state结构中的内容。

xfrm_get_ae

根据传入参数,查询指定xfrm_state结构中的内容(包括attr)。

漏洞相关的重要数据结构:

xfrm_state数据结构,由于表示SA:

代码语言:txt
复制
struct xfrm_state {
	possible_net_t		xs_net;
	union {
		struct hlist_node	gclist;
		struct hlist_node	bydst;
	};
	struct hlist_node	bysrc;
	struct hlist_node	byspi;

	atomic_t		refcnt;
	spinlock_t		lock;

	struct xfrm_id		id; // 用于识别一个SA身份,包含daddr、spi、proto三个参数
	struct xfrm_selector	sel;
	struct xfrm_mark	mark;
	u32			tfcpad;

	u32			genid;

	/* Key manager bits */
	struct xfrm_state_walk	km;

	/* Parameters of this state. */
	struct {
		u32		reqid;
		u8		mode;
		u8		replay_window;
		u8		aalgo, ealgo, calgo;
		u8		flags;
		u16		family;
		xfrm_address_t	saddr;
		int		header_len;
		int		trailer_len;
		u32		extra_flags;
	} props;

	struct xfrm_lifetime_cfg lft;

	/* Data for transformer */
	struct xfrm_algo_auth	*aalg;
	struct xfrm_algo	*ealg;
	struct xfrm_algo	*calg;
	struct xfrm_algo_aead	*aead;
	const char		*geniv;

	/* Data for encapsulator */
	struct xfrm_encap_tmpl	*encap;

	/* Data for care-of address */
	xfrm_address_t	*coaddr;

	/* IPComp needs an IPIP tunnel for handling uncompressed packets */
	struct xfrm_state	*tunnel;

	/* If a tunnel, number of users + 1 */
	atomic_t		tunnel_users;

	/* State for replay detection */
	struct xfrm_replay_state replay;
	struct xfrm_replay_state_esn *replay_esn; // ***

	/* Replay detection state at the time we sent the last notification */
	struct xfrm_replay_state preplay;
	struct xfrm_replay_state_esn *preplay_esn; // ***

	/* The functions for replay detection. */
	const struct xfrm_replay *repl;

	/* internal flag that only holds state for delayed aevent at the
	 * moment
	*/
	u32			xflags;

	/* Replay detection notification settings */
	u32			replay_maxage;
	u32			replay_maxdiff;

	/* Replay detection notification timer */
	struct timer_list	rtimer;

	/* Statistics */
	struct xfrm_stats	stats;

	struct xfrm_lifetime_cur curlft;
	struct tasklet_hrtimer	mtimer;

	/* used to fix curlft->add_time when changing date */
	long		saved_tmo;

	/* Last used time */
	unsigned long		lastused;

	/* Reference to data common to all the instances of this
	 * transformer. */
	const struct xfrm_type	*type;
	struct xfrm_mode	*inner_mode;
	struct xfrm_mode	*inner_mode_iaf;
	struct xfrm_mode	*outer_mode;

	/* Security context */
	struct xfrm_sec_ctx	*security;

	/* Private data of this transformer, format is opaque,
	 * interpreted by xfrm_type methods. */
	void			*data;
};
代码语言:txt
复制
struct xfrm_replay_state_esn {
    unsigned int    bmp_len;//表示bmp数组的大小,最大值为4096/4/8
    __u32       oseq;
    __u32       seq;
    __u32       oseq_hi;
    __u32       seq_hi;
    __u32       replay_window;//seq的模数,表示索引的范围,以bit为单位的大小
    __u32       bmp[0];
};

bmp_len决定整个结构体的具体大小. 而replay_window则决定了bmp数组的索引范围(以bit为单位)。

bmp_len由用户输入控制,通过kzalloc申请64 + bmp_len 4 大小的内存块。

cred结构:

代码语言:txt
复制
struct cred {
	atomic_t	usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
	atomic_t	subscribers;	/* number of processes subscribed */
	void		*put_addr;
	unsigned	magic;
#define CRED_MAGIC	0x43736564
#define CRED_MAGIC_DEAD	0x44656144
#endif
	kuid_t		uid;		/* real UID of the task */
	kgid_t		gid;		/* real GID of the task */
	kuid_t		suid;		/* saved UID of the task */
	kgid_t		sgid;		/* saved GID of the task */
	kuid_t		euid;		/* effective UID of the task */
	kgid_t		egid;		/* effective GID of the task */
	kuid_t		fsuid;		/* UID for VFS ops */
	kgid_t		fsgid;		/* GID for VFS ops */
	unsigned	securebits;	/* SUID-less security management */
	kernel_cap_t	cap_inheritable; /* caps our children can inherit */
	kernel_cap_t	cap_permitted;	/* caps we're permitted */
	kernel_cap_t	cap_effective;	/* caps we can actually use */
	kernel_cap_t	cap_bset;	/* capability bounding set */
	kernel_cap_t	cap_ambient;	/* Ambient capability set */
#ifdef CONFIG_KEYS
	unsigned char	jit_keyring;	/* default keyring to attach requested
					 * keys to */
	struct key __rcu *session_keyring; /* keyring inherited over fork */
	struct key	*process_keyring; /* keyring private to this process */
	struct key	*thread_keyring; /* keyring private to this thread */
	struct key	*request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
	void		*security;	/* subjective LSM security */
#endif
	struct user_struct *user;	/* real user ID subscription */
	struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
	struct group_info *group_info;	/* supplementary groups for euid/fsgid */
	struct rcu_head	rcu;		/* RCU deletion hook */
};

漏洞分析

申请一个xfrm_state结构(SA)流程:

代码语言:txt
复制
xfrm_add_sa:
	->verify_newsa_info
		->verify_replay // 检验bmp_len是否小于最大值
	->xfrm_state_construct
		->xfrm_state_alloc // 申请xfrm_state结构
		->copy_from_user_state
		->xfrm_alloc_replay_state_esn(&x->replay_esn, &x->preplay_esn,
                           attrs[XFRMA_REPLAY_ESN_VAL]))//申请xfrm_replay_state_esn结构
        	->xfrm_replay_state_esn_len //获取申请空间的大小
        ->xfrm_init_replay(x)  //检验xfrm_replay_state_esn结构中的数据
代码语言:txt
复制
static inline int xfrm_replay_state_esn_len(struct xfrm_replay_state_esn *replay_esn)
{
	return sizeof(*replay_esn) + replay_esn->bmp_len * sizeof(__u32);
}

更新一个xfrm_state结构(SA)流程:

代码语言:txt
复制
xfrm_add_ae:
	->xfrm_state_lookup //循环查找hash表,得到xfrm_state结构体
    ->xfrm_replay_verify_len // 检查整个结构体的大小,但没有检查replay_window变量,导致越界
        ->ulen = xfrm_replay_state_esn_len(up);
        ->if (nla_len(rp) < ulen || xfrm_replay_state_esn_len(replay_esn) != ulen)
    ->xfrm_update_ae_params // memcpy复制更新的数据

replay_esn为之前的结构体,up为用户更新的结构体,检查两个结构体大小要相同,即只需要提供的bmp_len与xfrm_state中原来的bmp_len一致就可以通过检查。

所以漏洞成因在于更新xfrm_state时,没有检查x->replay_esn.replay_window,导致引用replay_window变量进行bitmap读写时会造成越界。

xfrm_replay_verify_len代码:

代码语言:txt
复制
static inline int xfrm_replay_verify_len(struct xfrm_replay_state_esn *replay_esn,
                     struct nlattr *rp)
{                    
    struct xfrm_replay_state_esn *up;
    int ulen;
    
    if (!replay_esn || !rp)
        return 0;

    up = nla_data(rp);    
    ulen = xfrm_replay_state_esn_len(up);
    
    if (nla_len(rp) < ulen || xfrm_replay_state_esn_len(replay_esn) != ulen)                   
        return -EINVAL;

    return 0;
} 

之后漏洞利用就需要找到使用replay_window进行访问的函数:

代码语言:txt
复制
static const struct xfrm_replay xfrm_replay_esn = {
    .advance    = xfrm_replay_advance_esn, <------
    .check      = xfrm_replay_check_esn,   <------
    .recheck    = xfrm_replay_recheck_esn,
    .notify     = xfrm_replay_notify_esn,
    .overflow   = xfrm_replay_overflow_esn,
};

通过AH数据包触发越界读写,漏洞触发链:

代码语言:txt
复制
xfrm4_ah_rcv -> xfrm4_rcv -> xfrm4_rcv_spi-> 
xfrm_input:
	->xfrm_state_lookup //找到对应的xfrm_state结构
    ->if (x->repl->check(x, skb, seq))  //x->repl 在xfrm_init_replay被赋值,此处调用xfrm_replay_check_esn
        ->if (replay_esn->bmp[nr] & (1U << bitnr)) // 检查AH中的seq是否被处理过
    ->if (async && x->repl->recheck(x, skb, seq))//调用xfrm_replay_recheck_esn
	->x->repl->advance(x, seq); //调用xfrm_replay_advance_esn

xfrm_replay_check_esn 中只是对bmp中的值进行判断,没有读写操作,而xfrm_replay_advance_esn函数中有三处对bmp进行写操作。

代码语言:txt
复制
static void xfrm_replay_advance_esn(struct xfrm_state *x, __be32 net_seq)
{
	unsigned int bitnr, nr, i;
	int wrap;
	u32 diff, pos, seq, seq_hi;
	struct xfrm_replay_state_esn *replay_esn = x->replay_esn;

	if (!replay_esn->replay_window)
		return;

	seq = ntohl(net_seq);
	pos = (replay_esn->seq - 1) % replay_esn->replay_window;
	seq_hi = xfrm_replay_seqhi(x, net_seq);
	wrap = seq_hi - replay_esn->seq_hi;

	if ((!wrap && seq > replay_esn->seq) || wrap > 0) {
		if (likely(!wrap))
			diff = seq - replay_esn->seq;
		else
			diff = ~replay_esn->seq + seq + 1;

		if (diff < replay_esn->replay_window) {
			for (i = 1; i < diff; i++) {
				bitnr = (pos + i) % replay_esn->replay_window;
				nr = bitnr >> 5;
				bitnr = bitnr & 0x1F;
[1]				replay_esn->bmp[nr] &=  ~(1U << bitnr);//某一个bit执行置零操作
			}
		} else {
			nr = (replay_esn->replay_window - 1) >> 5;
			for (i = 0; i <= nr; i++)
[2]				replay_esn->bmp[i] = 0; // 对从bmp[0]到bmp[(replay_esn->replay_window - 1) >> 5]块内存均置零
		}

		bitnr = (pos + diff) % replay_esn->replay_window;
		replay_esn->seq = seq;

		if (unlikely(wrap > 0))
			replay_esn->seq_hi++;
	} else {
		diff = replay_esn->seq - seq;

		if (pos >= diff)
			bitnr = (pos - diff) % replay_esn->replay_window;
		else
			bitnr = replay_esn->replay_window - (diff - pos);
	}

	nr = bitnr >> 5;
	bitnr = bitnr & 0x1F;
[3]	replay_esn->bmp[nr] |= (1U << bitnr); //对某一个bit写1

	if (xfrm_aevent_is_on(xs_net(x)))
		x->repl->notify(x, XFRM_REPLAY_UPDATE);
}

综上所述,通过用户态空间发送一个AH数据包可以造成越界读写,可以每次写1bit大小的数据,同时也可以将(replay_windows -1)>>5比特大小的内存块清空。

漏洞利用

将cred结构堆喷到xfrm_replay_state_esn结构体后,通过越界置零操作,将cred的uid和gid写0,进行提权。

cred结构体的申请是通过prepare_creds中的new = kmem_cache_alloc(cred_jar, GFP_KERNEL);得到的。

cred结构体由kmalloc-192分配,所以要控制bmp_len大小,保证申请的xfrm_replay_state_esn结构大小是由kmalloc-192分配,并需要满足128 < bmp_len * 4 +0x18 < 192。根据slub分配机制,这样申请完xfrm_replay_state_esn,fork出进程的cred结构有很大概率会分配在相邻位置,就可以对cred结构进行越界写0。

replay_esn->replay_window决定要置零的内存大小。

xfrm_alloc_replay_state_esn函数中会申请两个紧邻的xfrm_replay_state_esn结构体分别存放replay_esn和preplay_esn,所以利用时的内存布局如下:

代码语言:txt
复制
内存低地址 | xfrm_replay_state_esn 结构0 | xfrm_replay_state_esn 结构1 | cred 结构 | 高地址

那么cred结构的相对于esn->bmp偏移为0xc0-0x18+0xc0,对应32位数据的数组索引,cred->usage的nr值为 (0xc0-0x18+0xc0)/4。

在xfrm_user_rcv_msg函数中,需要CAP_NET_ADMIN权限进行漏洞利用:

代码语言:txt
复制
/* All operations require privileges, even GET */
	if (!netlink_net_capable(skb, CAP_NET_ADMIN))
		return -EPERM;

也可以通过利用下面方法运行exp,该方法应该是偷懒了,需要sudo权限。非特权用户可以创建用户命名空间、网络命名空间。在命名空间内部,我们就可以有相应的capability来触发漏洞。

代码语言:txt
复制
sudo setcap cap_net_raw,cap_net_admin=eip ./exp

如果不能使用,需要安装sudo apt-get install libcap2-bin

漏洞利用伪代码:

代码语言:txt
复制
/* 用于向内核发送信息, 然后生成/更新xfrm_state结构体的套接字 */
xfrm_state_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_XFRM); // init_xfrm_socket
/* 用于接收IPPROTO_AH信息, 触发xfrm_input的接收套接字 */
recvfd = socket(AF_INET, SOCK_RAW, IPPROTO_AH); //init_recvfd
/* 用于发送自定义数据包的发送套接字 */
sendfd = socket(AF_INET, SOCK_RAW, IPPROTO_AH);// init_sendfd
// fork_spary_n
alloc_xfrm_state(...); // xfrm_add_sa
update_esn(...);   //xfrm_new_sa
trigger_oob(...); 

补丁分析

1.png
1.png

补丁commit: 677e806da4d916052585301785d847c3b3e6186a

上述补丁是在更新xfrm_state结构时,在xfrm_replay_verify_len中添加对用户更新的up->replay_window字段进行检查,必须小于bmp数组的大小,防止越界。

2.png
2.png

补丁commit: f843ee6dd019bcece3e74e76ad9df0155655d0df

该补丁添加了前xfrm_state结构中bmp_len和用户更新的bmp_len要相等的验证,一般来说,xfrm_replay_state_esn_len(replay_esn) 和ulen相等说明整个结构体大小相等,扣掉固定字段大小,bmp数组大小是相等的,这里应该是防止未知的越界访问手段。

参考链接

https://github.com/snorez/blog/blob/master/cve-2017-7184%20(%E9%95%BF%E4%BA%AD%E5%9C%A8Pwn2Own%E4%B8%8A%E7%94%A8%E4%BA%8E%E6%8F%90%E6%9D%83Ubuntu%E7%9A%84%E6%BC%8F%E6%B4%9E)%20%E7%9A%84%E5%88%86%E6%9E%90%E5%88%A9%E7%94%A8.md

https://bbs.pediy.com/thread-250749.htm

https://xz.aliyun.com/t/4133

poc地址:https://github.com/snorez/exploits/blob/master/cve-2017-7184/exp.c

Netlink 内核实现分析(一):创建 :https://blog.csdn.net/luckyapple1028/article/details/50839395

Netlink+内核实现分析(二):通信:https://blog.csdn.net/sinat_32343037/article/details/78487445

补丁链接:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=677e806da4d916052585301785d847c3b3e6186a

commit: 677e806da4d916052585301785d847c3b3e6186a

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f843ee6dd019bcece3e74e76ad9df0155655d0df

commit: f843ee6dd019bcece3e74e76ad9df0155655d0df

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 漏洞相关的重要数据结构:
    • 漏洞分析
    • 漏洞利用
    • 补丁分析
    • 参考链接
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档