Стандартная библиотека Java предоставляет класс ObjectInputStream, который предлагает удобный способ десериализации объектов Java. К сожалению, по умолчанию этот способ небезопасен. Использование этого класса может открыть двери для атак десериализации Java, которые в худшем случае могут привести к выполнению произвольного кода.

Недавно я обнаружил, что библиотека Spring Security OAuth2 может быть уязвима для такой атаки. К счастью, для успешной атаки есть одно важное условие, с которым противнику может быть трудно справиться. Тем не менее, я подумал, что было бы лучше сделать библиотеку немного безопаснее, и сопровождающие проекта любезно приняли участие. Вот подробности.

(Первоначально опубликовано на https://blog.gypsyengineer.com)

Проблема

Spring Security OAuth2 может хранить информацию об аутентификации и данные пользователя в базе данных SQL или Redis. Перед сохранением данных в базе данных библиотека сериализует их с помощью механизма сериализации Java по умолчанию, предлагаемого ObjectOutputStream. Затем, после чтения данных из базы данных, библиотека небезопасно десериализует их с помощью класса ObjectInputStream. См. SerializationUtils класс:

public static  T deserialize(byte[] byteArray) {
         ObjectInputStream oip = null;
         try {
             oip = new ConfigurableObjectInputStream(
                    new ByteArrayInputStream(byteArray),
                    Thread.currentThread().getContextClassLoader());
             @SuppressWarnings("unchecked")
             T result = (T) oip.readObject();

Класс ConfigurableObjectInputStream, предоставляемый Spring Framework, просто обертывает класс InputObjectStream без добавления проверки безопасности.

Это означает, что если злоумышленник может поместить вредоносные данные в базу данных, то в худшем случае он может выполнить произвольный код и, как следствие, поставить под угрозу все приложение. Однако есть несколько требований для успешного использования эксплойта.

Во-первых, злоумышленник должен создать гаджет десериализации, используя классы, доступные в JVM приложения. Скорее всего, это не должно быть большой проблемой, поскольку стандартная библиотека Java предоставляет множество опасных классов. Кроме того, приложение может загружать множество библиотек, которые также могут помочь в создании гаджета десериализации.

Во-вторых, чтобы воспользоваться этим недостатком десериализации, злоумышленник должен найти способ, как он может поместить вредоносные данные в базу данных. Например, он может попытаться найти SQL-инъекцию. Однако это может быть не так просто, поэтому выполнить это требование может быть сложно.

Я обнаружил проблему, просмотрев код. Затем я сообщил об этом команде Pivotal Security. Но они решили не рассматривать это как уязвимость в библиотеке из-за второго требования выше. Однако они приветствовали патч, который добавляет меру глубокой защиты для предотвращения таких атак десериализации.

Решение

Чтобы предотвратить уязвимости десериализации Java, приложение должно ограничить набор классов, которые могут быть десериализованы. Один из лучших способов - реализовать белый список разрешенных классов. Существует три способа реализации такого механизма защиты:

  1. Используйте API фильтрации, представленный в JEP 290.
  2. Используйте класс ValidatingObjectInputStream из Apache Commons IO.
  3. Переопределите метод ObjectInputStream.resolveClass() и внесите его в белый список.

Первый способ потребует, чтобы приложение использовало версии Java с JEP 290. Второй способ добавит дополнительную зависимость к Spring Security OAuth. Я решил пойти по третьему пути, поскольку он не вводит никаких дополнительных зависимостей:

  • Добавлен новый класс SaferObjectInputStream, который проверяет, разрешена ли десериализация классов.
  • Определил белый список классов как java.lang.*, java.util.* и org.springframework.security.*.
  • Обновлен класс RedisTokenStore для использования SaferObjectInputStream с белым списком.
  • Обновлены классы JDBC для использования SaferObjectInputStream с белым списком.

Обновление было выпущено в Spring Security OAuth2 2.3.7, но, к сожалению, решение вызвало проблему. Оказалось, что белый список слишком строг. Если приложение хранит пользовательские реализации токенов или сведений о пользователях, то в новой версии библиотеки не удастся десериализовать их из-за ограничительного белого списка. Более того, у пользователя не было возможности изменить белый список, чтобы приложение снова заработало. О проблеме сообщили несколько пользователей.

Я исправил проблему в 2.4.0, представив новый API, который позволяет пользователю указывать собственный белый список. Белый список по умолчанию по-прежнему содержит классы java.lang.*, java.util.* и org.springframework.security.*. Сначала я обновил библиотеку, чтобы по умолчанию применять белый список. Но затем сопровождающий проекта попросил меня сделать это опцией согласия. Причина в том, что 2.4.0 является второстепенным выпуском, поэтому он не должен создавать никаких проблем для приложения при переходе на новую версию.

Вот что я сделал, чтобы снова сделать десериализацию великолепной:

  1. Добавлен новый SerializationStrategy интерфейс с двумя реализациями: DefaultSerializationStrategy и WhitelistedSerializationStrategy.
  2. DefaultSerializationStrategy использует небезопасную десериализацию.
  3. WhitelistedSerializationStrategy позволяет указать белый список классов, которые разрешены для десериализации. Если классы не указаны, стратегия использует белый список по умолчанию: java.lang.*, java.util.* и org.springframework.security.*.
  4. В класс SerializationUtils добавлено новое статическое поле SerializationStrategy. Стратегия по умолчанию небезопасна DefaultSerializationStrategy.
  5. Стратегию можно изменить, вызвав новый метод SerializationUtils.setSerializationStrategy() или указав стратегию в файле META-INF/spring.factories.

Теперь пользователь может включить белый список по умолчанию с помощью следующего кода:

SerializationUtils.setSerializationStrategy(new WhitelistedSerializationStrategy());

Или пользователь может реализовать свою собственную стратегию сериализации, например, расширив класс WhitelistedSerializationStrategy:

package org.custom.impl.oauth2;
public class CustomSerializationStrategy
    extends WhitelistedSerializationStrategy {
        private static final List<String> ALLOWED_CLASSES 
                = new ArrayList<String>();
        static {
            ALLOWED_CLASSES.add("java.lang.");
            ALLOWED_CLASSES.add("java.util.");
            ALLOWED_CLASSES.add("org.springframework.security.");
            ALLOWED_CLASSES.add("org.custom.impl.oauth2.");
        }
        CustomSerializationStrategy() {
            super(ALLOWED_CLASSES);
        }
    }
}

А вот как пользователь может указать WhitelistedSerializationStrategy стратегию в META-INF/spring.factories файле:

org.springframework.security.oauth2.common.util.SerializationStrategy = \
org.springframework.security.oauth2.common.util.WhitelistedSerializationStrategy

Конечно, META-INF/spring.factories также позволяет использовать индивидуальную стратегию сериализации.

Заключение

По умолчанию библиотека Spring Security OAuth2 по-прежнему использует десериализацию небезопасным способом, что может сделать приложение уязвимым. Хотя описанный недостаток десериализации может быть трудно использовать, может быть лучше включить WhitelistedSerializationStrategy на всякий случай.

использованная литература

  1. Spring Security OAuth
  2. ObjectInputStream
  3. JEP 290
  4. ValidatingObjectInputStream

Первоначально опубликовано на https://blog.gypsyengineer.com 16 ноября 2019 г.

Подпишитесь на Infosec Write-ups, чтобы увидеть больше таких замечательных статей.