开启enableDefaultTyping()
漏洞类javax.swing.JEditorPane来源于JDK不需要依赖任何jar包,该类在jackson-databind进行反序列化时可造成SSRF
pom.xml文件如下:
<dependencies> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.10.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.2</version> </dependency> <!-- https://mvnrepository.com/artifact/javax.transaction/jta --> <dependency> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> <version>1.1</version> </dependency> </dependencies>
Poc.java代码如下所示
package com.jacksonTest; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; public class Poc { public static void main(String[] args) throws Exception { ObjectMapper mapper = new ObjectMapper(); mapper.enableDefaultTyping(); String payload = "[\"javax.swing.JEditorPane\",{\"page\":\"http://qb7fky.dnslog.cn\"}]"; try { mapper.readValue(payload, Object.class); } catch (IOException e) { e.printStackTrace(); } } }
之后在DNSlog端成功收到请求:
首先定位到javax.swing.JEditorPane类的setPage方法,之后在此处下断点进行断点调试分析:
在setPage方法中会首先去判断传入的page是否为空,如果为空则抛出异常信息,不为空则初始化一个page上下文环境(矩形:长高各为1,从(0,0)开始,类似于一个新建的word文档):
之后设置reloaded为false,并根据之前返回的loaded是否为空等组合判断语句来决定是否进入if语句中,而loaded源自getPage(),getPage为空所以此时的loaded为null,即满足if条件的第一项,由于是采用的或关系条件判断语句,所以直接进入if语句中执行后续代码:
之后通过函数getAsynchronousLoadPriority来判断document的加载优先级并将其赋值给p变量,从下述代码中可以看出如果不支持则返回“-1”:
之后判断p的值是否小于0,如果小于0,则进入if语句中,此时的p返回值为—“-1”,之后进入if语句中,之后page(我们构造DNSlog域名)会作为参数传递进入getStream中,我们继续跟进去:
之后可以看到在在getStream中调用page.openConnection()从而得到一个HttpURLConnection对象实例:
之后判断新建的conn是否是一个HttpURLConnection对象的实例:
之后新建HttpURLConnection实例对象,并将conn赋值给hconn,之后设置是否跟随重定向,以及postData,然而此时的postData并没有任何数据信息,所以会直接跳过if语句:
之后继续跟进getResponseCode获取响应值的函数中:
之后调用getInputStream(),并确保我们已经连接到服务器,如果没有status信息,则抛出异常,下面我们继续跟进到getInputStream()函数中:
可以看到,截止目前为止我们还未连接到目标服务器,connecting为false,而接下来要做的就是连接目标服务器,也就是我们传递进去的DNSlog域名地址,我们继续跟进分析:
之后connecting被设置为"true",并检查URL的Socket通信是否允许,之后调用getInputStream0()函数:
在getInputStream0()函数中首先判断此时的doInput是否为false,如果此时的doInput为false则无法使用URL连接进行输入,也无法判断是否成功连接,故而会抛出异常,此时doInput为true,之后进入到else判断语句中,而此时的rememberException为null,所以也不会进入后续的else中,直接继续往下执行:
streaming为false,继续跳过if中的语句,往下执行:
之后一路往下跟踪,最后调用this.connect()来建立连接:
之后调用this.plainConnect()方法
之后再去调用plainConnect0():
之后跟进到this.getNewHttpClient(this.url, var4, this.connectTimeout);处,下面继续跟进去
之后调用get方法,并设置keep-alive:
....
之后来到HttpClient()处,继续跟进:
设置请求参数(host、port、proxy、keeepAliveConnection、KeepAliveTimeout等等)
之后调用openServer()开启连接:
之后检查协议等信息,并调用openServer(host,port)来建立连接,下面继续跟进:
之后再次调用doConnect():
之后调用InetSocketAddress()函数:
之后调用InetAddress.getByName(),这个函数想必大家已经都很熟悉了,该函数在给定的主机名的情况下来获取主机的IP地址,这也是触发SSRF的根本所在点:
同时在DNSLog端也接收到请求:
整个跟踪流程相对来说转接很多,涉及多个文件,有兴趣的小伙伴可以尝试跟踪分析一波看看~
官方在github的更新方式依旧是添加javax.swing.JEditorPane至黑名单类,但这种方式治标不治本,后续可能出现其他绕过黑名:
https://github.com/FasterXML/jackson-databind/commit/4d038c9de0aa80a5dae27f552a975cb39cc42b60
及时将jackson-databind升级到安全版本
升级到较高版本的JDK。
https://github.com/FasterXML/jackson-databind/issues/2642
https://github.com/FasterXML/jackson-databind/commit/4d038c9de0aa80a5dae27f552a975cb39cc42b60