CVE-2020-27818¶
Abstract
- CVE ID: CVE-2020-27818
- CWE ID: CWE-125 (Out-of-bounds Read)
- Description: A flaw was found in the check_chunk_name() function of pngcheck. This flaw allows an attacker who can pass a malicious file to be processed by pngcheck to cause a temporary denial of service.
- CVSS: 3.3
- Published: 2020-12-08
- Affected: pngcheck 2.4.0
漏洞概要 ¶
pngcheck 是一个用于验证 PNG / JNG / MNG 文件格式的命令行工具。该工具的 check_chunk_name()
函数在检查 PNG chunk 名称时,将字符强制转换成 int
类型作为 ascii_alpha_table
数组的下标,导致带符号扩展问题。当 chunk 名包含负值字符 (>0x7F) 时,会导致访问越界,造成全局缓冲区的越界读取。
漏洞原理 ¶
源码地址:http://www.libpng.org/pub/png/src/pngcheck-2.4.0.zip
程序在处理 PNG 文件时会校验 chunk 名的合法性。check_chunk_name
函数(位于 pngcheck.c:4927)用于检查 chunk 名是否仅由 ASCII 字母组成:
pngcheck.c | |
---|---|
4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 |
|
函数通过 isASCIIalpha
宏对 chunk 名的每个字符进行检查。该宏使用字符值作为下标查询预定义的 ascii_alpha_table
数组:
pngcheck.c | |
---|---|
233 |
|
pngcheck.c | |
---|---|
282 283 284 285 286 287 288 289 290 291 |
|
当 chunk_name
中字符的高位为 1
时(值大于 0x7Fint
会成为负值,将其作为数组下标访问 ascii_alpha_table
就会导致缓冲区越界读取。
例如,如果 chunk 名中包含字符 0x80,转换为 int
后会变成 -128
,超出了 ascii_alpha_table
的范围。
漏洞复现 ¶
触发条件 ¶
漏洞触发需要满足以下条件:
- PNG 文件中的 chunk 名称字符包含高位字节(值大于 0x7F)
- 在有符号
char
的平台上运行程序
漏洞利用 ¶
以下 PoC 使用 construct 库构造一个包含恶意 chunk 的最小合法 PNG 文件。虽然仅需 PNG 签名和恶意 chunk 就能触发漏洞,但这里构造了一个完整的 PNG 文件(包含 IHDR、IDAT 等必需 chunks
poc.py | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
|
为了检测内存访问错误,在 Makefile 中添加编译选项 -fsanitize=address
来开启 ASAN。
--- Makefile.unx.orig 2025-01-10 02:44:56.825087965 +0800
+++ Makefile.unx 2025-01-10 02:45:01.105088592 +0800
@@ -29,7 +29,7 @@
CC = gcc
LD = gcc
RM = rm
-CFLAGS = -O -Wall $(INCS) -DUSE_ZLIB
+CFLAGS = -O -Wall $(INCS) -DUSE_ZLIB -fsanitize=address
# [note that -Wall is a gcc-specific compilation flag ("all warnings on")]
O = .o
E =
当程序尝试解析 poc.png
时,会得到:
./pngcheck ../../CVE-2020-27818/poc.png
=================================================================
==529059==ERROR: AddressSanitizer: global-buffer-overflow on address 0x64252146789f at pc 0x642521443431 bp 0x7fff8e5bb540 sp 0x7fff8e5bb530
READ of size 1 at 0x64252146789f thread T0
#0 0x642521443430 in check_chunk_name (pngcheck-vulns/src/pngcheck-2.4.0/pngcheck+0x15430) (BuildId: 4e62327e74df0aef03619703308abb9e340be5c6)
#1 0x642521454798 in pngcheck (pngcheck-vulns/src/pngcheck-2.4.0/pngcheck+0x26798) (BuildId: 4e62327e74df0aef03619703308abb9e340be5c6)
#2 0x6425214576fd in main (pngcheck-vulns/src/pngcheck-2.4.0/pngcheck+0x296fd) (BuildId: 4e62327e74df0aef03619703308abb9e340be5c6)
#3 0x7f7e81434e07 (/usr/lib/libc.so.6+0x25e07) (BuildId: 98b3d8e0b8c534c769cb871c438b4f8f3a8e4bf3)
#4 0x7f7e81434ecb in __libc_start_main (/usr/lib/libc.so.6+0x25ecb) (BuildId: 98b3d8e0b8c534c769cb871c438b4f8f3a8e4bf3)
#5 0x642521442324 in _start (pngcheck-vulns/src/pngcheck-2.4.0/pngcheck+0x14324) (BuildId: 4e62327e74df0aef03619703308abb9e340be5c6)
0x64252146789f is located 1 bytes before global variable 'ascii_alpha_table' defined in 'pngcheck.c:282:18' (0x6425214678a0) of size 256
0x64252146789f is located 31 bytes after global variable 'latin1_keyword_forbidden' defined in 'pngcheck.c:294:18' (0x642521467780) of size 256
SUMMARY: AddressSanitizer: global-buffer-overflow (pngcheck-vulns/src/pngcheck-2.4.0/pngcheck+0x15430) (BuildId: 4e62327e74df0aef03619703308abb9e340be5c6) in check_chunk_name
Shadow bytes around the buggy address:
0x642521467600: f9 f9 f9 f9 00 02 f9 f9 f9 f9 f9 f9 00 00 00 00
0x642521467680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x642521467700: 00 00 00 00 00 00 00 00 00 00 00 00 f9 f9 f9 f9
0x642521467780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x642521467800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x642521467880: f9 f9 f9[f9]00 00 00 00 00 00 00 00 00 00 00 00
0x642521467900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x642521467980: 00 00 00 00 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9
0x642521467a00: 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9
0x642521467a80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x642521467b00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==529059==ABORTING
ASAN 的输出显示,程序在访问 ascii_alpha_table
的前一个字节时触发了全局缓冲区越界读,读取区域位于上一个全局变量 latin1_keyword_forbidden
的范围内。
由于 char
类型的带符号值范围是 [-128, 127]
,数组下溢的范围有限,影响相对较小。
漏洞修复 ¶
官方在 v3.0.0 版本中通过在类型转换时引入 unsigned char
来修复这个问题:
@@ -4926,8 +4986,10 @@
/* GRR 20061203: now EBCDIC-safe */
int check_chunk_name(char *chunk_name, char *fname)
{
- if (isASCIIalpha((int)chunk_name[0]) && isASCIIalpha((int)chunk_name[1]) &&
- isASCIIalpha((int)chunk_name[2]) && isASCIIalpha((int)chunk_name[3]))
+ if (isASCIIalpha((int)(uch)chunk_name[0]) &&
+ isASCIIalpha((int)(uch)chunk_name[1]) &&
+ isASCIIalpha((int)(uch)chunk_name[2]) &&
+ isASCIIalpha((int)(uch)chunk_name[3]))
return 0;
printf("%s%s invalid chunk name \"%.*s\" (%02x %02x %02x %02x)\n",