montana/Русский/Совет/Cursor/атака_затмения_07.01.2026_14:15.md

27 KiB
Raw Permalink Blame History

Детальный анализ 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

Статус: КРИТИЧЕСКАЯ УЯЗВИМОСТЬ

Анализ:

  1. 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 таблиц
    • НЕ сохраняются активные соединения
    • При рестарте все соединения теряются
  2. ConnectionManager не сохраняет соединения:

    // connection.rs:188-229
    pub struct ConnectionManager {
        outbound_count: AtomicUsize,
        inbound_count: AtomicUsize,
        netgroup_counts: Mutex<HashMap<u32, usize>>,
        // ← НЕТ МЕХАНИЗМА СОХРАНЕНИЯ СОЕДИНЕНИЙ
    }
    
    • Сохраняются только баны (ban_list)
    • Активные соединения не сохраняются
    • При рестарте netgroup_counts пуст
  3. 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

Анализ:

  1. 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 хорошими адресами
  2. 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, честные адреса выталкиваются
  3. 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

Анализ:

  1. 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 таблицами
    • Проблема: Если обе таблицы заполнены адресами атакующего, выбор будет из них
  2. Случайный выбор из 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

Статус: Работает, но недостаточно для защиты

Анализ:

  1. 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 часа
  2. 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 атаки

Текущие механизмы защиты:

  1. Netgroup diversity — работает для активных соединений
  2. Bucket collision handling — защищает от замены хороших адресов
  3. Feeler connections — валидирует адреса, но слишком медленно
  4. Per-IP limits — ограничивает количество соединений с одного IP
  5. Rate limiting — ограничивает скорость заполнения AddrMan

Отсутствующие механизмы защиты:

  1. Anchor connections — критическая уязвимость
  2. Приоритизация источников — нет приоритизации DNS seeds/hardcoded
  3. Разнообразие источников — нет требования разнообразия источников
  4. Быстрая валидация — feeler слишком медленный

Выводы

Критическая уязвимость: Eclipse Attack возможна

Основные причины:

  1. Отсутствие anchor connections между рестартами
  2. Netgroup diversity не защищает при первом выборе после рестарта
  3. Select logic уязвим к заполнению AddrMan адресами атакующего
  4. Feeler connections слишком медленные для защиты

Вероятность успешной атаки:

  • При заполнении 80% AddrMan адресами атакующего: ~80%
  • При заполнении 90% AddrMan адресами атакующего: ~90%

Сложность атаки:

  • Средняя: требует контроля множества IP адресов и подсетей
  • Время подготовки: несколько дней для заполнения AddrMan
  • Триггер: рестарт жертвы (может быть спровоцирован)

Рекомендации:

  1. Немедленно: Реализовать anchor connections механизм
  2. Высокий приоритет: Улучшить netgroup diversity при первом выборе
  3. Средний приоритет: Добавить приоритизацию источников адресов
  4. Низкий приоритет: Ускорить 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 атаки.