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

91 lines
8.3 KiB
Markdown
Raw Normal View History

**Модель:** GPT-5.2
**Компания:** OpenAI
**Дата:** 07.01.2026 18:11 UTC
## Задача
Найти практический вектор **Eclipse Attack** на сеть Montana (P2P), опираясь на код в `/Montana ACP/montana/src/net/`.
## TL;DR (главная уязвимость)
Заявленная защита “full bootstrap verification (100 peers, 25+ /16 subnets, hardcoded median check)” **фактически не исполняется** в текущей реализации:
- `main.rs` только **логирует** «Startup verification … Full bootstrap…», но **не вызывает** `StartupVerifier` / `BootstrapVerifier`.
- `Network::start()` запускает listener/connector/maintenance и сразу добавляет user-supplied `seeds` в `AddrMan`, после чего `connection_loop` начинает диалить адреса **без какого-либо гейта “verification passed”**.
- `net/startup.rs` содержит заглушку `query_hardcoded_tips()``Vec::new()`, то есть даже если verifier вызывать, он **не сможет** набрать `MIN_HARDCODED_RESPONSES`.
Следствие: Eclipse сводится к классике — **poisoning AddrMan + перехват outbound**.
## Доказательства по коду (ключевые места)
### 1) Startup verification не выполняется
`montana/src/main.rs`:
- Есть лог о “Startup verification … Full bootstrap…”, но дальше код сразу делает `Network::new()` и `network.start()` (без вызова verifier).
`montana/src/net/protocol.rs`:
- `Network::start()` спаунит `listener_loop`, `connection_loop`, `maintenance_loop`, затем добавляет `seeds` через `add_seed()` и возвращает `Ok(())`.
- Никакой проверки, что “startup verification passed”, нет.
### 2) StartupVerifier — заглушка
`montana/src/net/startup.rs`:
- `query_hardcoded_tips()` возвращает `Vec::new()` (placeholder), а `total_peers` в результате = `responses.len()` с комментарием “Would be 100 in full implementation”.
### 3) Addr poisoning реально влияет на выбор outbound
`montana/src/net/protocol.rs`:
- `connection_loop` выбирает адрес через `addrman.select()` (50/50 NEW vs TRIED) и затем коннектится.
### 4) TRIED заполняется после handshake (это хорошо), но это делает eclipse ещё проще при отсутствии bootstrapping
`montana/src/net/protocol.rs`:
- На `Message::Verack` вызывается `addresses.write().await.mark_connected(&peer.addr);` — перевод адреса в TRIED происходит после успешного handshake.
### 5) Addr rate-limit легко обходится Sybilом (лимит per-connection)
`montana/src/net/rate_limit.rs`:
- `AddrRateLimiter`: **burst 1000**, sustained **0.1 addr/sec**.
- Лимитер хранится в `Peer` → лимит **на соединение**, а не глобальный.
- Значит атакующий открывает много соединений (входящих/исходящих) и с каждого отправляет по 1000 адресов → быстро забивает NEW таблицу.
## Практическая схема Eclipse Attack (эксплуатация)
### Предпосылки
- Жертва запускает узел и имеет хотя бы 12 канала к атакующему (например, через `--seeds` или просто подключившись к атакующему узлу).
- Атакующий контролирует набор IPов, желательно распределённых по разным /16 (или IPv6-префиксам так, как считает `get_netgroup`).
### Шаг 1 — “засорить NEW” адресной книгой атакующего
1) Атакующий устанавливает соединение с жертвой и доводит handshake до `Verack` (иначе нельзя слать `Addr`).
2) Сразу после handshake отправляет `Addr` с максимумом адресов (до 1000 в сообщении).
3) Повторяет через множество Sybil-соединений: каждый `Peer` имеет свой `AddrRateLimiter` → снова 1000 burst.
Результат: `AddrMan::new_table` (1024 buckets × 64) — до ~65k слотов — может быть существенно заполнена адресами атакующего.
### Шаг 2 — перевести атакующие адреса в TRIED
1) Т.к. `connection_loop` выбирает адреса из AddrMan (50/50 NEW/TRIED) и **нет гейта на bootstrap-verification**, жертва начнёт диалить адреса из отравленного AddrMan.
2) Атакующие узлы принимают соединение и корректно отвечают handshake.
3) На `Verack` адрес переводится в `TRIED` (`mark_connected`).
Результат: со временем TRIED (256×64 = 16384 слота) тоже становится “атакующий-доминант”.
### Шаг 3 — рестарт жертвы и «полный eclipse»
После рестарта жертва снова будет выбирать peers из AddrMan (частично TRIED) и практически все outbound уйдут на атакующего.
### Ограничения и как атакующий их обходит
- **MAX_OUTBOUND = 8**, **MAX_PEERS_PER_NETGROUP = 2** (т.е. максимум 2 исходящих на один /16).
- Для полного перехвата 8 outbound нужно иметь ≥4 разных netgroup (/16) и по 2 адреса в каждом.
- **MAX_CONNECTIONS_PER_IP = 2** — не даёт одному IP занять много слотов.
- Но атакующий использует несколько IP.
- Факт отсутствия “100 peers / 25+ subnets / hardcoded median check” делает стоимость атаки **на порядки ниже**, чем заявлено в комментариях.
## Почему это именно Eclipse (а не просто Sybil)
Потому что цель — **монополизировать видимость сети жертвы** за счёт контроля всех/почти всех outbound + доминирования адресной книги, а не просто “иметь много узлов”. Здесь это достигается через AddrMan poisoning и отсутствие обязательной startup-верификации.
## Рекомендации (high level)
1) **Сделать startup verification реальным гейтом**:
- до запуска `connection_loop` собрать 100 ответов, проверить 25+ /16, проверить hardcoded-median, и только затем разрешать outbound.
2) **Убрать/жёстко ограничить доверие к `--seeds`**:
- сейчас `add_seed()` bypassит часть проверок и фактически даёт атакующему якорь.
3) **Глобальный и per-IP/per-netgroup лимит на Addr ingestion**:
- текущий лимит per-peer легко обходится множеством соединений.
4) (Если включать feelers) **Feeler должен проходить protocol handshake**, иначе “TCP-only reachability” снова откроет прямой путь к TRIED poisoning.
---
## Приложение: ключевые файлы
- `montana/src/net/protocol.rs` — выбор outbound (`AddrMan::select`), обработка `Addr`, перевод в `TRIED` на `Verack`.
- `montana/src/net/addrman.rs` — NEW/TRIED таблицы, bucket logic.
- `montana/src/net/startup.rs` и `montana/src/net/bootstrap.rs` — заявленная модель защиты, но сейчас не интегрирована/частично заглушена.
- `montana/src/net/rate_limit.rs` — per-peer токенбакеты.