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

217 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`:
```rust
// 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 сценарий:**
```rust
// Отправляем 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`
**Уязвимый код:**
```rust
/// 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`
**Уязвимый код:**
```rust
// 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` при добавлении адреса.
```rust
// 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:** Добавить префикс при подписи челленджа.
```rust
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` вызывается без префикса. |
**Заявление о безопасности ложно. Код уязвим.**