Переходим ко второй атаке, которая связана с двоичной сериализацией в Java. Мы, как ты помнишь, были несколько ограничены в возможностях: да, поменять данные объектов можно, а вот отправлять произвольные объекты и получить в итоге RCE (как бывает в случае других языков) можем лишь при определенных условиях :).

Java, как мы видим, берет на себя все необходимое для сериализации и десериализации. Но это не всегда удобно. Есть ряд ситуаций, когда программисту необходимо дополнить или изменить эти механизмы. Например, добавить шаги, выполняемые несериализуемым родителем класса в конструкторе с параметрами. Для этих целей программист может переопределить для класса методы readObject и writeObject и добавить туда необходимые команды.

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
    in.defaultReadObject();
    System.out.println("It happens");
}

Самый важный момент для нас с точки зрения атаки в том, что readObject всегда вызывается раньше приведения типа (casting) объекта.

ObjectInputStream ois = new ObjectInputStream(fis);
Employee emp1 = (Employee) ois.readObject();

Другими словами, сначала будет вызван readObject и создастся некий объект, а уже его попробуют привести к нужному классу.

Эти два факта нам дают вот что. В приложение мы можем отправлять любой объект, класс которого «известен» приложению (то есть в classpath приложения), и при этом будет вызван readObject этого класса.

Правда, вторая необходимая для атаки часть зависит от конкретных уязвимостей. Нужен класс с уязвимым readObject. Поясню на примере. Есть такая библиотека, как Apache Common Uploads, она используется для управления загрузкой файлов на сервер и очень распространена. В ней есть куча различных классов. Один из них — DiskFileItem.

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

Но получается, что класс DiskFileItem сериализуем. Кроме того, у него переопределены writeObject и readObject. Код в readObject очень размазан, так что приведу лишь его суть. В соответствии с указанной выше логикой, при десериализации должен быть создан временный файл и в него должен попасть контент из объекта. Важно и то, что путь до временного файла состоит из двух частей: путь к директории хранится в private переменной DiskFileItem (repository), а имя задается случайным образом в процессе readObject. Суть же уязвимости библиотеки заключалась в том, что она «не беспокоилась» о null-байте в имени директории в repository. В repository мы могли указать полный путь до файла, а не только до директории (/any/path/in/OS/including.file.name\x00), а добавляемое при readObject случайное имя файла обрезалось бы уже нативными библиотеками ОС (для которых null-байт — конец строки).

Таким образом, если у нас есть приложение, которое читает сериализуемые данные, то мы можем отправить ему объект класса DiskFileItem с переменной repository, исправленной на произвольный путь (с private поможет предыдущая задачка). И в итоге при десериализации приложение выполнит readObject класса DiskFileItem и создаст файл с нашим контентом в произвольном месте. На практике это дает нам RCE.

Конечно, был выпущен патч, в котором readObject при десериализации проверяет присутствие null-байта. Да и к тому же с какой-то из версий Java 1.7, саму возможность атаки с null-байтом прикрыли. Тем не менее остается возможность контролировать полный путь до директории, что тоже часто очень существенно.

Важнее всего то, что приложение ждет на вход объект определенного класса (например, Employee), а мы ему подаем DiskFileItem и при этом из-за readObject можем как-то влиять (атаковать) на саму ОС или приложение.

Если крупное приложение работает с сериализуемыми данными, то оно должно быть уверено, что ни в одном сериализуемом классе (из тех, что есть в classpath) нет readObject с уязвимостями.

Я здесь сконцентрировал внимание на readObject (writeObject). Но есть еще readResolve (writeReplace), которая представляет аналогичный интерес. А также есть интерфейс Externalizable. Это дочерний класс Serializable, но с ним программист должен сам полностью описывать процесс сериализации и десериализации объекта.

Спасибо за внимание и успехов в познании нового!

Оставить мнение