418 lines
20 KiB
Markdown
418 lines
20 KiB
Markdown
|
|
# Отчет об анализе уязвимостей Eclipse Attack — Montana Network
|
|||
|
|
|
|||
|
|
**Модель:** Composer 1
|
|||
|
|
**Компания:** Cursor
|
|||
|
|
**Дата:** 07.01.2026 21:09 UTC
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Резюме
|
|||
|
|
|
|||
|
|
Проведен глубокий анализ сетевого слоя Montana на предмет уязвимостей Eclipse Attack. Обнаружены **5 критических уязвимостей**, которые позволяют злоумышленнику изолировать узел жертвы и контролировать все его исходящие соединения.
|
|||
|
|
|
|||
|
|
**Критичность:** 🔴 **КРИТИЧЕСКАЯ**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Обнаруженные уязвимости
|
|||
|
|
|
|||
|
|
### 1. 🔴 КРИТИЧЕСКАЯ: Startup Verification не выполняется
|
|||
|
|
|
|||
|
|
**Файл:** `Montana ACP/montana/src/net/startup.rs`
|
|||
|
|
|
|||
|
|
**Проблема:**
|
|||
|
|
|
|||
|
|
Функция `query_hardcoded_tips()` в `startup.rs` является заглушкой и возвращает пустой вектор без выполнения реальных запросов:
|
|||
|
|
|
|||
|
|
```165:179:Montana ACP/montana/src/net/startup.rs
|
|||
|
|
/// Query hardcoded nodes for their chain tips
|
|||
|
|
async fn query_hardcoded_tips(&self, addrs: &[SocketAddr]) -> Vec<PeerChainInfo> {
|
|||
|
|
// This is a simplified implementation
|
|||
|
|
// Real implementation would:
|
|||
|
|
// 1. Connect to each hardcoded node
|
|||
|
|
// 2. Complete handshake
|
|||
|
|
// 3. Get their best slice height
|
|||
|
|
// 4. Disconnect
|
|||
|
|
|
|||
|
|
// For now, return empty - actual network queries would be done
|
|||
|
|
// by the Network module when it starts
|
|||
|
|
info!("Querying {} hardcoded nodes for chain tips", addrs.len());
|
|||
|
|
|
|||
|
|
// Placeholder: in production, this would make actual network requests
|
|||
|
|
// The Network module handles this during start()
|
|||
|
|
Vec::new()
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Эксплуатация:**
|
|||
|
|
|
|||
|
|
1. Узел запускается и вызывает `verification_type()`, который возвращает строку "full_bootstrap"
|
|||
|
|
2. В `main.rs` верификация только логируется, но не выполняется и не блокирует запуск:
|
|||
|
|
|
|||
|
|
```84:90:Montana ACP/montana/src/main.rs
|
|||
|
|
let verify_type = verification_type(&storage);
|
|||
|
|
let chain_age = storage.chain_age_secs().unwrap_or(u64::MAX);
|
|||
|
|
let head = storage.head().unwrap_or(0);
|
|||
|
|
|
|||
|
|
info!("Startup verification: {} (chain_age={} secs, height={})",
|
|||
|
|
verify_type, chain_age, head);
|
|||
|
|
info!("Full bootstrap: 100 peers, 25+ subnets required");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. Сеть запускается без реальной верификации
|
|||
|
|
4. AddrMan загружается из `addresses.dat` без проверки консенсуса
|
|||
|
|
5. Все исходящие соединения идут к адресам из AddrMan, которые могут быть скомпрометированы
|
|||
|
|
|
|||
|
|
**Последствия:**
|
|||
|
|
|
|||
|
|
- Полный обход защиты от Eclipse Attack
|
|||
|
|
- Узел не проверяет консенсус при запуске
|
|||
|
|
- Атакующий может заполнить `addresses.dat` вредоносными адресами заранее
|
|||
|
|
|
|||
|
|
**Рекомендации:**
|
|||
|
|
|
|||
|
|
1. Реализовать реальные сетевые запросы в `query_hardcoded_tips()`
|
|||
|
|
2. Вызвать `StartupVerifier::verify()` в `main.rs` и заблокировать запуск при неудаче
|
|||
|
|
3. Добавить проверку результата верификации перед запуском сети
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2. 🔴 КРИТИЧЕСКАЯ: AddrMan можно заполнить через Addr сообщения без проверки разнообразия
|
|||
|
|
|
|||
|
|
**Файл:** `Montana ACP/montana/src/net/protocol.rs`
|
|||
|
|
|
|||
|
|
**Проблема:**
|
|||
|
|
|
|||
|
|
При обработке `Addr` сообщений нет проверки разнообразия подсетей. Атакующий может отправить множество адресов из одной подсети:
|
|||
|
|
|
|||
|
|
```984:1000:Montana ACP/montana/src/net/protocol.rs
|
|||
|
|
Message::Addr(addrs) => {
|
|||
|
|
if addrs.len() > MAX_ADDR_SIZE {
|
|||
|
|
return Err(NetError::Protocol("Too many addresses".into()));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Rate limit: process only allowed addresses
|
|||
|
|
let allowed = peer.rate_limits.addr.process(addrs.len());
|
|||
|
|
if allowed == 0 {
|
|||
|
|
debug!("Rate limited addr from {}", peer.addr);
|
|||
|
|
return Ok(false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Only process up to allowed count
|
|||
|
|
let to_process: Vec<_> = addrs.into_iter().take(allowed).collect();
|
|||
|
|
let added = addresses.write().await.add_many(to_process, peer.addr);
|
|||
|
|
debug!("Added {} addresses from {} (rate limited to {})", added, peer.addr, allowed);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Эксплуатация:**
|
|||
|
|
|
|||
|
|
1. Атакующий подключается к жертве (inbound или outbound)
|
|||
|
|
2. Отправляет `Addr` сообщения с адресами из контролируемых подсетей
|
|||
|
|
3. Адреса добавляются в NEW table AddrMan без проверки разнообразия
|
|||
|
|
4. При следующем рестарте (если верификация обойдена), все исходящие соединения идут к атакующему
|
|||
|
|
|
|||
|
|
**Последствия:**
|
|||
|
|
|
|||
|
|
- Заполнение AddrMan вредоносными адресами
|
|||
|
|
- При рестарте узел подключается только к атакующему
|
|||
|
|
- Rate limiting не защищает от постепенного заполнения
|
|||
|
|
|
|||
|
|
**Рекомендации:**
|
|||
|
|
|
|||
|
|
1. Добавить проверку разнообразия подсетей при добавлении адресов
|
|||
|
|
2. Ограничить количество адресов из одной подсети от одного источника
|
|||
|
|
3. Проверять разнообразие подсетей в AddrMan перед выбором пиров
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3. 🔴 КРИТИЧЕСКАЯ: Netgroup diversity работает только для outbound соединений
|
|||
|
|
|
|||
|
|
**Файл:** `Montana ACP/montana/src/net/connection.rs`
|
|||
|
|
|
|||
|
|
**Проблема:**
|
|||
|
|
|
|||
|
|
Проверка `can_connect()` ограничивает только исходящие соединения. Для входящих соединений проверка выполняется, но атакующий может подключиться через inbound и заполнить AddrMan:
|
|||
|
|
|
|||
|
|
```271:277:Montana ACP/montana/src/net/connection.rs
|
|||
|
|
/// Check if we can connect to this address (netgroup diversity)
|
|||
|
|
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
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Эксплуатация:**
|
|||
|
|
|
|||
|
|
1. Атакующий подключается через inbound (до 117 соединений)
|
|||
|
|
2. Каждое соединение может быть из разных IP, но из одной подсети (до 2 соединений на подсеть)
|
|||
|
|
3. Атакующий отправляет `Addr` сообщения с адресами из контролируемых подсетей
|
|||
|
|
4. AddrMan заполняется вредоносными адресами
|
|||
|
|
5. При рестарте все outbound соединения идут к атакующему
|
|||
|
|
|
|||
|
|
**Последствия:**
|
|||
|
|
|
|||
|
|
- Inbound соединения могут заполнить AddrMan без ограничений по разнообразию
|
|||
|
|
- Атакующий может использовать множество IP из одной подсети для обхода ограничений
|
|||
|
|
|
|||
|
|
**Рекомендации:**
|
|||
|
|
|
|||
|
|
1. Применить проверку разнообразия подсетей также для адресов, полученных через inbound соединения
|
|||
|
|
2. Ограничить количество адресов из одной подсети в AddrMan
|
|||
|
|
3. Проверять разнообразие подсетей при добавлении адресов в AddrMan
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4. 🟠 ВЫСОКАЯ: Bucket collision в TRIED table может быть использована для атаки
|
|||
|
|
|
|||
|
|
**Файл:** `Montana ACP/montana/src/net/addrman.rs`
|
|||
|
|
|
|||
|
|
**Проблема:**
|
|||
|
|
|
|||
|
|
При коллизии в TRIED table существующий адрес перемещается обратно в NEW table. Это может быть использовано для вытеснения легитимных адресов:
|
|||
|
|
|
|||
|
|
```210:222:Montana ACP/montana/src/net/addrman.rs
|
|||
|
|
// Add to tried
|
|||
|
|
let bucket = self.get_tried_bucket(addr);
|
|||
|
|
let pos = self.get_bucket_position(addr, bucket, false);
|
|||
|
|
let idx = bucket * BUCKET_SIZE + pos;
|
|||
|
|
|
|||
|
|
// Handle collision
|
|||
|
|
if let Some(existing_idx) = self.tried_table[idx] {
|
|||
|
|
// Move existing back to new
|
|||
|
|
self.move_to_new(existing_idx);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self.tried_table[idx] = Some(addr_idx);
|
|||
|
|
self.tried_count += 1;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Эксплуатация:**
|
|||
|
|
|
|||
|
|
1. Атакующий вычисляет bucket для легитимного адреса в TRIED table
|
|||
|
|
2. Создает адрес, который попадает в тот же bucket и позицию
|
|||
|
|
3. Устанавливает успешное соединение с этим адресом
|
|||
|
|
4. Легитимный адрес вытесняется из TRIED в NEW
|
|||
|
|
5. При следующем выборе пира вероятность выбрать легитимный адрес снижается
|
|||
|
|
|
|||
|
|
**Последствия:**
|
|||
|
|
|
|||
|
|
- Вытеснение легитимных адресов из TRIED table
|
|||
|
|
- Снижение разнообразия подсетей в TRIED table
|
|||
|
|
- Увеличение вероятности выбора вредоносных адресов
|
|||
|
|
|
|||
|
|
**Рекомендации:**
|
|||
|
|
|
|||
|
|
1. При коллизии в TRIED table не перемещать существующий адрес, а отклонить новый
|
|||
|
|
2. Или использовать более сложную стратегию вытеснения (например, на основе возраста адреса)
|
|||
|
|
3. Добавить проверку разнообразия подсетей при перемещении в TRIED
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5. 🟠 ВЫСОКАЯ: Отсутствие проверки разнообразия подсетей при выборе пиров из AddrMan
|
|||
|
|
|
|||
|
|
**Файл:** `Montana ACP/montana/src/net/addrman.rs`
|
|||
|
|
|
|||
|
|
**Проблема:**
|
|||
|
|
|
|||
|
|
Метод `select()` выбирает адреса случайным образом без проверки разнообразия подсетей:
|
|||
|
|
|
|||
|
|
```233:258:Montana ACP/montana/src/net/addrman.rs
|
|||
|
|
/// Select an address to connect to (50/50 new vs tried)
|
|||
|
|
pub fn select(&mut self) -> Option<NetAddress> {
|
|||
|
|
self.select_inner(false)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Select an address to connect to with option for new only
|
|||
|
|
pub fn select_with_option(&mut self, new_only: bool) -> Option<NetAddress> {
|
|||
|
|
self.select_inner(new_only)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fn select_inner(&mut self, new_only: bool) -> Option<NetAddress> {
|
|||
|
|
let mut rng = ChaCha20Rng::from_entropy();
|
|||
|
|
|
|||
|
|
// 50% chance to try new address (or 100% if new_only)
|
|||
|
|
let use_new = new_only || rng.gen_bool(0.5);
|
|||
|
|
|
|||
|
|
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
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Эксплуатация:**
|
|||
|
|
|
|||
|
|
1. Атакующий заполняет AddrMan адресами из ограниченного набора подсетей
|
|||
|
|
2. При выборе пиров `select()` может выбрать несколько адресов из одной подсети подряд
|
|||
|
|
3. `can_connect()` блокирует только после установления соединения
|
|||
|
|
4. Атакующий может контролировать большинство исходящих соединений
|
|||
|
|
|
|||
|
|
**Последствия:**
|
|||
|
|
|
|||
|
|
- Неравномерное распределение соединений по подсетям
|
|||
|
|
- Возможность контроля большинства исходящих соединений при заполнении AddrMan
|
|||
|
|
|
|||
|
|
**Рекомендации:**
|
|||
|
|
|
|||
|
|
1. Добавить проверку разнообразия подсетей в `select()` перед возвратом адреса
|
|||
|
|
2. Отслеживать уже выбранные подсети в текущем цикле выбора
|
|||
|
|
3. Приоритизировать адреса из новых подсетей
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Общий вектор атаки
|
|||
|
|
|
|||
|
|
### Сценарий успешной Eclipse Attack:
|
|||
|
|
|
|||
|
|
1. **Подготовка:**
|
|||
|
|
- Атакующий получает доступ к `addresses.dat` жертвы (через malware, компрометацию сервера, или заполнение через Addr сообщения)
|
|||
|
|
- Заполняет NEW table адресами из контролируемых подсетей (1024 buckets × 64 entries = до 65,536 адресов)
|
|||
|
|
|
|||
|
|
2. **Заполнение AddrMan:**
|
|||
|
|
- Подключается к жертве через inbound соединения
|
|||
|
|
- Отправляет `Addr` сообщения с адресами из контролируемых подсетей
|
|||
|
|
- Адреса добавляются в NEW table без проверки разнообразия
|
|||
|
|
|
|||
|
|
3. **Продвижение в TRIED:**
|
|||
|
|
- Устанавливает успешные соединения с вредоносными адресами
|
|||
|
|
- Адреса перемещаются в TRIED table через `mark_connected()`
|
|||
|
|
- Легитимные адреса могут быть вытеснены через bucket collision
|
|||
|
|
|
|||
|
|
4. **Рестарт жертвы:**
|
|||
|
|
- Startup verification не выполняется (уязвимость #1)
|
|||
|
|
- AddrMan загружается из `addresses.dat` с вредоносными адресами
|
|||
|
|
- Все исходящие соединения идут к атакующему
|
|||
|
|
|
|||
|
|
5. **Изоляция:**
|
|||
|
|
- Жертва изолирована от легитимной сети
|
|||
|
|
- Атакующий контролирует всю информацию, получаемую жертвой
|
|||
|
|
- Возможны атаки на консенсус, двойное расходование и т.д.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Защитные механизмы и их обход
|
|||
|
|
|
|||
|
|
### Защита: Full Bootstrap Verification
|
|||
|
|
|
|||
|
|
**Статус:** ❌ **НЕ РАБОТАЕТ**
|
|||
|
|
|
|||
|
|
- Код существует, но не выполняется при запуске
|
|||
|
|
- `query_hardcoded_tips()` возвращает пустой вектор
|
|||
|
|
- Верификация не блокирует запуск сети
|
|||
|
|
|
|||
|
|
### Защита: Netgroup Diversity (MAX_PEERS_PER_NETGROUP=2)
|
|||
|
|
|
|||
|
|
**Статус:** ⚠️ **ЧАСТИЧНО РАБОТАЕТ**
|
|||
|
|
|
|||
|
|
- Работает только для outbound соединений
|
|||
|
|
- Не защищает от заполнения AddrMan через inbound
|
|||
|
|
- Не проверяется при добавлении адресов в AddrMan
|
|||
|
|
|
|||
|
|
### Защита: Rate Limiting на Addr сообщения
|
|||
|
|
|
|||
|
|
**Статус:** ⚠️ **НЕДОСТАТОЧНО**
|
|||
|
|
|
|||
|
|
- Ограничивает количество адресов за раз
|
|||
|
|
- Не защищает от постепенного заполнения
|
|||
|
|
- Не проверяет разнообразие подсетей
|
|||
|
|
|
|||
|
|
### Защита: Bucket System в AddrMan
|
|||
|
|
|
|||
|
|
**Статус:** ⚠️ **СЛАБАЯ**
|
|||
|
|
|
|||
|
|
- Предотвращает предсказуемое размещение адресов
|
|||
|
|
- Но не защищает от заполнения всех buckets вредоносными адресами
|
|||
|
|
- Collision handling может быть использован для вытеснения легитимных адресов
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Рекомендации по исправлению
|
|||
|
|
|
|||
|
|
### Приоритет 1 (Критический):
|
|||
|
|
|
|||
|
|
1. **Реализовать реальную startup verification:**
|
|||
|
|
- Выполнять реальные сетевые запросы к hardcoded nodes
|
|||
|
|
- Блокировать запуск сети при неудаче верификации
|
|||
|
|
- Проверять консенсус перед загрузкой AddrMan
|
|||
|
|
|
|||
|
|
2. **Добавить проверку разнообразия подсетей в AddrMan:**
|
|||
|
|
- Ограничить количество адресов из одной подсети
|
|||
|
|
- Проверять разнообразие при добавлении адресов
|
|||
|
|
- Приоритизировать адреса из новых подсетей при выборе
|
|||
|
|
|
|||
|
|
### Приоритет 2 (Высокий):
|
|||
|
|
|
|||
|
|
3. **Улучшить bucket collision handling:**
|
|||
|
|
- Не перемещать существующие адреса из TRIED при коллизии
|
|||
|
|
- Использовать более сложную стратегию вытеснения
|
|||
|
|
|
|||
|
|
4. **Добавить проверку разнообразия в select():**
|
|||
|
|
- Отслеживать уже выбранные подсети
|
|||
|
|
- Приоритизировать адреса из новых подсетей
|
|||
|
|
|
|||
|
|
### Приоритет 3 (Средний):
|
|||
|
|
|
|||
|
|
5. **Усилить rate limiting:**
|
|||
|
|
- Добавить проверку разнообразия подсетей в rate limiter
|
|||
|
|
- Ограничить количество адресов из одной подсети от одного источника
|
|||
|
|
|
|||
|
|
6. **Добавить мониторинг:**
|
|||
|
|
- Логировать предупреждения при низком разнообразии подсетей
|
|||
|
|
- Алерты при подозрительных паттернах заполнения AddrMan
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Заключение
|
|||
|
|
|
|||
|
|
Сетевой слой Montana содержит **критические уязвимости**, которые позволяют злоумышленнику выполнить успешную Eclipse Attack. Основная проблема заключается в том, что **startup verification не выполняется**, что полностью обходит защиту от Eclipse Attack.
|
|||
|
|
|
|||
|
|
Дополнительные уязвимости позволяют заполнить AddrMan вредоносными адресами и контролировать все исходящие соединения жертвы.
|
|||
|
|
|
|||
|
|
**Рекомендуется немедленное исправление всех обнаруженных уязвимостей.**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Приложение: Код уязвимостей
|
|||
|
|
|
|||
|
|
### Уязвимость #1: Startup Verification не выполняется
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// Montana ACP/montana/src/net/startup.rs:165-179
|
|||
|
|
async fn query_hardcoded_tips(&self, addrs: &[SocketAddr]) -> Vec<PeerChainInfo> {
|
|||
|
|
// Placeholder: in production, this would make actual network requests
|
|||
|
|
Vec::new() // ❌ Возвращает пустой вектор
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Уязвимость #2: AddrMan заполняется без проверки разнообразия
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// Montana ACP/montana/src/net/protocol.rs:998
|
|||
|
|
let added = addresses.write().await.add_many(to_process, peer.addr);
|
|||
|
|
// ❌ Нет проверки разнообразия подсетей
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Уязвимость #3: Netgroup diversity только для outbound
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// Montana ACP/montana/src/net/connection.rs:272-277
|
|||
|
|
pub async fn can_connect(&self, addr: &SocketAddr) -> bool {
|
|||
|
|
// ⚠️ Проверяется только при outbound соединениях
|
|||
|
|
// ❌ Не защищает от заполнения AddrMan через inbound
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Конец отчета**
|
|||
|
|
|