CVE-2019-1306:Azure DevOps RCE漏洞分析

阅读量    42207 |   稿费 180

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

0x00 前言

2019年9月,微软公布了安全补丁,以修复Azure DevOps (ADO)Team Foundation Server (TFS)中的远程代码执行(RCE)漏洞。在这个严重级别的漏洞中,攻击者需要将一个精心构造的文件上传到存在漏洞的ADO或TFS服务器仓库,等待系统索引该文件。成功利用后,就可以在目标系统上实现代码执行效果。该漏洞由Mikhail Shcherbakov向ZDI提交,同时小伙伴也提供了CVE-2019-1306的细节分析。

 

0x01 漏洞分析

BinaryFormatter是.NET平台上常用的二进制序列化类,同时也是一个反序列化类,默认配置下并不安全。早在2012年,James Forshaw在Black Hat的一次演讲中已经提到过关于.NET序列化的第一个gadget。随后,.NET开发者开始呼吁“不要使用BinaryFormatter来反序列化不可信的数据”,或者至少不要使用默认配置的BinaryFormatter。然而在现代复杂的系统中,想要区分可信及不可信的数据是非常有挑战的一个任务。典型的例子就是最近被修复的CVE-2019-1306,该漏洞可以影响Microsoft Azure DevOps Server。

Microsoft Azure DevOps Server(之前的名称为TFS)是一种CI/CD/源代码控制/问题跟踪/Wiki系统,该系统架构复杂,包含各种功能以及许多内部数据格式,因此是查找不安全反序列化漏洞的一个绝佳目标。由于Azure DevOps Server提供了自托管版本,我们可以使用静态及动态方法来分析相关程序。大多数应用使用.NET编写,因此我开发了适用于CIL(通用中间语言)数据流(Data Flow)及控制流(Control Flow)的分析工具:DeReviewer。该工具支持类DSL语法,可以描述存在漏洞的模式及payload,可以自动测试payload、构建调用图,分析可能存在漏洞方法的调用路径。我运行DeReviewer来分析Microsoft Azure DevOps Server,找到了一个有趣的调用路径,其中涉及到BinaryFormatter::Deserialize方法。

我们可以放大上图,发现Microsoft.VisualStudio.Services.Search.Server.Jobs.dll程序集中包含DeserializeToObject方法。如果我们反编译该方法,就能看到其中以不安全方式使用了BinaryFormatter

 

0x02 漏洞利用

如果攻击者能将任意二进制数组传递给arrayBytes参数,那么就有可能实现远程代码执行。然而,Microsoft.VisualStudio.Services.Search.Server.Jobs.dll的代码由后台服务TFSJobAgent所使用,该服务会构建并处理内部索引。TFSJobAgent只使用了不安全实现的DeserializeToObject来反序列化自己的索引,索引数据看上去非常可信。接下来让我们详细分析TFSJobAgent的具体设计细节。

TFSJobAgent服务加载Azure DevOps组织账户Wiki页面的索引时,就会调用DeserializeToObject方法。TFSJobAgent服务会使用多个方法来创建并更新Wiki索引,其中一种方法涉及到一个爬虫(crawler),爬虫会监控Git仓库,当有新的改动被push到服务端时,就会更新对应的索引。用户需要设置Azure DevOps账户,以便在Git中存储Wiki页面。系统上权限较低的用户只需要在Web接口中点击“Publish code as wiki”就能完成该操作。在这种情况下,TFSJobAgent服务会运行爬虫,解析新的Wiki内容,随后将结果序列化为索引文件,目前一切操作看上去都比较安全。Microsoft.VisualStudio.Services.Search.Parser.WikiParserExecutor类会转换来自Git的二进制内容,使用Markdig库将其解析为Markdown格式。负责这些逻辑的代码片段如下所示:

看到这段代码时,我都有点难以置信。如果Markdig在解析Wiki页面内容时抛出异常,那么该方法就会使用未解析的二进制来初始化ParsedData类。随后,Content字段会被存储在索引中。后续代码期望内部索引数据已经经过验证,会使用DeserializeToObject方法来重构ParsedMarkDownData对象。因此,我们需要找到一些无效的Markdown文本来触发异常,将该文本与payload组合使用,存放到文件中,将文件push到Git作为Wiki页面。这看上去是实现RCE的不错思路。Azure DevOps Server并没有使用最新版的Markdig库,这也方便我们搜索哪些是无效的Markdown文本。我从GitHub上下载了Markdig库的源码,首先查看了单元测试。我在对应版本的Markdig上测试了Markdig.Tests.MiscTests::TestInvalidCodeEscape,解析器的确会抛出异常。根据此次测试,攻击者可以使用“`**Header**t来内容的正常解析逻辑。整个探索过程只花了我10分钟,让我们拥抱开源及GitHub!

接下来就是生成适用于BinaryFormatter的RCE payload。James Forshaw在2017年描述过针对BinaryFormatterTypeConfuseDelegate gadget,大家可以参考Project Zero Team的博客了解更多内容。payload生成器的代码如下所示。

接下来将payload与不正确的Markdown字符串结合:

将结果保存成文件,commit到Git仓库,push到Azure DevOps Server ,然后……什么都没发生!TFSJobAgent并没有按照预期运行任何cmd命令。我做了一些调试,更深入分析代码,发现爬虫会验证来自Git的所有文件,只为文本文件创建索引。这一点很正常,但很有可能导致我们无法利用成功。然而,代码又再次让我大吃一惊。爬虫使用了FileTypeUtil::DetermineEncoding方法,根据内容的前几个字节来“猜测”内容的Unicode编码。这些逻辑发生在payload push之前。

让我们研究一下经过BinaryFormatter序列化后的数据头,微软在官方文档中描述了这个格式。经过BinaryFormatter序列化后的数据流的第一个字节必须为0,接下来4个字节为RootId,默认情况下等于整数类型的1FileTypeUtil::DetermineEncoding方法只有一个允许的头部,从0偏移处开始,为0x0000FEFF。因此,我们可以将头部中的RootId及序列化后的数据改为0x00FEFF00。如下代码会在添加不正确的Markdown字符串之前执行该操作:

将结果存储到文件中,再次push到Azure DevOps Server。我在演示视频中记录了攻击过程,攻击者只需要能够访问Git仓库,就能实现RCE效果。

 

0x03 总结

微软已经修复了CVE-2019-1306,用户需要为Azure DevOps Server打上补丁。需要注意的是,Windows的自动更新中并没有包含该补丁,我们需要手动安装补丁。开发者可以在BinaryFormatter配置中添加自定义SerializationBinder,只允许反序列化已知类型的数据,这也是反序列化不可信数据的最佳实践。即使我们认为面对的是可信的数据,我们也应当为不安全的序列化类应用相同的防御方法。从本文中大家可知,现代复杂系统的内部实现非常复杂,虽然内部索引看上去比较可信,是经过解析及验证后的数据,但攻击者仍然可以找到利用路径。

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多