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(); } }
|
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
的远程通信。
调用机制大概如下:
RMI客户端
在调用远程方法时会先创建Stub(sun.rmi.registry.RegistryImpl_Stub)
。
Stub
会将Remote
对象传递给远程引用层(java.rmi.server.RemoteRef)
并创建java.rmi.server.RemoteCall(远程调用)
对象。
RemoteCall
序列化RMI服务名称
、Remote
对象。
RMI客户端
的远程引用层
传输RemoteCall
序列化后的请求信息通过Socket
连接的方式传输到RMI服务端
的远程引用层
。
RMI服务端
的远程引用层(sun.rmi.server.UnicastServerRef)
收到请求会请求传递给Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch)
。
Skeleton
调用RemoteCall
反序列化RMI客户端
传过来的序列化。
Skeleton
处理客户端请求:bind
、list
、lookup
、rebind
、unbind
,如果是lookup
则查找RMI服务名
绑定的接口对象,序列化该对象并通过RemoteCall
传输到客户端。
RMI客户端
反序列化服务端结果,获取远程对象的引用。
RMI客户端
调用远程方法,RMI服务端
反射调用RMI服务实现类
的对应方法并序列化执行结果返回给客户端。
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) { buffer.append(line); 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(待填坑)