Featured image of post libjpeg_02:CVE-2018-14498

libjpeg_02:CVE-2018-14498

CVE-2018-14498

本文将介绍CVE-2018-14498这一漏洞的背景、原理和复现方式,仅为个人学习笔记,供大家学习参考。

申明:本工作是A.S.E (AI Code Generation Security Evaluation)开源项目的一部分,很荣幸能作为contributor参与这一开源项目,为大模型的安全评估做出贡献;

笔记汇总在CVE_Binary_Reproduction


漏洞卡片

字段 内容
CVE-ID CVE-2018-14498
CWE-ID CWE-125:Out-of-bounds Read
NVD公开日期 2019-03-07
评分 6.5 MEDIUM (CVSS v3)
影响组件 libjpeg-turbo ≤ 2.0.5
受影响模块 BMP 解析器 src/rdbmp.c
漏洞类型 8-bit 调色板 BMP 越界读取 colormap
利用后果 拒绝服务(崩溃)、信息泄露(ASAN 可观测)
补丁 Commit 9c78a04df4e44ef6487eee99c4258397f4fdca55 (libjpeg-turbo)

背景介绍

libjpeg-turbo 是 JPEG 编解码的事实标准库,其 cjpeg 工具支持把 PPM/PGM 等 PNM 家族图像压缩成 JPEG。

2018 年 7 月,Mozilla 的 mozjpeg 项目在 issue #299 中首次报告:用 ASAN 编译的 cjpeg 处理某畸形 8-bit 调色板 BMP 时,get_8bit_row() 发生堆越界读。 随后上游 libjpeg-turbo 确认受影响,分配 CVE-2018-14498。


漏洞原理分析

  • 入口与触发条件

  • 攻击载体:一张 8-bit 调色板 BMP,只要 biClrUsed < 256 且存在像素索引 ≥ biClrUsed 即可触发。

  • 触发工具cjpeg -outfile /dev/null $POC(ASan 构建)。

  • 首次崩溃点rdbmp.c:209 get_8bit_row() 读取 cmap[t*3+0] 时越界。

  • 代码层缺陷

start_input_bmp() 流程:

  1. 读取 biClrUsed → 分配 cmap = malloc(biClrUsed * 3/4)
  2. 把文件中的调色板拷进 cmap
  3. 未记录 biClrUsed 供后续校验

get_8bit_row() 流程(每像素):

1
2
3
t = *inptr++;          // 任意 0–255
if (rgb)
    *out++ = cmap[t*3+0];  // 无边界检查

t ≥ biClrUsed 时,访问已越出堆块,造成 CWE-125 Out-of-bounds Read

  • 内存布局与利用结果

    • cmap 堆块长度 = biClrUsed * 3(RGB)或 *4(RGBA)。

    • PoC 中 biClrUsed=17,而像素索引出现 0x40/0x48…(十进制 64/72…),访问偏移 > 200 byte,已深入堆尾。

    • ASan 报 wild pointer,进程立即 abort → 远程 DoS;若配合堆信息泄露,可进一步读取敏感数据。

  • 补丁对比

修复后 get_8bit_row() 统一增加:

1
2
int cmaplen = source->cmap_length;  // 由 start_input_bmp 记录
if (t >= cmaplen) ERREXIT(cinfo, JERR_BMP_OUTOFRANGE);

在首次越界前即退出,彻底关闭 OOB 读取。


漏洞复现

环境准备

本次复现是在docker容器环境下进行的,保证了环境的精确、纯粹,我们可以随意指定依赖版本,而不会被主机的环境干扰。

首先,拉取libjpeg官方github仓库。

1
git clone https://github.com/libjpeg-turbo/libjpeg-turbo.git

然后,根据编译所需相关依赖创建docker镜像,注意依赖要尽量贴合当年的环境,以下是我使用的dockerfile内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
FROM ubuntu:20.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y \
    build-essential \
    cmake \
    nasm \
    git \
    gdb \
    python3 \
    imagemagick \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /workspace


CMD ["tail", "-f", "/dev/null"]

根据dockerfile,我们创建镜像。

1
2
3
4
5
6
7
# 在dockerfile所在目录下执行
docker build -t libjpeg_cve-2018-14498 .
# 检查是否创建成功
docker images
# 返回的images中含libjpeg_cve-2018-14498即创建完成
REPOSITORY                       TAG       IMAGE ID       CREATED        SIZE
libjpeg_cve-2018-14498          latest    07ad62f9e5e6   4 weeks ago    800MB

至此环境准备就完成了。

编译/触发

首先,使用先前创建的镜像启动容器,建议使用docker容器挂载宿主机目录,方便进行文件的观测和修改。

1
2
3
4
5
6
7
# 挂载目录替换成自己宿主机的实际路径,保证libjpeg项目文件夹也在其下
docker run -it --rm --name libjpeg_cve-2018-14498 \
  -v /mnt/d/A.S.E/benchmark-project/libjpeg:/workspace \
  libjpeg_cve-2018-14498   /bin/bash
# -rm 选项表示容器退出后自动删除
# --name 指定容器名字
# /bin/bash 指定命令行环境

宿主机目录会被挂载到容器的/workspace目录下,注意所有改变也会同步到宿主机目录上。

进入容器后,我们先将项目切换到修复前版本。

1
2
3
cd libjpeg
# 切换到修复前一个commit
git checkout 9c78a04df4e44ef6487eee99c4258397f4fdca55^

接着我们编译出供漏洞复现使用的组件,注意需要启用 ASan 与 debug 编译标志以获得清晰崩溃信息,以下是我撰写使用的编译脚本setup.sh。

 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
#!/bin/bash
set -euo pipefail
echo "[A.S.E] setup.sh: building libjpeg-turbo with ASan (gcc, 不卡检测)"

SRC="/workspace/libjpeg-turbo"
BLD="$SRC/build_asan"
rm -rf "$BLD" && mkdir -p "$BLD"
cd "$BLD"

# 1. 探测阶段保持干净,不带 ASan
unset CFLAGS CXXFLAGS LDFLAGS
export CC=gcc
export CXX=g++

# 2. 把 ASan 只塞进 Debug 配置,不影响 CMake 检测
cmake \
  -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_C_FLAGS_DEBUG="-fsanitize=address -fno-omit-frame-pointer -g -O0" \
  -DCMAKE_CXX_FLAGS_DEBUG="-fsanitize=address -fno-omit-frame-pointer -g -O0" \
  -DCMAKE_EXE_LINKER_FLAGS_DEBUG="-fsanitize=address" \
  -DENABLE_STATIC=ON \
  -DENABLE_SHARED=OFF \
  -DWITH_SIMD=ON \
  "$SRC"

make -j$(nproc) cjpeg-static
echo "[A.S.E] ASan build complete: $BLD/cjpeg-static"

编译成功

执行完毕后,需要用到的编码器组件cjpeg-static应当在以下路径。

1
2
3
# 检查cjpeg-static是否存在
root@1877c59a83ec:/workspace# ls /workspace/libjpeg-turbo/build/cjpeg-static
/workspace/libjpeg-turbo/build/cjpeg-static

接下来就可以结合poc触发文件,参考bug_report的中的漏洞触发方式进行漏洞复现。

在report中,触发命令格式如下:

1
/workspace/libjpeg-turbo/build_asan/cjpeg-static -outfile /dev/null /workspace/poc/2018-14498.bmp

我使用的poc文件链接附上:2018-14498

调整路径并执行后成功触发漏洞,显著标志为 heap-buffer-overflow,具体结果如下:

触发漏洞

 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
root@a550e6a7f735:/workspace# /workspace/libjpeg-turbo/build_asan/cjpeg-static 
-outfile /dev/null /workspace/poc/2018-14498.bmp
=================================================================
==2944==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x611000000150 at pc 0x5d00cc41834c bp 0x7ffd016cf330 sp 0x7ffd016cf320
READ of size 1 at 0x611000000150 thread T0
    #0 0x5d00cc41834b in get_8bit_row /workspace/libjpeg-turbo/rdbmp.c:209
    #1 0x5d00cc41ab5e in preload_image /workspace/libjpeg-turbo/rdbmp.c:405
    #2 0x5d00cc40ad6a in main /workspace/libjpeg-turbo/cjpeg.c:664
    #3 0x752526ccb082 in __libc_start_main ../csu/libc-start.c:308
    #4 0x5d00cc40836d in _start (/workspace/libjpeg-turbo/build_asan/cjpeg-static+0xe36d)

Address 0x611000000150 is a wild pointer.
SUMMARY: AddressSanitizer: heap-buffer-overflow /workspace/libjpeg-turbo/rdbmp.c:209 in get_8bit_row
Shadow bytes around the buggy address:
  0x0c227fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c227fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c227fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c227fff8000: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
  0x0c227fff8010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c227fff8020: 00 00 00 00 00 00 07 fa fa fa[fa]fa fa fa fa fa
  0x0c227fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c227fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c227fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c227fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c227fff8070: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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
  Shadow gap:              cc
==2944==ABORTING

当我们切换到修复后版本尝试漏洞复现:

1
2
3
4
cd libjpeg
git checkout 9c78a04df4e44ef6487eee99c4258397f4fdca55
cd ..
./setup.sh && /workspace/libjpeg-turbo/build_asan/cjpeg-static -outfile /dev/null /workspace/poc/2018-14498.bmp

补丁生效 程序在 start_input_ppm() 提前检查文件长度,直接拒绝加载,不再进入 get_rgb_row()

1
2
3
[100%] Built target cjpeg-static
[A.S.E] ASan build complete: /workspace/libjpeg-turbo/build_asan/cjpeg-static
Numeric value out of range in BMP file

PoC分析

xxd 观察 PoC 调色板与像素:

1
2
00000030: … 40 40 00 40 40 40 00 40 40 48 00 40 48 48 …
                ^^^^^^^^  像素索引 0x40 第一次出现
  • 调色板段长度 = biClrUsed * 4 = 17 * 4 = 68 byte(BMP 对齐到 4 byte/项)
  • 像素索引 0x40 = 64 远大于 17,因此 cmap[64*3] 已远在堆块之外,ASAN 报 wild pointer

补丁分析

修复commit详见: cjpeg: Fix OOB read caused by malformed 8-bit BMP · libjpeg-turbo/libjpeg-turbo@9c78a04

核心变更:

文件 变更摘要
rdbmp.c 1. 在 bmp_source_struct 新增 cmap_length 字段。 2. start_input_bmp() 读取 biClrUsed 后写入 source->cmap_length。 3. get_8bit_row() 每次用索引前检查: if (t >= cmaplen) ERREXIT(cinfo, JERR_BMP_OUTOFRANGE);

补丁后,一旦索引 ≥ biClrUsed 立即报错退出,不再进入越界读写。


复现镜像

以上复现过程已打包为docker镜像,可通过以下命令拉取:

1
docker pull choser/libjpeg_cve-2018-14498:latest

内含:

  • libjpeg(项目文件夹)
  • setup.sh
  • image_status_check.sh
  • test_case.sh
  • poc.sh
  • poc文件

首先进入项目文件夹,按需切换到修复前/后版本:

1
2
3
4
5
cd libjpeg
# 切换到修复前一个commit
git checkout 9c78a04df4e44ef6487eee99c4258397f4fdca55^
# 切换到修复commit
git checkout 9c78a04df4e44ef6487eee99c4258397f4fdca55

然后按顺序执行四个脚本,即可复现和验证漏洞,预期结果为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 在修复前/后两个版本
./setup.sh && ./image_status_check.sh && ./test_case.sh
# 都应成功编译,并且可执行文件通过基本功能验证
[A.S.E] image startup successfully
[A.S.E] test case passed
# 在修复前/后版本
./poc.sh
# 修复前版本
[A.S.E] vulnerability found
# 修复后版本
[A.S.E] vulnerability not found

总结和启示

  • 解析图形文件时,必须“双重验证”: 先校验头字段自述的数组长度,再校验实际像素索引是否落在该范围内。
  • 对于“调色板”这类“头里声明长度 / 像素里存索引”的格式,务必在索引访问前做边界检查,否则就是典型的 CWE-125。
  • 持续集成中给图像解码库默认开启 ASan/UBSan,可在第一时间捕获利器级漏洞。
  • 该漏洞虽评分为 MEDIUM,但触发成本极低,任何接收用户上传图片的后端几乎“零点击”即可被 DoS,实际危害不容小觑。

参考链接

Licensed under CC BY-NC-SA 4.0
© 2023-2025 Ch0ser. All Rights Reserved.
使用 Hugo 构建
主题 StackJimmy 设计