282 lines
13 KiB
Markdown
282 lines
13 KiB
Markdown
|
|
**Модель:** Gemini 3 Pro
|
|||
|
|
**Компания:** Google
|
|||
|
|
**Дата:** 07.01.2026 14:55 UTC
|
|||
|
|
|
|||
|
|
# CRITICAL SECURITY REPORT: Eclipse Protection Bypass
|
|||
|
|
|
|||
|
|
## 1. Резюме (Summary)
|
|||
|
|
Заявленная защита от Eclipse Attack в сети Montana **полностью неактивна**. Несмотря на утверждения в документации и комментариях о том, что "Full Bootstrap Verification" выполняется при каждом запуске, код, отвечающий за эту проверку (`StartupVerifier`), **никогда не вызывается** в точке входа приложения (`main.rs`) или в сетевом контроллере (`Network::start`).
|
|||
|
|
|
|||
|
|
Дополнительно, сама логика верификации содержит "заглушку" (stub), которая привела бы к невозможности запуска узла, если бы она была вызвана.
|
|||
|
|
|
|||
|
|
## 2. Детали уязвимости (Vulnerability Details)
|
|||
|
|
|
|||
|
|
### A. Отсутствие вызова защиты (Critical)
|
|||
|
|
**Файлы:** `src/main.rs`, `src/net/protocol.rs`
|
|||
|
|
|
|||
|
|
Файл `main.rs` инициализирует узел и сеть, логируя информацию о типе верификации ("Startup verification: full_bootstrap"). Однако затем он немедленно запускает сеть без вызова `StartupVerifier::verify()`.
|
|||
|
|
|
|||
|
|
В `src/net/protocol.rs`, метод `connection_loop` содержит обширные комментарии, описывающие "Phase 1: Startup Verification". Однако код немедленно переходит к логике "Phase 2: Peer Selection", пропуская первую фазу.
|
|||
|
|
|
|||
|
|
**Код (`src/main.rs`):**
|
|||
|
|
```rust
|
|||
|
|
// Информация о верификации логируется...
|
|||
|
|
info!("Startup verification: {} ...", verify_type);
|
|||
|
|
|
|||
|
|
// ...но сеть запускается сразу, без проверки
|
|||
|
|
let (network, event_rx) = Network::new(net_config).await?;
|
|||
|
|
network.start().await?; // Начинает подключения немедленно
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### B. Реализация-заглушка (High)
|
|||
|
|
**Файл:** `src/net/startup.rs`
|
|||
|
|
|
|||
|
|
Даже если бы `StartupVerifier` был вызван, метод `query_hardcoded_tips` является заглушкой, возвращающей пустой список.
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
async fn query_hardcoded_tips(&self, addrs: &[SocketAddr]) -> Vec<PeerChainInfo> {
|
|||
|
|
// ...
|
|||
|
|
// Placeholder: in production, this would make actual network requests
|
|||
|
|
Vec::new() // Возвращает пустой вектор
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Так как логика требует `MIN_HARDCODED_RESPONSES` (15), проверка `responses.len() < MIN_HARDCODED_RESPONSES` всегда будет провалена, возвращая ошибку `StartupError::InsufficientHardcoded`.
|
|||
|
|
|
|||
|
|
## 3. Вектор атаки (Eclipse)
|
|||
|
|
|
|||
|
|
1. **Подготовка:** Атакующий использует ботнет (50+ IP в разных подсетях /16).
|
|||
|
|
2. **Flooding:** Атакующий отправляет `Addr` сообщения жертве, чтобы заполнить "New" таблицы в `AddrMan` вредоносными адресами.
|
|||
|
|
3. **Триггер:** Узел жертвы перезапускается (или принудительно перезагружается через DoS).
|
|||
|
|
4. **Исполнение:**
|
|||
|
|
- Жертва запускается.
|
|||
|
|
- **Bypass:** Проверка `StartupVerifier` пропускается.
|
|||
|
|
- `Network` начинает подключения.
|
|||
|
|
- `AddrMan` выбирает IP атакующего из заполненных бакетов.
|
|||
|
|
- Жертва подключается к узлам атакующего.
|
|||
|
|
- Так как проверка "Hardcoded Node Consensus" пропущена, жертва принимает изолированную версию цепи от атакующего.
|
|||
|
|
5. **Влияние:** Двойная трата, цензура, кража средств.
|
|||
|
|
|
|||
|
|
## 4. Рекомендации (Remediation)
|
|||
|
|
|
|||
|
|
1. **Реализовать `query_hardcoded_tips`:** Заменить заглушку в `src/net/startup.rs` на реальные сетевые запросы (подключение, рукопожатие, получение заголовков, отключение).
|
|||
|
|
2. **Принудительная верификация:** В `src/main.rs` вызвать `StartupVerifier::verify()` **перед** запуском основного сетевого цикла, или внутри `Network::start` перед разрешением общих подключений.
|
|||
|
|
3. **Fail-Safe:** Гарантировать, что узел откажется работать, если верификация не пройдена.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# ОПРОВЕРЖЕНИЕ: Claude Opus 4.5 (Adversarial Mode)
|
|||
|
|
|
|||
|
|
**Дата:** 07.01.2026 15:30 UTC
|
|||
|
|
**Модель:** Claude Opus 4.5
|
|||
|
|
**Компания:** Anthropic
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Вердикт: ОТЧЁТ GEMINI ОШИБОЧЕН
|
|||
|
|
|
|||
|
|
Gemini 3 Pro допустил критическую ошибку в анализе кодовой базы. Он читал **устаревший/неиспользуемый файл** (`startup.rs`) вместо **реально вызываемого кода** (`verification.rs`).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Детальное опровержение
|
|||
|
|
|
|||
|
|
### Ошибка 1: "StartupVerifier не вызывается"
|
|||
|
|
|
|||
|
|
**Утверждение Gemini:** Код логирует информацию о верификации, но сеть запускается сразу.
|
|||
|
|
|
|||
|
|
**Факт:** `main.rs:124-167` содержит **BLOCKING** вызов верификации:
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// main.rs:123-167
|
|||
|
|
} else {
|
|||
|
|
// Run bootstrap verification (BLOCKING)
|
|||
|
|
info!("Running bootstrap verification...");
|
|||
|
|
info!("Requirements: 100 peers, 25+ /16 subnets, hardcoded consensus");
|
|||
|
|
|
|||
|
|
let verifier = VerificationClient::new(testnet, port, genesis.clone());
|
|||
|
|
|
|||
|
|
match verifier.verify().await { // <-- BLOCKING CALL
|
|||
|
|
Ok(result) => {
|
|||
|
|
info!("Bootstrap verification PASSED");
|
|||
|
|
// ...success handling
|
|||
|
|
}
|
|||
|
|
Err(e) => {
|
|||
|
|
error!("CRITICAL: Bootstrap verification FAILED");
|
|||
|
|
return Err(format!("Bootstrap verification failed: {}", e).into());
|
|||
|
|
// ^^^^ NODE STARTUP ABORTED
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Network starts ONLY AFTER verification passes:
|
|||
|
|
let (network, event_rx) = Network::new(net_config).await?; // Line 185
|
|||
|
|
network.start().await?; // Line 186
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Код `Network::new()` вызывается на строке 185, ПОСЛЕ завершения `verifier.verify().await`.**
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Ошибка 2: "query_hardcoded_tips — заглушка"
|
|||
|
|
|
|||
|
|
**Утверждение Gemini:** Метод `query_hardcoded_tips` в `startup.rs` возвращает пустой вектор.
|
|||
|
|
|
|||
|
|
**Факт:** `main.rs` использует `VerificationClient`, а не `StartupVerifier`.
|
|||
|
|
|
|||
|
|
`VerificationClient` находится в `verification.rs` и имеет **полную реализацию** с реальными сетевыми запросами:
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// verification.rs:344-403 — РЕАЛЬНАЯ реализация
|
|||
|
|
async fn query_hardcoded_nodes(
|
|||
|
|
&self,
|
|||
|
|
addrs: &[SocketAddr],
|
|||
|
|
) -> (Vec<PeerChainInfo>, Vec<SocketAddr>) {
|
|||
|
|
let responses = Arc::new(Mutex::new(Vec::new()));
|
|||
|
|
let discovered = Arc::new(Mutex::new(HashSet::new()));
|
|||
|
|
|
|||
|
|
let mut handles = Vec::new();
|
|||
|
|
|
|||
|
|
for addr in addrs {
|
|||
|
|
let handle = tokio::spawn(async move {
|
|||
|
|
let _permit = sem.acquire().await.ok()?;
|
|||
|
|
|
|||
|
|
// РЕАЛЬНЫЙ сетевой запрос:
|
|||
|
|
match query_single_node(addr, listen_port, true, testnet).await {
|
|||
|
|
Ok((info, peer_addrs)) => {
|
|||
|
|
responses.lock().await.push(info);
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
Err(e) => { /* error handling */ }
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
handles.push(handle);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Wait for ALL queries to complete
|
|||
|
|
for handle in handles {
|
|||
|
|
let _ = handle.await;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Return collected responses
|
|||
|
|
(responses, discovered)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Функция `query_single_node()` (`verification.rs:507-711`) выполняет:
|
|||
|
|
1. TCP connect с таймаутом
|
|||
|
|
2. AuthChallenge (криптографический вызов)
|
|||
|
|
3. AuthResponse с ML-DSA-65 подписью
|
|||
|
|
4. Верификацию подписи против hardcoded pubkey
|
|||
|
|
5. Version/Verack handshake
|
|||
|
|
6. GetAddr для обнаружения P2P пиров
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Ошибка 3: "Network начинает подключения немедленно"
|
|||
|
|
|
|||
|
|
**Факт:** Последовательность в main.rs:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. verifier.verify().await — BLOCKING, может занять 30+ секунд
|
|||
|
|
2. Network::new() — только если verify() вернул Ok
|
|||
|
|
3. Network::start() — только после new()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Если `verify()` возвращает `Err`, узел завершается с ошибкой (`main.rs:163-164`):
|
|||
|
|
```rust
|
|||
|
|
return Err(format!("Bootstrap verification failed: {}", e).into());
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Причина ошибки Gemini
|
|||
|
|
|
|||
|
|
Gemini читал файл `startup.rs`, который содержит **альтернативную/старую реализацию** `StartupVerifier`. Но `main.rs` импортирует и использует `VerificationClient` из `verification.rs`:
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
// main.rs:16-18
|
|||
|
|
use crate::net::{
|
|||
|
|
NetConfig, NetEvent, Network, VerificationClient, NODE_FULL, NODE_PRESENCE,
|
|||
|
|
verification_type,
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Gemini не проследил цепочку импортов и проанализировал неправильный файл.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Реальный Adversarial Review: Eclipse Attack на Montana
|
|||
|
|
|
|||
|
|
### Attack Surface
|
|||
|
|
|
|||
|
|
| Input | Trust Level | Protection |
|
|||
|
|
|-------|-------------|------------|
|
|||
|
|
| Hardcoded nodes | Cryptographically verified (ML-DSA-65) | Challenge-response auth |
|
|||
|
|
| P2P peers | Consensus-verified | 100 peers, 25+ subnets |
|
|||
|
|
| AddrMan (peers.dat) | Untrusted candidates | Cryptographic bucketing |
|
|||
|
|
| System clock | Local | Verified vs network median |
|
|||
|
|
|
|||
|
|
### Attempted Attacks
|
|||
|
|
|
|||
|
|
| # | Attack Vector | Result |
|
|||
|
|
|---|---------------|--------|
|
|||
|
|
| 1 | BGP/ISP MITM on hardcoded nodes | **PROTECTED** — ML-DSA-65 signature verification (`verification.rs:557-559`) |
|
|||
|
|
| 2 | Sybil flood into AddrMan | **PROTECTED** — Startup verification ignores peers.dat, queries live network |
|
|||
|
|
| 3 | DDoS hardcoded nodes | **PROTECTED** — Requires 75% failure; node refuses to start otherwise (`verification.rs:213-230`) |
|
|||
|
|
| 4 | Clock manipulation | **PROTECTED** — Median time from 100 peers, >10min divergence = abort (`bootstrap.rs:283-346`) |
|
|||
|
|
| 5 | Runtime connection takeover | **PROTECTED** — Netgroup limits (2 per /16), eviction diversity (28 protected peers) |
|
|||
|
|
| 6 | ADDR message poisoning | **PROTECTED** — Rate limiting (0.1/sec), cryptographic bucket assignment |
|
|||
|
|
|
|||
|
|
### Critical Protection Chain
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
+-------------------------------------------------------------+
|
|||
|
|
| Node Startup |
|
|||
|
|
+-------------------------------------------------------------+
|
|||
|
|
| 1. VerificationClient::verify() [BLOCKING] |
|
|||
|
|
| +-- query_hardcoded_nodes() [ML-DSA-65 auth] |
|
|||
|
|
| +-- query_p2p_peers() [100 peers target] |
|
|||
|
|
| +-- verify_subnet_diversity() [25+ /16 required] |
|
|||
|
|
| +-- verify_network_time() [median consensus] |
|
|||
|
|
| +-- verify_hardcoded_consensus() [+/-1% agreement] |
|
|||
|
|
| |
|
|||
|
|
| 2. IF verify() fails -> ABORT (node does not start) |
|
|||
|
|
| |
|
|||
|
|
| 3. Network::new() + Network::start() [only on success] |
|
|||
|
|
+-------------------------------------------------------------+
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Findings
|
|||
|
|
|
|||
|
|
| Severity | Finding | Status |
|
|||
|
|
|----------|---------|--------|
|
|||
|
|
| CRITICAL | None | - |
|
|||
|
|
| HIGH | None | - |
|
|||
|
|
| MEDIUM | Single hardcoded node (current testnet) | Known limitation, planned expansion |
|
|||
|
|
| LOW | startup.rs contains stale code | Should be removed to prevent confusion |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Verdict
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[X] GEMINI REPORT INVALID — Analysis based on wrong file
|
|||
|
|
[X] MONTANA ECLIPSE PROTECTION VERIFIED — Full implementation exists and is called
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Montana's eclipse resistance is **correctly implemented** in `verification.rs`:
|
|||
|
|
- Cryptographic hardcoded node authentication (ML-DSA-65)
|
|||
|
|
- 100 peer queries with 25+ /16 subnet diversity
|
|||
|
|
- Blocking verification before network start
|
|||
|
|
- Node refuses to run if verification fails
|
|||
|
|
|
|||
|
|
**Recommendation:** Remove or clearly deprecate `startup.rs` to prevent future analysis confusion.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Reviewed by:** Claude Opus 4.5 (Adversarial Mode)
|
|||
|
|
**Date:** 2026-01-07 15:30 UTC
|