237

Apereo CAS 4.X反序列化漏洞分析及复现

 4 years ago
source link: https://www.freebuf.com/vuls/226149.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

0×01 前言

放假前看到很多文章对这个漏洞进行分析复现,又因为过年期间的特殊情况,实在是无聊至极,所以自己也来学习一下,顺便恶补一下反序列化漏洞的知识。这篇文章记录了自己的一些想法以及相关的知识点,方便自己日后忘记可以重新拾起。第一次写文章有不好的,希望大家见谅。

0×02 环境搭建

由于部分cas版本的加密函数不同有相应的变化,因此想要按照此文章来复现漏洞的话还是选择和我一样的版本。

jdk8u144(不一定完全一样)
ApereoCas-4.1.5

下载CAS-Overlay-Template

github链接: https://github.com/apereo/cas-overlay-template/tree/4.1

github上有详细的部署操作,这里要注意要修改pom.xml文件cas的版本:

<cas.version>4.1.5</cas.version>

编译完后,会在target目录生成一个cas.war的war包,将该war包放在tomcat的web目录上,启动tomcat即可通过 http://localhost/cas访问example。

成功部署后:

3EjIbe3.jpg!web

0×03 漏洞分析

该漏洞存在于登录的execution参数,抓包发现该参数值应该是加密过的,故要知道对应的加密方法以及处理过程才行。

MVfAzqN.jpg!web

web.xml

查看登录url对应的servlet可知道交给了Spring的DispatcherServlet处理了,配置文件为/WEB-INF/cas-servlet.xml

rqmM7zm.jpg!web

从springmvc的执行流程图(网上找的)可以知道只要找到对应的处理器适配器,就能找到对应的处理器。

Ffame2J.jpg!web

cas-servlet.xml

全局搜索login字眼,看到loginHandlerAdapter适配器,处理器的类名为org.jasig.cas.web.flow.SelectiveFlowHandlerAdapter

6zYZFnN.jpg!web

org.jasig.cas.web.flow.SelectiveFlowHandlerAdapter该类继承FlowHandlerAdapter类,登录时调用继承类的handler方法:

//org.springframework.webflow.mvc.servlet.FlowHandlerAdapter#handle

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        FlowHandler flowHandler = (FlowHandler)handler;
        this.checkAndPrepare(request, response, false);
        String flowExecutionKey = this.flowUrlHandler.getFlowExecutionKey(request);
        if (flowExecutionKey != null) {
            try {
                ServletExternalContext context = this.createServletExternalContext(request, response);
                FlowExecutionResult result = this.flowExecutor.resumeExecution(flowExecutionKey, context);
                this.handleFlowExecutionResult(result, context, request, response, flowHandler);
            } catch (FlowException var11) {
                this.handleFlowException(var11, request, response, flowHandler);
            }
        } else {
            try {
                String flowId = this.getFlowId(flowHandler, request);
                MutableAttributeMap<Object> input = this.getInputMap(flowHandler, request);
                ServletExternalContext context = this.createServletExternalContext(request, response);
                FlowExecutionResult result = this.flowExecutor.launchExecution(flowId, input, context);
                this.handleFlowExecutionResult(result, context, request, response, flowHandler);
            } catch (FlowException var10) {
                this.handleFlowException(var10, request, response, flowHandler);
            }
        }

        return null;
    }

其中flowExecutionKey通过getFlowExecutionKey方法获取参数execution的值

String flowExecutionKey = this.flowUrlHandler.getFlowExecutionKey(request);

flowExecutionKey作为参数传入resumeExecution方法,跟进函数。在第91行对flowExecutionKey值的格式进行判断,通过”-”分割字符串为两部分uuid以及base64编码flowstate,因此格式不满足的话是无法继续走下去的。

fumeaiF.jpg!webqmEJZzM.jpg!web

跟进第96行getFlowExecution。

public FlowExecution getFlowExecution(FlowExecutionKey key) throws FlowExecutionRepositoryException {
        if (!(key instanceof ClientFlowExecutionKey)) {
            throw new IllegalArgumentException("Expected instance of ClientFlowExecutionKey but got " + key.getClass().getName());
        } else {
            byte[] encoded = ((ClientFlowExecutionKey)key).getData();

            try {
                ClientFlowExecutionRepository.SerializedFlowExecutionState state = (ClientFlowExecutionRepository.SerializedFlowExecutionState)this.transcoder.decode(encoded);
                FlowDefinition flow = this.flowDefinitionLocator.getFlowDefinition(state.getFlowId());
                return this.flowExecutionFactory.restoreFlowExecution(state.getExecution(), flow, key, state.getConversationScope(), this.flowDefinitionLocator);
            } catch (IOException var5) {
                throw new ClientFlowExecutionRepositoryException("Error decoding flow execution", var5);
            }
        }
    }

在第105行对之前base64解码后的encoded进行解密,跟进解密函数this.transcoder.decode(encoded)

Rn2mEzZ.jpg!web32UfiyJ.jpg!web

可以看出在第83行对密文进行解密,经过一系列的操作后在99行进行反序列化,触发漏洞。可以看出调用的decode方法属于EncryptedTranscoder类,该类还定义的加密方法encode,这里可以直接生成恶意对象,直接调用org.jasig.spring.webflow.plugin.EncryptedTranscoder#encode生成加密字节数组后base64,加上”uuid-”构成execution的值。

整个调用栈

6nIZrmb.jpg!web

0×03 构造payload

默认环境的jar包中有commons-collections4-4.0.jar,直接使用ysoserial生成payload,这里记得要将payload的特殊符号进行url编码。

2eQBRnj.jpg!web3EbaE3b.jpg!web

演示结果

成功执行系统命令

zUNJj22.jpg!web

0×04 构造回显payload

看了大佬的 分析 ,知道可以回显,文章提及到org.springframework.webflow.context.ExternalContextHolder.getExternalContext()方法可以获取到上下文关联信息,然后通过getNativeRequest()方法获取request对象通过getNativeResponse()方法获取response对象。同时提及到org.springframework.cglib.core.ReflectUtils.defineClass().newInstance();加载payload。我的猜测大佬的想法是通过defineClass从byte[]还原出一个Class对象,该恶意对象主要是执行命令,获取response对象,将执行命令后的结果通过response对象的输出流输出。在利用commons-collections1是发现ReflectUtils利用不了,因为构造方法为private,要设置setAccessible为true。因此使用commons-collections2的话,实际就不需要这么麻烦用defineClass来加载payload了,直接在利用类里面写就好了。

//org.springframework.cglib.core.ReflectUtils
private ReflectUtils() {
    }

//org.springframework.cglib.core.ReflectUtils#defineClass

 public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception {
        Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length), PROTECTION_DOMAIN};
        Class c = (Class)DEFINE_CLASS.invoke(loader, args);
        Class.forName(className, true, loader);
        return c;
    }

这里看看ysoserial的commons-collections2的构造恶意对象的主要方法。这里使用javassist,第66行获取要操作的类,第75行在该类的构造方法中插入代码,因此这里只要修改该类ysoserial.payloads.util.Gadgets.StubTransletPayload的构造方法为执行系统命令,并修改response的输出流。大家可以直接修改ysoserial的源码并重新编译,我这里为了方便直接用了网上的payload改了一下。

bIjmUfI.jpg!webMjA3IfQ.jpg!web

演示结果

ZFvaMbe.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK