Категория OWASP: MASVS-CODE: качество кода
Обзор
При хранении или передаче больших объемов данных объектов Java часто эффективнее сначала сериализовать данные. Затем данные проходят процесс десериализации в принимающем приложении, действии или поставщике, который в конечном итоге обрабатывает данные. В обычных условиях сериализация и десериализация данных выполняются без участия пользователя. Однако доверительные отношения между процессом десериализации и его целевым объектом могут быть нарушены злоумышленником, который может, например, перехватывать и изменять сериализованные объекты. Это позволит злоумышленнику выполнять атаки типа "отказ в обслуживании" (DoS), повышать привилегии и удаленно выполнять код (RCE).
Хотя класс Serializable часто используется для управления сериализацией, в Android есть собственный класс для этой цели – Parcel. С помощью класса Parcel данные объекта можно сериализовать в поток байтов и упаковать в Parcel, используя интерфейс Parcelable.
Это позволяет более эффективно транспортировать или хранить Parcel.
Тем не менее, класс Parcel следует использовать с осторожностью, поскольку он предназначен для высокоэффективного механизма передачи данных IPC, но не должен использоваться для хранения сериализованных объектов в локальном постоянном хранилище, так как это может привести к проблемам с совместимостью данных или их потере. Когда данные нужно прочитать, интерфейс Parcelable можно использовать для десериализации Parcel и преобразования обратно в данные объекта.
Существует три основных вектора эксплуатации десериализации в Android:
- Использование ошибочного предположения разработчика о том, что десериализация объектов, происходящих из пользовательского типа класса, безопасна. На самом деле любой объект, полученный из любого класса, может быть заменен вредоносным контентом, который в худшем случае может помешать работе загрузчиков классов этого или других приложений. Это вмешательство заключается во внедрении опасных значений, которые, в зависимости от цели класса, могут привести, например, к краже данных или взлому аккаунта.
- Использование методов десериализации, которые считаются небезопасными по своей природе. Например, CVE-2023-35669 – уязвимость, позволяющая локально повысить привилегии и внедрить произвольный код JavaScript через вектор десериализации ссылок на контент.
- Использование уязвимостей в логике приложения. Например, CVE-2023-20963 – уязвимость, связанная с локальным повышением привилегий, которая позволяла приложению скачивать и выполнять код в привилегированной среде из-за ошибки в логике пакета WorkSource в Android.
Влияние
Любое приложение, которое десериализует ненадежные или вредоносные сериализованные данные, может быть уязвимо для удаленного выполнения кода или DoS-атак.
Риск: десериализация ненадежных входных данных
Злоумышленник может воспользоваться отсутствием проверки пакетов в логике приложения, чтобы внедрить произвольные объекты. После десериализации они могут заставить приложение выполнить вредоносный код, что приведет к отказу в обслуживании (DoS), повышению привилегий и удаленному выполнению кода (RCE).
Такие атаки могут быть незаметными. Например, приложение может содержать намерение, ожидающее только один параметр, который после проверки будет десериализован. Если злоумышленник отправит второй, неожиданный вредоносный дополнительный параметр вместе с ожидаемым, это приведет к десериализации всех внедренных объектов данных, поскольку намерение обрабатывает дополнительные параметры как Bundle. Злоумышленник может воспользоваться этим, чтобы внедрить данные объекта, которые после десериализации могут привести к удаленному выполнению кода, компрометации или потере данных.
Предотвращение атак
Рекомендуем считать все сериализованные данные ненадежными и потенциально вредоносными. Чтобы обеспечить целостность сериализованных данных, проверяйте, соответствуют ли они классу и формату, которые ожидаются приложением.
Возможное решение – реализовать шаблон упреждающего просмотра для java.io.ObjectInputStream библиотеки. Изменив код, отвечающий за десериализацию, вы можете сделать так, чтобы в намерении десериализовался только явно указанный набор классов.
В Android 13 (уровень API 33) в классе Intent обновлены некоторые методы, которые считаются более безопасными альтернативами устаревшим методам обработки пакетов. Эти новые методы, такие как getParcelableExtra(java.lang.String, java.lang.Class) и getParcelableArrayListExtra(java.lang.String, java.lang.Class), проверяют типы данных, чтобы выявлять несоответствия, которые могут приводить к сбоям в работе приложений и использоваться для атак с повышением привилегий, например CVE-2021-0928.
В примере ниже показано, как можно реализовать безопасную версию класса Parcel.
Предположим, что класс UserParcelable реализует Parcelable и создает экземпляр пользовательских данных, которые затем записываются в Parcel. Для чтения сериализованного пакета можно использовать следующий более типобезопасный метод readParcelable:
Kotlin
val parcel = Parcel.obtain()
val userParcelable = parcel.readParcelable(UserParcelable::class.java.classLoader)
Java
Parcel parcel = Parcel.obtain();
UserParcelable userParcelable = parcel.readParcelable(UserParcelable.class, UserParcelable.CREATOR);
Обратите внимание, что в приведенном выше примере на Java в методе используется UserParcelable.CREATOR. Этот обязательный параметр сообщает методу readParcelable, какой тип данных ожидать, и работает эффективнее, чем устаревшая версия метода readParcelable.
Особые риски
В этом разделе собраны риски, которые требуют нестандартных стратегий устранения или были устранены на определенном уровне SDK. Они приведены здесь для полноты картины.
Риск: нежелательная десериализация объектов
Реализация интерфейса Serializable в классе автоматически приведет к тому, что все подтипы этого класса будут реализовывать интерфейс. В этом случае некоторые объекты могут унаследовать вышеупомянутый интерфейс, а значит, будут обработаны определенные объекты, которые не должны быть десериализованы.
Это может непреднамеренно увеличить направления атак.
Предотвращение атак
Если класс наследует интерфейс Serializable, то, согласно рекомендациям OWASP, метод readObject должен быть реализован следующим образом, чтобы избежать десериализации набора объектов в классе:
Kotlin
@Throws(IOException::class)
private final fun readObject(in: ObjectInputStream) {
throw IOException("Cannot be deserialized")
}
Java
private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}
Ресурсы
- Parcelables
- Parcel
- Serializable
- Намерение
- Краткая история уязвимостей десериализации в Android
- Android Parcels: The Bad, the Good and the Better (видео)
- Android Parcels: The Bad, the Good and the Better (слайды презентации)
- CVE-2014-7911. Повышение привилегий в Android <5.0 с помощью ObjectInputStream
- CVE-CVE-2017-0412
- CVE-2021-0928: несоответствие сериализации и десериализации Parcel
- Руководство OWASP