Идём по киберследу: Анализ защищенности Active Directory c помощью утилиты BloodHound — страница 7 из 46

Внимание

В Cypher нет двунаправленных стрелок, но могут быть запросы, где направление не указано.

В браузере neo4j есть справочник по командам и операторам. Получить справку по операторам можно с помощью команды

:help
:

:help <Оператор>

:help MATCH


Рис. 3.5. Справка по оператору MATCH


Оператор MATCH

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

MATCH
, следом идет шаблон поиска, условия выборки с помощью оператора
WHERE
, и завершается запрос выводом результатов
RETURN
.


Рис. 3.6. Оператор MATCH


Наша задача – создать правильный шаблон с добавлением условий. Неверно составленный запрос выдаст неверную информацию.

Рассмотрим простой запрос Cypher, где будут выбираться пользователи, у которых есть права локального администратора на компьютеры:

MATCH (u: User)-[r: AdminTo]->(c: Computer) RETURN u,r,c

Здесь переменным

u
,
r
и
c
будут передаваться результаты выборки, это не обязательно, если не нужно выделять какие-то особые условия, но для возврата данных все равно нужно определить переменную. Такой запрос нельзя профилировать и оптимизировать, а если добавить еще несколько узлов и связей, то перечисление будет требовать дополнительных затрат.

Выходом из этой ситуации будет назначить общую переменную для всего запроса. Запрос приобретет следующий вид.


Рис. 3.7. Добавление общей переменной в запрос


Если выполнить измененный запрос, то результат будет аналогичным:

MATCH p=(:User)-[: AdminTo]->(:Computer) RETURN p

Различные варианты использования оператора

RETURN
мы рассмотрим позже.

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

CanRDP
. Таким образом, в запросе можно опустить указание метки
User
, и он будет выглядеть следующим образом:

MATCH p=(u)-[r: AdminTo]->(c: Computer) RETURN p

Варианты запросов

Cypher – достаточно свободный язык запросов, одинаковых результатов можно добиться разными путями. Рассмотрим выполнение запроса выше другими способами. Мы можем отдельно определить начальный и конечный узлы и затем запросить, есть ли между ними связь.

MATCH (u: User)

MATCH (c: Computer)

MATCH p=(u)-[r: AdminTo]->(c) RETURN p

Все это можно записать в одну строчку, но для удобства чтения сложные запросы лучше записывать в несколько строк.

И этот запрос можно оптимизировать, оставив только первый

MATCH
и после каждой строчки поставив запятую.

MATCH (u: User),

(c: Computer),

p=(u)-[r: AdminTo]->(c) RETURN p

Браузер neo4j пометит, что данный запрос не очень удачный и его обработка может потребовать больше времени и ресурсов, тем не менее задача будет выполнена.

Объединение связей

Запрос может быть сложным: первым шагом мы можем запросить один шаблон, вторым – уже другой. Самый обычный пример для BloodHound – это поиск различных прав через членство в группах.


Рис. 3.8. Двухэтапный запрос


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

Информация

В сложных запросах стоит идти от конечной цели (последнего запроса) к начальному узлу.

MATCH p=(u: User)-[: MemberOf]-(g: Group)-[: AdminTo]->(c: Computer) RETURN p

Внимание

Данный запрос не вернет информацию, если пользователь имеет права локального администратора напрямую.

Мы можем объединить связи в этом запросе, используя логический оператор ИЛИ (представлен как |).

MATCH p=(u: User)-[: MemberOf|AdminTo]->(c: Computer) RETURN p

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


Рис. 3.9. Объединение связей


И тогда наш запрос примет вид

MATCH p=(u: User)-[: MemberOf|AdminTo*1..]->(c: Computer) RETURN p

В предыдущем примере мы использовали запись *1.. – указание количества промежуточных узлов, к которым может применяться шаблон, в данном случае – от одного перехода до бесконечности. Число переходов здесь – это количество различных промежуточных узлов от начального узла до конечного.

Ниже приведены две таблицы, в которых описаны различные варианты синтаксиса.

Без указания типа связи:



Приведу несколько примеров. Все прямые связи между узлами:

MATCH p=(u: User)->(c: Group) RETURN p

MATCH p=(u: User)-[]->(c: Group) RETURN p

Все непрямые связи между узлами с указанием ограничений от одного до двух переходов:

MATCH p=(u: User)-[*1..2]->(c: Group) RETURN p

С указанием типа связей:



Пример – получить всех пользователей и их членство в группах:

MATCH p=(u: User)-[: MemberOf*1..]->(g: Group) RETURN p

Другой пример – получить все компьютеры, где пользователи являются локальными администраторами:

MATCH p=(u: User)-[: MemberOf|AdminTo*1..]->(c: Computer) RETURN p

Короткие пути

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

ShortestPath
и
AllShortestPaths
. Разница между ними в том, что первый находит один короткий путь, а второй – все, при условии, что они существуют.

Для использования коротких путей необходимо поместить шаблон в круглые скобки. Так, предыдущий пример будет выглядеть следующим образом:

MATCH p=ShortestPath((u: User)-[*1..]->(c: Group)) RETURN p

Совет

Ограничение количества переходов поможет найти самые короткие пути.

В интерфейсе BloodHound в форме Поиск путей (Pathfinding) используется оператор

AllShortestPaths
, который применяется для поиска коротких путей между двумя указанными узлами.

Оператор OPTIONAL MATCH

Оператор

OPTIONAL MATCH
работает точно так же, как и
MATCH
; разница в том, что при использовании
OPTIONAL MATCH
будет добавлять
NULL
для недостающих элементов.

Рассмотрим два примера, в которых будем искать локальных администраторов на компьютерах с помощью операторов

MATCH
и
OPTIONAL MATCH
.

MATCH (u: User)

MATCH (u)-[r: AdminTo]->(c: Computer)

RETURN u.name, c.name


Рис. 3.10. Результат с MATCH


MATCH (u: User)

OPTIONAL MATCH (u)-[r: AdminTo]-(c: Computer)

RETURN u.name, c.name


Рис. 3.11. Результат с OPTIONAL MATCH


Как можно увидеть на рисунке 3.11, там, где нет прав локального администратора, neo4j выставил

null
. То же самое в графическом представлении: мы получим всех пользователей, и только у некоторых будет связь
AdminTo
с компьютерами.


Рис. 3.12. Графическое представление OPTIONAL MATCH


Условия фильтрации запросов

Ранее мы уже использовали фильтры по меткам

User
и
Computer
, но только в некоторых случаях этого будет достаточно. Для указания более точных критериев поиска применяется оператор
WHERE
, который может относиться ко всем свойствам узла и связи.

Обычно оператор

WHERE
используется после формирования шаблона, но он может быть применен и внутри узла, и все условия будут относиться только к этому узлу:

MATCH (g: Group) WHERE g.name = "DOMAIN ADMINS@DOMAIN.LOCAL" RETURN g.name

MATCH (g: Group WHERE g.name = "DOMAIN ADMINS@DOMAIN.LOCAL") RETURN g.name

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

Внимание

Помним, что Cypher чувствителен к регистру для свойств узла.

Рассмотрим синтаксис использования оператора

WHERE
на простых примерах.