Обработка уязвимостей из-за десериализации в приложениях Java
22 марта 2022 г.Java — это язык программирования, который существует уже давно и продолжает оставаться актуальным и сегодня. Многие известные фирмы и проекты с открытым исходным кодом в значительной степени полагаются на Java. Без сомнения, Java — популярный язык программирования.
Но почему?
Java превосходно работает в большинстве ситуаций. Этому способствует объектно-ориентированный характер, эффективное выделение/освобождение памяти и стандартный синтаксис. Значит ли это, что Java — это «Святой Грааль» языка программирования?
Конечно, нет! Как у всего есть свои преимущества и недостатки, так и у Java есть свой набор проблем. В результате разработчики часто выбирают гибридное решение, в котором сочетаются несколько хорошо работающих технологий.
Когда дело доходит до уязвимостей, очень важно понимать, как хранятся объекты Java и как осуществляется доступ к ним. Как мы все знаем, Java — это объектно-ориентированный язык программирования, а это означает, что классы создаются как экземпляры для доступа к его методам и данным.
Геттеры и сеттеры — это два популярных метода, используемых для обеспечения инкапсуляции данных в программе. Это одна из наиболее важных функций Java с точки зрения безопасности. Вся обработка происходит в основном во время компиляции и выполнения, а это означает, что любая выделенная память является временной и энергозависимой.
Это не очень хорошая идея, когда речь идет о создании приложений или систем, которые должны сохранять информацию во время сеансов. Должен быть какой-то механизм хранения для хранения и извлечения любой части информации. Этот механизм реализуется в Java с помощью сериализации и десериализации.
Сериализация, десериализация и поток байтов
[Источник] (https://www.geeksforgeeks.org/serialization-in-java/)
Как упоминалось ранее, Java широко использует объекты для создания, доступа и управления данными класса. Однако этот объект удаляется сборщиком мусора, когда он больше не используется.
Допустим, этот объект нужно, скажем, например, сохранить на диск или передать по сети. Его необходимо преобразовать в формат, подходящий для хранения или передачи потока байтов.
Таким образом, сериализация — это процесс преобразования объекта или экземпляра в поток байтов. Этот поток байтов не содержит фактического кода. Вместо этого он содержит информацию в виде битов и байтов, которые представляют объект. Как правило, чтобы преобразовать объект в поток байтов, класс экземпляра должен реализовать интерфейс 'сериализуемый'.
Ниже приведен фрагмент, показывающий процесс сериализации:
Как мы видим выше, класс «Демо» реализует сериализуемый интерфейс. Далее мы вызываем метод writeObject для экземпляра ObjectOutputStream и передаем сериализуемый объект в качестве аргумента.
Как вы могли догадаться, десериализация — это прямо противоположный процесс, когда мы создаем исходный объект из массива байтов. При десериализации объекта он не использует конструктор для трассировки значений. Скорее, он создает новый объект и использует отражение для записи данных в поля (это то же самое, как сериализация использует отражение для очистки всех данных, которые необходимо сериализовать).
Обратитесь к приведенному ниже фрагменту, чтобы понять процесс десериализации:
[Источник] (https://ray.so/?title=Deserialization.java&theme=midnight&spacing=16&background=true&darkMode=true&code=IHRyeQogICAgICAgIHsgICAKICAgICAgICAgICAgLy8gUmVhZGluZyB0aGUgb2JqZWN0IGZyb20gYSBmaWxlCiAgICAgICAgICAgIEZpbGVJbnB1dFN0cmVhbSBmaWxlID0gbmV3IEZpbGVJbnB1dFN0cmVhbShmaWxlbmFtZSk7CiAgICAgICAgICAgIE9iamVjdElucHV0U3RyZWFtIGluID0gbmV3IE9iamVjdElucHV0U3RyZWFtKGZpbGUpOwogICAgICAgICAgICAgIAogICAgICAgICAgICAvLyBNZXRob2QgZm9yIGRlc2VyaWFsaXphdGlvbiBvZiBvYmplY3QKICAgICAgICAgICAgb2JqZWN0MSA9IChEZW1vKWluLnJlYWRPYmplY3QoKTsKICAgICAgICAgICAgICAKICAgICAgICAgICAgaW4uY2xvc2UoKTsKICAgICAgICAgICAgZmlsZS5jbG9zZSgpOwogICAgICAgICAgICAgIAogICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4oIk9iamVjdCBoYXMgYmVlbiBkZXNlcmlhbGl6ZWQgIik7CiAgICAgICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbigiYSA9ICIgKyBvYmplY3QxLmEpOwogICAgICAgICAgICBTeXN0ZW0ub3V0LnByaW50bG4oImIgPSAiICsgb2JqZWN0MS5iKTsKICAgICAgICB9CiAgICAgICAgICAKICAgICAgICBjYXRjaChJT0V4Y2VwdGlvbiBleCkKICAgICAgICB7CiAgICAgICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbigiSU9FeGNlcHRpb24gaXMgY2F 1Z2h0Iik7CiAgICAgICAgfQ&language=d)
Здесь мы используем метод readObject в отличие от writeObject (используемого во время сериализации).
Прежде чем мы перейдем к уязвимости в десериализации, давайте обсудим инструмент, который может помочь вам распознать уязвимость. В конце концов, вы не можете решить проблему, если не знаете, что она существует!
WhiteSource Cure — замечательный бесплатный инструмент для исправления пользовательского кода, уделяющий большое внимание безопасности. Подход заключается в написании кода, в основном свободного от любых возможных уязвимостей, и он интегрируется прямо в вашу IDE.
После выявления уязвимого фрагмента кода и пометки его для проверки предоставляется исправление, позволяющее внести необходимые корректировки, чтобы сделать код менее уязвимым. Он также производит подробный анализ того, что было не так с существующим кодом и как исправление поможет исправить ошибку. Это возможно с помощью статического анализа кода.
Давайте посмотрим, как исправление поможет вам написать код, устойчивый к атакам путем внедрения кода SQL:
Ниже приведены два фрагмента запроса до и после исправления:
[Источник] (https://ray.so/?title=DBUtil.java&theme=crimson&spacing=16&background=true&darkMode=true&code=cHVibGljIHN0YXRpYyBVc2VyIGdldFVzZXJJbmZvKFN0cmluZyB1c2VybmFtZSkgdGhyb3dzIFNRTEV4Y2VwdGlvbnsKCQlpZiAodXNlcm5hbWUgPT0gbnVsbCB8fCB1c2VybmFtZS50cmltKCkubGVuZ3RoKCkgPT0gMCkKCQkJcmV0dXJuIG51bGw7IAoJCQoJCUNvbm5lY3Rpb24gY29ubmVjdGlvbiA9IGdldENvbm5lY3Rpb24oKTsKCQlTdGF0ZW1lbnQgc3RhdGVtZW50ID0gY29ubmVjdGlvbi5jcmVhdGVTdGF0ZW1lbnQoKTsKCQlSZXN1bHRTZXQgcmVzdWx0U2V0ID1zdGF0ZW1lbnQuZXhlY3V0ZVF1ZXJ5KCJTRUxFQ1QgRklSU1RfTkFNRSxMQVNUX05BTUUsUk9MRSBGUk9NIFBFT1BMRSBXSEVSRSBVU0VSX0lEID0gJyIrIHVzZXJuYW1lICsiJyAiKTsKfQ&language=java)
После исправления:
[Источник] (https://ray.so/?title=DBUtil2.java&theme=meadow&spacing=16&background=true&darkMode=true&code=cHVibGljIHN0YXRpYyBVc2VyIGdldFVzZXJJbmZvKFN0cmluZyB1c2VybmFtZSkgdGhyb3dzIFNRTEV4Y2VwdGlvbnsKCQlpZiAodXNlcm5hbWUgPT0gbnVsbCB8fCB1c2VybmFtZS50cmltKCkubGVuZ3RoKCkgPT0gMCkKCQkJcmV0dXJuIG51bGw7IAoJCQoJCUNvbm5lY3Rpb24gY29ubmVjdGlvbiA9IGdldENvbm5lY3Rpb24oKTsKCQlQcmVwYXJlZFN0YXRlbWVudCBzdGF0ZW1lbnQgPSBjb25uZWN0aW9uCgkJCQkucHJlcGFyZVN0YXRlbWVudCgiU0VMRUNUIEZJUlNUX05BTUUsTEFTVF9OQU1FLFJPTEUgRlJPTSBQRU9QTEUgV0hFUkUgVVNFUl9JRCA9ICIgKyAiPyIpOwoJCXN0YXRlbWVudC5zZXRTdHJpbmcoMSwgdXNlcm5hbWUpOwoJCVJlc3VsdFNldCByZXN1bHRTZXQgPXN0YXRlbWVudC5leGVjdXRlUXVlcnkoKTsgLyogQkFEIC0gdXNlciBpbnB1dCBzaG91bGQgYWx3YXlzIGJlIHNhbml0aXplZCAqLwo&language=java)
Если мы внимательно посмотрим, то увидим небольшую разницу в строке запроса после исправления (2-е изображение). Входные данные не интерполируются напрямую со строкой запроса, и входные данные сначала проверяются на их достоверность (если это обычная строка или оператор SQL).
Также есть дополнительный комментарий, в котором говорится: «Плохой пользовательский ввод всегда должен быть санирован», что он и делает. Для получения полного кода, подробного объяснения уязвимости и исправления посетите [WhiteSource] (https://cure.whitesource.io/#/report/71cbdad81bba11ecbe330242ac110002/cwe/89/nodeId/7670d9801bba11eca52e0242ac110002/runId/2).
Этот инструмент исправления безопасности не ограничивается обнаружением уязвимостей SQL, но и любых уязвимостей, будь то атака XSS или уязвимость десериализации, о которых мы поговорим далее.
Проблема с десериализацией и обходными путями
До сих пор мы видели, как сериализация и десериализация создают среду для хранения и извлечения объектов Java. В то же время важно знать, что при десериализации существует потенциальная опасность.
Уязвимость десериализации Java возникает, когда злоумышленник пытается вставить измененный сериализованный объект в систему, что приводит к компрометации системы или ее данных.
Учитывайте возможность выполнения произвольного кода при десериализации сериализованного элемента. Причиной такой эксплуатации является то, как на самом деле происходит процесс десериализации.
Как обсуждалось выше, мы понимаем, что Java не использует конструктор класса во время десериализации, а использует отражение для вставки полей данных в пустой объект. Поскольку конструктор не вызывается, проверка, выполненная в конструкторе, также пропускается, создавая лазейку для злоумышленника, чтобы добавить вредоносный код.
В качестве простого примера мы можем рассмотреть код примера десериализации, показанный выше. Мы видим, что при десериализации поток извлекается из файла (file.ser). Манипуляции с потоком байтов — это, по сути, вмешательство в сериализованный файл для изменения или добавления других значений. Таким образом, когда обработанный файл передается в потоковом режиме, измененные изменения также отражаются в программе.
Хотя это уже вредно, что еще хуже, такие уязвимости еще больше подвержены [цепочкам гаджетов] (https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated- Discovery-of-Deserialization-Gadget-Chains-wp.pdf), в котором изменены значения полей и может быть выполнено удаленное выполнение кода.
Для защиты от этих уязвимостей можно использовать несколько подходов, некоторые из них:
- При обработке объектов из десериализованного потока байтов одним из очевидных способов является применение базовой очистки ввода.
- Разрешение десериализации только определенных типов (классов) объектов является еще одним важным компонентом смягчения небезопасных атак десериализации. Это устраняет любую неопределенность в вашем приложении и является изящным методом, позволяющим избежать сбоев приложения или DoS-атак.
- Такие ограничения могут быть применены путем включения подхода белого списка. Подход с использованием белого списка более безопасен, поскольку программа десериализует только объекты, принадлежащие предварительно утвержденному набору классов.
- Наконец, самый надежный способ устранения таких уязвимостей — запретить использование сериализации и десериализации в приложении. Понятно, что это трудный и длительный процесс перепроектирования приложения, но это предпочтительнее, если вы не рискуете, когда речь идет о безопасности.
Заключение
Какой бы метод вы ни использовали для предотвращения таких атак и исправления уязвимостей, суть в том, что вы никогда не должны доверять входным данным, даже если они исходят из законных источников или приложения (а не пользователя). Перед обработкой входных данных выполнение базовых проверок с использованием таких инструментов, как WhiteSource Cure, может помочь предотвратить серьезное использование.
Оригинал