Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
TEST-CVE-2023-32233 — CVE-2023-32233 | Kitploit
Tools/GitHubGitHub/rogeliopumajulca/test-cve-2023-32233
SpeicherforensikSchwachstellenanalyseExploitationLernen & BildungBinary-Exploitation
GitHubrogeliopumajulca/test-cve-2023-32233

TEST-CVE-2023-32233

CVE-2023-32233

Repository anzeigen
vor 2 JahrenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

Use-After-Free in Netfilter nf_tables bei der Verarbeitung von Batch-Anfragen

Details zur Sicherheitslücke

Der betroffene Code stammt vom offiziellen Linux-Kernel von https://kernel.org/ und ist Teil der Netfilter nf_tables-Komponente (net/netfilter/nf_tables_api.c).

Netfilter nf_tables erlaubt es, seine Konfiguration als atomare Operation zu aktualisieren. Bei Verwendung dieser Funktion senden die Benutzermodus-Clients Batch-Anfragen, die eine Liste von Basisoperationen enthalten. Netfilter nf_tables verarbeitet dann alle Operationen innerhalb des Batches als einzelne Transaktion. Bei der Verarbeitung des Batches prüft Netfilter nf_tables die Konfigurationszustandsänderungen, um sicherzustellen, dass jede aufeinanderfolgende Basisoperation gültig ist, und dies berücksichtigt auch die Zustandsänderungen aller vorherigen Operationen innerhalb des Batches. Allerdings ist die derzeit implementierte Prüfung unzureichend.

In unserem spezifischen Szenario starten wir mit einer Netfilter nf_tables-Konfiguration, die eine nft_rule mit lookup-Ausdruck auf einem anonymen nft_set hat, und wobei das anonyme nft_set einige Elemente enthält. Als nächstes senden wir eine Batch-Anfrage, die die folgenden zwei Basisoperationen enthält:

  1. NFT_MSG_DELRULE-Operation zum Löschen der nft_rule.
    Beachten Sie, dass dies auch implizit den lookup-Ausdruck und das anonyme nft_set löscht.
  2. NFT_MSG_DELSETELEM-Operation zum Löschen eines der Elemente des gelöschten anonymen nft_set.

Die aktuelle Version von Netfilter nf_tables akzeptiert die obige Batch-Anfrage. Sie ruft dann nf_tables_commit_release() auf, das freigegebene Ressourcen an nf_tables_destroy_list anhängt. Die nf_tables_destroy_list wird dann von nf_tables_trans_destroy_work() verarbeitet, das zunächst Ressourcen freigibt, die mit der NFT_MSG_DELRULE-Operation zusammenhängen, indem es aufruft:

root@kitploit:~
nft_commit_release()
    nf_tables_rule_destroy()
        nf_tables_expr_destroy()
            expr->ops->destroy() zeigt auf nft_lookup_destroy()
                nf_tables_destroy_set()
                    nft_set_destroy()
                        kvfree() gibt den von `nft_set` verwendeten Speicher frei

bevor die NFT_MSG_DELSETELEM-Operation verarbeitet wird, bei der eine Referenz auf das freigegebene nft_set über nft_trans_elem_set() während der folgenden Aufrufe zugegriffen wird:

root@kitploit:~
nft_commit_release()
    nf_tables_set_elem_destroy()
        nft_set_elem_ext()

Innerhalb von nft_set_elem_ext() oben wird auf die Speicherstelle des freigegebenen nft_set zugegriffen, um die Position von nft_set_ext zu bestimmen:

root@kitploit:~
static inline struct nft_set_ext *nft_set_elem_ext(const struct nft_set *set,
                                                   void *elem)
{
        return elem + set->ops->elemsize;
}

für die folgenden Operationen. Sobald also der Wert von set->ops->elemsize korrumpiert wird, könnte eine bestimmte unerwartete Speicherstelle als Liste von zu zerstörenden nft_expr interpretiert werden:

root@kitploit:~
static void nf_tables_set_elem_destroy(const struct nft_ctx *ctx,
                                       const struct nft_set *set, void *elem)
{
        struct nft_set_ext *ext = nft_set_elem_ext(set, elem);

        if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPRESSIONS))
                nft_set_elem_expr_destroy(ctx, nft_set_ext_expr(ext));

Ausbeutungstechniken

Die Ausnutzung der obigen Schwachstelle erfordert, ein Rennen mit nf_tables_trans_destroy_work() zu gewinnen, das im Hintergrund-Worker-Thread des Linux-Kernels ausgeführt wird. Dies scheint die praktische Ausnutzung selbst dann zu erschweren, wenn wir bestehende Schutzmaßnahmen berücksichtigen, wie die Härtung des Kernel-Slab-Allokators, Kernel Address Space Layout Randomization (KASLR) und insbesondere Control-Flow Integrity. Der beigefügte PoC beweist jedoch, dass es dennoch möglich ist, eine einigermaßen zuverlässige Ausnutzung in der Praxis zu erreichen.

Um die Schwachstelle auszunutzen, müssen wir den Inhalt des Speichers von nft_set ändern, nachdem es unter nf_tables_rule_destroy() freigegeben wurde, aber bevor es unter nf_tables_set_elem_destroy() verwendet wird. Beide nf_tables_rule_destroy() und nf_tables_set_elem_destroy() werden innerhalb eines einzelnen Aufrufs von nf_tables_trans_destroy_work() aufgerufen, das im Hintergrund-Worker-Thread des Linux-Kernels ausgeführt wird. Darüber hinaus ist der freigegebene Speicherblock normalerweise nur vom selben CPU-Kern aus zur Wiederverwendung verfügbar.

Wenn wir mit nf_tables_trans_destroy_work() um die Wette laufen, verbessern wir unsere Chancen, indem wir eine kontrollierte Verzögerung für den Hintergrund-Worker-Thread zwischen seinen Aufrufen nf_tables_rule_destroy() und nf_tables_set_elem_destroy() hinzufügen. Dazu fügen wir eine zusätzliche Operation ein, um ein weiteres nft_set mit einer großen Anzahl von Elementen zu zerstören. Zusätzlich halten wir alle anderen CPU-Kerne beschäftigt, sodass der Hintergrund-Worker-Thread wahrscheinlich auf einem bestimmten CPU-Kern geplant wird, sodass wir versuchen können, eine neue Struktur vom selben CPU-Kern zu allozieren, direkt nachdem er nft_set unter nf_tables_rule_destroy() freigegeben hat. Unser Ziel ist es, ein neues nft_set eines anderen Typs zu allozieren, um die Speicherstelle des unter nf_tables_rule_destroy() freigegebenen nft_set wiederzuverwenden.

Der neue nft_set-Typ wird so gewählt, dass er einen anderen Wert für set->ops->elemsize verwendet. Wenn der Hintergrund-Worker-Thread schließlich nf_tables_set_elem_destroy() aufruft, um die NFT_MSG_DELSETELEM-Operation zu verarbeiten, interpretiert er sein elem-Argument falsch, sodass das korrumpierte nft_set_ext *ext einige Bytes nach der korrekten Position liegt. Dies bedeutet, dass bestimmte benutzergesteuerte Datenfelder des ursprünglichen nft_set_ext nun als Header interpretiert werden, was zu einer Typverwirrung führt.

Eine Möglichkeit, diese Typverwirrung auszunutzen, besteht darin, die korrumpierten nft_set_ext-Header mit Offsetwerten so zu präparieren, dass nf_tables_set_elem_destroy() den Inhalt beliebiger benachbarter Speicherblöcke als die Liste der zu zerstörenden nft_expr interpretiert, und zwar über die folgenden Aufrufe:

root@kitploit:~
nft_set_elem_expr_destroy()
    __nft_set_elem_expr_destroy()
        nf_tables_expr_destroy()
            expr->ops->destroy()

An diesem Punkt der Ausnutzung haben wir noch keine Details zum Kernel-Speicherlayout. Daher ist es nicht möglich, absolute Zeigeradressen zu präparieren. Bei der Präparierung der korrumpierten nft_set_ext-Header können wir jedoch immer noch außerhalb des gültigen Bereichs liegende Offsets verwenden, sodass expr->ops->destroy() auf bestimmten gültigen nft_expr in den benachbarten Speicherblöcken aufgerufen wird.

Dazu spammen wir nft_log-Ausdrücke mit kontrolliertem NFTA_LOG_PREFIX. Dieses nft_log->prefix wird dann durch nft_log_destroy() freigegeben, sobald expr->ops->destroy() aufgerufen wird:

root@kitploit:~
static void nft_log_destroy(const struct nft_ctx *ctx,
                            const struct nft_expr *expr)
{
        struct nft_log *priv = nft_expr_priv(expr);
        struct nf_loginfo *li = &priv->loginfo;

        if (priv->prefix != nft_log_null_prefix)
                kfree(priv->prefix);

Beachten Sie, dass wir auf diesen Speicher über die andere Referenz des gespammten nft_log-Ausdrucks immer noch zugreifen und ihn sogar erneut freigeben können.

Darüber hinaus können wir auch die Größe von nft_log->prefix steuern, sodass er aus einem der Slabs kmalloc-{8, ..., 192} alloziert werden kann. Schließlich wird der referenzierte Speicher vom Kernel als Zeichenkette interpretiert, sodass wir uns keine Gedanken über Korruption machen müssen, wenn wir verschiedene Objekte darüber legen. Dies ist im Wesentlichen "Game Over".

Ein Nachteil ist, dass NULL-Zeichen nft_log->prefix terminieren, sodass wir beim Auslesen von Speicherinhalten nicht über NULL-Bytes hinauslesen können. Dies wird im nächsten Schritt adressiert, bei dem wir nft_object->udata allozieren, um den nft_log->prefix-Speicherblock wiederzuverwenden und den nft_log-Ausdruck zu zerstören. Dies gibt den nft_object->udata-Speicher frei, aber jetzt können wir den hängenden Zeiger nft_object->udata immer noch verwenden, um Speicherinhalte ohne Einschränkungen bezüglich NULL-Bytes auszulesen.

Auf der Suche nach geeigneten Strukturen für die folgenden Schritte haben wir uns für nft_expr entschieden, das aus nft_dynset_new() alloziert wird. Diese leben in denselben Slabs wie nft_log->prefix und nft_object->udata. Außerdem haben wir eine angemessene Kontrolle über die Allokationsgröße, sodass wir später bei Bedarf problemlos zwischen Slabs unterschiedlicher Größe wechseln könnten.

Um diese Strukturen zu verwenden, erstellen wir einen Paketfilter mit nft_dynset-Ausdruck. Und wenn wir Pakete über die Loopback-Schnittstelle senden, ruft der nft_dynset-Ausdruck nft_dynset_new() auf, um neue Elemente für das zugehörige nft_set zu erstellen. Die erstellten Elemente sind zustandsbehaftete Ausdrücke der folgenden Typen:

  • nft_counter, um die Adresse von nf_tables.ko im Kernel-Speicher zu erhalten.
    Die Struktur enthält einen Zeiger auf nft_counter_ops im Kernel-Modul nf_tables.ko. Wir leaken diesen Zeiger, indem wir nft_object->udata lesen.

  • nft_quota für beliebiges Speicherlesen und -schreiben.
    Wir können nft_object->udata wiederholt freigeben und neu allozieren, um den nft_quota->consumed-Zeiger zu modifizieren. Als nächstes führen wir eine NFT_MSG_GETSETELEM-Operation durch, die nft_quota_do_dump() aufruft, um den Inhalt des referenzierten Speichers zu lesen und das Ergebnis als NFTA_QUOTA_CONSUMED-Attribut im Ergebnis zurückzugeben. Für das Schreiben senden wir einfach Pakete über die Loopback-Schnittstelle, wobei nft_quota_do_eval() aufruft:

    root@kitploit:~
      static inline bool nft_overquota(struct nft_quota *priv,
                                       const struct sk_buff *skb)
      {
              return atomic64_add_return(skb->len, priv->consumed) >=
    

Wir verwenden das obige beliebige Speicherlesen, um die Basisadresse des Kernel-Kerns zu erhalten. Und dann fahren wir fort, die Teilzeichenkette "sbin" des Pfadnamens "/sbin/modprobe" zu modifizieren, sodass sie durch "/tmp" ersetzt wird. Der resultierende Pfadname "//tmp/modprobe" wird dann vom Kernel verwendet, um einen Prozess mit Root-Rechten zu starten, wobei wir den Dateiinhalt kontrollieren.

Beachten Sie, dass wir keine bewussten Anstrengungen unternommen haben, um die Control-Flow Integrity zu umgehen. Allerdings haben wir für jeden der Ausbeutungsschritte bewusst die flexibelsten und robustesten Primitive gewählt. Es stellte sich heraus, dass unsere Auswahl irgendwie alle Primitive vermied, die möglicherweise durch Control-Flow Integrity blockiert werden könnten. Wir sind nun gespannt, durch Tests zu bestätigen, ob der resultierende Exploit wirklich gegen Systeme mit Control-Flow-Integrity-Schutzmaßnahmen funktioniert.

Tool herunterladen

um nft_quota->consumed zu modifizieren.