前言
这几天抽空研究学习了 CVE-2016-3714 这个漏洞 , 并分析了恶意代码的执行过程 . 该漏洞是一个非常经典的漏洞 , Google 上有很多师傅已经写过了源码级分析报告 , 但都没有明确说明恶意代码完整的执行流程 .
本文将从 ImageMagick 主函数( ConvertMain()
函数 ) 开始 , 逐步分析源码及恶意代码被拼接执行的具体原因 .
CVE-2016-3714
漏洞环境
根据 NVD( National Vulnerability Database , 国际漏洞数据库 ) 可以得知 , 当 ImageMagick 的版本符合以下条件时 , 该应用可能存在 ImageTragick 漏洞
-
Version < 6.9.3_10
-
7.0.0_0 < Version < 7.0.1_1
漏洞复现
在 Vulhub 上存在该漏洞的复现环境 , 其对应 ImageMagick 的版本为 6.9.2 , 符合对应版本 .
docker build && docker-compose up -d
即可成功搭建环境 .
任意代码执行
访问 http://127.0.0.1:80
, 即可看到当前目录下存在三个文件 .
进入 docker 环境的 bash , 查看 /tmp
目录下的文件
然后访问 demo.php
文件
再次查看 /tmp
目录下的文件 , 会发现生成了 success
文本文件 , 其中存放了 /etc/passwd
的内容
-
demo.php
文件内容调用了
shell_exec()
函数来执行系统命令 ,identify
命令主要用于获取一个或多个图像文件的格式和特性 , 我们来看 vul.jpg 的文件内容 -
vul.jpg
文件内容该文件明显不是一个
.jpg
格式文件 , 但能看到其中执行了系统命令 .
反弹 Shell
上文是通过访问 demo.php
来调用 vul.jpg
, 从而执行恶意代码 . 现在我们手工上传 vul.jpg
图片 , 同时修改上传数据包
访问 upload.php
, 并且打开 BurpSuite , 监听 80 端口 , 同时上传 vul.jpg
文件 , 查看上传数据包 .
在本地构造一个反弹 Shell 的链接 , 并且通过 Base64
编码
注意 , 构造链接时需要使用绝对路径 " /bin/bash "
在数据包中替换对应部分 , 如下所示
url() 中的路径可以任意指定 , 不过需要使用 https 协议
base64 -d 进行 base64 解码操作
同时 SSH 远程服务器 , 并且监听对应端口
然后发送修改后的请求 , 即可拿到目标主机的 Shell
能看到我们反弹了目标主机的 Shell , 并且能执行系统命令 !
你此时肯定有很多疑问 , 比如为什么恶意文件需要构造成上述格式 ? 为什么恶意文件中的代码可以被系统执行 ? 为什么一定要使用 HTTPS 协议 ? 等等 .
下面我将从源码入手 , 对上述这些问题一一作出解答 .
漏洞源码分析
一般来说 , 我们分析一个程序的执行流程 , 会先去看它的主函数( 即 " Main() " 函数 ) , 对于旧版本的 ImageMagick 而言 , 其主函数位于 utilities
目录的 convert.c
文件中 , 我们从这入手
在较新的 ImageMagick 版本中 , 已不再使用 Main() 函数 . 因此我这里选择使用 ImageMagick 6.9.0_0 这个版本的源代码来分析 .
-
utilities/convert.c : 90
这里定义了
main()
函数 , 该函数非常简单 , 仅调用了ConvertMain()
函数 , 并返回其返回值 . 我们追踪ConvertMain()
函数 , 看一看它的作用 . -
utilities/convert.c : 67
ConvertMain() 函数定义了 image_info 参数 , 该参数十分重要 , 贯穿整个解析过程 . MagickCoreGenesis() : 该函数用于初始化 ImageCore 环境 ( magick/magick.c : 1232 ) MagickCoreTerminus() : 该函数用于销毁 ImageCore 环境 ( magick/magick.c:1360 ) AcquireExceptionInfo() 函数用于程序异常处理 , 不去管它 . 而 AcquireImageInfo() 函数用于创建 Image_Info 结构 , 查看定义( magick/image.c : 329 )发现其调用了 GetImageInfo() 函数( magick/image.c : 1281 ) , 该函数用于初始化 Image_Info 结构 . 此时 , Image_Info 参数已经被初始化完毕 . 并被带入了 MagickCommandGenesis() 函数 中. 这个函数是重点 , 注意其第二个参数为 ConvertImageCommand , 后面将要用到
-
wand/mogrify.c : 112
MagickCommandGenesis() 函数会将图像处理选项应用于命令行中指定的图像 , 它会寻找并处理命令行中的特殊选项( 例如 -debug , -bench 等 ) , 然后将处理后的选项参数传递到真正处理请求的函数中 . 即 command 参数代表真正处理请求的函数 这里是一个函数动态调用的过程 , 程序会根据不同的 command 参数调用不同的函数 , 而command 参数来源于 MagickCommandGenesis() 函数的第二个参数 . 从上文了解到这里传入的值是 ConvertImageCommand , 因此现在会调用 ConvertImageCommand() 函数
-
wand/convert.c : 492
这里定义了
ConvertIMageCommand()
函数 , 该函数会读取一张或者多张图片 , 并且对不同的图片应用不同的操作过程 , 根据结果以不同的格式写出图像 .在读取图片的时候 , 会调用
ReadImages()
函数 , 我们追踪该函数 . -
magick/constitute.c : 845
这里定义了
ReadImages()
函数 . 该函数会读取一个或者多个图像 , 将他们放入一个图像列表并返回 . 在函数定义的最后 , 会调用ReadImage()
函数继续追踪
ReadImage()
函数 -
magick/constitute.c : 352
这里定义了
ReadImage()
函数 , 该函数将从文件或者文件句柄中读取图像或图像序列 . 根据函数注释 , 其过程分为三个步骤- 根据文件前缀或者后缀来确定图像类型
- 根据不同的图像类型调用不同的图像处理程序
- 在调用图像处理程序时 , 会释放并解码委托程序来处理图像 .
那什么是委托程序呢 ? 在继续往下看代码之前 , 你要先了解 ImageMagick 委托机制 , 现在由我来解释一下 .
ImageMagick Delegate( 委托 )机制
我在 Google 上没有找到关于 ImageMagick Delegate
机制的文档 . 因此我这里无法给出具体的定义 . 不过 , 该机制实现的功能是很容易理解的 .
-
ImageMagick
是一个功能强大的开源图形处理软件 , 可以用来读 , 写 , 处理超过 90 种的图片文件 . -
由于
ImageMagick
功能强大 , 性能较好 , 对很多编程语言都有拓展支持 , 所以在程序开发( PHP , Ruby , NodeJs , ... )中被广泛使用 , 而且许多图像处理插件都依赖于ImageMagick
库 . -
ImageMagick
之所以支持那么多的文件格式 , 是因为它内置了非常多的图像处理库 .ImageMagick
将这些图像处理库命名为 "Delegate
"( 委托 ) . 每个Delegate
对应一种格式的图片文件 . -
这些
Delegate
是不能单独处理图片文件的 , 不过它可以根据不同格式的图片文件 , 利用系统函数system()
来调用外部程序对图片文件进行处理 .
举个例子 , 在 www/source/delegates.xml
文档中定义了 ImageMagick Delegate
机制的默认配置 . 我们来看一下该文件的内容 .
-
文件格式转换
ImageMagick Delegate
程序在处理图片格式文件时 , 会根据其内容将其自动转换为ImageMagick
内置的某种图片格式 . -
占位符的定义
虽然
ImageMagick Delegate
程序会根据不同的图片格式调用不同的系统命令 , 但是这些命令大都需要一些共同的参数( 比如 文件名 , 文件大小 ... ) . 使用占位符 , 能很大程度上减小代码的复杂性 . -
委托程序的定义
ImageMagick Delegate
程序会根据转换后不同的文件格式 , 调用不同的系统命令 .
此时 , 如果您对安全问题比较敏感 , 就立刻能发现问题 : ImageMagick Delegate
程序调用了系统命令 , 还拼接了占位符字符串 . 那么如果攻击者可以控制对应占位符的值 , 那么就可能拿到命令执行漏洞 .
那么是否存在这种可能呢 ? ( 废话 , 不存在就不会有这篇文章了 )
前辈们注意到了 HTTPS
协议 , 来看下 ImageMagick Delegate
程序是如何解析该协议的 .
ImageMagick Delegate
程序会调用 Curl 这个系统命令来解析 HTTPS 内置格式 , 来分析一下这个指令 .
curl -s -k -o %o https:%M
-s : 安静模式 , curl 不会显示进程进度和错误信息
-k : 允许不安全的服务器建立 SSL 连接
-o : 指定输出文件
%o , %M : 两个占位符 , %o 已经被定义为 输出的图片文件 , 而我们现在还不确定 %M 代表什么 .
总的来说 , 若想利用该委托程序 , 被解析的文件就必须被解析为 HTTPS
这个内置格式 . 也就是说 , 图片内容至少要包含一个 HTTPS
链接 .
那么是否存在某种格式的图片内容可以包含 HTTPS
链接呢 ? 前辈们注意到了 MVG 图片格式 .
MVG 图片格式
MVG 图片格式是什么 ? 我只听过 SVG ( Scalable Vector Graphics
) .
事实上 , MVG ( Magick Vector Graphics
) 是 ImageMagick
默认支持的一种图片格式 , MVG 与 SVG 格式类似 , 可以通过文本形式写入矢量图的内容( 想要深入了解该格式可以访问 MVG文件摘要 )
因此 , 我们在分析 MVG 图片内容时可以参考 SVG 图片内容 . 为了便于研究学习 , 我们查看漏洞复现时用到的 vul.jpg
你可能会疑问 , 这是 JPG 格式图片 , 并不是 MVG 格式图片啊
上文我提到过 , ImageMagick Delegate 程序在进行文件格式转换时 , 是根据 文件内容 来判断其对应的内置格式 , 而不是根据文件扩展名
因此 , 你可以将文件名转换为任何你需要的格式 , 这也是该漏洞攻击面非常广的原因之一
那么 ImageMagick
是如何处理 MVG 格式文件呢 ? 我们来看代码 .
-
coders/mvg.c
该文件中定义了
ImageMagick
解析 MVG 格式文件的过程 -
magick/draw.c : 1694
这里定义了
DrawImage()
函数 , 其中定义了很多解析关键字和解析规则 .image over
是 SVG 格式文件的语法( 定义于coders/svg.c : 2299
) , 但也可以在 MVG 格式文件中使用 . 我们可以在解析规则中找到反弹Shell时使用的fill
关键字经过一系列操作 ,再次调用了
ReadImage()
函数 -
magick/constitute.c : 523
又回到了这里 , 现在将会调用
InvokeDelegate()
函数 , 并且在调用时将开头的https
删除了 . 我们追踪该函数 . -
magick/delegate.c : 1279
这里定义了
InvokeDelegate()
函数 , 该函数会用适当的 Image 属性来替换所有嵌入的格式设置字符( 占位字符 ) , 并执行拼接后的命令 , 来看一下具体步骤 . -
magick/property.c : 3261
先来看
InterpretImageProperties()
函数 , 该函数用来解析一些特殊字符 .值得一提的是 , 在 3532 行 , 该函数调用了
GetMagickProperty()
函数来解析占位字符 , 我们追踪该函数 .可以看到这里对 "
o
" 和 "M
" 两个占位符进行了解析 . 如果我们使用fill url()
把 图片URL 填充到当前元素内 , 那么这里 string 就是具体的 图片URL . -
magick/delegate.c : 345
再来看
ExternalDelegateCommand()
函数 .经过一系列操作后 , 该函数将会判断要执行的命令中是否包含 "
&;<>|
" 这些特殊字符 , 若有就带入system()
函数中执行 .
好了 , 整个流程走完了 , 纵观全局 , 程序并没有对输入数据进行有效的过滤( 其实有个白名单的 , 不过放行的字符太多了 , 等于没用 )
攻击者的恶意代码将通过上述流程被拼接到系统命令中 , 从而引发任意代码执行漏洞 .
总结
整个流程的分析过程还是比较困难的 , 我花了很长时间来整理思路 . 如果您发现哪里出现错误或者不准确的地方 , 非常感谢您的留言 .
在查阅相关文档时 , 我发现很多前辈都使用 GDB 工具来分析代码 , 看起来非常简便 . 我准备在下一周抽空学习如何使用 GDB 工具 .
Post Scripts :
第一周实习和师傅去了一个现场 , 学到了很多东西 , 实际生产环境和实验环境的差别还是非常非常大的 , 很多问题在实验环境中并不会被暴露出来 .
下周要写一份报告 , 还会涉及到一个攻防平台的搭建 , 感觉很有意思啊 ! 又能看到很多新东西了哈哈~