Рис. 3.20. Добавление элемента в список
Аналогично можно добавить в список несколько элементов:
MATCH (c: Computer {name: "COMP.DOMAIN.LOCAL"}) SET c.ports = c.ports + ["5985","5986"]
RETURN c.name, c.ports
Удаление элемента из спискаВозможно, в каких-то ситуациях придется удалить элемент из списка. Существует несколько способов, один из которых – использовать процедуру
apoc.coll.remove
из плагина APOC. Запрос с использованием процедуры будет следующим:MATCH (c: Computer {name: "COMP.DOMAIN.LOCAL"})
SET c.ports = apoc.coll.remove(c.ports,2)
RETURN c.name, c.ports
В данном запросе мы удаляем третий элемент из списка, имеющий индекс 2.
Замена элементов в спискеМожет потребоваться заменить значение одного элемента другим, это также можно сделать с помощью процедуры
apoc.coll.remove
из плагина APOC:MATCH (c: Computer {name: "COMP.DOMAIN.LOCAL"})
SET c.ports = apoc.coll.set(c.ports,1,"5985")
RETURN c.name, c.ports
Здесь мы заменяем порт 3389, который имеет индекс 1, на порт 5985.
Рис. 3.21. Результат замены элемента
Функция COLLECTВ разделе про вывод результатов мы уже видели возможность создания списка. Для этого используется функция
collect
. Например, нужно собрать всех пользователей, которые являются администраторами домена, в список. Запрос в Cypher будет следующим:MATCH (u: User)-[r: MemberOf*1..]->(g: Group {name: "DOMAIN ADMINS@DOMAIN.LOCAL"}) return collect(u.name)
Рис. 3.22. Результат в виде списка
Объединение списковПри выводе результатов в виде списков их можно объединить в общий список.
MATCH (u: User) WITH collect (u.name) as col1
MATCH (c: Computer) WITH col1, collect (c.name) AS col2
WITH col1 + col2 AS result
RETURN result
Оператор UNWINDДля разбора списка используется оператор
UNWIND
. Чтобы получить данные SPN в виде отдельных записей, можно выполнить запрос Cypher.MATCH (c: Computer) WHERE c.name CONTAINS "COMP"
UNWIND c.serviceprincipalnames AS spn
RETURN c.name, spn
В результате каждая SPN-запись будет содержаться в отдельной строчке.
Рис. 3.23. Результат использования UNWIND
Для поиска по определенному SPN необходимо разобрать свойство SPN для каждого компьютера и отфильтровать по необходимому значению. Например, нужно найти все доменные компьютеры, у которых в атрибуте
serviceprincipalname
есть MSSQL. Запрос Cypher будет следующим:MATCH (c: Computer)
UNWIND c.serviceprincipalnames AS spn
WITH c, spn
WHERE spn CONTAINS "SQL"
RETURN DISTINCT c.name
Оператор INОператор
IN
проверяет вхождение переданного значения в список. Вернем наши порты в исходное состояние.MATCH (c: Computer {name: "COMP.DOMAIN.LOCAL"}) SET c.ports = ["445", "3389"]
Теперь получим компьютеры, у которых в свойствах есть порт 445:
MATCH (c: Computer) WHERE '445' IN (c.ports)
RETURN c.name
Неудобство в том, что в примере выше нужно передать полное значение свойства, и тут можно использовать внутреннее условие:
MATCH (c: Computer)
WHERE ANY (x IN c.serviceprincipalnames WHERE x CONTAINS "ldap")
RETURN c.name
В данном запросе появляется новая переменная
x
– это промежуточный результат, в котором уже выполняется проверка на совпадение. По факту мы ищем все контроллеры домена. Тот же самый запрос можно выполнить с использованием регулярных выражений. Тогда запрос преобразуется в следующий вид:MATCH(c: Computer)
WHERE ANY (x IN c.serviceprincipalnames WHERE x =~ "(?i).*ldap.*")
RETURN c.name
Для реверсивного поиска (найти все машины, у которых нет SPN ldap) ключевое слово
ANY
в предыдущем запросе нужно заменить на NOT ANY
или NONE
.MATCH (c: Computer)
WHERE NONE (x IN c.serviceprincipalnames WHERE x =~ "(?i).*ldap.*")
RETURN c.name
В результате мы получим все компьютеры, кроме контроллеров домена.
Кроме
ANY
и NONE
есть и другие функции, ALL
и SINGLE
.●
ANY
– возвращает true
, если хотя бы один элемент в коллекции соответствует правилу;●
ALL
– возвращает true
, если все элементы в коллекции соответствуют правилу;●
NONE
– возвращает true
, если ни один элемент в коллекции не соответствует правилу;●
SINGLE
– возвращает true
, если правилу соответствует ровно один элемент.С помощью оператора
IN
можно удалять элементы из списка:MATCH (c: Computer {name: "COMP.DOMAIN.LOCAL"}) SET c.ports = [x in c.ports WHERE x <>"5985"]
RETURN c.name, c.ports
Создание черных списковПри построении коротких путей возникает ситуация, в которой основной узел в цепочке недоступен или мы не имеем соответствующих прав на выполнение цепочки. В этом случае можно удалить узел и перестроить граф, но лучше исключить этот узел из запроса. Для примера возьмем запрос короткого пути до группы доменных администраторов:
MATCH p=shortestPath((n)-[*1..]->(m: Group {name: "DOMAIN ADMINS@D
OMAIN.LOCAL"}))
WHERE NOT n=m RETURN p
Для наглядности выполним его в BloodHound через Raw Query:
Рис. 3.24. Поиск короткого пути
Теперь исключим контроллер домена, тогда запрос будет выглядеть следующим образом:
MATCH p=shortestPath((n)-[*1..]->(m: Group {name: "DOMAIN ADMINS@DOMAIN.LOCAL"}))
WHERE NOT n=m AND NONE (x IN nodes(p) WHERE x.name = "DC.DOMAIN.LOCAL") RETURN p
Рис. 3.25. Результат запроса без контроллера домена
Чтобы не перечислять все узлы, которые нужно исключить из запроса, можно добавить новое свойство, например
blacklisted
:MATCH (c: Computer {name: "DC.DOMAIN.LOCAL"})
SET c.blacklisted = TRUE
RETURN c.name, c.blacklisted
При создании запроса с исключением возникает небольшая проблема. В большинстве случаев мы будем устанавливать свойство
blacklisted
только для тех узлов, которые хотим исключить из запроса, и тут запрос будет выглядеть следующим образом:MATCH p=shortestPath((n)-[*1..]->(m: Group {name: "DOMAIN ADMINS@DOMAIN.LOCAL"}))
WHERE NOT n=m AND NONE(x IN nodes(p) WHERE x.blacklisted IS NOT NULL)
RETURN p
Результат будет аналогичен изображенному выше. В данном запросе мы проверяем наличие свойства
blacklisted
, а не его значение. Но если нам потребуется изменить значение blacklisted
с true
на false
, то данный запрос будет выполнен неправильно. Давайте добавим узлу comp.domain.local
свойство blacklisted
в значении false
и повторим запрос:MATCH (c: Computer {name: "COMP.DOMAIN.LOCAL"})
SET c.blacklisted = FALSE
RETURN c.name, c.blacklisted
Результат будет совершенно другим, а не таким, как мы ожидали.
Рис. 3.26. Результат запроса с разными значениями blacklisted
И теперь придется или удалять свойство для узла со значением
true
свойства blacklisted
, или использовать дополнительную выборку.MATCH (wo {blacklisted: TRUE})
MATCH p=shortestPath((n)-[*1..]->(m: Group {name: "DOMAIN ADMINS@DOMAIN.LOCAL"}))
WHERE NOT n=m AND NONE(x IN nodes(p) WHERE x=wo)
RETURN p