JAVA2-反序列化和RMI

java反序列化

博客前面也记了很多php,python反序列化的内容,所以本文也就不再复述序列化反序列化的作用之类的,直奔主题-java的反序列化利用

Java 序列化是指把 Java 对象转换为字节序列的过程
ObjectOutputStream类的 writeObject() 方法可以实现序列化
Java 反序列化是指把字节序列恢复为 Java 对象的过程
ObjectInputStream 类的 readObject() 方法用于反序列化。

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
class User implements Serializable {
private String name;
public void setName(String name) {
this.name=name;
}
public String getName() {
return name;
}
}
public class Helloworld{
public static void main(String[] args) throws Exception {
User user=new User();
user.setName("st4ck");
byte[] serializeData=serialize(user);
FileOutputStream fout = new FileOutputStream("user.bin");
fout.write(serializeData);
fout.close();

User user2=(User) unserialize(serializeData);
System.out.println(user2.getName());
}
public static byte[] serialize(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
public static Object unserialize(final byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
}
//输出st4ck

tips:从文件中读取字节流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static byte[] getContent(String filePath) throws IOException {
File file = new File(filePath);
long fileSize = file.length();
if (fileSize > Integer.MAX_VALUE) {
System.out.println("file too big...");
return null;
}
FileInputStream fi = new FileInputStream(file);
byte[] buffer = new byte[(int) fileSize];
int offset = 0;
int numRead = 0;
while (offset < buffer.length
&& (numRead = fi.read(buffer, offset, buffer.length - offset)) >= 0) {
offset += numRead;
}
if (offset != buffer.length) {
throw new IOException("Could not completely read file "
+ file.getName());
}
fi.close();
return buffer;
}

直接将用户序列化了,然后反序列化
我们可以查看生成的bin文件

我们发现对于php的反序列化大部分是肉眼可读了,java的序列化的字节流确实不太友好。


修改readObject()方法,引发恶意代码执行

在进行反序列化时,java会调用ObjectInputStream类的readObject()方法。如果被反序列化的类重写了readObject(),那么该类在进行反序列化时,Java会优先调用重写的readObject()方法

writeObject类同
这就有点像php反序列化的魔法函数和python反序列化的__reduce__函数了
我们可以尝试修改上面的user类,添加一个恶意函数,参数就为名字了,这样我们名字可以当作任意代码执行的参数比如calc.exe

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
class User implements Serializable {
private String name;
public void setName(String name) {
this.name=name;
}
public String getName() {
return name;
}
private void readObject(java.io.ObjectInputStream stream) throws Exception {
stream.defaultReadObject();
Runtime.getRuntime().exec(name);
}
}
public class Helloworld{
public static void main(String[] args) throws Exception {
User user=new User();
user.setName("calc.exe");
byte[] serializeData=serialize(user);
FileOutputStream fout = new FileOutputStream("user.bin");
fout.write(serializeData);
fout.close();
User user2=(User) unserialize(serializeData);
System.out.println(user2.getName());
}
public static byte[] serialize(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
public static Object unserialize(final byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
}

如我们所愿

当然java直接反序列化的情况太少了这个只能当基础,更多情况需要结合反射,rmi等机制

RMI介绍

RMI(Remote Method Invocation)Java远程方法调用,RMI用于构建分布式应用程序,RMI实现了Java程序之间跨JVM的远程通信。

调用机制大概如下:

  1. RMI客户端在调用远程方法时会先创建Stub(sun.rmi.registry.RegistryImpl_Stub)
  2. Stub会将Remote对象传递给远程引用层(java.rmi.server.RemoteRef)并创建java.rmi.server.RemoteCall(远程调用)对象。
  3. RemoteCall序列化RMI服务名称Remote对象。
  4. RMI客户端远程引用层传输RemoteCall序列化后的请求信息通过Socket连接的方式传输到RMI服务端远程引用层
  5. RMI服务端远程引用层(sun.rmi.server.UnicastServerRef)收到请求会请求传递给Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch)
  6. Skeleton调用RemoteCall反序列化RMI客户端传过来的序列化。
  7. Skeleton处理客户端请求:bindlistlookuprebindunbind,如果是lookup则查找RMI服务名绑定的接口对象,序列化该对象并通过RemoteCall传输到客户端。
  8. RMI客户端反序列化服务端结果,获取远程对象的引用。
  9. RMI客户端调用远程方法,RMI服务端反射调用RMI服务实现类的对应方法并序列化执行结果返回给客户端。
  10. RMI客户端反序列化RMI远程方法调用结果。

rmi-demo

通过rmi调用服务器读取flag的函数,(ps:好像是server不该是serve,英语tcl)
代码https://github.com/Kit4y/rmi-demo

/client/src/Client

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {
public static void main(String[] args){
try {
Registry registry= LocateRegistry.getRegistry("localhost", 8086);
RmiTestInterface t=(RmiTestInterface) registry.lookup("test");
System.out.println("Client:"+t.getTest());
} catch (RemoteException e) {
e.printStackTrace();
}catch (NotBoundException e) {
e.printStackTrace();
}
}
}

/client/src/RmiTestInterface和/serve/src/RmiTestInterface

1
2
3
public interface RmiTestInterface extends Remote {
public String getTest() throws RemoteException;
}

/serve/src/RmiTestImpl

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
public class RmiTestImpl implements RmiTestInterface{
public RmiTestImpl() throws RemoteException {

}
public static void readToBuffer(StringBuffer buffer, String filePath) throws IOException {
InputStream is = new FileInputStream(filePath);
String line; // 用来保存每行读取的内容
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
line = reader.readLine(); // 读取第一行
while (line != null) { // 如果 line 为空说明读完了
buffer.append(line); // 将读到的内容添加到 buffer 中
buffer.append("\n"); // 添加换行符
line = reader.readLine(); // 读取下一行
}
reader.close();
is.close();
}
@Override
public String getTest() throws IOException {
StringBuffer sb = new StringBuffer();
RmiTestImpl.readToBuffer(sb, "C:\\Users\\38138\\Desktop\\rmi-demo\\serve\\src\\flag.txt");
return sb.toString();
}
public static void main(String[] args) throws RemoteException {
RmiTestImpl t=new RmiTestImpl();
RmiTestInterface tt=(RmiTestInterface) UnicastRemoteObject.exportObject(t, 0);
Registry registry= LocateRegistry.createRegistry(8086);
registry.rebind("test", tt);
System.out.println("server is start");
}
}

先运行RmiTestImpl,然后运行Client

RMI引起的反序列化漏洞

RMI通信中所有的对象都是通过Java序列化传输的
既然RMI使用了反序列化机制来传输Remote对象,那么可以通过构建一个恶意的Remote对象,这个对象经过序列化后传输到服务器端,服务器端在反序列化时候就会触发反序列化漏洞。

RMI引起的反序列化漏洞-demo(待填坑)