zoukankan      html  css  js  c++  java
  • Java反序列化基础

    Java反序列化基础

    1.扫盲

    • 常用单词

      source:入口,gadget chain:调用链,sink:目标方法,RMI:remote method invocation远程方法调用

    • 反序列化漏洞的理解

      反序列化漏洞的挖掘,本质上就是已知source和sink,如何走通整个流程的问题

      这里source包括:

      • Java原生的反序列化,,即通过objectInputStream.readObject(),处理二进制格式的内容得到java对象
      • 专有格式的反序列化,列如通过Fastjson、Xstream等第三方库,处理json、xml等格式内容,得到java对象

      而要执行的sink包括:

      • Runtime.exec()直接在目标环境中执行命令
      • Method.Invoke()这种需要选择合适的方法和参数,通过反射执行方法
      • RMI/JNDI/JRMP等,通过引用远程对象,间接命令执行

    2.ObjectOutputStream序列化分析

    示例(来自https://y4er.com/post/java-deserialization-1/)

    import java.io.*;
    
    interface Animal {
        public void eat();
    }
    
    class Ani implements Serializable {
        public String name;
    
        private void writeObject(java.io.ObjectOutputStream out) throws IOException, ClassNotFoundException{
            out.defaultWriteObject();
            System.out.println("重新writeObject");
        }
    
        private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
            //执行默认的readObject()方法
            in.defaultReadObject();
            //执行打开计算器程序命令
            Runtime.getRuntime().exec("calc");
        }
    }
    
    class Cat extends Ani implements Animal {
        @Override
        public void eat() {
            System.out.println("cat eat.");
        }
    }
    
    public class Test {
    
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            Ani cat = new Cat();
            cat.name = "tom";
            FileOutputStream fos = new FileOutputStream("obj");
            ObjectOutputStream os = new ObjectOutputStream(fos);
            os.writeObject(cat);
            os.close();
            //从文件中反序列化obj对象
            FileInputStream fis = new FileInputStream("obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            //恢复对象
            Cat objectFromDisk = (Cat) ois.readObject();
            System.out.println(objectFromDisk.name);
            ois.close();
        }
    }
    

    ObjectOutputStream的writeObject调用栈:

    writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->defaultWriteFields或者invokeWriteObject
    

    PS:如果对象重写了writeObject则会通过invokeWriteObject反射调用重写的writeObject,反之直接调用defaultWriteFields进行序列化
    writeObject:

        public final void writeObject(Object obj) throws IOException {
            if (enableOverride) {
                writeObjectOverride(obj);
                return;
            }
            try {
                writeObject0(obj, false);
            } catch (IOException ex) {
                if (depth == 0) {
                    writeFatalException(ex);
                }
                throw ex;
            }
        }
    

    如果ObjectOutputStream是有参构造器的话enableOverride = false;

    writeObject0关键代码
    代码1

    // check for replacement object
                Object orig = obj;
                Class<?> cl = obj.getClass();
                ObjectStreamClass desc;
                for (;;) {
                    // REMIND: skip this check for strings/arrays?
                    Class<?> repCl;
                    desc = ObjectStreamClass.lookup(cl, true);
                    if (!desc.hasWriteReplaceMethod() ||
                        (obj = desc.invokeWriteReplace(obj)) == null ||
                        (repCl = obj.getClass()) == cl)
                    {
                        break;
                    }
                    cl = repCl;
                }
                if (enableReplace) {
                    Object rep = replaceObject(obj);
                    if (rep != obj && rep != null) {
                        cl = rep.getClass();
                        desc = ObjectStreamClass.lookup(cl, true);
                    }
                    obj = rep;
                }
    

    desc存放的是当前类的描述信息
    代码2

    if (obj instanceof String) {
                    writeString((String) obj, unshared);
                } else if (cl.isArray()) {
                    writeArray(obj, desc, unshared);
                } else if (obj instanceof Enum) {
                    writeEnum((Enum<?>) obj, desc, unshared);
                } else if (obj instanceof Serializable) {
                    writeOrdinaryObject(obj, desc, unshared);
                } else {
                    if (extendedDebugInfo) {
                        throw new NotSerializableException(
                            cl.getName() + "
    " + debugInfoStack.toString());
                    } else {
                        throw new NotSerializableException(cl.getName());
                    }
                }
            } finally {
                depth--;
                bout.setBlockDataMode(oldMode);
            }
    

    如果对象是Serializable则进入writeOrdinaryObject

    writeOrdinaryObject

        private void writeOrdinaryObject(Object obj,
                                         ObjectStreamClass desc,
                                         boolean unshared)
            throws IOException
        {
            if (extendedDebugInfo) {
                debugInfoStack.push(
                    (depth == 1 ? "root " : "") + "object (class "" +
                    obj.getClass().getName() + "", " + obj.toString() + ")");
            }
            try {
                desc.checkSerialize();
    
                bout.writeByte(TC_OBJECT);
                writeClassDesc(desc, false);
                handles.assign(unshared ? null : obj);
                if (desc.isExternalizable() && !desc.isProxy()) {
                    writeExternalData((Externalizable) obj);
                } else {
                    writeSerialData(obj, desc);
                }
            } finally {
                if (extendedDebugInfo) {
                    debugInfoStack.pop();
                }
            }
        }
    

    writeSerialData

        private void writeSerialData(Object obj, ObjectStreamClass desc)
            throws IOException
        {
            ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
            for (int i = 0; i < slots.length; i++) {
                ObjectStreamClass slotDesc = slots[i].desc;
                if (slotDesc.hasWriteObjectMethod()) {
                    PutFieldImpl oldPut = curPut;
                    curPut = null;
                    SerialCallbackContext oldContext = curContext;
    
                    if (extendedDebugInfo) {
                        debugInfoStack.push(
                            "custom writeObject data (class "" +
                            slotDesc.getName() + "")");
                    }
                    try {
                        curContext = new SerialCallbackContext(obj, slotDesc);
                        bout.setBlockDataMode(true);
                        slotDesc.invokeWriteObject(obj, this);
                        bout.setBlockDataMode(false);
                        bout.writeByte(TC_ENDBLOCKDATA);
                    } finally {
                        curContext.setUsed();
                        curContext = oldContext;
                        if (extendedDebugInfo) {
                            debugInfoStack.pop();
                        }
                    }
    
                    curPut = oldPut;
                } else {
                    defaultWriteFields(obj, slotDesc);
                }
            }
        }
    

    这里面slotDesc.hasWriteObjectMethod()判断了是否重写writeObject,重写之后通过反射调用重写的writeObject,否则直接进入defaultWriteFields

    示例运行结果

    成功调用了我们重写的writeObject

    3.ObjectInputStream反序列化分析

    示例(来自https://y4er.com/post/java-deserialization-1/)

    import java.io.*;
    
    interface Animal {
        public void eat();
    }
    
    class Ani implements Serializable {
        public String name;
    
        private void writeObject(java.io.ObjectOutputStream out) throws IOException, ClassNotFoundException{
            out.defaultWriteObject();
            System.out.println("重新writeObject");
        }
    
        private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
            //执行默认的readObject()方法
            in.defaultReadObject();
            //执行打开计算器程序命令
            Runtime.getRuntime().exec("calc");
        }
    }
    
    class Cat extends Ani implements Animal {
        @Override
        public void eat() {
            System.out.println("cat eat.");
        }
    }
    
    public class Test {
    
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            Ani cat = new Cat();
            cat.name = "tom";
            FileOutputStream fos = new FileOutputStream("obj");
            ObjectOutputStream os = new ObjectOutputStream(fos);
            os.writeObject(cat);
            os.close();
            //从文件中反序列化obj对象
            FileInputStream fis = new FileInputStream("obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            //恢复对象
            Cat objectFromDisk = (Cat) ois.readObject();
            System.out.println(objectFromDisk.name);
            ois.close();
        }
    }
    

    反序列化调用栈
    已示例为例

    writeObject--->writeObject0--->readOrdinaryObject--->readSerialData--->invokeReadObject
    

    readObject代码

        public final Object readObject()
            throws IOException, ClassNotFoundException
        {
            if (enableOverride) {
                return readObjectOverride();
            }
    
            // if nested read, passHandle contains handle of enclosing object
            int outerHandle = passHandle;
            try {
                Object obj = readObject0(false);
                handles.markDependency(outerHandle, passHandle);
                ClassNotFoundException ex = handles.lookupException(passHandle);
                if (ex != null) {
                    throw ex;
                }
                if (depth == 0) {
                    vlist.doCallbacks();
                }
                return obj;
            } finally {
                passHandle = outerHandle;
                if (closed && depth == 0) {
                    clear();
                }
            }
        }
    

    readObject0代码

        private Object readObject0(boolean unshared) throws IOException {
            boolean oldMode = bin.getBlockDataMode();
            if (oldMode) {
                int remain = bin.currentBlockRemaining();
                if (remain > 0) {
                    throw new OptionalDataException(remain);
                } else if (defaultDataEnd) {
                    /*
                     * Fix for 4360508: stream is currently at the end of a field
                     * value block written via default serialization; since there
                     * is no terminating TC_ENDBLOCKDATA tag, simulate
                     * end-of-custom-data behavior explicitly.
                     */
                    throw new OptionalDataException(true);
                }
                bin.setBlockDataMode(false);
            }
    
            byte tc;
            while ((tc = bin.peekByte()) == TC_RESET) {
                bin.readByte();
                handleReset();
            }
    
            depth++;
            totalObjectRefs++;
            try {
                switch (tc) {
                    case TC_NULL:
                        return readNull();
    
                    case TC_REFERENCE:
                        return readHandle(unshared);
    
                    case TC_CLASS:
                        return readClass(unshared);
    
                    case TC_CLASSDESC:
                    case TC_PROXYCLASSDESC:
                        return readClassDesc(unshared);
    
                    case TC_STRING:
                    case TC_LONGSTRING:
                        return checkResolve(readString(unshared));
    
                    case TC_ARRAY:
                        return checkResolve(readArray(unshared));
    
                    case TC_ENUM:
                        return checkResolve(readEnum(unshared));
    
                    case TC_OBJECT:
                        return checkResolve(readOrdinaryObject(unshared));
    
                    case TC_EXCEPTION:
                        IOException ex = readFatalException();
                        throw new WriteAbortedException("writing aborted", ex);
    
                    case TC_BLOCKDATA:
                    case TC_BLOCKDATALONG:
                        if (oldMode) {
                            bin.setBlockDataMode(true);
                            bin.peek();             // force header read
                            throw new OptionalDataException(
                                bin.currentBlockRemaining());
                        } else {
                            throw new StreamCorruptedException(
                                "unexpected block data");
                        }
    
                    case TC_ENDBLOCKDATA:
                        if (oldMode) {
                            throw new OptionalDataException(true);
                        } else {
                            throw new StreamCorruptedException(
                                "unexpected end of block data");
                        }
    
                    default:
                        throw new StreamCorruptedException(
                            String.format("invalid type code: %02X", tc));
                }
            } finally {
                depth--;
                bin.setBlockDataMode(oldMode);
            }
        }
    

    readObject0中根据对象不同类型调用不同的方法,这里调用的是readOrdinaryObject

    readOrdinaryObject关键代码

    Object obj;
            try {
                obj = desc.isInstantiable() ? desc.newInstance() : null;
            } catch (Exception ex) {
                throw (IOException) new InvalidClassException(
                    desc.forClass().getName(),
                    "unable to create instance").initCause(ex);
            }
    
            passHandle = handles.assign(unshared ? unsharedMarker : obj);
            ClassNotFoundException resolveEx = desc.getResolveException();
            if (resolveEx != null) {
                handles.markException(passHandle, resolveEx);
            }
    
            if (desc.isExternalizable()) {
                readExternalData((Externalizable) obj, desc);
            } else {
                readSerialData(obj, desc);
            }
    
            handles.finish(passHandle);
    

    从desc中获取对象实例后调用readSerialData

    readSerialData中判断了是否重写readObject方法,如果重写则使用反射调用重写的readObject,反之使用默认的方法进行反序列化
    readSerialData

        private void readSerialData(Object obj, ObjectStreamClass desc)
            throws IOException
        {
            ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
            for (int i = 0; i < slots.length; i++) {
                ObjectStreamClass slotDesc = slots[i].desc;
    
                if (slots[i].hasData) {
                    if (obj == null || handles.lookupException(passHandle) != null) {
                        defaultReadFields(null, slotDesc); // skip field values
                    } else if (slotDesc.hasReadObjectMethod()) {
                        ThreadDeath t = null;
                        boolean reset = false;
                        SerialCallbackContext oldContext = curContext;
                        if (oldContext != null)
                            oldContext.check();
                        try {
                            curContext = new SerialCallbackContext(obj, slotDesc);
    
                            bin.setBlockDataMode(true);
                            slotDesc.invokeReadObject(obj, this);
                        } catch (ClassNotFoundException ex) {
                            /*
                             * In most cases, the handle table has already
                             * propagated a CNFException to passHandle at this
                             * point; this mark call is included to address cases
                             * where the custom readObject method has cons'ed and
                             * thrown a new CNFException of its own.
                             */
                            handles.markException(passHandle, ex);
                        } finally {
                            do {
                                try {
                                    curContext.setUsed();
                                    if (oldContext!= null)
                                        oldContext.check();
                                    curContext = oldContext;
                                    reset = true;
                                } catch (ThreadDeath x) {
                                    t = x;  // defer until reset is true
                                }
                            } while (!reset);
                            if (t != null)
                                throw t;
                        }
    
                        /*
                         * defaultDataEnd may have been set indirectly by custom
                         * readObject() method when calling defaultReadObject() or
                         * readFields(); clear it to restore normal read behavior.
                         */
                        defaultDataEnd = false;
                    } else {
                        defaultReadFields(obj, slotDesc);
                        }
    
                    if (slotDesc.hasWriteObjectData()) {
                        skipCustomData();
                    } else {
                        bin.setBlockDataMode(false);
                    }
                } else {
                    if (obj != null &&
                        slotDesc.hasReadObjectNoDataMethod() &&
                        handles.lookupException(passHandle) == null)
                    {
                        slotDesc.invokeReadObjectNoData(obj);
                    }
                }
            }
                }
    

    之前的疑问

    • 为什么示例中的命令执行不能直接拿来利用?

    通过对Java反序列化的JDK代码分析可知,反序列化首先会获取利用对象的描述(desc),通过反射的方式执行readObject。这里反射执行的是当前环境的那个利用类和readObject,不是直接执行序列化对象的readObject,而开发者的代码中不会这样去写readObject,所以在真实攻击中无法利用成功。

  • 相关阅读:
    【转】CDH rpm+http方式离线部署
    处理CDH环境Hadoop:NameNode is not formatted
    使用 prometheus-operator 监控 Kubernetes 集群【转】
    Prometheus监控k8s(10)-PrometheusOperator-更优雅的Prometheus部署【转】
    kali 网络配置作死踩坑
    01 校招信息收集渠道
    Pypora打开markdown(md)文件保存为PDF文件
    Github上的md文件查看后下载PDF文档方法(将HTML文件保存为PDF格式)
    利用后裔采集器快速采集文本数据(以京东为例)
    office安装公式编辑器mathtype6.9及mathtype过期解决方案
  • 原文地址:https://www.cnblogs.com/g0udan/p/14914804.html
Copyright © 2011-2022 走看看