27 KiB
Детальный анализ Eclipse Attack на сеть Montana
Модель: Composer 1
Компания: Cursor
Дата: 07.01.2026 14:15 UTC
Резюме
Обнаружена критическая уязвимость к Eclipse атаке в сетевом слое Montana. Основная проблема: отсутствие механизма anchor connections между рестартами узла, что позволяет атакующему изолировать жертву от честной сети после перезапуска.
Что такое Eclipse Attack?
Eclipse атака — это изоляция узла от честной сети путем заполнения всех его соединений вредоносными узлами атакующего. После рестарта жертва подключается только к узлам атакующего и не может получить доступ к честной сети.
Анализ защиты от Eclipse в Montana
1. Netgroup Diversity — ЧАСТИЧНО РАБОТАЕТ
Код: connection.rs:264-270
pub async fn can_connect(&self, addr: &SocketAddr) -> bool {
let netgroup = get_netgroup(addr);
let counts = self.netgroup_counts.lock().await;
let current = counts.get(&netgroup).copied().unwrap_or(0);
current < MAX_PEERS_PER_NETGROUP // MAX_PEERS_PER_NETGROUP = 2
}
Статус: ✅ Работает для активных соединений
Проблема: ❌ При рестарте netgroup_counts пуст, проверка неэффективна
Анализ:
MAX_PEERS_PER_NETGROUP = 2— максимум 2 peers из одной /16 подсети- Для
MAX_OUTBOUND = 8нужно минимум 4 различных /16 подсети - При рестарте
netgroup_countsсбрасывается в пустое состояние - Первые 2 соединения из одной подсети проходят проверку
- Атакующий может использовать 2 IP из каждой /16 подсети
Слабое место:
// При рестарте:
netgroup_counts = HashMap::new() // ← ПУСТОЙ
// Первое соединение: current = 0 < 2 ✅ ПРОХОДИТ
// Второе соединение: current = 1 < 2 ✅ ПРОХОДИТ
// Третье соединение: current = 2 < 2 ❌ ОТКЛОНЯЕТСЯ
2. Anchor Connections — ОТСУТСТВУЕТ
Код: protocol.rs:167-204, connection.rs:188-229
Статус: ❌ КРИТИЧЕСКАЯ УЯЗВИМОСТЬ
Анализ:
-
AddrMan сохраняет только адреса:
// protocol.rs:171-179 let addr_path = config.data_dir.join("addresses.dat"); let addresses = if addr_path.exists() { AddrMan::load(&addr_path).unwrap_or_else(|e| { warn!("Failed to load addresses: {}", e); AddrMan::new() }) } else { AddrMan::new() };- Сохраняются адреса из NEW и TRIED таблиц
- НЕ сохраняются активные соединения
- При рестарте все соединения теряются
-
ConnectionManager не сохраняет соединения:
// connection.rs:188-229 pub struct ConnectionManager { outbound_count: AtomicUsize, inbound_count: AtomicUsize, netgroup_counts: Mutex<HashMap<u32, usize>>, // ← НЕТ МЕХАНИЗМА СОХРАНЕНИЯ СОЕДИНЕНИЙ }- Сохраняются только баны (
ban_list) - Активные соединения не сохраняются
- При рестарте
netgroup_countsпуст
- Сохраняются только баны (
-
Protocol не приоритизирует предыдущие соединения:
// protocol.rs:494-502 let net_addr = { let mut addrman = addresses.write().await; match addrman.select() { Some(addr) => addr, // ← СЛУЧАЙНЫЙ ВЫБОР None => continue, } };select()выбирает случайно из AddrMan- Нет приоритизации предыдущих outbound соединений
- Нет гарантии восстановления соединений после рестарта
Вектор атаки:
1. Атакующий заполняет AddrMan своими адресами
2. Жертва перезапускается
3. Все соединения теряются
4. select() выбирает случайно из AddrMan
5. Высокая вероятность выбора адресов атакующего
6. Все outbound → к атакующему
3. Bucket Collision Handling — РАБОТАЕТ, НО НЕДОСТАТОЧНО
Код: addrman.rs:154-163, 201-205
Статус: ✅ Работает, но не защищает от Eclipse
Анализ:
-
NEW table collision handling:
// addrman.rs:154-163 if let Some(existing_idx) = self.new_table[idx] { if let Some(existing) = self.addrs.get(&existing_idx) && !existing.is_terrible() { return false; // Keep existing good address } self.remove_from_new(existing_idx); }- Если слот занят хорошим адресом, новый адрес отклоняется
- Если слот занят плохим адресом, он заменяется
- Проблема: Атакующий может заполнить большинство buckets хорошими адресами
-
TRIED table collision handling:
// addrman.rs:201-205 if let Some(existing_idx) = self.tried_table[idx] { self.move_to_new(existing_idx); // Move existing back to new }- При коллизии существующий адрес перемещается обратно в NEW
- Проблема: Если атакующий заполнил TRIED, честные адреса выталкиваются
-
Bucket distribution:
// addrman.rs:441-450 fn get_new_bucket(&self, addr: &SocketAddr, source: Option<&SocketAddr>) -> usize { let mut hasher = SipHasher24::new_with_key(&self.key[..16].try_into().unwrap()); hasher.write(&get_netgroup_bytes(addr)); if let Some(src) = source { hasher.write(&get_netgroup_bytes(src)); } (hasher.finish() as usize) % NEW_BUCKET_COUNT // 1024 buckets }- Random key предотвращает предсказание buckets
- Но атакующий может заполнить большинство buckets через множество адресов
- NEW table: 1024 buckets × 64 слота = 65,536 адресов
- Если атакующий заполняет 80% buckets → высокая вероятность выбора его адресов
Слабое место:
- Коллизии защищают от замены хороших адресов плохими
- Но не защищают от заполнения большинства buckets хорошими адресами атакующего
4. Select Logic — СЛУЧАЙНЫЙ ВЫБОР ИЗ ЗАПОЛНЕННОГО ADDRMAN
Код: addrman.rs:223-248, 511-545, 547-569
Статус: ⚠️ Работает корректно, но уязвим к Eclipse
Анализ:
-
50/50 выбор между NEW и TRIED:
// addrman.rs:233-247 fn select_inner(&mut self, new_only: bool) -> Option<NetAddress> { let mut rng = ChaCha20Rng::from_entropy(); let use_new = new_only || rng.gen_bool(0.5); // 50% chance if use_new && self.new_count > 0 { self.select_from_new(&mut rng) } else if self.tried_count > 0 { self.select_from_tried(&mut rng) } else if self.new_count > 0 { self.select_from_new(&mut rng) } else { None } }- Случайный выбор между NEW и TRIED таблицами
- Проблема: Если обе таблицы заполнены адресами атакующего, выбор будет из них
-
Случайный выбор из buckets:
// addrman.rs:519-544 for _ in 0..1000 { let bucket = rng.gen_range(0..NEW_BUCKET_COUNT); // Random bucket let pos = rng.gen_range(0..BUCKET_SIZE); // Random position let idx = bucket * BUCKET_SIZE + pos; if let Some(addr_idx) = self.new_table[idx] && let Some(info) = self.addrs.get(&addr_idx) { // Skip if connected, terrible, or too old if self.connected.contains(&socket_addr) { continue; } if info.is_terrible() || info.addr.timestamp < horizon { continue; } return Some(info.addr.clone()); } }- Случайный выбор bucket и позиции
- До 1000 попыток найти валидный адрес
- Проблема: Если большинство buckets заполнены адресами атакующего, высока вероятность выбора его адресов
Вектор атаки:
1. Атакующий заполняет 80% buckets в NEW table
2. Атакующий заполняет 80% buckets в TRIED table
3. При select():
- 50% вероятность выбрать из NEW → 80% вероятность выбрать адрес атакующего
- 50% вероятность выбрать из TRIED → 80% вероятность выбрать адрес атакующего
4. Общая вероятность выбора адреса атакующего: ~80%
5. Feeler Connections — СЛИШКОМ МЕДЛЕННО
Код: feeler.rs:15-122
Статус: ✅ Работает, но недостаточно для защиты
Анализ:
-
Feeler interval:
// feeler.rs:15-16 pub const FEELER_INTERVAL: Duration = Duration::from_secs(120); // 2 минуты pub const MAX_CONCURRENT_FEELERS: usize = 1; // Только 1 feeler одновременно- Один feeler каждые 2 минуты
- Проблема: Слишком медленно для защиты от Eclipse
- Для проверки 1000 адресов нужно: 1000 × 2 минуты = 2000 минут = 33 часа
-
Feeler валидирует только NEW table:
// feeler.rs:110-114 let addr = { let mut am = addrman.lock().await; am.select_with_option(true).map(|a| a.socket_addr()) // new_only = true };- Проверяет только адреса из NEW table
- Успешные feeler перемещают адреса в TRIED
- Проблема: Не проверяет адреса в TRIED table
Слабое место:
- Feeler слишком медленный для защиты от Eclipse
- Не проверяет адреса в TRIED table
- Атакующий может заполнить TRIED через fake connections быстрее, чем feeler проверит их
Детальный вектор Eclipse атаки
Фаза 1: Заполнение NEW table
Цель: Заполнить большинство buckets в NEW table вредоносными адресами
Метод:
// Атакующий отправляет Addr сообщения с множеством адресов
// Каждый адрес попадает в случайный bucket через SipHash
for i in 0..50000 {
let addr = NetAddress::new(
IpAddr::V4(Ipv4Addr::new(
attacker_prefix,
(i / 256) as u8,
(i % 256) as u8,
1
)),
19333,
NODE_FULL
);
send_addr_message(victim, vec![addr]);
}
// NEW table: 1024 buckets × 64 слота = 65,536 адресов
// Атакующий заполняет 50,000 адресов → ~76% buckets заполнены
Результат:
- Большинство buckets в NEW table содержат адреса атакующего
- Честные адреса занимают только ~24% buckets
- При
select_from_new()высока вероятность выбора адреса атакующего
Фаза 2: Продвижение в TRIED table
Цель: Переместить адреса из NEW в TRIED через fake connections
Метод:
// Атакующий устанавливает соединения и завершает handshake
for addr in attacker_addresses_from_new_table {
let stream = connect(victim, addr);
// Завершаем handshake
send_version(stream);
receive_verack(stream);
// mark_good() вызывается автоматически
// Адрес перемещается из NEW в TRIED
close_connection(stream);
}
// После множества успешных соединений:
// - Адреса атакующего перемещаются в TRIED
// - Честные адреса остаются в NEW или выталкиваются
Результат:
- Большинство адресов в TRIED table принадлежат атакующему
- Честные адреса выталкиваются из TRIED при коллизиях
- При
select_from_tried()высока вероятность выбора адреса атакующего
Фаза 3: Ожидание рестарта
Цель: Дождаться рестарта жертвы для активации атаки
Метод:
// Атакующий мониторит жертву
while victim_is_online {
// Поддерживаем соединения
// Отправляем валидные сообщения
// Избегаем подозрительного поведения
wait_for_restart();
}
// При рестарте:
// 1. Все соединения теряются
// 2. AddrMan загружается из addresses.dat
// 3. netgroup_counts сбрасывается
// 4. connection_loop() начинает выбирать адреса заново
Результат:
- При рестарте все соединения теряются
- AddrMan загружается с адресами атакующего
- Нет anchor connections для восстановления честных соединений
Фаза 4: Захват outbound соединений
Цель: Заставить жертву подключиться только к узлам атакующего
Метод:
// После рестарта жертвы:
// connection_loop() начинает выбирать адреса из AddrMan
for i in 0..MAX_OUTBOUND { // MAX_OUTBOUND = 8
let addr = addrman.select(); // Случайный выбор
// Вероятность выбора адреса атакующего: ~80%
if addr.is_attacker() {
accept_connection(victim, addr);
// Жертва подключается к атакующему
}
}
// Результат:
// - Все 8 outbound соединений → к атакующему
// - Жертва изолирована от честной сети
Результат:
- Все outbound соединения направлены к атакующему
- Жертва не может получить доступ к честной сети
- Eclipse атака успешна
Эксплуатация Eclipse атаки
Шаг 1: Подготовка адресов
// Атакующий готовит множество адресов из разных /16 подсетей
// Для обхода MAX_PEERS_PER_NETGROUP = 2 использует 2 IP из каждой подсети
let mut attacker_addresses = Vec::new();
for subnet in 0..1000 { // 1000 различных /16 подсетей
let base = subnet * 256;
attacker_addresses.push((base, 1)); // Первый IP
attacker_addresses.push((base, 2)); // Второй IP
}
// Итого: 2000 адресов из 1000 подсетей
Шаг 2: Заполнение AddrMan
// Отправка Addr сообщений с адресами атакующего
// Используем rate limiting: 1000 burst, 0.1/sec sustained
let mut sent = 0;
for batch in attacker_addresses.chunks(1000) {
send_addr_message(victim, batch);
sent += batch.len();
// Ждем refill token bucket (0.1/sec)
if sent >= 1000 {
wait_for_refill();
sent = 0;
}
}
// Результат: AddrMan заполнен адресами атакующего
Шаг 3: Продвижение в TRIED
// Установка fake connections для перемещения адресов в TRIED
// Используем MAX_CONNECTIONS_PER_IP = 2 для обхода ограничений
for subnet in 0..1000 {
let addr1 = (subnet * 256, 1);
let addr2 = (subnet * 256, 2);
// Первое соединение
connect_and_handshake(victim, addr1);
// mark_good() → перемещение в TRIED
// Второе соединение
connect_and_handshake(victim, addr2);
// mark_good() → перемещение в TRIED
}
// Результат: Большинство адресов в TRIED принадлежат атакующему
Шаг 4: Ожидание рестарта
// Атакующий поддерживает соединения и ждет рестарта
while victim_is_online {
// Отправляем валидные сообщения
send_ping();
send_slice();
send_transaction();
// Избегаем подозрительного поведения
avoid_ban();
// Мониторим рестарт
if victim_restarted() {
break;
}
}
Шаг 5: Захват после рестарта
// После рестарта жертвы:
// 1. Все соединения потеряны
// 2. AddrMan загружен с адресами атакующего
// 3. connection_loop() начинает выбирать адреса
// Жертва пытается установить 8 outbound соединений
for i in 0..8 {
let addr = addrman.select(); // ~80% вероятность выбрать адрес атакующего
if addr.is_attacker() {
accept_connection(victim, addr);
// Жертва подключается к атакующему
}
}
// Результат: Все outbound → к атакующему
// Eclipse атака успешна!
Критические слабые места
1. Отсутствие Anchor Connections
Проблема:
- При рестарте все соединения теряются
- Нет механизма восстановления предыдущих outbound соединений
- Узел начинает выбирать адреса заново из AddrMan
Влияние:
- Высокая уязвимость к Eclipse атаке после рестарта
- Атакующий может заполнить AddrMan и захватить все соединения
Рекомендация:
- Реализовать anchor connections механизм
- Сохранять последние 8 outbound соединений в файл
- При рестарте приоритизировать эти адреса для переподключения
2. Netgroup Diversity не защищает при первом выборе
Проблема:
- При рестарте
netgroup_countsпуст - Первые 2 соединения из одной подсети проходят проверку
- Атакующий может использовать 2 IP из каждой подсети
Влияние:
- Атакующий может обойти netgroup diversity при первом выборе
- Достаточно 4 различных /16 подсетей для 8 outbound соединений
Рекомендация:
- Требовать минимум 4 различных /16 подсетей для первых outbound соединений
- Не позволять выбирать адреса из одной подсети до достижения разнообразия
3. Select Logic уязвим к заполнению AddrMan
Проблема:
- Случайный выбор из заполненного AddrMan
- Если большинство адресов принадлежат атакующему, высока вероятность выбора его адресов
- Нет приоритизации честных адресов
Влияние:
- При заполнении 80% AddrMan адресами атакующего, вероятность выбора его адресов ~80%
- Eclipse атака становится вероятной
Рекомендация:
- Добавить приоритизацию адресов по источнику (DNS seeds, hardcoded)
- Ограничить количество адресов от одного источника
- Использовать разнообразие источников при выборе
4. Feeler Connections слишком медленные
Проблема:
- Один feeler каждые 2 минуты
- Для проверки 1000 адресов нужно 33 часа
- Не проверяет адреса в TRIED table
Влияние:
- Атакующий может заполнить AddrMan быстрее, чем feeler проверит адреса
- Недостаточно для защиты от Eclipse атаки
Рекомендация:
- Увеличить количество concurrent feelers
- Уменьшить feeler interval
- Добавить проверку адресов в TRIED table
Защита от Eclipse атаки
Текущие механизмы защиты:
- ✅ Netgroup diversity — работает для активных соединений
- ✅ Bucket collision handling — защищает от замены хороших адресов
- ✅ Feeler connections — валидирует адреса, но слишком медленно
- ✅ Per-IP limits — ограничивает количество соединений с одного IP
- ✅ Rate limiting — ограничивает скорость заполнения AddrMan
Отсутствующие механизмы защиты:
- ❌ Anchor connections — критическая уязвимость
- ❌ Приоритизация источников — нет приоритизации DNS seeds/hardcoded
- ❌ Разнообразие источников — нет требования разнообразия источников
- ❌ Быстрая валидация — feeler слишком медленный
Выводы
Критическая уязвимость: Eclipse Attack возможна
Основные причины:
- Отсутствие anchor connections между рестартами
- Netgroup diversity не защищает при первом выборе после рестарта
- Select logic уязвим к заполнению AddrMan адресами атакующего
- Feeler connections слишком медленные для защиты
Вероятность успешной атаки:
- При заполнении 80% AddrMan адресами атакующего: ~80%
- При заполнении 90% AddrMan адресами атакующего: ~90%
Сложность атаки:
- Средняя: требует контроля множества IP адресов и подсетей
- Время подготовки: несколько дней для заполнения AddrMan
- Триггер: рестарт жертвы (может быть спровоцирован)
Рекомендации:
- Немедленно: Реализовать anchor connections механизм
- Высокий приоритет: Улучшить netgroup diversity при первом выборе
- Средний приоритет: Добавить приоритизацию источников адресов
- Низкий приоритет: Ускорить feeler connections
Дополнительные векторы атаки
Вектор 1: BGP Hijacking
Описание:
- Атакующий использует BGP hijacking для контроля множества /16 подсетей
- Заполняет AddrMan адресами из контролируемых подсетей
- После рестарта жертва подключается к контролируемым подсетям
Сложность: Высокая (требует BGP access)
Вероятность: Средняя (при наличии BGP access)
Вектор 2: Sybil Attack + Eclipse
Описание:
- Атакующий создает множество Sybil узлов
- Заполняет AddrMan адресами Sybil узлов
- После рестарта жертва подключается только к Sybil узлам
Сложность: Средняя (требует множество IP адресов)
Вероятность: Высокая (при наличии множества IP)
Вектор 3: DNS Seed Poisoning
Описание:
- Атакующий компрометирует DNS seeds
- DNS seeds возвращают адреса атакующего
- Жертва загружает адреса атакующего при bootstrap
Сложность: Высокая (требует компрометации DNS)
Вероятность: Низкая (DNS seeds защищены)
Заключение
Сеть Montana уязвима к Eclipse атаке из-за отсутствия механизма anchor connections и недостаточной защиты при первом выборе адресов после рестарта. Рекомендуется немедленно реализовать anchor connections и улучшить механизмы защиты от Eclipse атаки.