Фишинговые кампании — классическое начало многих хакерских атак. Они направлены на доставку вредоносного файла потенциальной жертве и обычно ведутся по электронной почте. Чаще всего оболочкой для боевой нагрузки служит офисный документ, при открытии которого выполняются нестандартные действия. Мы рассмотрим, как выполнить произвольный код с помощью модифицированного файла OpenDocument Text.
Еще по теме: Как хакеры скрывают вирусы в документах Office
Большинство фишинговых приемов направлены на эксплуатацию уязвимостей в Microsoft Office, так как это самый популярный офисный пакет. Однако сейчас многие фирмы переходят на LibreOffice и OpenOffice, в том числе ошибочно считая их неуязвимыми.
С ростом популярности этих программных пакетов они тоже стали привлекать внимание хакеров и независимых исследователей. Просто, в отличие от набивших оскомину багов MS Office, информации об уязвимостях в опенсорсных пакетах пока накопилось не так уж много. Целью моего исследования будет не столько рассмотреть уязвимость какой-то конкретной версии программы, сколько разобрать формат ODT и особенности его обработки.
Эксплуатация CVE-2018-16858
Теория
Найденная уязвимость позволяет выполнить несанкционированный доступ к файловой системе, используя атаку обхода пути (path traversal attack). Это одна из самых часто встречающихся уязвимостей и канонический тип атаки. В данном случае злонамеренно модифицированный файл ODT может загрузить за пределами родительского каталога скрипт на Python, который получит доступ к файловой системе и выполнит любые указанные действия с правами текущего пользователя.
Первым данную уязвимость обнаружил старший пентестер немецкой фирмы Cure53 Алекс Инфур (Alex Infuhr). Он предал свою находку огласке, и LibreOffice пропатчили, однако все версии до 6.0.7 (крайне популярные сейчас) по-прежнему уязвимы.
Немного размытой остается ситуация с Apache OpenOffice. Отдельного патча нет, и текущая версия 4.1.6 уязвима до сих пор. Сторонняя компания ACROS Security включила в свой агент 0patch заплатку для данной уязвимости, но только в версиях OpenOffice и LibreOffice для Windows. Более подробно см. на их сайте.
Данный агент распознает и блокирует известные уязвимости, препятствуя их эксплуатации. Для его запуска надо будет пройти несложную регистрацию. Во время работы агента при запуске уязвимого файла всплывает небольшое уведомление, а при наведении курсора на гиперссылку он заблокирует запуск неоригинального скрипта. Я связался с разработчиками агента, пытаясь пролить свет на работу данной функции. Их ответ приведен на скриншоте.
Наш микропатч для LibreOffice и OpenOffice просто проверяет, что [в документе] нет типичных шаблонов для выполнения атаки «обход пути», таких как «../», «\..» или «..|».
Интересно, что некоторые антивирусы также ищут признаки эксплоита по всему документу, включая его текстовые поля и игнорируя формат. Например, «Антивирус Касперского» ругается даже на эту статью, если попытаться открыть ее черновик в OpenOffice как .odt или в MS Office как .docx.
Практика
Разберем суть уязвимости. Во время инсталляции LibreOffice в Windows также устанавливается интерпретатор Python 3. Он находится по известному адресу C:\Program Files\LibreOffice\program\python-core-3.5.5\bin. Офисный пакет может исполнить Python-скрипт, если указать ссылку на него внутри документа и правильно сослаться на него.
Документы .odt основаны на XML (как и .docx), поэтому их внутреннюю структуру легко изучать и модифицировать. Здесь открывается большой простор для разных вариантов атак, но в общем случае нам необходимо создать в документе какую-либо задачу, которая запустит скрипт на Python.
Вот тут и наступает самое интересное. Если подменить путь к исполняемому коду и он будет соответствовать правилам исполнения, то LibreOffice выполнит его, не проверяя путь к скрипту. Например, мы можем выйти за рамки рабочей директории, и ничего нам за это не будет… а вот жертве — будет!
Все описанное выше справедливо и для OpenOffice, просто в нем используется Python 2, поэтому местоположение некоторых скриптов будет другое. Это нужно учесть, если ты захочешь написать универсальный файл, эксплуатирующий уязвимость в обоих офисных пакетах.
Все тесты проведены на Windows 10 версия 1803 сборка 17134.523. За основу взят пакет LibreOffice последней уязвимой версии 6.0.6.2.
Создаем вредоносный файл
Открываем LibreOffice и создаем документ .odt с помощью Writer. Теперь нам необходимо добавить вызов любого Python-скрипта. На помощь приходит событие OnMouseOver, которое возникает при простом наведении курсора мыши на гиперссылку в документе. Преимущество состоит в том, что жертве даже кликать никуда не придется: навести курсор на ссылку в документе можно и случайно, особенно если она занимает весь лист и такого же белого цвета.
Идем в меню «Вставка → Гиперссылка». Заполняем поле URL любой ссылкой (неважно какой). В дополнительных настройках, напротив «Фрейм:», в конце окна есть кнопка «События», которая вызывает настройки макроса. В поле «Назначения» выбираем «Мышь над объектом» (то, о чем и было сказано). И в поле «Существующие макросы» добавим createTable (находится в ветке «Макросы LibreOffice → pythonSample»). Назначаем нужное нам событие.
Теперь заполняем поле «Текст» и кликаем «Применить». На листе появляется гиперссылка, при наведении на которую исполняется макрос. В моем примере так открывается еще один документ с информацией о первом в виде таблицы и парой примеров из руководства.
Сохраняем документ и закрываем. Упрощенно ODT — это XML в ZIP, поэтому посетить «недра» нашего файла можно с помощью любого архиватора с поддержкой ZIP. Я использую WinRAR. Открываем ODT как архив и смотрим структуру.
В корневом каталоге нас интересует файл content. Разархивируем его и откроем в любом текстовом редакторе (я использую Notepad++). В конце разметки (на скриншоте выделил это в отдельную строку) видно, что явно указывается скрипт, который будет исполняться. Имя ему TableSample.py.
Располагается данный файл (как и все примеры скриптов) по пути C:\Program Files\LibreOffice\share\Scripts\python\pythonSamples. Если открыть его и посмотреть на исходный код, то можно заметить, что название одной из двух функций (а именно createTable()) совпадает со словами в теле content после названия выполняемого скрипта. Это значит, что в файле разметки указано не только название файла со скриптом, но и функция, которую необходимо выполнить!
Проведем эксперимент: заменим в TableSample.py функцию createTable() на insertTextIntoCell(), не вписывая ее параметры. При открытии файла получим ошибку с названием новой функции.
Отлично! Сообщения об ошибках — это та обратная связь, которая поможет сделать правильный вызов «боевой нагрузки». Добавим параметры для insertTextIntoCell() и запустим снова. Теперь возникает другая ошибка. Она показывает, что параметры неверны, а значит, принимались на исполнение и проверялись. Довольно приятные новости!
Идем дальше. Восстановим документ в первоначальное состояние и попробуем изменить путь к скрипту (для этого я просто скопировал скрипт на уровень выше, а в файле content перед именем скрипта добавил ../). В этот раз файл отработал как надо, без единой ошибки.
Подведем промежуточный итог: мы можем явно указать в документе ODT загрузку любого скрипта на питоне по известному адресу и вызывать его функцию по стандартному событию (например, OnMouseOver). В использовавшемся для примера TableSample.py никаких потенциально опасных функций нет, а вот в каком-то другом скрипте из стандартной подборки они могут быть.
Алекс Инфур нашел такой — pydoc.py, который находится в C:\Program Files\LibreOffice\program\python-core-3.5.5\lib. В нем есть функция tempfilepager(). С ее помощью через вызов os.system()можно запустить любой исполняемый файл, причем с произвольными аргументами — достаточно передать их в строчку
1 |
[crayon-66c4e43269ff2363303555 inline="true" ]<span class="pln">os</span><span class="pun">.</span><span class="pln">system</span><span class="pun">(</span><span class="pln">cmd </span><span class="pun">+</span> <span class="str">' "'</span> <span class="pun">+</span><span class="pln"> filename </span><span class="pun">+</span> <span class="str">'"'</span><span class="pun">)</span> |
[/crayon]
Чтобы все заработало, нам необходимо лишь прописать путь до скрипта и вызывать опасную функцию с нужными параметрами.
Запуск эксплоита
Настал момент X! Мы открываем модифицированный документ ODT, курсор оказывается наведенным на скрытую ссылку, и происходит магия — без разрешения пользователя запускается произвольный файл (злобный пейлоад или простой калькулятор в качестве PoC).
Для OpenOffice строка будет выглядеть так:
1 |
<office:event-listeners><script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:../../../program/python-core-2.7.6/lib/pydoc.py$tempfilepager(1, calc.exe )?language=Python&location=share" xlink:type="simple"/> |
В LibreOffice она же записывается так:
1 |
<office:event-listeners><script:event-listener script:language="ooo:script" script:event-name="dom:mouseover" xlink:href="vnd.sun.star.script:../../../program/python-core-3.5.5/lib/pydoc.py$tempfilepager(1, calc.exe )?language=Python&location=share" xlink:type="simple"/> |
Вот и вся разница.
Неуязвимые версии
После проделанных опытов я начал изучать заплатку этой уязвимости. В LibreOffice версии 6.0.7 я заметил ошибку, которая стала возникать при запуске скрипта, до этого работавшего в LibreOffice 6.0.6.2. Она ссылается на строку 998 файла pythonscript.py, расположенного в папке C:\Program Files\LibreOffice\program.
Как видно на скриншоте, функция getScript() просит параметр scriptUri. Как раз в этом параметре и возникает ошибка в строке
1 |
mod = self.provCtx.getModuleByUrl(fileUri) |
Та же самая ошибка возникает в OpenOffice, если эксплуатационный файл сконфигурирован неправильно (неверно указан путь к скрипту). Эта ошибка возникала и когда я пытался вызвать скрипт на удаленной машине по SMB-протоколу в формате \\\server\pydoc.py.
Изучив код pythonscript.py, я увидел, что функция scriptURI2StorageUri() (214-я строка файла) претерпела кардинальные изменения. Теперь она проверяет, находится ли скрипт в рабочей директории или нет. Выглядит этот фрагмент так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# base path to the python script location sBaseUri = self.m_baseUri + "/" xBaseUri = self.m_uriRefFac.parse(sBaseUri) # path to the .py file + "$functionname, arguments, etc xStorageUri = self.m_uriRefFac.parse(scriptURI) sStorageUri = xStorageUri.getName().replace( "|", "/" ); # path to the .py file, relative to the base sFileUri = sStorageUri[0:sStorageUri.find("$")] xFileUri = self.m_uriRefFac.parse(sFileUri) if not xFileUri: message = "pythonscript: invalid relative uri '" + sFileUri+ "'" log.debug( message ) raise RuntimeException( message ) # absolute path to the .py file xAbsScriptUri = self.m_uriRefFac.makeAbsolute(xBaseUri, xFileUri, True, RETAIN) sAbsScriptUri = xAbsScriptUri.getUriReference() # ensure py file is under the base path if not sAbsScriptUri.startswith(sBaseUri): message = "pythonscript: storage uri '" + sAbsScriptUri + "' not in base uri '" + self.m_baseUri + "'" log.debug( message ) raise RuntimeException( message ) ret = sBaseUri + sStorageUri |
Более подробное сравнение приводится на скриншоте.
Сама функция tempfilepager() никак не изменилась вплоть до LibreOffice v.6.2.1 (на момент написания статьи — последняя из стабильных). Строчку обращения к операционной системе по заданным параметрам все так же можно использовать. Если удастся обойти запрет на указание местоположения файла, то можно будет эксплуатировать очередную уязвимость, изменив лишь название папки на python-core-3.5.6.
Заключение
Мы разобрали уязвимость CVE-2018-16858, эксплуатация которой считается очень легкой. Проблема в том, что дырявые версии LibreOffice продолжают широко использоваться, а OpenOffice не пропатчен до сих пор. Поэтому не откладывай обновление LibreOffice и озаботься дополнительным уровнем защиты для OpenOffice.