这个漏洞比较强,官方说明漏洞只会导致拒绝服务攻击,但实际上利用得当可以实现提权。影响范围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编辑
,原因: