В этой статье мы погово­рим о раз­ных вари­ациях кросс‑про­токоль­ной ата­ки NTLM Relay с исполь­зовани­ем экс­пло­ита RemotePotato0, а так­же на этом при­мере обсу­дим, как мож­но спря­тать сиг­натуру исполня­емо­го фай­ла от ста­тичес­кого ана­лиза.

Эта исто­рия отно­сит­ся к катего­рии «бай­ки с внут­ренних пен­тестов», ког­да мы попали в сре­ду Active Directory, где чле­ны груп­пы безопас­ности Domain Users (все поль­зовате­ли домена) обла­дали при­виле­гией для уда­лен­ного под­клю­чения к кон­трол­лерам домена по про­токо­лу RDP. Хоть это уже само по себе ужас­ная «мис­конфи­га», потен­циаль­ный зло­умыш­ленник все еще дол­жен най­ти спо­соб для локаль­ного повыше­ния при­виле­гий на DC, что проб­лематич­но, если на сис­теме сто­ят все хот­фиксы.

Здесь и при­ходит на помощь баг фича из серии Microsoft Won’t Fix List — кросс‑сес­сион­ное про­воци­рова­ние вынуж­денной аутен­тифика­ции по про­токо­лу RPC. При отсутс­твии защиты служ­бы LDAP от атак NTLM Relay оно мгно­вен­но подарит тебе «клю­чи от королевс­тва».

ПРЕДЫСТОРИЯ

Итак, внут­ренний пен­тест. Все по клас­сике: толь­ко я, мой ноут­бук, ка­пюшон и мас­ка Гая Фок­са перего­вор­ка, ском­мутиро­ван­ная розет­ка RJ-45 и прос­торы кор­поратив­ной сети жер­твы ауди­та. Отсутс­твие пра­вил филь­тра­ции IPv6 в моем широко­веща­тель­ном домене — в роли уяз­вимос­ти, отравлен­ные пакеты DHCPv6 Advertise с link-local IPv6-адре­сом моего ноут­бука (mitm6) — в роли ата­ки, и вот получен пер­воначаль­ный аутен­тифици­рован­ный дос­туп в сре­ду AD. Далее сбор дам­па «бла­да» с помощью BloodHound.py, пока все как обыч­но.

Но вот то, что было даль­ше, повер­гло меня в шок... Ока­залось, что все домен­ные «поль­заки» могут кон­нектить­ся к кон­трол­лерам домена по RDP. Дей­стви­тель­но, что может пой­ти не так?

Найди уязвимость на картинке
Най­ди уяз­вимость на кар­тинке

В этот момент мож­но начинать потирать руки в пред­вку­шении кре­дов доменад­мина. Убе­дим­ся, что мы можем реле­ить Net-NTLMv2-аутен­тифика­цию на служ­бы LDAP(S) с помощью LdapRelayScan.

~$ python3 LdapRelayScan.py -method BOTH -dc-ip -u -p

PARTY TIME!
PARTY TIME!

Не­уди­витель­но, что LDAP Signing (защита LDAP, 389/TCP) и LDAP Channel Binding (защита LDAPS, 636/TCP) отклю­чены, — еще мало кто осоз­нал, что это мас­тхев-mitigations для AD в наше вре­мя.

А теперь по поряд­ку, что со всем этим мож­но сде­лать.

НЕМНОГО О «КАРТОШКАХ»

RottenPotato & Co

В далеком 2016 году умные люди при­дума­ли RottenPotato — тех­нику локаль­ного повыше­ния при­виле­гий с сер­висных акка­унтов Windows (нап­ример, IIS APPPOOL\DefaultAppPool или NT Service\MSSQL$SQLEXPRESS), обла­дающих при­виле­гией оли­цет­ворения чужих токенов безопас­ности (aka SeImpersonatePrivilege), до NT AUTHORITY\SYSTEM.

Для это­го ата­кующий дол­жен был:

  1. Спро­воци­ровать вынуж­денную аутен­тифика­цию со сто­роны NT AUTHORITY\SYSTEM на машине‑жер­тве через триг­гер API-руч­ки DCOM/RPC CoGetInstanceFromIStorage в отно­шении локаль­ного слу­шате­ля (выс­тупа­ет в роли «челове­ка посере­дине»).
  2. Од­новре­мен­но про­вес­ти ло­каль­ную ата­ку NTLM Relay на служ­бу RPC (135/TCP) и дер­нуть API-вызов DCOM/RPC AcceptSecurityContext, переда­вая ему содер­жимое NTLM-час­ти зап­роса Negotiate (NTLM Type 1) от NT AUTHORITY\SYSTEM.
  3. Под­менить NTLM-чел­лендж (NTLM Type 2), исхо­дящий от служ­бы RPC (135/TCP), чел­лен­джем, получен­ным из отве­та AcceptSecurityContext, и про­дол­жить изна­чаль­ный релей на RPC из шага 1. В этом кон­тек­сте NTLM-ответ служ­бы RPC (135/TCP) исполь­зует­ся прос­то как шаб­лон сетево­го отве­та, в который мы инжектим нуж­ное нам тело NTLM-чел­лен­джа.
  4. Пос­ле успешно­го получе­ния NTLM-аутен­тифика­ции (NTLM Type 3) кли­ента RPC из шага 1 в ответ на NTLM-чел­лендж (NTLM Type 2) из шага 3 зареле­ить ее на RPC-руч­ку AcceptSecurityContext и получить токен сис­темы. На этом NTLM Relay окон­чен.
  5. Им­персо­ниро­вать NT AUTHORITY\SYSTEM. Мы можем это сде­лать потому, что у нас есть при­виле­гия SeImpersonatePrivilege.
Не­кото­рое вре­мя спус­тя лавоч­ку прик­рыли, зап­ретив DCOM/RPC общать­ся с локаль­ными слу­шате­лями, — никаких тебе боль­ше мит­мов. Но «кар­тошки» все рав­но пре­тер­певали изме­нения: были напиле­ны LonelyPotato (неак­туаль­но) и JuicyPotato — улуч­шенная вер­сия RottenPotato, уме­ющая работать с раз­ными зна­чени­ями CLSID (Class ID, иден­тифика­тор COM-клас­са) для «арбу­зин­га» дру­гих служб (помимо BITS, которую исполь­зовала ори­гиналь­ная «кар­тошка»), в которых реали­зован интерфейс IMarshal для триг­гера NTLM-аутен­тифика­ции.

JuicyPotato

В дан­ном слу­чае про­воци­рова­ние NTLM-аутен­тифика­ции в сво­ей осно­ве име­ет прин­цип, схо­жий с вре­донос­ной десери­али­заци­ей объ­ектов, толь­ко здесь это называ­ется «ан­марша­линг» — про­цесс вос­ста­нов­ления COM-объ­екта из пос­ледова­тель­нос­ти битов пос­ле его переда­чи в целевой метод в качес­тве аргу­мен­та.

Ата­кующий соз­дает вре­донос­ный COM-объ­ект клас­са IStorage и вызыва­ет API CoGetInstanceFromIStorage с ука­зани­ем соз­дать объ­ект клас­са с кон­крет­ным иден­тифика­тором CLSID и ини­циали­зиро­вать его сос­тоянием из мар­шализи­рован­ного вре­донос­ного объ­екта. Одно из полей мар­шализи­рован­ного объ­екта содер­жит ука­затель на под­кон­троль­ный ата­кующе­му слу­шатель, на который авто­мати­чес­ки при­ходит отстук с NTLM-аутен­тифика­цией в про­цес­се анмарша­лин­га.

public static void BootstrapComMarshal(int port)
{
IStorage stg = ComUtils.CreateStorage();

// Use a known local system service COM server, in this cast BITSv1
Guid clsid = new Guid("4991d34b-80a1-4291-83b6-3328366b9097");

TestClass c = new TestClass(stg, String.Format("127.0.0.1[{0}]", port));

MULTI_QI[] qis = new MULTI_QI[1];

qis[0].pIID = ComUtils.IID_IUnknownPtr;
qis[0].pItf = null;
qis[0].hr = 0;

CoGetInstanceFromIStorage(null, ref clsid,
null, CLSCTX.CLSCTX_LOCAL_SERVER, c, 1, qis);
}


Под­робнее о механиз­ме триг­гера NTLM-аутен­тифика­ции в ходе абь­юза DCOM/RPC мож­но почитать в пер­вом репор­те Project Zero на эту тему.

RoguePotato

С релизом RoguePotato — эво­люци­они­ровав­шей вер­сией JuicyPotato — был про­демонс­три­рован аль­тер­натив­ный под­ход к имперсо­нации при­виле­гиро­ван­ных сис­темных токенов:

  1. Зло­умыш­ленник под­нима­ет кас­томный сер­вис OXID (Object Exporter ID) Resolver на локаль­ном пор­те ата­куемой машины, отличном от 135/TCP. OXID-резол­вер исполь­зует­ся в Windows для раз­решения иден­тифика­тора вызыва­емо­го интерфей­са RPC (в нашем слу­чае под­кон­троль­ного атта­керу) в его имя, то есть в стро­ку RPC-бин­динга.
  2. Зло­умыш­ленник говорит служ­бе DCOM/RPC машины‑жер­твы пос­тучать­ся на уда­лен­ный IP-адрес (кон­тро­лиру­ется ата­кующим) для резол­ва той самой OXID-записи. Это необ­ходимо из‑за того, что в Microsoft зап­ретили обра­щать­ся к локаль­ным OXID-резол­верам, слу­шающим не на пор­те 135/TCP.
  3. На том самом уда­лен­ном IP-адре­се зло­умыш­ленник под­нима­ет socat (или любой дру­гой TCP-редирек­тор) на пор­те 135/TCP и «зер­калит» при­шед­ший OXID-зап­рос на ата­куемую машину в порт, на котором слу­шает кас­томный сер­вис OXID Resolver из шага 1. А он уже резол­вит пре­дос­тавлен­ный иден­тифика­тор в стро­ку RPC-бин­динга име­нован­ного канала ncacn_np:localhost/pipe/RoguePotato[\pipe\epmapper].
  4. Да­лее машина‑жер­тва наконец‑то дела­ет вре­донос­ный RPC-вызов (API-руч­ка IRemUnkown2) с под­клю­чени­ем к под­кон­троль­ному ата­кующе­му пай­пу из шага 3, что поз­воля­ет нам имперсо­ниро­вать под­клю­чив­ший­ся кли­ент с помощью RpcImpersonateClient, как это опи­сал @itm4n в судь­бонос­ном ресер­че PrintSpoofer — Abusing Impersonation Privileges on Windows 10 and Server 2019.

С базовой теорией закон­чили.