Проверяем закрытую уязвимость и получаем четыре новые CVE

    Меня попросили продолжить цикл статей про закрытие уязвимостей и про то, как их закрывают (нашу первую статью прочитать можно здесь). В прошлый раз мы выяснили, что даже если производитель рапортует о закрытии уязвимости, то на деле все может быть и не так.



    Критерии отбора:


    Критерии отбора на рассматриваемые уязвимости в этот раз были такими же (за тем исключением, что в этот раз хотелось посмотреть на уязвимости другого типа):

    • должен быть эксплойт – мы хотим посмотреть, что до обновления все было хорошо эксплуатируемо плохо, а после стало хорошо;
    • уязвимость должна быть критичной (в идеале RCE) и с высоким score;
    • продукт должен быть Open Source;
    • продукт должен быть не заброшенным и активно используемым;
    • уязвимость должна быть относительно новой;
    • как и всегда, главное, чтобы нам самим было интересно.

    Что и как я выбирал:


    Я зашел на vulners.com и попросил показать все эксплойты с exploit-db.com за последние пару недель. В этот раз в категории «веб» почти все эксплойты были за авторством Ihsan Sencan, но из-за того, что чаще всего они относятся к sql-инъекциям в старых не поддерживаемых приложениях и плагинах, я их убрал. Из оставшихся продуктов в категорию «не заброшен и активно развивается» попал только ProjeQtOr Project Management Tool 7.2.5 с уязвимостью CVE-2018-18924.
    Данная уязвимость удовлетворила все критерии отбора:

    • есть эксплойт;
    • уязвимость RCE (хоть и требует, чтобы пользователь был авторизован);
    • продукт вполне себе Open Source;
    • продукт не заброшен, за 2018 год было 28 релизов и только с sourceforge.net было 702 скачивания (причем большинство скачиваний обновления решающего проблему CVE, что, вероятнее всего, свидетельствует о том, что люди увидели CVE и стали обновлять);
    • CVE от 4 ноября, эксплойт от 25 октября, это удовлетворяет требованиям новизны;
    • я посмотрел на проблему и ее решение, мне стало интересно (об этом будет дальше).

    Разбираемся с ProjeQtOr Project Management Tool


    Читаем описание эксплойта и описание CVE на nist.gov, понимаем, что уязвима версия 7.2.5 только для авторизованного пользователя. А еще что в качестве изображения можно загрузить файл формата .shtml, на который хоть и будет высвечена ошибка “This file is not a valid image”, но файл все равно будет сохранен в изображения на сервере и доступен по прямой ссылке в host/files/images/image_name.



    Доступность по прямой ссылке это хорошо, но тут надо еще угадать имя, с которым файл будет загружен. Нам везет, и оно не случайное, а генерируется из текущего времени в формате: год, месяц, число, часы, минуты, секунды. В результате получается вот такое число 20181114140320. Дальше через нижнее подчеркивание идет ID пользователя, а затем оригинальное имя файла. Остается довольно много неизвестных:

    • часовой пояс на сервере;
    • не сбиты ли на сервере часы;
    • ID пользователя.

    И вновь нам везет: если загрузить валидное изображение, то все эти параметры нам сообщат. Дальше пробежаться по нескольким вариантам ссылок не составит труда (несколько, так как есть секунды, а в них сразу попасть сложно).



    В целом получить имя файла не является проблемой. Двигаемся дальше. И думаем, почему бы не загрузить просто php-скрипт? Пытаемся его загрузить, выскакивает такое же окно, но файл не появляется в директории. Пора смотреть в код!

    За загрузку изображения на сервер отвечает скрипт uploadImage.php в версии 7.2.5 нас интересуют строки с 100 по 117.

     if (substr($ext,0,3)=='php' or substr($ext,0,4)=='phtm') {
        if(@!getimagesize($uploadedFile['tmp_name'])) {
          $error=i18n('errorNotAnImage');
        } else {
          traceHack("Try to upload php file as image in CKEditor");
        }
      } else {
        if ( ! move_uploaded_file($uploadedFile['tmp_name'], $uploadfile)) {
          $error = htmlGetErrorMessage(i18n('errorUploadFile','hacking ?'));
          errorLog(i18n('errorUploadFile','hacking ?'));
        } 
      }
    }
    if (!$error) {
      if(@!getimagesize($uploadfile)) {
        $error=i18n('errorNotAnImage');
      }
    }
    

    Строка 100 отвечает за проверку расширения файла: если оно php или phtm, то файл отбрасывается и не сохраняется. Поэтому php файлы не появляются в директории “files/images/”. Строка 115 создает ошибку, которую мы видим, но ничего не делает с файлом.

    Ну ладно, давайте не отходить от эксплойта и зальем файл с расширением .shtml. Тут стоит сделать небольшое отступление и рассказать, что такое .shtml и с чем его едят.

    SHTML и SSI


    Определение с википедии:

    SSI (Server Side Includes — включения на стороне сервера) — несложный язык для динамической «сборки» веб-страниц на сервере из отдельных составных частей и выдачи клиенту полученного HTML-документа. Реализован в веб-сервере Apache при помощи модуля mod_include. Включенная в настройках по умолчанию веб-сервера возможность позволяет подключать HTML-файлы, поэтому для использования инструкций файл должен оканчиваться расширением .shtml, .stm или .shtm.

    Своими словами:

    SHTML — это такой HTML, который может выполнять наборы команд на стороне сервера. Из полезного там есть функция exec, которая выполняет произвольные команды на сервере (да, мы можем кодом на HTML скачать файл и запустить его).

    Вот пример кода для запуска произвольного кода:

    <!--#exec cmd=”ls” -->

    Хорошая новость в том, что этот функционал не включен по умолчанию на Apache2-сервере, а чтобы его включить надо потанцевать с бубном. За пару часов ковыряния конфига я смог заставить работать отдачу переменных окружения, но не команды. Вот мой SSI код:

    <html>
      <head> 
      	<title>thegeekstuff.com</title>
      </head>
      <body>
      	<p> 
      	  Today is <!--#echo var="DATE_LOCAL" --> 
    	<!--#exec cmd="ls" -->
    	</p>
      </body>
    </html>
    
    Его вывод:
    Today is Wednesday, 14-Nov-2018 17:29:14 MSK [an error occurred while processing this directive]
    

    Если кто-то расскажет, что надо написать в конфиге, чтобы оно отработало корректно, я бы с радостью прочитал.

    Эксплуатируем уязвимость


    Если звезды сошлись, вы сможете загрузить shtml файл и выполнить произвольные команды (или как я, посмотреть время на сервере).

    Смотрим патч


    Следующая версия — 7.2.6, но в ней нет никаких изменений относительно интересующей нас уязвимости (nist.gov опять обманул).

    Смотрим версию 7.2.7 и там кажется, что все исправлено (сами разработчики говорят, что все исправлено как раз в этой версии). Ключевых изменений два:

    1. Среди запрещенных расширений добавилось “shtm” (если первые 4 символа таких, сюда же попадает и shtml):

    if (substr($ext,0,3)=='php' or substr($ext,0,4)=='phtm' or substr($ext,0,4)=='shtm') {


    2. Файлы, которые не являются картинками, теперь удаляются:

     if(@!getimagesize($uploadfile)) {
        $error=i18n('errorNotAnImage');
      kill($uploadfile);
    }

    Казалось бы, можно расходиться, ведь некартинки удаляются, а shtml даже не пытается сохраниться. Но мне всегда не нравилось, если какую-то проблему пытаются решить черными списками. Для примера в некоторых странах компании запрещают соц.сети. Это приводит к тому, что пользователи начинают пользоваться “зеркалами” соц.сетей, где у них крадут логины и пароли. Пароли у них совпадают с корпоративными паролями, ну а дальше могут появиться куда большие проблемы, чем сотрудник, листающий инстаграмм за чашкой кофе.

    В веб-программировании и его безопасности черные списки тоже зло.

    Обходим черный список от ProjeQtOr


    Ну, тут все просто. Для начала смотрим, какие файлы при дефолтных настройках Apache2+PHP может интерпретировать (все ставилось на ubuntu 16.04 с обновленным репозиторием). За возможность интерпретировать файлы отвечает директива “FilesMatch”. Делаем поиск по ней командой “grep -r "<FilesMatch" /etc/apache2” и вот результат:

    /etc/apache2/mods-available/php7.0.conf:<FilesMatch ".+\.ph(p[3457]?|t|tml)$">
    /etc/apache2/mods-available/php7.0.conf:<FilesMatch ".+\.phps$">
    /etc/apache2/mods-available/php7.0.conf:<FilesMatch "^\.ph(p[3457]?|t|tml|ps)$">
    /etc/apache2/sites-available/default-ssl.conf:		<FilesMatch "\.(cgi|shtml|phtml|php)$">
    /etc/apache2/apache2.conf:<FilesMatch "^\.ht">

    В конфиге default-ssl.conf все расширения просто перечислены целиком, это: cgi, shtml, phtml, php. Увы, но все кроме cgi отфильтровывается в ProjeQtOr.

    Куда интереснее конфиг php7.0.conf, в нем расширения заданы регулярным выражением. Получаем:
    Расширение Чем фильтруется
    php substr($ext,0,3)=='php'
    php3 substr($ext,0,3)=='php'
    php4 substr($ext,0,3)=='php'
    php5 substr($ext,0,3)=='php'
    php7 substr($ext,0,3)=='php'
    pht НИЧЕМ
    phtml3 substr($ext,0,4)=='phtm'

    Отлично, расширение файла, которое не фильтруется, найдено. Проверяем, что оно действительно интерпретируется.

    Создаем файл test.pht со следующим содержимым:

    <?php
    phpinfo();

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

    Грузим наш тестовый файл в ProjeQtOr Project Management Tool. Разумеется, получаем ошибку, ведь это не картинка (в версии до 7.2.7 уже имеем исполнение кода на сервере, т.к. изменить phpinfo на исполнения команд не составляет труда). В версии 7.2.7 файлик удаляется, и код не исполнить.

    Но мы не расстраиваемся и обходим проверку на картинку.

    Картинка с PHP


    Проверка того, является ли загруженный файл картинкой в ProjeQtOr Project Management Tool, осуществляется функцией getimagesize, которая просто смотрит на заголовок переданного файла.

    Пользуясь тем, что в php-файле может быть любой мусор, а интерпретация php-кода начинается только с символов “<?php”, пишем небольшой код, который сначала пишет картинку, а потом нужный нам php-код. В качестве картинки будем слать скриншот окна ошибки. 3 строки кода на python и все готово:

    data = open ('test.png','rb').read()
    data += open ('test.pht','rb').read()
    open ('new_pht_png.pht','wb').write(data)

    Наверное, можно было просто написать в начало файла валидный заголовок картинки, но так проще, и тем более картинка отображается в любых просмотрщиках.

    Загружаем сие творение на сервер и, о чудо, оно грузится (и отображается в просмотрщике как картинка), и еще славно то, что нам показывается полное имя, с которым этот файл был загружен.



    Переходим на загруженный файл localhost/files/images/20181114171730_1_new_pht_png.pht и видим текстом загруженную картинку, а под ней вывод phpinfo. Понятно, что заменить phpinfo на простой веб-шелл не составляет труда. Например, такой: <?php system($_GET['cmd']);



    Раз начали ковырять загрузку файлов, то надо довести работу до конца и посмотреть, где еще есть загрузка файлов с черными списками или без них.

    Еще загрузка файлов


    Смотреть будем в последней доступной версии. Предположив, что для загрузки файлов используется та же функция, что и раньше, т.е. move_uploaded_file, производим ее поиск в директории с проектами “grep -r «move_uploaded_file» ./”. Получаем следующие 5 файлов:

    ./tool/uploadImage.php
    ./tool/saveDocumentVersion.php
    ./tool/uploadPlugin.php
    ./tool/import.php
    ./tool/saveAttachment.php

    Файл uploadImage.php – уже посмотрели.
    Файл saveDocumentVersion.php – загружает версии документов (что следует из названия). Пробуем загрузить документ и посмотреть на него (для начала всегда будем грузить картинку). После загрузки видим, что к файлу добавилось расширение .1. Смотрим в коде как получается имя (делается это в строке 229):

    $uploadfile = $dv->getUploadFileName();

    Функция getUploadFileName объявлена в файле DocumentVersionMain.php. Там в строке 227 видим, что к возвращаемому имени добавляется “.” и ID документа. Даже добавленную точку нам уже быстро не обойти:

    return $uploaddir . $paramPathSeparator . $fileName . '.' . $this->id;

    Файл uploadPlugin.php доступен только администраторам и то, что в плагине может быть плохой код, это весьма логично, и от этого сложно избавиться, не введя валидацию плагинов (как делают популярные CMS). Разумеется, при попытке туда что-то загрузить, оно успешно загружается, а потом исполняется.

    Файл import.php тоже доступен только администраторам. При загрузке файла нам говорят, что это должен быть csv-файл или xlsx-файл. Разумеется, пробуем загрузить php-файл и видим ошибку:

    ERROR — Provided file type and selected file format do not match


    Import aborted

    Незадача заключается в том, что как в изначальном баге из CVE, файл не удаляется, а остается доступным по адресу localhost/files/attach/import/test.php.

    Файл saveAttachment применяется при загрузке любых вложений (например, при загрузке собственного изображения). PHP-скрипт туда не пролезает, так как есть защита вида:

    if (substr($ext,0,3)=='php' or substr($ext,0,4)=='phtm' or substr($ext,0,4)=='shtm') {
    	    	$attachment→fileName.=".projeqtor";

    получается к файлам расширений php*, phtm*, shtm* добавляется расширение ".projeqtor", то есть очевидно туда пролезет наш pht файл (даже без обхода картинок). Пробуем, и у нас все получается по адресу localhost/files/attach/attachment_1/test.pht.

    Итого в сухом остатке из быстро найденных пяти мест загрузки файлов:

    • удалось загрузить php или pht скрипт 4 раза;
    • валидация по черным спискам есть в двух;
    • валидации по белым спискам нет нигде;
    • один раз не удалось загрузить файл (загрузить удалось, а вот исполнить нет), т.к. менялось расширение

    Выводы по ProjeQtOr Project Management Tool и CVE-2018-18924



    • репортнутая уязвимость фактически устранена;
    • в коде присутствуют другие уязвимости (сообщено разработчикам, и они даже обещали белые списки расширений);
    • от всего могла бы спасти грамотная настройка сервера Apache2 (ограничить исполняемые форматы только необходимыми, запретить исполнение скриптов в пользовательских папках);
    • на nist.gov указана не последняя уязвимая версия.

    Хозяйке на заметку


    • отказаться от черных списков везде, где это возможно (не знаю, где это сделать невозможно);
    • быть аккуратным и внимательным при обработке загружаемых файлов (лучше чтобы это было в одном месте, а не размазано по 5 );
    • грамотно настроенный веб-сервер спасает от многих проблем в коде проекта (важно писать код и настраивать сервер хорошо).

    Развёрнутый ответ от разработчиков


    Первый ответ от разработчиков был, что-то типа “мы все исправили, вот смотрите исправленный код”. Пришлось очень подробно расписывать, где какие проблемы и как их можно проэксплуатировать.

    После чего получил развёрнутый ответ: “да проблемы есть, и они будут исправлены в версии 7.3.0. Также будут добавлены белые списки для изображений и для xlslx и csv.”. Еще они написали, что у них есть рекомендация по добавлению директории “attachments” и “documents” за пределы веб-доступа в инструкции по установки.

    Разработчики разрешили мне зарегистрировать CVE и написать статью, после выхода обновления (которое вышло и доступно для скачивания).

    Заключение


    Как я писал в начале, обновление, решающее CVE, скачало довольно много людей (более 500 скачиваний), и это круто, что люди обновляют свое уязвимое ПО, но грустно, что ПО остается уязвимым.

    В итоге мне и нашей компании присвоили четыре CVE: CVE-2018-19307, CVE-2018-19308, CVE-2018-19309, CVE-2018-19310.
    Акрибия
    61,00
    Компания
    Поделиться публикацией

    Комментарии 0

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

    Самое читаемое