519 lines
20 KiB
Markdown
519 lines
20 KiB
Markdown
|
|
# Security Audit: Montana Network Layer — Adversarial Analysis
|
|||
|
|
|
|||
|
|
**Модель:** Claude Opus 4.5
|
|||
|
|
**Компания:** Anthropic
|
|||
|
|
**Дата:** 08.01.2026 12:00 UTC
|
|||
|
|
**Scope:** `montana/src/net/` (21 файлов, ~12,000 LOC Rust)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Понимание архитектуры
|
|||
|
|
|
|||
|
|
### ACP vs Traditional Consensus
|
|||
|
|
|
|||
|
|
Montana использует **Atemporal Coordinate Presence (ACP)** — модель консенсуса, основанную на:
|
|||
|
|
|
|||
|
|
1. **Физическая упорядоченность (Layer -1)** — фундаментальное ограничение энтропии, требующее реального времени для накопления присутствия. Нельзя ускорить.
|
|||
|
|
|
|||
|
|
2. **Слайсы вместо блоков** — каждый слайс представляет фиксированный интервал времени (τ₂ = 5 минут), а не произвольный набор транзакций.
|
|||
|
|
|
|||
|
|
3. **Presence Proofs** — узлы подтверждают своё присутствие в момент времени τ₂, накапливая вес.
|
|||
|
|
|
|||
|
|
4. **Fork Choice через Cumulative Weight** — не PoW/PoS, а сумма весов presence proofs за всё время существования цепи.
|
|||
|
|
|
|||
|
|
### Отличия от традиционных систем
|
|||
|
|
|
|||
|
|
| Аспект | Bitcoin/Ethereum | Montana |
|
|||
|
|
|--------|------------------|---------|
|
|||
|
|
| Единица консенсуса | Блок | Слайс (τ₂ = 5 min) |
|
|||
|
|
| Право на создание | Mining/Staking | Любой узел с presence |
|
|||
|
|
| Fork resolution | Longest chain / Finality | Cumulative weight |
|
|||
|
|
| Time model | Block timestamps | Physically derived coordinate time |
|
|||
|
|
| Sybil resistance | PoW/PoS | Presence age + subnet diversity |
|
|||
|
|
|
|||
|
|
### Следствия для безопасности
|
|||
|
|
|
|||
|
|
1. **Атаки на майнинг не применимы** — нет mining power
|
|||
|
|
2. **Nothing-at-stake не применим** — нет staking
|
|||
|
|
3. **Критична синхронизация времени** — физические инварианты привязывают к реальному времени
|
|||
|
|
4. **Bootstrap особенно критичен** — начальный вектор состояния определяет всё
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Изученные файлы
|
|||
|
|
|
|||
|
|
| Файл | LOC | Ключевые компоненты |
|
|||
|
|
|------|-----|---------------------|
|
|||
|
|
| mod.rs | ~50 | Module exports |
|
|||
|
|
| types.rs | ~200 | Constants, NetAddress, InvItem |
|
|||
|
|
| message.rs | ~300 | Wire protocol messages |
|
|||
|
|
| peer.rs | ~250 | Peer state, rate limits |
|
|||
|
|
| connection.rs | ~510 | Connection manager, bans, netgroup |
|
|||
|
|
| addrman.rs | ~740 | Cryptographic bucket system |
|
|||
|
|
| rate_limit.rs | ~150 | Token bucket rate limiting |
|
|||
|
|
| eviction.rs | ~200 | Peer eviction strategy |
|
|||
|
|
| discouraged.rs | ~180 | Bloom filter for misbehavior |
|
|||
|
|
| startup.rs | ~300 | Bootstrap orchestration |
|
|||
|
|
| bootstrap.rs | ~400 | Chain verification protocol |
|
|||
|
|
| verification.rs | ~850 | Bootstrap verification client |
|
|||
|
|
| hardcoded_identity.rs | ~205 | ML-DSA-65 hardcoded nodes |
|
|||
|
|
| noise.rs | ~895 | Hybrid Noise XX + ML-KEM-768 |
|
|||
|
|
| encrypted.rs | ~435 | Encrypted stream wrapper |
|
|||
|
|
| subnet.rs | ~390 | Subnet reputation tracking |
|
|||
|
|
| feeler.rs | ~260 | Feeler connections, addr cache |
|
|||
|
|
| sync.rs | ~670 | Headers-first sync |
|
|||
|
|
| protocol.rs | ~1650 | Main network protocol |
|
|||
|
|
| inventory.rs | ~595 | Relay cache, LRU eviction |
|
|||
|
|
| dns.rs | ~270 | DNS seeds, fallback IPs |
|
|||
|
|
|
|||
|
|
**Всего:** ~8,500 LOC анализированного кода
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Attack Surface
|
|||
|
|
|
|||
|
|
### External Inputs (Точки входа для атакующего)
|
|||
|
|
|
|||
|
|
| Категория | Источник | Trust Level |
|
|||
|
|
|-----------|----------|-------------|
|
|||
|
|
| TCP connections | Any IP | Untrusted |
|
|||
|
|
| Noise handshake | Post-TCP | Transport-secure |
|
|||
|
|
| P2P messages | Post-handshake | Partially trusted |
|
|||
|
|
| Addr gossip | Any peer | Untrusted |
|
|||
|
|
| DNS seeds | Hardcoded domains | Semi-trusted |
|
|||
|
|
| Hardcoded nodes | Embedded in binary | Trusted |
|
|||
|
|
| Bootstrap data | Network consensus | Verified |
|
|||
|
|
|
|||
|
|
### Trust Boundaries
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Internet → TCP → Noise Handshake → Montana Protocol → Consensus
|
|||
|
|
↑ ↑ ↑ ↑
|
|||
|
|
DoS Crypto attacks Protocol abuse Fork manipulation
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Critical Assets (Что защищаем)
|
|||
|
|
|
|||
|
|
1. **Consensus state** — cumulative_weight, canonical chain
|
|||
|
|
2. **Private keys** — Noise keypair, wallet keys
|
|||
|
|
3. **Peer set** — защита от eclipse
|
|||
|
|
4. **Network connectivity** — защита от DoS
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Найденные уязвимости
|
|||
|
|
|
|||
|
|
### [CRITICAL] V-001: Single Point of Failure — One Hardcoded Node
|
|||
|
|
|
|||
|
|
**Файл:** `hardcoded_identity.rs:59-68`
|
|||
|
|
|
|||
|
|
**Уязвимый код:**
|
|||
|
|
```rust
|
|||
|
|
pub static MAINNET_HARDCODED: LazyLock<Vec<HardcodedNode>> = LazyLock::new(|| {
|
|||
|
|
vec![HardcodedNode {
|
|||
|
|
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(176, 124, 208, 93)), 19333),
|
|||
|
|
pubkey: TIMEWEB_MOSCOW_PUBKEY,
|
|||
|
|
name: "timeweb-moscow-1",
|
|||
|
|
region: "eu-east",
|
|||
|
|
}]
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Вектор атаки:**
|
|||
|
|
1. Атакующий получает контроль над сервером 176.124.208.93 (TimeWeb Moscow)
|
|||
|
|
2. Bootstrap verification (`verification.rs:140`) требует ответа от hardcoded nodes
|
|||
|
|
3. С одним узлом атакующий контролирует 100% hardcoded ответов
|
|||
|
|
4. Атакующий может поставить любой `cumulative_weight` и `head_hash`
|
|||
|
|
5. Все новые узлы синхронизируются с поддельной цепью
|
|||
|
|
|
|||
|
|
**Импакт:** Полный контроль над консенсусом всех новых узлов
|
|||
|
|
|
|||
|
|
**Сложность:**
|
|||
|
|
- Компрометация одного VPS (~$1000-10000 через социальную инженерию или уязвимость хостинга)
|
|||
|
|
- Или DDoS на один IP для отказа в обслуживании bootstrap
|
|||
|
|
|
|||
|
|
**PoC сценарий:**
|
|||
|
|
```bash
|
|||
|
|
# Вариант 1: Compromise
|
|||
|
|
ssh root@176.124.208.93 # После компрометации
|
|||
|
|
systemctl stop montana
|
|||
|
|
./fake-bootstrap-server --cumulative-weight 999999999
|
|||
|
|
|
|||
|
|
# Вариант 2: DDoS
|
|||
|
|
hping3 -S --flood -p 19333 176.124.208.93
|
|||
|
|
# Все новые узлы не могут пройти bootstrap
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### [CRITICAL] V-002: Unencrypted Private Key Storage
|
|||
|
|
|
|||
|
|
**Файл:** `encrypted.rs:274-316`
|
|||
|
|
|
|||
|
|
**Уязвимый код:**
|
|||
|
|
```rust
|
|||
|
|
pub fn load_or_generate_keypair(data_dir: &Path) -> io::Result<StaticKeypair> {
|
|||
|
|
let key_path = data_dir.join("noise_key.bin");
|
|||
|
|
// ...
|
|||
|
|
// Save to file
|
|||
|
|
std::fs::write(&key_path, &keypair.secret)?; // PLAINTEXT!
|
|||
|
|
|
|||
|
|
#[cfg(unix)]
|
|||
|
|
{
|
|||
|
|
perms.set_mode(0o600); // Only owner read/write
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Вектор атаки:**
|
|||
|
|
1. Атакующий получает read access к data_dir (через malware, physical access, backup leak)
|
|||
|
|
2. Читает `noise_key.bin` — 32 байта plaintext private key
|
|||
|
|
3. Импортирует keypair и выдаёт себя за легитимный узел
|
|||
|
|
4. Если это hardcoded node — полная компрометация bootstrap
|
|||
|
|
|
|||
|
|
**Импакт:**
|
|||
|
|
- Обычный узел: возможность MITM его соединений
|
|||
|
|
- Hardcoded узел: полная компрометация сети
|
|||
|
|
|
|||
|
|
**Сложность:** Средняя (требуется доступ к файловой системе)
|
|||
|
|
|
|||
|
|
**PoC сценарий:**
|
|||
|
|
```bash
|
|||
|
|
# Атакующий на скомпрометированной машине
|
|||
|
|
cat /home/node/.montana/noise_key.bin | xxd
|
|||
|
|
# 00000000: 7a8b... (32 bytes secret key)
|
|||
|
|
|
|||
|
|
# Импорт в другой узел
|
|||
|
|
cp noise_key.bin /attacker/.montana/
|
|||
|
|
./montana --impersonate
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### [HIGH] V-003: Empty DNS Seeds — No Redundancy
|
|||
|
|
|
|||
|
|
**Файл:** `dns.rs:8-21`
|
|||
|
|
|
|||
|
|
**Уязвимый код:**
|
|||
|
|
```rust
|
|||
|
|
pub const DNS_SEEDS: &[&str] = &[
|
|||
|
|
// Primary seeds (to be populated with actual domains)
|
|||
|
|
// "seed1.efir.org",
|
|||
|
|
// ...
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
pub const FALLBACK_IPS: &[(u8, u8, u8, u8)] = &[
|
|||
|
|
// TimeWeb primary (Moscow)
|
|||
|
|
(176, 124, 208, 93),
|
|||
|
|
// Additional fallback IPs (to be populated)
|
|||
|
|
];
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Вектор атаки:**
|
|||
|
|
1. DNS_SEEDS пуст — DNS discovery отключен
|
|||
|
|
2. FALLBACK_IPS содержит только один IP (тот же TimeWeb)
|
|||
|
|
3. Сеть полностью зависит от одного сервера
|
|||
|
|
4. DDoS или компрометация = сеть мертва для новых узлов
|
|||
|
|
|
|||
|
|
**Импакт:** Denial of Service для всех новых узлов
|
|||
|
|
|
|||
|
|
**Сложность:** Низкая (DDoS одного IP)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### [HIGH] V-004: Eclipse via Cloud Provider Subnet Diversity Bypass
|
|||
|
|
|
|||
|
|
**Файл:** `subnet.rs:16-17`, `verification.rs:200-250`
|
|||
|
|
|
|||
|
|
**Уязвимый код:**
|
|||
|
|
```rust
|
|||
|
|
pub const MAX_NODES_PER_SUBNET: usize = 5;
|
|||
|
|
pub const MIN_DIVERSE_SUBNETS: usize = 25;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Вектор атаки:**
|
|||
|
|
1. Bootstrap требует 25 уникальных /16 подсетей
|
|||
|
|
2. Атакующий арендует VPS у 25+ провайдеров (AWS, GCP, Azure, DigitalOcean, Vultr, Linode, OVH, Hetzner, ...)
|
|||
|
|
3. Каждый провайдер = другая /16 подсеть
|
|||
|
|
4. Атакующий контролирует 125 IP (5 × 25) в разных подсетях
|
|||
|
|
5. Bootstrap verification проходит успешно с поддельными данными
|
|||
|
|
|
|||
|
|
**Импакт:** Eclipse attack на новые узлы
|
|||
|
|
|
|||
|
|
**Сложность:**
|
|||
|
|
- 125 VPS × $5/month = $625/month
|
|||
|
|
- Один раз настроить автоматизацию
|
|||
|
|
- Долгосрочная атака экономически выгодна
|
|||
|
|
|
|||
|
|
**PoC сценарий:**
|
|||
|
|
```python
|
|||
|
|
# terraform/attack.tf
|
|||
|
|
providers = [
|
|||
|
|
"aws_us-east-1", # /16: 54.X
|
|||
|
|
"aws_eu-west-1", # /16: 52.X
|
|||
|
|
"gcp_us-central1", # /16: 35.X
|
|||
|
|
"azure_eastus", # /16: 40.X
|
|||
|
|
# ... 21 more providers
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
for provider in providers:
|
|||
|
|
for i in range(5):
|
|||
|
|
create_vm(provider, run="fake-montana-node")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### [HIGH] V-005: Orphan Pool Memory Exhaustion
|
|||
|
|
|
|||
|
|
**Файл:** `sync.rs:17-18`
|
|||
|
|
|
|||
|
|
**Уязвимый код:**
|
|||
|
|
```rust
|
|||
|
|
pub const MAX_ORPHANS: usize = 100;
|
|||
|
|
// Each orphan can be MAX_SLICE_SIZE = 4MB
|
|||
|
|
// Total: 100 × 4MB = 400MB potential memory
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Вектор атаки:**
|
|||
|
|
1. Атакующий генерирует 100 фейковых "orphan" слайсов по 4MB каждый
|
|||
|
|
2. Слайсы имеют валидный формат, но parent не существует
|
|||
|
|
3. Узел-жертва хранит их в orphan pool
|
|||
|
|
4. 400MB памяти заблокировано мусором
|
|||
|
|
5. Повторить с разных IP для обхода rate limiting
|
|||
|
|
|
|||
|
|
**Импакт:** Memory exhaustion DoS
|
|||
|
|
|
|||
|
|
**Сложность:** Низкая (генерация случайных данных)
|
|||
|
|
|
|||
|
|
**PoC сценарий:**
|
|||
|
|
```rust
|
|||
|
|
for i in 0..100 {
|
|||
|
|
let fake_slice = Slice {
|
|||
|
|
parent_hash: random_hash(), // Non-existent parent
|
|||
|
|
data: vec![0u8; 4_000_000], // 4MB payload
|
|||
|
|
// ... valid structure
|
|||
|
|
};
|
|||
|
|
send_to_victim(fake_slice);
|
|||
|
|
}
|
|||
|
|
// Victim now holds 400MB of garbage
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### [MEDIUM] V-006: Flow Control Counts Messages, Not Bytes
|
|||
|
|
|
|||
|
|
**Файл:** `protocol.rs:779-803`
|
|||
|
|
|
|||
|
|
**Уязвимый код:**
|
|||
|
|
```rust
|
|||
|
|
// Flow control: pause reading if receive queue is overloaded
|
|||
|
|
if peer.flow_control.should_pause_recv() {
|
|||
|
|
flow_control_pauses += 1;
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
// ...
|
|||
|
|
let msg_size = msg.estimated_size();
|
|||
|
|
peer.flow_control.add_recv(msg_size);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Проблема:** `should_pause_recv()` проверяется ПОСЛЕ чтения сообщения. Атакующий может отправить одно огромное сообщение (MAX_TX_SIZE = 1MB), которое будет полностью прочитано до проверки.
|
|||
|
|
|
|||
|
|
**Вектор атаки:**
|
|||
|
|
1. Отправить множество 1MB сообщений подряд
|
|||
|
|
2. Каждое полностью читается перед pause check
|
|||
|
|
3. Memory spike до обработки flow control
|
|||
|
|
|
|||
|
|
**Импакт:** Временные memory spikes
|
|||
|
|
|
|||
|
|
**Сложность:** Низкая
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### [MEDIUM] V-007: Self-Connection Nonce Race Condition
|
|||
|
|
|
|||
|
|
**Файл:** `protocol.rs:764-765, 911-915`
|
|||
|
|
|
|||
|
|
**Уязвимый код:**
|
|||
|
|
```rust
|
|||
|
|
// Insert nonce
|
|||
|
|
sent_nonces.write().await.insert(version.nonce);
|
|||
|
|
// ...later...
|
|||
|
|
// Check nonce
|
|||
|
|
if sent_nonces.read().await.contains(&version.nonce) {
|
|||
|
|
return Err(NetError::Protocol("Self-connection detected".into()));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Проблема:** Между вызовом `insert()` на одном соединении и `contains()` на другом есть временное окно. При высокой параллельности возможны false negatives.
|
|||
|
|
|
|||
|
|
**Вектор атаки:**
|
|||
|
|
1. Узел инициирует множество параллельных исходящих соединений
|
|||
|
|
2. Один из них случайно к самому себе
|
|||
|
|
3. Race condition позволяет обойти проверку
|
|||
|
|
|
|||
|
|
**Импакт:** Self-connection создаёт петлю сообщений
|
|||
|
|
|
|||
|
|
**Сложность:** Требует точного timing
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### [MEDIUM] V-008: Timing Side Channel in ML-DSA-65 Verification
|
|||
|
|
|
|||
|
|
**Файл:** `verification.rs:380-400`, криптобиблиотека
|
|||
|
|
|
|||
|
|
**Вектор атаки:**
|
|||
|
|
1. Bootstrap verification вызывает ML-DSA-65 signature verification
|
|||
|
|
2. Время верификации может зависеть от signature content
|
|||
|
|
3. Атакующий может извлечь информацию о public key через timing
|
|||
|
|
|
|||
|
|
**Импакт:** Потенциальная утечка криптографических секретов
|
|||
|
|
|
|||
|
|
**Сложность:** Высокая (требует точных измерений времени)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### [LOW] V-009: Unbounded sent_nonces HashSet
|
|||
|
|
|
|||
|
|
**Файл:** `protocol.rs:135, 866-868`
|
|||
|
|
|
|||
|
|
**Уязвимый код:**
|
|||
|
|
```rust
|
|||
|
|
sent_nonces: Arc<RwLock<HashSet<u64>>>,
|
|||
|
|
// ...
|
|||
|
|
// Cleanup only on successful disconnect:
|
|||
|
|
if let Some(nonce) = our_sent_nonce {
|
|||
|
|
sent_nonces.write().await.remove(&nonce);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Проблема:** Если соединение обрывается до cleanup (crash, timeout), nonce остаётся в HashSet навсегда. При миллионах неудачных соединений — memory leak.
|
|||
|
|
|
|||
|
|
**Импакт:** Медленный memory leak
|
|||
|
|
|
|||
|
|
**Сложность:** Требует длительной эксплуатации
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### [LOW] V-010: Version Information Disclosure
|
|||
|
|
|
|||
|
|
**Файл:** `protocol.rs:904-948`
|
|||
|
|
|
|||
|
|
**Уязвимый код:**
|
|||
|
|
```rust
|
|||
|
|
// Record IP vote from peer (addr_recv is how they see us)
|
|||
|
|
let their_view_of_us = version.addr_recv.ip;
|
|||
|
|
if !peer.inbound
|
|||
|
|
&& !their_view_of_us.is_loopback()
|
|||
|
|
&& !their_view_of_us.is_unspecified()
|
|||
|
|
{
|
|||
|
|
ip_votes.write().await.insert(peer.addr, their_view_of_us);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Вектор атаки:**
|
|||
|
|
1. Sybil-атакующий подключается к жертве с разных IP
|
|||
|
|
2. Каждое соединение получает Version с addr_recv
|
|||
|
|
3. Атакующий видит, какой IP жертва считает своим external
|
|||
|
|
4. Fingerprinting для идентификации узла за NAT
|
|||
|
|
|
|||
|
|
**Импакт:** Privacy leak, fingerprinting
|
|||
|
|
|
|||
|
|
**Сложность:** Низкая
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Атаки, которые НЕ работают
|
|||
|
|
|
|||
|
|
### 51% Attack
|
|||
|
|
**Не применима.** Montana не использует PoW/PoS. Консенсус через cumulative weight presence proofs, который накапливается со временем. Нельзя "намайнить" больше веса.
|
|||
|
|
|
|||
|
|
### Nothing-at-Stake
|
|||
|
|
**Не применима.** Нет staking. Presence proofs привязаны к реальному времени через физические ограничения.
|
|||
|
|
|
|||
|
|
### Long-Range Attack
|
|||
|
|
**Ограниченно применима.** Subnet reputation накапливается годами и снапшотится каждые τ₃. Атакующему нужно контролировать репутацию подсетей годами.
|
|||
|
|
|
|||
|
|
### Selfish Mining
|
|||
|
|
**Не применима.** Нет mining. Слайсы создаются по расписанию τ₂.
|
|||
|
|
|
|||
|
|
### Transaction Malleability (Classic)
|
|||
|
|
**Не проверена.** Требует анализа transaction layer (вне scope network audit).
|
|||
|
|
|
|||
|
|
### BGP Hijack
|
|||
|
|
**Частично защищено.** Noise Protocol обеспечивает authenticated encryption. Hijack позволит DoS, но не MITM контента.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Рекомендации
|
|||
|
|
|
|||
|
|
### CRITICAL Fixes (Требуется немедленно)
|
|||
|
|
|
|||
|
|
| # | Уязвимость | Рекомендация |
|
|||
|
|
|---|------------|--------------|
|
|||
|
|
| V-001 | Single hardcoded node | Добавить минимум 5 hardcoded nodes в разных юрисдикциях |
|
|||
|
|
| V-002 | Plaintext key storage | Шифровать ключи через OS keychain или password-derived key |
|
|||
|
|
| V-003 | Empty DNS seeds | Настроить DNS seeds на нескольких доменах |
|
|||
|
|
|
|||
|
|
### HIGH Priority
|
|||
|
|
|
|||
|
|
| # | Уязвимость | Рекомендация |
|
|||
|
|
|---|------------|--------------|
|
|||
|
|
| V-004 | Cloud subnet bypass | Добавить ASN diversity check в дополнение к /16 |
|
|||
|
|
| V-005 | Orphan exhaustion | Уменьшить MAX_ORPHANS до 20 или добавить size-based limit |
|
|||
|
|
|
|||
|
|
### MEDIUM Priority
|
|||
|
|
|
|||
|
|
| # | Уязвимость | Рекомендация |
|
|||
|
|
|---|------------|--------------|
|
|||
|
|
| V-006 | Flow control timing | Проверять size limit ДО чтения payload |
|
|||
|
|
| V-007 | Nonce race | Использовать атомарный check-and-insert |
|
|||
|
|
| V-008 | Timing side channel | Использовать constant-time comparison в crypto |
|
|||
|
|
|
|||
|
|
### LOW Priority
|
|||
|
|
|
|||
|
|
| # | Уязвимость | Рекомендация |
|
|||
|
|
|---|------------|--------------|
|
|||
|
|
| V-009 | Nonce leak | Добавить periodic cleanup для sent_nonces |
|
|||
|
|
| V-010 | Version disclosure | Не включать точный external IP в addr_recv |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. Вердикт
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[X] CRITICAL — есть уязвимости, позволяющие уничтожить сеть
|
|||
|
|
[ ] HIGH — есть серьёзные уязвимости
|
|||
|
|
[ ] MEDIUM — есть уязвимости среднего риска
|
|||
|
|
[ ] LOW — только minor issues
|
|||
|
|
[ ] SECURE — уязвимостей не найдено
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Обоснование
|
|||
|
|
|
|||
|
|
**V-001 (Single Hardcoded Node)** и **V-003 (Empty DNS Seeds)** создают катастрофическую точку отказа. Компрометация или DDoS одного IP (176.124.208.93) полностью парализует bootstrap новых узлов и позволяет навязать поддельную цепь.
|
|||
|
|
|
|||
|
|
**V-002 (Plaintext Keys)** в сочетании с V-001 означает: если атакующий получит доступ к файловой системе hardcoded node, он получит полный контроль над сетью.
|
|||
|
|
|
|||
|
|
**V-004 (Cloud Bypass)** показывает, что даже при наличии subnet diversity, экономически мотивированный атакующий может обойти защиту за ~$600/месяц.
|
|||
|
|
|
|||
|
|
### Общая оценка
|
|||
|
|
|
|||
|
|
Код сетевого слоя демонстрирует зрелый подход к безопасности (rate limiting, eviction, cryptographic bucketing). Однако **инфраструктурные** решения (один hardcoded node, пустые DNS seeds, plaintext ключи) создают критические уязвимости уровня **сети целиком**, а не отдельного узла.
|
|||
|
|
|
|||
|
|
**Рекомендация:** Не запускать mainnet до исправления V-001, V-002, V-003.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Appendix: Файлы и строки
|
|||
|
|
|
|||
|
|
| Уязвимость | Файл | Строки |
|
|||
|
|
|------------|------|--------|
|
|||
|
|
| V-001 | hardcoded_identity.rs | 59-68 |
|
|||
|
|
| V-002 | encrypted.rs | 274-316 |
|
|||
|
|
| V-003 | dns.rs | 8-32 |
|
|||
|
|
| V-004 | subnet.rs | 16-17, 169-233 |
|
|||
|
|
| V-005 | sync.rs | 17-18 |
|
|||
|
|
| V-006 | protocol.rs | 779-803 |
|
|||
|
|
| V-007 | protocol.rs | 764-765, 911-915 |
|
|||
|
|
| V-008 | verification.rs | 380-400 |
|
|||
|
|
| V-009 | protocol.rs | 135, 866-868 |
|
|||
|
|
| V-010 | protocol.rs | 904-948 |
|