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

217 lines
12 KiB
Markdown
Raw Permalink Normal View 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`:
```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` вызывается без префикса. |
**Заявление о безопасности ложно. Код уязвим.**