Tag_Tier_Zero и не только
Добавление меток (label) или определенных свойств узлам позволяют группировать узлы по общему признаку и использовать их в запросах Cypher вместо общего перечисления. Так, например, для всех рабочих станций можно добавить метку "Workstation", а для всех серверов "Server" и в зависимости от потребностей использовать эти метки в запросах.
Эволюция таких меток в BloodHound была следующей: в BloodHound Legacy использовались свойства "n.highvalue" для определения узлов с высокими привилегиями "n.owned" – скомпрометированный узел, в BloodHound CE начали использовать свойство "n.system_tag", в которой прописывались метки "admin_tier_0" для привилегированных групп и "owned" для скомпрометированных. С конца июля 2026 года будет использоваться только метки "Tag_Tier_Zero" и "Tag_Owned". Это связанно с тем, что индексированные метки работают быстрее.
Если вы следите за обновлениями BloodHound CE учитывайте это в своих собственных Cypher запросах. Можно использовать универсальный подход в запросах:
((n:Tag_Tier_Zero) OR COALESCE(n.system_tags, '') CONTAINS 'admin_tier_0' OR n.highvalue = TRUE)
В этом случае будут проверяться все три варианта. Но для написания быстрых запросов нужно учитывать версию BloodHound.
Но зачем останавливаться только на "Tag_Tier_Zero"? В тировой модели три уровня: на нулевом находятся администраторы домена и им можно ходить только на контроллеры домена, на первом – администраторы серверов, приложений, баз данных и так далее, и они занимаются только серверной инфраструктурой, а на втором уровне это администраторы рабочих станций и всего, что связанно с пользовательским сегментом.
Добавим новые метки "Tag_Tier_One" и "Tag_Tier_Two", но для начала добавим новые индексы:
CREATE INDEX IF NOT EXISTS FOR (n:Tag_Tier_One) ON (n.objectid); CREATE INDEX IF NOT EXISTS FOR (n:Tag_Tier_One) ON (n.name); CREATE INDEX IF NOT EXISTS FOR (n:Tag_Tier_Two) ON (n.objectid); CREATE INDEX IF NOT EXISTS FOR (n:Tag_Tier_Two) ON (n.name);
Итак, метки определены теперь нужно найти объекты, на которые можно эти метки установить. Делаем запрос на поиск всех групп, у которых в имени есть ADMIN, но исключаем "Tag_Tier_Zero" и выводим имя и описание:
MATCH (g:Group) WHERE g.name CONTAINS 'ADMIN' AND NOT ((g:Tag_Tier_Zero) OR COALESCE(g.system_tags, '') CONTAINS 'admin_tier_0' OR g.highvalue = TRUE) RETURN g.name, g.description
Поле description может нам помочь определить к какому уровню относится та или иная группа. А так по имени определяем уровень и устанавливаем новую метку, например мы нашли две группы SERVER_ADMINS и WORKSTATION_ADMINS.
MATCH (g1:Group) WHERE g1.name STARTS WITH 'SERVER_ADMINS' SET g:Tag_Tier_One MATCH (g2:Group) WHERE g2.name STARTS WITH 'WORKSTATION_ADMINS' SET g:Tag_Tier_Two
Добавим членам группы SERVER_ADMINS ту же метку.
MATCH (u:Base)-[r:MemberOf*1..]->(g:Tag_Tier_One) SET u: Tag_Tier_One
Теперь можно использовать эти метки при построении путей, включать или исключать. Например, мы хотим посмотреть есть ли у нас короткие пути от группы доменных пользователей до группы доменных администраторов минуя все "Tag_Tier_One" узлы:
MATCH p=AllShortestPaths((m:Group {name:"DOMAIN USERS@DOMAIN.LOCAL"})-[*]->(n:Group {name:"DOMAIN ADMINS@DOMAIN.LOCAL"}))
WHERE ALL(x IN relationships(p) WHERE x.is_traversable = TRUE)
AND NONE(x in nodes(p) WHERE x:Tag_Tier_One)
RETURN pВ этом запросе используется is_traversable = TRUE, чтобы не перечислять все проходимые ребра.
Стоит отметить, что в больших сетях могут использоваться сотни различных групп администраторов, но зачастую они разделены по регионам или городам, а функционал будет одинаковым, и поэтому будут иметь общий шаблон наименования. Если определить такой шаблон, то по факту групп администраторов останется в разы меньше.