SandboxEscaper/PoC-LPE: что внутри?

https://github.com/SandboxEscaper/randomrepo/blob/master/PoC-LPE.rar
  • Перевод

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

Демо


В архиве так же можно обнаружить видео с демонстрацией:
Скрытый текст

Поделиться публикацией

Похожие публикации

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

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


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

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

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

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

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

            0
            Да. Она «на лету» применяет необходимые патчи, как только запустился уязвимый процесс.
        –1
        ИМХО, пример с dll-кой от XPS-принтера — более чем никакой, с другой стороны — «хакер», вероятнее всего, никогда не дождется запуска своего кода из подменённой dll-ки…
        Не хватает опроса на тему «Как часто Вы пользуетесь виртуальным XPS-принтером от Microsoft?»
          +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
          В старух версиях планировшика была кнопка — запустить задачу. Видимо Microsoft подсуетился и убрал из-за этого.
            +2
            была кнопка — запустить задачу. Видимо Microsoft подсуетился и убрал из-за этого.

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


            0

            .

              0
              Хочу заметить, что создание 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.
                +2

                Проблема скорее всего в том, что встроенная команда 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;
                        }
                    }
                +3
                Кстати, если кому-то интересно, я переписал код PoC.

                1. Вместо довольно странного метода инжекта библиотеки в процесс, для простой демонстрации я переделал проект в .exe.
                2. Я добавил корректное формирование пути, теперь всё будет работать, даже если ОС установлена не в стандартную папку C:\Windows.
                3. Переписал код для лучшего понимания.

                Код на GitHub

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

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