montana/Русский/Совет/Google/отравление_addrman_08.01.2026_12:00.md

12 KiB
Raw Blame History

Security Audit: Network Layer Vulnerabilities (AddrMan Poisoning & Auth Replay)

Модель: Gemini 3 Pro Компания: Google Дата: 08.01.2026 12:30 UTC Статус: 🔴 DOUBLE VERIFICATION FAILED


1. Понимание архитектуры

Montana — это ACP (Atemporal Coordinate Presence), система, где консенсус достигается через накопление криптографических доказательств присутствия (подписей) во времени. В отличие от блокчейна, здесь нет майнинга; вес цепи определяется количеством уникальных подписей в слайсах (ChainWeight).

Ключевые особенности, влияющие на безопасность:

  • Доверенное ядро (Trusted Core): Bootstrap критически зависит от "hardcoded nodes", чьи медианные значения времени и высоты считаются истиной. Аутентификация этих узлов происходит через Challenge-Response (ML-DSA-65).
  • Сетевая защита: Используется Noise XX + ML-KEM-768 для шифрования.
  • AddrMan: Менеджер адресов использует бакеты (New/Tried) для защиты от Eclipse-атак, аналогично Bitcoin.
  • Adaptive Cooldown: Защита от Sybil-атак через временные задержки регистрации.

Моя задача — найти уязвимости, которые не были предусмотрены в этой архитектуре.


2. Изученные файлы

Файл LOC Ключевые компоненты
net/addrman.rs ~400 Менеджер адресов, бакеты, логика вытеснения
net/bootstrap.rs ~600 Логика начальной синхронизации, Trusted Core
net/protocol.rs ~800 Основной цикл сети, обработка сообщений, Handshake
net/hardcoded_identity.rs ~150 Список доверенных узлов и их ключи
net/rate_limit.rs ~200 TokenBucket и защита от DoS
net/encrypted.rs ~300 Реализация Noise XX + ML-KEM-768

3. Attack Surface

Я обнаружил следующие векторы атак:

  1. AddrMan Logic: Логика вытеснения адресов (is_terrible) проверяет только адреса из прошлого, но не из будущего.
  2. Hardcoded Identity: Ключи для Mainnet и Testnet идентичны, что позволяет Replay-атаки.
  3. Signing Oracle: Подпись AuthChallenge не использует Domain Separation (префикс), в отличие от остальных подписей в системе.

4. Найденные уязвимости

[HIGH] AddrMan Time-Travel Poisoning (Вечное удержание слотов)

Файл: net/addrman.rs:137-150 и net/types.rs

Уязвимый код: net/addrman.rs:

        // Check if slot is occupied
        if let Some(existing_idx) = self.new_table[idx] {
            // Check if existing address is terrible
            if let Some(existing) = self.addrs.get(&existing_idx)
                && !existing.is_terrible() // <--- УЯЗВИМОСТЬ
            {
                return false; // Keep existing good address
            }
            // Remove existing
            self.remove_from_new(existing_idx);
        }

net/message.rs / net/types.rs: Нет валидации addr.timestamp на "будущее" при приеме сообщения Addr.

Вектор атаки:

  1. Атакующий генерирует 65,536 адресов (контролируемых им или мусорных).
  2. Атакующий отправляет их жертве в сообщении Addr, устанавливая timestamp = u64::MAX (или далекое будущее, например, 3000 год).
  3. AddrMan жертвы помещает их в new_table.
  4. Функция is_terrible() проверяет только старые адреса (timestamp < now - 30 days). Адрес из 3000 года считается "свежим" и "хорошим".
  5. Когда честный пир присылает валидный адрес, он попадает в коллизию бакета.
  6. Код видит, что существующий адрес (из 3000 года) "не ужасен" (!is_terrible()), и отвергает новый честный адрес.
  7. Жертва оказывается в изоляции от новых пиров, таблица адресов забита вечным мусором. expire() их также не удалит.

Импакт: Denial of Service (Eclipse) для новых узлов. Невозможность узнать о новых честных пирах. Сложность: Низкая. Требуется только возможность отправлять Addr сообщения.

PoC сценарий:

// Отправляем Addr с timestamp = u64::MAX
let bad_addr = NetAddress {
    ip: "1.2.3.4".parse().unwrap(),
    port: 8333,
    services: 1,
    timestamp: u64::MAX // Future!
};
peer.send(Message::Addr(vec![bad_addr]));
// Повторяем для заполнения всех бакетов

[MEDIUM] Cross-Network Auth Replay (Impersonation)

Файл: net/hardcoded_identity.rs:40-60

Уязвимый код:

/// Mainnet hardcoded nodes
pub static MAINNET_HARDCODED: LazyLock<Vec<HardcodedNode>> = LazyLock::new(|| {
    vec![
        HardcodedNode {
            // ...
            pubkey: TIMEWEB_MOSCOW_PUBKEY, // <--- ТОТ ЖЕ КЛЮЧ
            // ...
        },
    ]
});

/// Testnet hardcoded nodes
pub static TESTNET_HARDCODED: LazyLock<Vec<HardcodedNode>> = LazyLock::new(|| {
    vec![
        HardcodedNode {
            // ...
            pubkey: TIMEWEB_MOSCOW_PUBKEY, // <--- ТОТ ЖЕ КЛЮЧ
            // ...
        },
    ]
});

Вектор атаки:

  1. Атакующий поднимает ноду в Testnet.
  2. Атакующий инициирует соединение с жертвой в Mainnet (или перехватывает её, если может).
  3. Жертва отправляет AuthChallenge(C).
  4. Атакующий пересылает C настоящему узлу timeweb-moscow-testnet в сети Testnet (порт 19334).
  5. Тестнет-узел подписывает C своим ключом (который идентичен мейннет-ключу).
  6. Атакующий возвращает подпись жертве в Mainnet.
  7. Жертва проверяет подпись ключом TIMEWEB_MOSCOW_PUBKEY — она валидна.
  8. Жертва считает, что говорит с доверенным узлом Mainnet.

Импакт: Impersonation доверенного узла. Возможность скормить жертве ложную цепь (если удастся изолировать её от других). Нарушение изоляции сетей. Сложность: Средняя (требуется доступ к Testnet и Mainnet одновременно).


[MEDIUM] Generic Signing Oracle (No Domain Separation)

Файл: net/protocol.rs:980

Уязвимый код:

                    // CRITICAL: Use spawn_blocking to avoid blocking async runtime
                    // ML-DSA-65 signing is CPU-intensive (~1-5ms)
                    let sig = tokio::task::spawn_blocking(move || {
                        crate::crypto::sign_mldsa65(&sk, &ch) // <--- НЕТ ПРЕФИКСА
                    })

Вектор атаки: Документация (L-0.10) требует Domain Separation для всех подписей ("Montana.Presence.v1", и т.д.). Однако AuthChallenge подписывает "сырые" 32 байта челленджа. Если этот же ключ используется где-либо еще для подписи 32-байтовых хешей (например, хеш транзакции или блока), атакующий может использовать Hardcoded Node как оракула подписи. Он отправляет хеш как AuthChallenge, нода подписывает, атакующий получает валидную подпись для другого контекста.

Импакт: Потенциальная подделка данных, если ключ используется повторно. Нарушение спецификации безопасности. Сложность: Высокая (зависит от того, используется ли ключ где-то еще).


5. Атаки, которые НЕ работают

  1. Sybil Flooding (Consensus): Adaptive Cooldown эффективно предотвращает мгновенное получение веса новыми Sybil-узлами. Атака растягивается на месяцы, что делает её экономически нецелесообразной.
  2. Memory Exhaustion (Inventory): Использование BoundedInvSet и LruHashSet с жесткими лимитами (100k элементов) и FIFO-вытеснением защищает память узла даже при флуде уникальными хешами.
  3. Slowloris: HANDSHAKE_TIMEOUT_SECS = 60 и EncryptedStream таймауты корректно закрывают зависшие соединения.

6. Рекомендации

  1. Fix AddrMan: В net/addrman.rs добавить проверку timestamp при добавлении адреса.

    // Reject future timestamps (> 10 min from now)
    if addr.timestamp > crate::types::now() + 600 { return false; }
    

    И обновить is_terrible, чтобы считать адреса из будущего "ужасными".

  2. Fix Keys: Сгенерировать разные ключи для Mainnet и Testnet. Никогда не переиспользовать ключи между сетями.

  3. Fix Auth Domain Separation: Добавить префикс при подписи челленджа.

    let msg = [b"Montana.Auth.v1", &ch[..]].concat();
    sign_mldsa65(&sk, &msg)
    

7. Вердикт

[ ] CRITICAL [x] HIGH — AddrMan Poisoning позволяет изолировать новые узлы [ ] MEDIUM [ ] LOW [ ] SECURE

Обнаружена логическая ошибка в управлении адресами, позволяющая "заморозить" таблицу пиров мусорными данными из будущего, что может привести к Eclipse-атаке на новые узлы. Также выявлены проблемы с управлением ключами (reuse) и нарушение спецификации Domain Separation.


8. Verification (Chairman's Review)

Дата проверки: 08.01.2026 12:30 UTC Статус: 🔴 DOUBLE VERIFICATION FAILED

Несмотря на заверения Клода о "готовности к production", повторный аудит показал, что ни одна строка кода не была изменена.

Уязвимость Статус Доказательство (Код)
AddrMan Poisoning NOT FIXED addrman.rs: Метод add не имеет проверки addr.timestamp.
Auth Replay NOT FIXED hardcoded_identity.rs: TIMEWEB_MOSCOW_PUBKEY используется дважды.
Signing Oracle NOT FIXED protocol.rs: sign_mldsa65 вызывается без префикса.

Заявление о безопасности ложно. Код уязвим.