На хабре уже есть новость об этой уязвимости, но, к сожалению, без технических деталей. Предлагаю заглянуть внутрь опубликованного архива (автор — SandboxEscaper).
Под катом расположен перевод документа-описания, находящегося в архиве.
Описание уязвимости
Служба Task Scheduler (планировщик задач) имеет RPC-интерфейс (доступный через транспорт ALPC), поддерживающий метод SchRpcSetSecurity.
Так выглядит прототип этого метода:
long _SchRpcSetSecurity(
[in][string] wchar_t* arg_1, //Task name
[in][string] wchar_t* arg_2, //Security Descriptor string
[in]long arg_3);
Задачи, созданные планировщиком задач, создают соответствующую директорию/файл в c:\windows\system32\tasks. Вероятно, этот метод предназначен для записи DACL'а задач, расположенных там. Но запись будет происходить происходит после имперсонации. Однако, по некоторым причинам, реализация метода так же проверяет наличие .job-файла в c:\windows\tasks и записывает ему DACL без имперсонации. Поскольку пользователь (даже пользователь в группе гостей) может создавать в этой директории файлы, мы просто можем создать hardlink на любой другой файл, доступный нам на чтение. Используя такой hardlink, мы можем заставить службу планировщика (исполняющейся с правами SYSTEM) записать произвольный DACL (смотри второй параметр SchRpcSetSecurity) в файл по нашему выбору.
Таким образом: у любого файла, доступного на чтение, можно сменить DACL, что позволяет полностью его перезаписать.
Эксплуатация уязвимости
Эта уязвимость дает нам действительно сильный примитив! Основная проблема заключается в том, что после установки (по умолчанию) многие важные файлы могут быть модифицированы только пользователем TrustedInstaller (но не пользователем SYSTEM).
В архиве присутствует powershell-скрипт для перечисления файлов, которые вы можете контролировать. Просто запустите:
./enumerate.ps1 >output.txt
В системе есть много целей. Вы можете контролировать файлы Program Files и, если ваш целевой файл используется администратором/другим пользователем, перезаписанные вами файлы могу быть запущены с требуемыми привилегиями.
Вторая проблема заключается в том, что хотя мы можем получить контроль над множеством файлов, запись в них часто невозможна, поскольку эти DLL уже куда-то загружены на исполнение. Попытка записи DACL для загруженного на исполнение файла вызовет ошибку разделяемого доступа. Но уязвимость можно использовать и для других типов файлов, которые могут быть лучшей целью, чем DLL.
Для эксплуатации выбран файл C:\Windows\System32\DriverStore\FileRepository\prnms003.inf_amd64_4592475aca2acf83\Amd64\printconfig.dll (имя директории может различаться, это учтено в PoC). Похоже этот файл относится в принтеру XPS и не загружен в службу печати по умолчанию (может так получиться, что файл уже будет загружен… но чаще всего это не так).
А когда мы запустим задание на печать с использованием XPS-принтера, сервис загрузит эту DLL, которую мы можем предварительно переписать. Такой вектор атаки (hijacking) может быть легко применен для чего-то получше. Я могу попытаться найти лучшие варианты… просто дайте мне знать.
Замечание: На старом ноутбуке, где Windows 10 работает уже несколько лет, есть две директории prnms003.inf_amd64_*. Новая версия не удаляет старую, а значит нет гарантии, что FindFirstFile (используемый в PoC) найдет актуальную директорию. Поэтому вы можете расширить код, перезаписав все найденные printconfig.dll или проверить атрибут последней записи в файл и выбрать более новый.
Демо
В архиве так же можно обнаружить видео с демонстрацией:
Комментарии (14)
igrblkv
29.08.2018 16:44-1ИМХО, пример с dll-кой от XPS-принтера — более чем никакой, с другой стороны — «хакер», вероятнее всего, никогда не дождется запуска своего кода из подменённой dll-ки…
Не хватает опроса на тему «Как часто Вы пользуетесь виртуальным XPS-принтером от Microsoft?»kITerE Автор
29.08.2018 16:53+2Печать XPS-принтером инициируется програмно (ALPC-TaskSched-LPE.cpp@120):
//After writing PrintConfig.dll we start an XpsPrintJob to load the dll into the print spooler service. CoInitialize(nullptr); IXpsOMObjectFactory *xpsFactory = NULL; CoCreateInstance(__uuidof(XpsOMObjectFactory), NULL, CLSCTX_INPROC_SERVER, __uuidof(IXpsOMObjectFactory), reinterpret_cast<LPVOID*>(&xpsFactory)); HANDLE completionEvent = CreateEvent(NULL, TRUE, FALSE, NULL); IXpsPrintJob *job = NULL; IXpsPrintJobStream *jobStream = NULL; StartXpsPrintJob(L"Microsoft XPS Document Writer", L"Print Job 1", NULL, NULL, completionEvent, NULL, 0, &job, &jobStream, NULL); jobStream->Close(); CoUninitialize();
BalinTomsk
29.08.2018 19:52-1В старух версиях планировшика была кнопка — запустить задачу. Видимо Microsoft подсуетился и убрал из-за этого.
kITerE Автор
29.08.2018 20:03+1была кнопка — запустить задачу. Видимо Microsoft подсуетился и убрал из-за этого.
Запуск задачи никуда не убрали:
dth_apostle
29.08.2018 23:46Хочу заметить, что создание hardlink для файла, доступ к к-рому только read/execute, запрещено.
C:\Users\aleksey>mklink /H c:\WINDOWS\Tasks\23 23
Hardlink created for c:\WINDOWS\Tasks\23 <<===>> 23
C:\Users\aleksey>mklink /H c:\WINDOWS\Tasks\explorer.exe c:\WINDOWS\explorer.exe
Access is denied.
C:\Users\aleksey>mklink /H c:\WINDOWS\Tasks\w32time.dll c:\WINDOWS\System32\w32time.dll
Access is denied.kITerE Автор
30.08.2018 08:21+1Проблема скорее всего в том, что встроенная команда mklink использует Win32-функцию CreateHardLink. Ее реализация открывает существующий файл с правом FILE_WRITE_ATTRIBUTES ( == 0x100 ) — декомпиляция файла версии 10.0.18204.1001:
if ( !RtlDosPathNameToNtPathName_U(lpExistingFileName, &ExistingFileNativeName, 0, 0) ) goto LABEL_29; ObjectAttributes.Length = 24; ObjectAttributes.RootDirectory = 0; ObjectAttributes.Attributes = OBJ_CASE_INSENSITIVE; ObjectAttributes.ObjectName = &ExistingFileNativeName; ObjectAttributes.SecurityDescriptor = 0; ObjectAttributes.SecurityQualityOfService = 0; if ( lpSecurityAttributes ) ObjectAttributes.SecurityDescriptor = lpSecurityAttributes->lpSecurityDescriptor; Status = NtOpenFile(&FileHandle, 0x100100u, &ObjectAttributes, &IoStatusBlock, 7u, 0x204020u);
Однако для вызова NtSetInformationFile с аргументом FileLinkInformation (то есть для создания hardlink'а нативными функциями) файл можно открыть вообще с любыми правами (=0), чем пользуется PoC (Hardlink.cpp@91):
HANDLE hFile = OpenFileNative(full_targetname.c_str(), nullptr, MAXIMUM_ALLOWED, FILE_SHARE_READ, 0); if (hFile) { DEFINE_NTDLL(ZwSetInformationFile); IO_STATUS_BLOCK io_status = { 0 }; NTSTATUS status = fZwSetInformationFile(hFile, &io_status, link_info, link_info.size(), FileLinkInformation); CloseHandle(hFile); if (NT_SUCCESS(status)) { return true; } }
fmj
Если отобрать права на запись на /windows/tasks у обычных пользователей — то все ок?
kITerE Автор
Непонятно что это может поломать при штатной работе.
Я бы попробовал оставить права на запись только пользователю SYSTEM. Но нужно будет это откатывать после фикса, когда MS станет имперсонироваться перед операцией в этой директории.
fmj
А группе админов почему не оставить?
kITerE Автор
Да, админы могут повыситься до SYSTEM документированными средствами. Но если в нее пишет только сервис без имперсонации, то все остальные — враги :)
dartraiden
Либо накатить патч от сторонних разработчиков.
kITerE Автор
Патч выглядит грамотно. Используете их системой патчей?
dartraiden
Да. Она «на лету» применяет необходимые патчи, как только запустился уязвимый процесс.