Java 对象分解成字节码过程叫做序列化,从字节码组装成 Java 对象的过程叫做反序列化,这两个过程分别对应于的 writeObject 和 readObject 方法。问题在于 readObject 在利用字节流组装 Java 对象时不会调用构造函数, 也就意味着没有任何类型的检查,用户可以复写 readObject() 方法执行任何希望执行的代码。
这可能会导致三方面问题:
1. 序列化对象修改了对象或者父类的某个未加保护的关键属性,导致未预料的结果。 例如:
class Client { private int value; public Client(int v) { if (v <= 0) { throw new RuntimeException("not positive number"); } value = v; } public void writeObject(ObjectOutputStream oos) throws IOException { int value = 0; //这里该值被改为0。(现实中可以通过调试模式,修改serialize字节码或者class instrument等多种方式修改该值) oos.defaultWriteObject(); } }class Controller { private ArrayBlockingQueue<Client> queue; public void receiveState(oin) throws IOException, ClassNotFoundException { Client s = (Client)oin.(); //反序列化不调用构造函数,value的非零检查不会触发 queue.add(s); } public Client getClient() throws InterruptedException { return (Client)queue.take(); } }class Server extends Thread { private Controller controller = new Controller(); private int result = 100; public void run() { while (true) { try { result = result / controller.getClient().getValue(); // 由于value是0,会导致算数异常,线程结束 Thread.sleep(100); } catch (InterruptedException e) {} } } }
2. 攻击者可以创建循环对象链,然后序列化。会导致反序列化无法结束, 空耗系统资源。例如:
Set root = new HashSet();Set s1 = root;Set s2 = new HashSet();for (int i = 0; i < 10; i++) { Set t1 = new HashSet(); Set t2 = new HashSet(); t1.add("foo"); //使t2不等于t1 s1.add(t1); s1.add(t2); s2.add(t1); s2.add(t2); s1 = t1; s2 = t2; } class Controller { public void receiveState(ObjectInputStream ois) { FileOutputStream fos = new FileOutputStream(new File("xxx.ser")); fos.write(ois); //实际并不知道存的是什么,可能是恶意脚本。 fos.close(); } }
参考
JAVA 反序列化攻击 | IT瘾
http://itindex.net/detail/54975-java-%E5%BA%8F%E5%88%97%E5%8C%96
Java反序列化漏洞分析 - ssooking - 博客园
http://www.cnblogs.com/ssooking/p/5875215.html
Java序列化机制和原理 - redcreen - 博客园
http://www.cnblogs.com/redcreen/archive/2011/02/15/1955307.html