На хабре уже есть новость об этой уязвимости, но, к сожалению, без технических деталей. Предлагаю заглянуть внутрь опубликованного архива (автор — 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)


  1. fmj
    29.08.2018 14:02

    Если отобрать права на запись на /windows/tasks у обычных пользователей — то все ок?


    1. kITerE Автор
      29.08.2018 14:22

      Непонятно что это может поломать при штатной работе.


      Я бы попробовал оставить права на запись только пользователю SYSTEM. Но нужно будет это откатывать после фикса, когда MS станет имперсонироваться перед операцией в этой директории.


      1. fmj
        29.08.2018 14:32

        А группе админов почему не оставить?


        1. kITerE Автор
          29.08.2018 14:43

          Да, админы могут повыситься до SYSTEM документированными средствами. Но если в нее пишет только сервис без имперсонации, то все остальные — враги :)


    1. dartraiden
      01.09.2018 01:33
      +1

      Либо накатить патч от сторонних разработчиков.


      1. kITerE Автор
        01.09.2018 11:33

        Патч выглядит грамотно. Используете их системой патчей?


        1. dartraiden
          01.09.2018 12:33

          Да. Она «на лету» применяет необходимые патчи, как только запустился уязвимый процесс.


  1. igrblkv
    29.08.2018 16:44
    -1

    ИМХО, пример с dll-кой от XPS-принтера — более чем никакой, с другой стороны — «хакер», вероятнее всего, никогда не дождется запуска своего кода из подменённой dll-ки…
    Не хватает опроса на тему «Как часто Вы пользуетесь виртуальным XPS-принтером от Microsoft?»


    1. 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();
      


  1. BalinTomsk
    29.08.2018 19:52
    -1

    В старух версиях планировшика была кнопка — запустить задачу. Видимо Microsoft подсуетился и убрал из-за этого.


    1. kITerE Автор
      29.08.2018 20:03
      +1

      была кнопка — запустить задачу. Видимо Microsoft подсуетился и убрал из-за этого.

      Запуск задачи никуда не убрали:



  1. kITerE Автор
    29.08.2018 20:02

    .


  1. 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.


    1. 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;
              }
          }