20 KiB
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) — модель консенсуса, основанную на:
-
Физическая упорядоченность (Layer -1) — фундаментальное ограничение энтропии, требующее реального времени для накопления присутствия. Нельзя ускорить.
-
Слайсы вместо блоков — каждый слайс представляет фиксированный интервал времени (τ₂ = 5 минут), а не произвольный набор транзакций.
-
Presence Proofs — узлы подтверждают своё присутствие в момент времени τ₂, накапливая вес.
-
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 |
Следствия для безопасности
- Атаки на майнинг не применимы — нет mining power
- Nothing-at-stake не применим — нет staking
- Критична синхронизация времени — физические инварианты привязывают к реальному времени
- 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 (Что защищаем)
- Consensus state — cumulative_weight, canonical chain
- Private keys — Noise keypair, wallet keys
- Peer set — защита от eclipse
- Network connectivity — защита от DoS
4. Найденные уязвимости
[CRITICAL] V-001: Single Point of Failure — One Hardcoded Node
Файл: hardcoded_identity.rs:59-68
Уязвимый код:
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",
}]
});
Вектор атаки:
- Атакующий получает контроль над сервером 176.124.208.93 (TimeWeb Moscow)
- Bootstrap verification (
verification.rs:140) требует ответа от hardcoded nodes - С одним узлом атакующий контролирует 100% hardcoded ответов
- Атакующий может поставить любой
cumulative_weightиhead_hash - Все новые узлы синхронизируются с поддельной цепью
Импакт: Полный контроль над консенсусом всех новых узлов
Сложность:
- Компрометация одного VPS (~$1000-10000 через социальную инженерию или уязвимость хостинга)
- Или DDoS на один IP для отказа в обслуживании bootstrap
PoC сценарий:
# Вариант 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
Уязвимый код:
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
}
Вектор атаки:
- Атакующий получает read access к data_dir (через malware, physical access, backup leak)
- Читает
noise_key.bin— 32 байта plaintext private key - Импортирует keypair и выдаёт себя за легитимный узел
- Если это hardcoded node — полная компрометация bootstrap
Импакт:
- Обычный узел: возможность MITM его соединений
- Hardcoded узел: полная компрометация сети
Сложность: Средняя (требуется доступ к файловой системе)
PoC сценарий:
# Атакующий на скомпрометированной машине
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
Уязвимый код:
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)
];
Вектор атаки:
- DNS_SEEDS пуст — DNS discovery отключен
- FALLBACK_IPS содержит только один IP (тот же TimeWeb)
- Сеть полностью зависит от одного сервера
- DDoS или компрометация = сеть мертва для новых узлов
Импакт: Denial of Service для всех новых узлов
Сложность: Низкая (DDoS одного IP)
[HIGH] V-004: Eclipse via Cloud Provider Subnet Diversity Bypass
Файл: subnet.rs:16-17, verification.rs:200-250
Уязвимый код:
pub const MAX_NODES_PER_SUBNET: usize = 5;
pub const MIN_DIVERSE_SUBNETS: usize = 25;
Вектор атаки:
- Bootstrap требует 25 уникальных /16 подсетей
- Атакующий арендует VPS у 25+ провайдеров (AWS, GCP, Azure, DigitalOcean, Vultr, Linode, OVH, Hetzner, ...)
- Каждый провайдер = другая /16 подсеть
- Атакующий контролирует 125 IP (5 × 25) в разных подсетях
- Bootstrap verification проходит успешно с поддельными данными
Импакт: Eclipse attack на новые узлы
Сложность:
- 125 VPS × $5/month = $625/month
- Один раз настроить автоматизацию
- Долгосрочная атака экономически выгодна
PoC сценарий:
# 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
Уязвимый код:
pub const MAX_ORPHANS: usize = 100;
// Each orphan can be MAX_SLICE_SIZE = 4MB
// Total: 100 × 4MB = 400MB potential memory
Вектор атаки:
- Атакующий генерирует 100 фейковых "orphan" слайсов по 4MB каждый
- Слайсы имеют валидный формат, но parent не существует
- Узел-жертва хранит их в orphan pool
- 400MB памяти заблокировано мусором
- Повторить с разных IP для обхода rate limiting
Импакт: Memory exhaustion DoS
Сложность: Низкая (генерация случайных данных)
PoC сценарий:
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
Уязвимый код:
// 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), которое будет полностью прочитано до проверки.
Вектор атаки:
- Отправить множество 1MB сообщений подряд
- Каждое полностью читается перед pause check
- Memory spike до обработки flow control
Импакт: Временные memory spikes
Сложность: Низкая
[MEDIUM] V-007: Self-Connection Nonce Race Condition
Файл: protocol.rs:764-765, 911-915
Уязвимый код:
// 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.
Вектор атаки:
- Узел инициирует множество параллельных исходящих соединений
- Один из них случайно к самому себе
- Race condition позволяет обойти проверку
Импакт: Self-connection создаёт петлю сообщений
Сложность: Требует точного timing
[MEDIUM] V-008: Timing Side Channel in ML-DSA-65 Verification
Файл: verification.rs:380-400, криптобиблиотека
Вектор атаки:
- Bootstrap verification вызывает ML-DSA-65 signature verification
- Время верификации может зависеть от signature content
- Атакующий может извлечь информацию о public key через timing
Импакт: Потенциальная утечка криптографических секретов
Сложность: Высокая (требует точных измерений времени)
[LOW] V-009: Unbounded sent_nonces HashSet
Файл: protocol.rs:135, 866-868
Уязвимый код:
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
Уязвимый код:
// 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);
Вектор атаки:
- Sybil-атакующий подключается к жертве с разных IP
- Каждое соединение получает Version с addr_recv
- Атакующий видит, какой IP жертва считает своим external
- 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 |