Найдите уязвимость в этом Java-фрагменте: ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); Object obj = in.readObject(); — какие риски несёт десериализация стороннего ввода и как их уменьшить?
Коротко — уязвимость: десериализация данных от незнакомого/внешнего источника даёт возможность атакующему выполнить произвольный код, вызвать DoS или получить/подменить данные в приложении (через т.н. gadget‑цепочки и поведение методов readObject()/readResolve()). Основные риски - Удалённое выполнение кода (RCE) через gadget‑цепочки (например, известные уязвимости в commons‑collections, spring и т.п.). - Отказ в обслуживании: большое/рекурсивное/злонамеренно сконструированное дерево объектов → OOM/CPU. - Информационное раскрытие: десериализация внутренних типов даёт доступ к чувствительным полям. - Подмена/повреждение данных, обход авторизации. - Зависимость от классов в classpath — загрузка неожиданных типов. Как уменьшить риск (рекомендации) 1. По возможности не использовать Java‑сериализацию для внешнего ввода: заменить на безопасные форматы (JSON, protobuf, CBOR) с явной схемой и валидацией. 2. Использовать фильтрацию типов (allowlist) при десериализации: ObjectInputFilter (в Java 9+Java\ 9+Java9+) или setObjectInputFilter. Пример: ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); ObjectInputFilter filter = ObjectInputFilter.Config.createFilter( "com.myapp.Safe*;java.lang.String;java.lang.Integer;!*" ); in.setObjectInputFilter(filter); Object obj = in.readObject(); Это позволяет отклонять любые типы, не в списке разрешённых. 3. Ограничивать ресурсы: максимальная глубина объектов, максимальное количество элементов, общий размер — через фильтр или проверкой после разбора. 4. Подписывать/шифровать поток сериализованных данных: отправитель подписывает байты (MAC/PKI), получатель проверяет подпись перед десериализацией — защита от подделки. 5. Валидировать объекты после десериализации: проверять поля и состояния, не доверять сразу. 6. Песочница/изолированный процесс: выполнять десериализацию в отдельном JVM/контейнере с минимальными правами и ограничением памяти/CPU; при обнаружении аномалий — убивать процесс. 7. Обновлять JRE и библиотеки (патчи против известных gadget‑уязвимостей). 8. При невозможности перейти от сериализации — использовать строгую программную проверку входящих классов (ручной allowlist) и избегать readObject в нестабильных типах. Короткий чек‑лист для внедрения - Отказаться от Java‑сериализации для внешних данных, если можно. - Если нет — применять ObjectInputFilter с allowlist. - Подписывать/шифровать данные. - Ограничивать ресурсы и запускать в изолированном окружении. - Логировать и обновлять зависимости. Если нужно — могу привести конкретный пример ObjectInputFilter‑реализации или показать схему подписи/проверки для вашего протокола.
Основные риски
- Удалённое выполнение кода (RCE) через gadget‑цепочки (например, известные уязвимости в commons‑collections, spring и т.п.).
- Отказ в обслуживании: большое/рекурсивное/злонамеренно сконструированное дерево объектов → OOM/CPU.
- Информационное раскрытие: десериализация внутренних типов даёт доступ к чувствительным полям.
- Подмена/повреждение данных, обход авторизации.
- Зависимость от классов в classpath — загрузка неожиданных типов.
Как уменьшить риск (рекомендации)
1. По возможности не использовать Java‑сериализацию для внешнего ввода: заменить на безопасные форматы (JSON, protobuf, CBOR) с явной схемой и валидацией.
2. Использовать фильтрацию типов (allowlist) при десериализации: ObjectInputFilter (в Java 9+Java\ 9+Java 9+) или setObjectInputFilter. Пример:
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.myapp.Safe*;java.lang.String;java.lang.Integer;!*"
);
in.setObjectInputFilter(filter);
Object obj = in.readObject();
Это позволяет отклонять любые типы, не в списке разрешённых.
3. Ограничивать ресурсы: максимальная глубина объектов, максимальное количество элементов, общий размер — через фильтр или проверкой после разбора.
4. Подписывать/шифровать поток сериализованных данных: отправитель подписывает байты (MAC/PKI), получатель проверяет подпись перед десериализацией — защита от подделки.
5. Валидировать объекты после десериализации: проверять поля и состояния, не доверять сразу.
6. Песочница/изолированный процесс: выполнять десериализацию в отдельном JVM/контейнере с минимальными правами и ограничением памяти/CPU; при обнаружении аномалий — убивать процесс.
7. Обновлять JRE и библиотеки (патчи против известных gadget‑уязвимостей).
8. При невозможности перейти от сериализации — использовать строгую программную проверку входящих классов (ручной allowlist) и избегать readObject в нестабильных типах.
Короткий чек‑лист для внедрения
- Отказаться от Java‑сериализации для внешних данных, если можно.
- Если нет — применять ObjectInputFilter с allowlist.
- Подписывать/шифровать данные.
- Ограничивать ресурсы и запускать в изолированном окружении.
- Логировать и обновлять зависимости.
Если нужно — могу привести конкретный пример ObjectInputFilter‑реализации или показать схему подписи/проверки для вашего протокола.