Граф связей — штука, которая в какой-то момент спасает почти любой анализ: расследование инцидента, OSINT, поиск мошеннических цепочек, разбор зависимостей в инфраструктуре, даже ревизию бизнес-процессов. Пока у вас десять строк в таблице, всё терпимо. Когда данных становится сотни и тысячи, мозг перестаёт удерживать картину — и вот тут граф начинает реально работать как «второй монитор» для мышления.
Что такое граф связей и чем он отличается от "просто схемы"
Граф связей — это модель данных, где есть сущности (узлы) и связи между ними (рёбра). Важный момент: граф не рисуется ради красоты. Он строится по правилам, которые позволяют однозначно отвечать на вопросы типа «кто с кем связан», «через кого проходит путь», «какие узлы самые "влиятельные"», «где кластер» и так далее.
"Просто схема" обычно статичная: квадратики, стрелочки, поясняющие подписи, но без строгих правил идентификации, без атрибутов, без возможности машинного анализа. Граф же можно:
- автоматически расширять (подгружать новые данные и связи);
- считать метрики (центральность, расстояния, компоненты связности);
- искать паттерны (например, одинаковые структуры взаимодействий);
- хранить и версионировать как данные, а не как картинку.
| Критерий | "Схема" | Граф связей |
|---|---|---|
| Идентификаторы | часто условные ("Иван", "сервер 1") | строгие ID, стабильные ключи |
| Атрибуты | на словах или на полях | часть модели (в узлах и связях) |
| Аналитика | почти всегда вручную | алгоритмы, запросы, метрики |
| Масштабирование | быстро превращается в "лапшу" | можно фильтровать, агрегировать, кластеризовать |
Из чего состоит: объекты, отношения, атрибуты
Чтобы граф был полезен, стоит заранее договориться о трёх вещах: какие у вас объекты, какие у них отношения и какие атрибуты вы храните. Это как минимальная грамматика, без которой любой граф расползается в хаос.
1) Объекты (узлы) — "кто/что" в вашем мире данных. Примеры для кибербезопасности и расследований:
- аккаунт, пользователь, роль;
- IP-адрес, домен, хост, контейнер, сервис;
- файл, хеш, процесс;
- компания, человек, телефон, email (в OSINT).
2) Отношения (рёбра) — "как связаны". Тут полезно сразу задавать типы связей, иначе потом будет "связан-связан" и непонятно чем:
- пользователь AUTHENTICATED_TO хост;
- хост RESOLVES_TO IP;
- домен HOSTS сервис;
- аккаунт OWNS email/телефон.
3) Атрибуты — свойства узлов и связей. Важно: атрибуты бывают не только у узлов, но и у рёбер. Например, связь "вход на сервер" может иметь время, метод, источник, успешность.
Практичный лайфхак: фиксируйте обязательный минимум атрибутов с самого начала:
- для узла: id, type, label (человекочитаемое имя), опционально source и confidence;
- для связи: type, from, to, опционально timestamp, weight, evidence.

Пример визуализации инцидента. На графе видно, как разрозненные события собираются в цепочку. В центре — скомпрометированный узел (IP-адрес или сервер), который выступает «хабом», связывая внешние угрозы с внутренними пользователями. Разные цвета обозначают типы сущностей (User, IP, Incident).
Минимальный алгоритм построения (сбор данных → нормализация → визуализация)
Если строить "по-взрослому", можно уйти в бесконечные методологии. Но базовый рабочий конвейер на практике выглядит довольно приземлённо. Важнее всего — не инструменты, а дисциплина шагов.
Шаг 1. Сбор данных
Определите, откуда берутся факты. Это могут быть логи, выгрузки из CRM, результаты сканов, OSINT-заметки, тикеты, отчёты. На этом этапе не пытайтесь сразу сделать красиво — просто собирайте наблюдения.
- Фиксируйте первоисточник (хотя бы названием файла/системы).
- Сохраняйте "сырые" значения как есть (даже если там бардак).
- Отдельно записывайте, что именно считается фактом (например: "email найден в профиле", "IP был источником подключения").
Шаг 2. Нормализация и "склейка" сущностей
Самый важный шаг, где граф либо становится сильным, либо превращается в кашу. Нормализация — это приведение данных к единым правилам: форматы, регистры, канонические значения. А "склейка" (entity resolution) — это когда вы решаете, что "Ivan Petrov", "Иван Петров" и "petrov_i" — один и тот же объект или всё-таки разные.
- Задайте ключи: что делает объект уникальным (email? UUID? комбинация полей?).
- Чистите мусор: лишние пробелы, разные написания, странные разделители.
- Разделяйте "значение" и "метку": ID один, отображаемое имя — другое.
- Если уверенность не 100%, храните confidence и не склеивайте насильно.
Шаг 3. Построение структуры графа
Теперь вы превращаете факты в узлы и связи. Удобно мыслить так: каждое утверждение превращается в ребро. Например: "аккаунт входил на хост" → связь AUTHENTICATED_TO. "домен резолвится в IP" → связь RESOLVES_TO.
Шаг 4. Визуализация
Визуализация нужна не только для "красоты", а для проверки здравого смысла: нет ли слишком толстых "узлов-монстров", не распался ли граф на отдельные островки, правильно ли направлены стрелки, не появились ли дубли.
- Начинайте с фильтра: один кейс/период/подграф.
- Раскрашивайте по типам сущностей (хотя бы логически, даже если инструмент делает это автоматически).
- Смотрите на степени узлов: кто соединён со всеми подряд — часто это либо "хаб", либо ошибка нормализации.
Шаг 5. Хранение и повторяемость
Если граф строится больше одного раза, он уже не "картинка", а процесс. Значит, нужны форматы хранения и правила обновления: что добавляем, что пересчитываем, как обрабатываем конфликтующие факты.
Практика: строим простой граф на Python (NetworkX)
Руками рисовать схемы долго. Настоящая мощь графов раскрывается, когда вы генерируете их скриптами. Вот пример на Python с библиотекой networkx, который связывает пользователя, IP-адрес и домен.
import networkx as nx
import matplotlib.pyplot as plt # Для вывода картинки
# 1. Создаем пустой граф
G = nx.DiGraph() # DiGraph = направленный граф (важно для связей "кто -> куда")
# 2. Добавляем узлы с атрибутами (тип сущности критически важен!)
G.add_node("ivan_admin", type="user", label="Ivan P.")
G.add_node("192.168.1.55", type="ip", is_internal=True)
G.add_node("malicious-site.com", type="domain", threat_level="high")
# 3. Добавляем связи (ребра)
# Иван зашел на IP
G.add_edge("ivan_admin", "192.168.1.55",
relation="LOGON",
timestamp="2023-10-27T10:00:00")
# Этот IP обратился к подозрительному домену
G.add_edge("192.168.1.55", "malicious-site.com",
relation="DNS_REQUEST",
count=14)
# 4. Простой анализ: найти кратчайший путь
path = nx.shortest_path(G, source="ivan_admin", target="malicious-site.com")
print(f"Цепочка атаки: {path}")
# Вывод: Цепочка атаки: ['ivan_admin', '192.168.1.55', 'malicious-site.com']
Что здесь произошло? Мы не просто нарисовали линию. Мы создали структуру, которую можно запросить: «Покажи всех пользователей, которые находятся в 2 шагах от хакерского домена». В Excel вы бы искали это часами, граф выдает ответ за миллисекунды.
Частые ошибки (дубли сущностей, "грязные" связи, неверная направленность)
Ошибки в графе коварны: он выглядит убедительно, даже когда он неправильный. И чем красивее визуализация, тем сложнее заметить, что вас аккуратно уводят в сторону.
1) Дубли сущностей
Классика: один и тот же объект размазан на пять узлов ("example.com", "EXAMPLE.COM", "example.com/", " www.example.com" и т.д.).
- Решение: канонизация (lowercase, trim, правила для доменов/телефонов), единый уникальный ключ.
- Проверка: топ узлов по "похожести" меток, отчёт по возможным дублям.
2) "Грязные" связи
Сюда попадает всё, что превращает граф в шум: связи без типа, связи "на всякий случай", связи без контекста. Особенно опасно, когда ребро означает разные вещи в разных местах.
- Решение: фиксированный набор типов связей + понятное описание, что означает каждый тип.
- Трюк: храните доказательство/контекст в атрибуте evidence (например, "logon event 4624", "WHOIS", "тикет #123").
3) Неверная направленность
Направление ребра — это не декоративная стрелочка. Это смысл. "Пользователь → сервер" и "сервер → пользователь" могут давать разные результаты при поиске путей и запросах.
- Решение: заранее решить, как читаются связи (обычно "субъект → объект действия").
- Проверка: возьмите 10 случайных связей и проговорите их вслух. Если звучит криво — значит, модель кривовата.
4) Узлы-универсалы и "суперхабы"
Иногда появляется узел, который связан почти со всем: "Google", "Unknown", "localhost", "N/A", "Russia", "admin". Часть таких хабов может быть реальной, но чаще это следствие грязных данных.
- Решение: выделять "служебные" значения в отдельный тип или вообще исключать из графа на уровне визуализации.
- Проверка: сортировка узлов по степени (degree) и ручной аудит топ-20.
Инструменты и форматы
Инструменты — это уже вопрос удобства и масштаба. Главное: разделяйте хранение и визуализацию. Рисовалка не обязана быть вашей базой данных, и наоборот.
Форматы данных: как хранить
- CSV — простой старт. Обычно делят на два файла: nodes.csv и edges.csv. Плюс: легко редактировать, дружит с Excel. Минус: вложенные атрибуты и сложные структуры неудобны.
- JSON — гибко для атрибутов и вложенности. Минус: разные инструменты ожидают разные "диалекты" JSON.
- GraphML — формат под графы, часто хорошо импортируется в визуализаторы. Плюс: структурированность и атрибуты. Минус: многословен, руками править не всегда приятно.
Пример правильной структуры JSON
Многие новички пытаются вкладывать объекты друг в друга. Не делайте так. Плоская структура «список узлов + список ребер» (как в примере ниже) — это золотой стандарт. Её понимают почти все библиотеки визуализации (D3.js, Cytoscape, Sigma.js).
{
"nodes": [
{
"id": "user_101",
"type": "employee",
"label": "Алексей С.",
"department": "IT"
},
{
"id": "server_db_01",
"type": "host",
"label": "Database Server Primary",
"ip": "10.0.0.5"
}
],
"edges": [
{
"source": "user_101",
"target": "server_db_01",
"relation": "ACCESS_GRANTED",
"weight": 1,
"metadata": {
"method": "SSH",
"last_seen": "2023-12-01"
}
}
]
}
Визуализация и анализ
- Gephi — популярный визуализатор для сетевого анализа (кластеры, метрики, фильтры).
- Graphviz — когда нужен быстрый рендер схемы из описания, полезно для отчётов.
- Cytoscape — мощная визуализация графов, особенно когда хочется гибко управлять стилями и данными.
Хранение и запросы
- Neo4j — один из самых известных вариантов для графового хранения и запросов.
- TigerGraph — когда граф большой и нужны производительные вычисления.
- JanusGraph — опция для тех, кто строит распределённое графовое хранилище.
Практика хранения "по-человечески"
- Храните сырые данные отдельно от нормализованного графа (иначе вы потеряете контекст).
- Держите словарь типов сущностей и связей (мини-справочник проекта).
- Используйте стабильные ID (не "Иван", а person:sha256(...) или person:12345).
- Если граф живой — продумайте версионирование (хотя бы датой снапшота) и правила дедупликации.
Если совсем коротко: граф связей — это не картинка, а способ держать реальность в руках, когда её слишком много. Начните с простого: узлы, связи, минимальные атрибуты, чистые ID. Дальше вы удивитесь, как быстро "разрозненные факты" превращаются в понятную карту.