首页
社区
课程
招聘
[原创][原创]cve-2019-15666 xfrm_policy 提权漏洞
发表于: 2020-9-10 15:11 10760

[原创][原创]cve-2019-15666 xfrm_policy 提权漏洞

2020-9-10 15:11
10760

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

漏洞成因,数组越界。需要插入用户定义的 index timer set。
XFRM_MSG_NEWSA请求的路径添加policy。

添加policy需要通过verify_newpolicy_info的认证。但漏洞版本认证缺陷。
https://duasynt.com/blog/ubuntu-centos-redhat-privesc

通过两次add_policy, 释放后会导致其中一个bin 释放不完全。

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

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

触发越界位于 xfrm_policy_timer函数中。

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

步骤三 . XFRM_SPD_IPV4_HTHRESH executes the following function re-inserting existing policies in reverse order into the bydst
list:
大值意思是通过HTRESH刷新二次插入bydst。

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:

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利用uffd监控缺页异常,并通过用户态对缺页进行填充。

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

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

大致懂了,通过不断竞争,我们要实现的是在新建的进程的cred结构体中 任意ruid euid suid置零的操作,通过我们的置零uaf。但其实本身这并不是通常的uaf利用过程,而且能不能提权全靠运气,但是大部分情况还真能。不得不服。

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提权成功。至此全篇结束。

参考:
1.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.2
xp = 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 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.2
xp = 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;
}
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;
}
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;
}
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;
}
 
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);
}
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;
}
其中 利用 xfrm_policy_id2dir();计算了direction。但是
 
static inline int xfrm_policy_id2dir(u32 index)
{
return index & 7;
}
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;
}
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;
}
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);
    }
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);
    }
 
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;
}
int xfrm_policy_flush(struct net *net, u8 type, bool task_valid)
{
...
for (dir = 0; dir < XFRM_POLICY_MAX; dir++) {
    struct xfrm_policy *pol;

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2020-12-13 12:39 被inquisiter编辑 ,原因:
收藏
免费 4
支持
分享
最新回复 (4)
雪    币: 7673
活跃值: (4088)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
有在centos7.x上测试过吗,测试了几个3.10.x的内核,基本都提权失败
2020-9-17 15:34
0
雪    币: 1319
活跃值: (1335)
能力值: ( LV8,RANK:140 )
在线值:
发帖
回帖
粉丝
3
残废小菜比 有在centos7.x上测试过吗,测试了几个3.10.x的内核,基本都提权失败
我测了两个内核  4.10 4.19可以,主要是技术原理
2020-9-17 15:38
0
雪    币: 1319
活跃值: (1335)
能力值: ( LV8,RANK:140 )
在线值:
发帖
回帖
粉丝
4
inquisiter 我测了两个内核 4.10 4.19可以,主要是技术原理
你可以分析下为什么失败
2020-9-17 15:39
0
雪    币: 295
活跃值: (64)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
请问一下,xfrm_policy结构位于kmalloc-0x400, 而cred结构位于kmalloc-192,这个是如何修改suid的呢?
2021-9-13 12:14
0
游客
登录 | 注册 方可回帖
返回
//