聚焦源代码安全,网罗国内外最新资讯!
本文内容共分六部分:
一、前言
二、Jdk7u21 的 PoC 分析
1、第一部分利用链分析
2、第二部分利用链分析
三、MarshalledObject分析
四、PoC 构造
五、实验复现
六、补丁分析
一、前言
本次攻防演习期间,有人发现 Weblogic T3反序列化0day漏洞。攻击者可在原Jdk7u21 POC 基础上,加上 java.rmi.MarshalledObject 绕过黑名单,达到入侵目的,具体如下。
二、Jdk7u21的PoC分析
gadget 如下:
HashSet.readObject()
HashMap.put()
HashMap.hash()
*.hashCode() (计算第一个对象hash值)
HashSet.readObject()
HashMap.put()
HashMap.hash()
*.hashcode()
AnnotationInvocationHandler.invoke()
AnnotationInvocationHandler.hashCodeImpl()
HashMap.put()
AnnotationInvocationHandler.invoke()
AnnotationInvocationHandler.equalsImpl()
TemplatesImpl.getOutputProperties()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TemplatesImpl$TransletClassLoader
TemplatesImpl.getTransletInstance()
Class.newInstance()
Runtime.exec()
1、分析第一部分利用链
Jdk7u21的第一部分利用链如下。
HashSet.readObject()
HashMap.put()
HashMap.hash()
*.hashCode()(计算第一个对象hash值)
HashSet.readObject()
HashMap.put()
HashMap.hash()
*.hashcode()
AnnotationInvocationHandler.invoke()
AnnotationInvocationHandler.hashCodeImpl()
HashMap.put()
AnnotationInvocationHandler.invoke()
AnnotationInvocationHandler.equalsImpl()
在对 LinkedHashSet 反序列化过程中,会进入 HashSet.readObject() 函数,关键代码块如下。
/ Read in all elements in the proper order.
for (int i=0; i<size; i++) {
E e = (E) s.readObject();
map.put(e, PRESENT);
}
根据 POC 信息,可知该函数会依次读取 LinkedHashSet 的 templates 和proxy对象,并将它们加入 map中。为什么需要加载两个对象呢?继续进入map.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))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
该函数会计算存储进map的第一个对象Templates的Hash值,进入hash(key),如下。
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
程序会执行常规hash运算,注意,其中的 k.hashCode() 调用了系统函数;而如果构造动态代理,编写或寻找到合适代理类,则极有可能使多个存储进map的对象的hash值一致。返回至HashMap.put() 函数,继续分析,进入至indexFor(hash, table.length) 函数,如下。
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
table对象的原定义代码是table[bucketIndex] = new Entry<>(hash, key, value, e),负责保存 map 中每个对象及 hash 值等信息。此函数没有保存对象信息功能,仅仅负责统计返回当前对象在 table 中的索引值。随后返回至HashMap.put() 函数。
随后,如当前对象不在 table 中,则不进入循环,而是进入 addEntry(hash, key, value, i); 且在此期间进入父类方法,最终当前对象被保存至 table 中,关键代码如下。
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
保存完第一个对象后,程序返回到 HashSet.readObject() 方法,并在 map 中添加第二个对象。如第二个对象是代理对象,则其 hash 值可能与前一个对象的hash 值碰撞,从而有利于绕过代码的验证逻辑,便于未来开展入侵测试;如POC中存在第二个代理对象,则会继续进入 map.put()。
进入 hash(key),计算第二个代理对象的hash值,进入 HashMap.hash() 函数。
在 POC 中,第二个代理对象的代理类是 AnnotationInvocationHandler,因此执行 k.hashCode() 时,会首先进入 AnnotationInvocationHandler.invoke() 方法,如下。
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]);
} else {
assert var5.length == 0;
if (var4.equals("toString")) {
return this.toStringImpl();
} else if (var4.equals("hashCode")) {
return this.hashCodeImpl();
} else if (var4.equals("annotationType")) {
return this.type;
} else {
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
根据方法名进入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;
}
private static int memberValueHashCode(Object var0) {
Class var1 = var0.getClass();
if (!var1.isArray()) {
return var0.hashCode();
} else if (var1 == byte[].class) {
return Arrays.hashCode((byte[])((byte[])var0));
} else if (var1 == char[].class) {
return Arrays.hashCode((char[])((char[])var0));
} else if (var1 == double[].class) {
return Arrays.hashCode((double[])((double[])var0));
} else if (var1 == float[].class) {
return Arrays.hashCode((float[])((float[])var0));
} else if (var1 == int[].class) {
return Arrays.hashCode((int[])((int[])var0));
} else if (var1 == long[].class) {
return Arrays.hashCode((long[])((long[])var0));
} else if (var1 == short[].class) {
return Arrays.hashCode((short[])((short[])var0));
} else {
return var1 == boolean[].class ? Arrays.hashCode((boolean[])((boolean[])var0)) : Arrays.hashCode((Object[])((Object[])var0));
}
}
var3信息来自AnnotationInvocationHandler的memberValues成员变量:如var3 中只有一条map信息,且该map的key为"f5a5a608"而value是加入map的第一个对象,则经调试分析发现,此时会发生两个对象hash值碰撞的情况。返回至 HashMap.put() 继续分析。
在循环代码块中,存在关键比较逻辑,如下。
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
该逻辑首先会比较两个对象的hash值是否相等:根据 PoC 可知两值相等,并且会执行至 key.equals(k)。k是第一个对象,key是第二个代理对象,则会执行代理类invoke()方法。POC中的代理类是AnnotationInvocationHandler,则进入AnnotationInvocationHandler.invoke(),随后再进入this.equalsImpl(var3[0]),如下。
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);