Рис. 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
С указанием типа связей:
Пример – получить всех пользователей и их членство в группах: