
Друзья, привет! Это Angara Security и наш эксперт по кибербезопасности Григорий Елфимов. Он проанализировал безопасность популярного на российском рынке веб-приложения Naumen Service Desk.
Naumen Service Desk — один из самых распространённых helpdesk-продуктов на отечественном рынке. Он используется в госсекторе, корпоративных ИТ-службах, интеграционных решениях. Именно поэтому любые найденные в нём уязвимости потенциально опасны — они используются в реальных атаках, которые могут привести к серьёзным последствиям.
В этой статье я попробовал собрать свой опыт тестирования веб-приложения этого продукта с точки зрения безопасности, в том числе описал найденные zero-day уязвимости, которые приводят к удаленному исполнению кода ОС.
Пару слов о Naumen SD
Naumen Service Desk — это программный продукт для автоматизации службы поддержки и управления ИТ-услугами (ITSM), разработанный российской компанией Naumen. Он предназначен для организации работы сервисных подразделений, обработки обращений пользователей и управления инцидентами, запросами на обслуживание и другими ИТ-процессами.
Само веб-приложение разработано на основе фреймворка GWT (Google Web Toolkit), который предоставляет возможность писать код на Java и транслировать его в JavaScript, что позволяет писать фронтенд-приложения, используя Java-синтаксис, избегая ручной работы с DOM и JavaScript.
Главной особенностью данного фреймворка также является собственный механизм вызова серверных методов из клиентского кода (GWT-RPC). Выглядит это в современном мире веба крайне непривычно. Типичный запрос на аутентификацию представлен на рисунке 1.

На практике — это самый примитивный вариант запроса. Обычно его тело не помещается целиком во вкладке Request и содержит кучу наименований Java-классов, хэш-значений, строк с данными, сигнатур, HTML-кода и прочего.
У неподготовленного пользователя от увиденного сначала случится сердечный приступ, а после возникнет желание поиграться с десериализацией на Java и, конечно же, пихнуть кавычку в каждое значение, отделенное символом |. К сожалению, с этим все не так просто, и представленная из коробки десериализация всего и вся имеет свои встроенные механизмы безопасности.
Несмотря на то, что фреймворк GWT устарел, и даже сам Google отдал его на опенсорс, где все и застыло, в интернете еще можно найти актуальные материалы по его тестированию. Полезная информация на эту тему представлена в статье, и изложенное в ней даже реализовано в утилите с, не побоюсь этого слова, легендарным названием gwtmap.
К счастью (или нет), в этой статье мы не будем затрагивать данный фреймворк и прикасаться к миру чудес от Google. У продукта Naumen Service Desk также существует понятное и документированное REST API для интеграции с внешними приложениями и расширения сценариев использования системы. В нем-то и была найдена уязвимость, приводящая нас к возможности удаленного исполнения команд ОС неаутентифицированным пользователем.
Про REST API в Naumen
Описанные уязвимости были обнаружены в версии 4.17.5.13.baee52d7 (1056), хотя вполне вероятно актуальны и в более ранних. Типичная страница аутентификации обычно любезно предоставляет нам информацию о версии и выглядит следующим образом (Рис. 2):

REST API реализовано в веб-приложении по умолчанию и из коробки предоставляет несколько методов, которые обычно располагаются по пути /sd/services/rest/*. Стандартное API хорошо документировано и описывает большинство существующих методов и возможностей.
Если попытаться кратко описать внутреннее устройство приложения, то можно сказать, что приложение хранит и обрабатывает различные сущности в виде объектов FQDN-типа и идентификатором самого объекта. Например, сотрудники представлены в типе employee, а конкретный сотрудник может быть получен по employee$ID, где ID – число. Так, в приложении могут существовать различные сущности вроде file$1337, employee$228, serviceCall$501 и т.п.
REST API в большинстве своем предоставляет функционал по работе с этими объектами – их создание, редактирование, поиск или удаление. Пример различных запросов к API, и что они делают из документации (Рис. 3):

Однако добрые 99% функционала REST API не доступны без аутентификации. Для доступа к API в веб-приложении используются Cookie JSESSIONID после прохождения стандартной формы входа, либо к запросам добавляется специальный параметр accessKey для передачи уникального ключа аутентификации.
Множественные IDOR
Не могу не отметить, что при наличии в Naumen Service Desk учетной записи с минимальным набором прав, зачастую через REST API можно вытаскивать полезную информацию, например, файлы, тексты обращений (это же сервис поддержки как-никак) и списки пользователей. Можно утверждать, что настроенное из коробки приложение подвержено множественным уязвимостям IDOR в API, хотя часто это касается только получения списка объектов методом find и чтения атрибутов через метод get, а также чтения файлов через метод get-file.
Примеры полезных запросов представлены на рисунках 4–6.



Unauthorized RCE
Уязвимость удаленного исполнения команд ОС была обнаружена в модуле переадресации запросов. Данный модуль может быть дополнительно использован при обращении на создание, редактирование или удаление объекта для переадресации пользователя по заданному URL и открытия на странице диалогового окна с заданным сообщением после обработки действий.
Запрос на редактирование объекта и переадресацию в ответе выглядит следующим образом (Рис. 7):

Здесь мы добавляем к обычному запросу на редактирование объекта четыре параметра: messageCode, redir, objparams и mode. Каждый из них имеет свой смысл и допустимые значения, но нам это не особо важно. Важно, что в ответе мы получаем переадресацию на диалоговое окно ошибки, а значит модуль переадресации свое дело сделал.
На что еще стоит обратить внимание в этом запросе? Мы пытаемся редактировать несуществующий объект notexist$2024 (на самом деле здесь можно писать что угодно – до редактирования и поиска объекта приложение не дойдет).
Почему? Потому что мы пытаемся редактировать объект без аутентификации, на что и указывает текст ошибки в перенаправлении (переадресации??): не получается привести java.String к EmployeeUser. Из этого можно сделать вывод, что отсутствие Cookie приложение обработало как пустую строку и попыталось из неё получить объект пользователя, что и привело к ошибке ещё до начала обработки команды по редактированию объекта.
Но самое главное, что отсутствие аутентификации не помешало нам воспользоваться функционалом модуля переадресации! Дальше дело за малым, и в игру вступает ОНА (Рис. 8).

Кавычка в деле
На этом можно было бы и закончить, но я продолжу. Если хорошо присмотреться к выводу, можно увидеть слово groovy в расширении файла, в котором произошла ошибка.
Groovy — это динамический язык программирования для JVM (Java Virtual Machine), который сочетает в себе черты Python, Ruby и Java. Худшее изобретение человечества!
Чтобы убедиться, что наш ввод встраивается и обрабатывается как программный код, можно провести следующий эксперимент: попробовать вызвать существующий метод toString и несуществующий метод toStringNE и посмотреть, что нам ответят.
Из текста сообщения об ошибке мы можем увидеть контекст, в который попадает наш ввод:
s.restModule.getMessage('rate<МЫ ЗДЕСЬ>');
Значит, чтобы встроить полезную нагрузку и не сломать синтаксис, нам нужно:
1. Выйти из контекста строки
2. Корректно завершить работу метода getMessage
3. Мягко избавиться от оставшейся части строки: ');
И так как мы хотим провести эксперимент с методом toString, наша полезная нагрузка примет следующий вид (Рис. 9–10):
rate'.toString());printl('


Данный эксперимент подтверждает, что мы имеем инъекцию в Groovy-script, а значит быстренько бежим за полезной нагрузкой для RCE. Но не тут-то было!
В случае с groovy одна из самых простых возможностей исполнения кода – создать строковый литерал с командой ОС и вызвать у него метод execute (Рис. 11).

Однако в нашем случае это не сработает (Рис. 12).


Из сообщения об ошибке SandboxSecurityException нетрудно догадаться, что наш скрипт исполняется в некоторой песочнице.
Методом проб и ошибок было обнаружено, что, хотя мы и работаем в песочнице, она не запрещает читать и писать содержимое файлов ОС. Исходя из этого, и того факта, что Naumen Service Desk функционирует на веб-сервере tomcat и включает в свой состав JSP файлы, было принято решение с помощью инъекции записать Web-Shell на сервер.
В роли Web-Shell выступил следующий файл (Рис. 13):

Здесь нет ничего необычного, проверяем статически заданный пароль из параметра password и исполняем код из параметра cmd.
Чтобы не иметь проблем с передачей содержимого в HTTP-запросе и для обхода простых блокировок WAF было принято решение закодировать команду на создание файла и содержимое файла в base64, чтобы затем исполнить строку как код через «местный» Eval.
JSP файлы обитают в директории веб-сервера /sd/, локальный путь к которой по умолчанию выглядит так: /opt/naumen/nausd4/tomcat/webapps/sd/.
Код, который мы поместим в Eval, будет выглядеть следующим образом (Рис. 14):

Предварительно закодируем его в base64 (Рис. 15):

Для того, чтобы осуществить задумку с Eval и кодированием в base64, мы модифицируем наш payload, и он примет следующий вид:
rate'%2bEval.me(new%20String('<PAYLOAD>'.decodeBase64())));println('
На месте <PAYLOAD> мы поместим нашу заготовленную строку base64 с созданием JSP файла Web-Shell.
Остается только отправить запрос со всей подготовленной полезной нагрузкой (Рис. 16).

Несмотря на то, что ответ содержит сообщение об ошибке, мы можем обратиться к созданному файлу и убедиться, что имеем возможность удаленного исполнения команд ОС (Рис. 17).

Стабильная альтернатива с нюансом
Хотя вся цепочка постфактум кажется элементарной, в процессе исследования постоянно возникали некоторые сложности. Главной сложностью было то, что время от времени веб-приложение отвечало по-разному на одни и те же запросы, не давая эксплуатировать уязвимость (Рис. 18–19).


В процессе поиска и эксплуатации описанной выше уязвимости также был найден иной способ эксплуатации уязвимости, который работал стабильно, но требовал наличия учетной записи пользователя и ряда других условий.
Данная уязвимость также приводит к инъекции в Groovy-скрипт в модуле переадресации, однако задействует дополнительный механизм переадресации, а именно кастомные переадресации.
Кастомные переадресации позволяют переадресовывать пользователя произвольным образом, однако нельзя просто в HTTP-запросе указать желаемые параметры и получить уязвимость OpenRedirect. Кастомная переадресация ссылается на внутренние rest-модули, которые могут быть вызваны при добавлении в HTTP-запрос параметров func и params и при указании значения параметра redir в custom.
В результате мы получим аналогичную инъекцию в groovy-скрипт, только на этот раз полезная нагрузка будет находиться в параметре params. Содержимое полезной нагрузки будет аналогично предыдущему случаю, создание веб-шелла в base64 через Eval. Однако этот способ эксплуатации требует своих условностей (Рис. 20).

Во-первых, нам нужна учетная запись в веб-приложении. Во-вторых, уязвимость присутствует при обращении к одной из трех конечных точек, которые представлены ниже:
- https://host/sd/services/rest/create/{uuid};
- https://host/sd/services/rest/delete/{uuid};
- https://host/sd/services/rest/edit/{uuid}.
В-третьих, нам нужен доступ к какому-либо объекту в системе. На рисунке с эксплуатацией приведен пример редактирования объекта сотрудника, где в качестве объекта используется объект текущего пользователя. Его UUID можно увидеть в личном кабинете, в адресной строке, при переходе на страницу персональных настроек (Рис. 21).

Ну и в-четвертых, нам нужно найти среди содержимого rest-модулей такую функцию, чтобы она принимала в качестве параметра один аргумент типа String и при этом не возвращала ошибку, когда параметр содержит нашу полезную нагрузку.
Если в предыдущем примере мы имели инъекцию в таком виде:
String.format("s.restModule.getMessage('%s');", messageCode),
то нынешняя инъекция представляет собой следующее:
String.format("return %s(%s);", new Object[] { func, params }).
При этом будет проведена предварительная проверка, что такая функция существует в указанном модуле и принимает указанное количество параметров переданного типа. Тема с функциями rest-модулей и их вызовом подробно описана в документации к REST API Naumen.
В ходе исследования была выбрана функция modules.restModule.getMessage, так как она подходит под необходимые условия и является стандартной функцией из коробки (Рис. 22).

Полезная нагрузка в параметре params при этом изменилась несущественно:
'qwe'%2bEval.me(new%20String('<PAYLOAD>'.decodeBase64())));println(
В результате успешно получаем веб-шелл (Рис. 23).

Заключение
Для эксплуатации уязвимости злоумышленник может загрузить Web-Shell одним HTTP-запросом и использовать его для удаленного исполнения команд операционной системы. При интеграции Naumen Service Desk с аутентификацией пользователей через LDAP, потенциальный злоумышленник сможет получить доступ во внутреннюю сеть и аутентификационные данные доменного пользователя из файла /opt/naumen/nausd4/conf/ldap-settings.xml, а также учетные данные для доступа к СУБД в файле /opt/naumen/nausd4/conf/dbaccess.properties.
Полученные возможности могут позволить проникнуть и закрепиться во внутреннем периметре организации, что существенно расширяет дальнейший вектор атаки. При этом конфиденциальность, целостность и доступность веб-приложения сервиса поддержки также могут быть полностью нарушены.
Для исправления уязвимостей с исполнением кода достаточно обновить продукт Naumen Service Desk до последней версии. В случае со множественным IDOR, это просто лишнее напоминание о том, что при разворачивании приложения стоит быть внимательным и не игнорировать рекомендации и предупреждения безопасности из официальной документации.
DoomeR18G
Один вопрос, Григорий занимался этим в свободное время?