Layers: Layer 1: XDP/eBPF — TCP state machine, SYN throttle, blacklist, ringbuf Layer 2: PoW Challenge — SHA-256 hashcash, dynamic difficulty, constant-time verify Layer 3: Rust Core — HMAC handshake, rate limit, death code (existing) Layer 4: Velocity — Physics check, CAPTCHA, protocol verification Layer 5: Paper — Heartbeat, auto-registration (existing) Layer 6: Traffic Intel — EWMA, 168h profiling, reputation, alerts Infra: XDP→Prometheus metrics, ClickHouse + Grafana dashboard, Docker Compose Testing: 100-IP DDoS simulation, MHDDoS ref analysis, load test report Fixes: VarInt sign extension UB, pure ACK deadlock, RST/FIN cleanup Ref: MHDDoS, Sonar, LimboFilter, AtomGuard, Infrarust, MC-XDP-eBPF, PowGo
17 KiB
17 KiB
Anti-Bot стратегия
Актуально: v0.2+
6 слоёв антибот защиты
┌──────────────────────────────────────────────────────────────────┐
│ Слой 1: XDP/eBPF дроп L3/L4 на уровне ядра │
│ ───────────────────────────────────── │
│ TCP state machine: SYN → SYN-ACK → ожидание MC handshake │
│ SYN throttle: N SYNs/IP/сек → временный бан │
│ Invalid flags: SYN+FIN, SYN+RST, URG → дроп │
│ UDP: дроп (MC работает только по TCP) │
│ │
│ Бот не может: открыть >N TCP соединений/сек с одного IP │
├──────────────────────────────────────────────────────────────────┤
│ Слой 2: PoW Challenge анти-handshake-flood │
│ ───────────────────────────────────── │
│ Перед HMAC handshake клиент решает SHA256 hashcash: │
│ 1. Edge шлёт {challenge, difficulty, allowedHex, timestamp} │
│ 2. Клиент ищет nonce: SHA256(challenge + nonce) начинается с │
│ difficulty символов из allowedHex │
│ 3. Edge верифицирует, challenge одноразовый (timestamp + nonce) │
│ 4. Dynamic difficulty: 12 при атаке, 4 в спокойное время │
│ │
│ Бот не может: открывать >50 handshake/сек (PoW жрёт CPU) │
│ Nonce replay невозможен: challenge + timestamp уникальны │
├──────────────────────────────────────────────────────────────────┤
│ Слой 3: Rust Core L7 проверки │
│ ───────────────────────────────────── │
│ Rate limit: N conn/IP/сек (token bucket) │
│ Death code: 8 паттернов малициозных пакетов → автобан │
│ Blacklist: global + per-IP, Redis sync │
│ ASN reputation: датацентры → строже, residential → мягче │
│ Timeout: 5 сек на полный handshake (anti-Slow Loris) │
│ │
│ Бот не может: слать >5 conn/сек, слать мусор в пакетах │
├──────────────────────────────────────────────────────────────────┤
│ Слой 4: Velocity верификация игроков │
│ ───────────────────────────────────── │
│ Domain whitelist: блок прямых IP, разрешены только наши домены │
│ HMAC verify: hostname содержит \0shield\0<sig> │
│ Falling check: spawn Y=512, 128 тиков физики падения │
│ Protocol check: Transaction, SetHeldItem, ArmAnimation │
│ Vehicle check: Boat + Minecart gravity + paddle packets │
│ CAPTCHA: map item или PoW │
│ │
│ Бот не может: зайти без HMAC, пройти физику без симуляции MC │
├──────────────────────────────────────────────────────────────────┤
│ Слой 5: Traffic Intelligence аналитика │
│ ───────────────────────────────────── │
│ 168-hour профиль: baseline соединений по часам и дням недели │
│ EWMA adaptive thresholds: аномалии относительно baseline │
│ Z-Score: 3σ от среднего → алерт │
│ Reputation: score -100..+100, влияет на rate limit множитель │
│ Attack detection: CPS > threshold → режим атаки │
│ │
│ Бот не может: атаковать незаметно — дёргает threshold │
├──────────────────────────────────────────────────────────────────┤
│ Слой 6: Verified DB (Redis) кэш верификации │
│ ───────────────────────────────────── │
│ HMAC-SHA256 fingerprint: SHA256(secret, username, IP) │
│ TTL: 24 часа без активности, продлевается при каждом входе │
│ Skip: верифицированные проходят слои 2-4 мгновенно │
│ │
│ Бот не может: подделать fingerprint (HMAC, не hashCode) │
└──────────────────────────────────────────────────────────────────┘
Путь игрока через защиту
Новый игрок
|
v
┌──────────────────┐
│ XDP/eBPF │ TCP handshake processing
│ Ядро Linux │ Прошёл SYN throttle + state machine
└────────┬─────────┘
v (TCP соединение установлено)
┌──────────────────┐
│ PoW Challenge │ SHA256 hashcash
│ Rust │ Dynamic difficulty, одноразовый challenge
└────────┬─────────┘
v (PoW решён)
┌──────────────────┐
│ Rust Core │ Rate limit, Death code, Blacklist
│ userspace │ HMAC sign hostname
└────────┬─────────┘
v (валидный MC handshake + HMAC)
┌──────────────────┐
│ Velocity │ Domain whitelist, HMAC verify
│ Java │ Falling check → Protocol check
│ │ → Vehicle check → CAPTCHA
└────────┬─────────┘
v (верифицирован)
┌──────────────────┐
│ Hub / Game │ Игрок на сервере
│ Server │
└──────────────────┘
Защита от AI-ботов (2026)
Проблема
Современные attack frameworks обходят существующие anti-bot решения:
| Решение | Обход |
|---|---|
| Sonar gravity check | AI симулирует MC физику |
| LimboFilter falling | Робот считает parabola |
| Map CAPTCHA (Sonar) | OCR решает 3-4 символа |
| Математические задачи | AI решает за <100ms |
| Timing check | AI имитирует human timing |
Что работает против AI
✓ PoW (Layer 2): вычислительная стоимость, GPU не помогает
достаточно (SHA256 не memory-hard)
✓ HMAC (Layer 3+4): криптография, не обходится без ключа
✓ ASN reputation: датацентры = боты
✓ Dynamic difficulty: при атаке повышаем PoW сложность
✓ Многослойность: нужно обойти 6 слоёв, а не 1
Fingerprinting (исправленный)
// В отличие от Sonar (использующего hashCode + сдвиги без соли),
// Rampart использует HMAC-SHA256 с ротацией ключа:
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
pub fn compute_fingerprint(secret: &[u8], username: &str, ip: &str) -> String {
let mut mac = HmacSha256::new_from_slice(secret)
.expect("HMAC key");
mac.update(username.as_bytes());
mac.update(b"\0");
mac.update(ip.as_bytes());
let result = mac.finalize();
hex::encode(result.into_bytes())
}
// Свойства:
// - Нет коллизий (SHA256)
// - Нет подделки (HMAC, не hash code)
// - Нет обратной инженерии (secret на edge ноде)
// - Ротация ключа каждые 24ч
// - Разные secret для разных слоёв (XDP_key, PoW_key, HMAC_key)
Verified Player Cache
// После прохождения всех слоёв — fingerprint в Redis:
//
// Ключ: rampart:verified:{sha256_fingerprint}
// Значение: { ip, username, verified_at, last_seen, ttl }
// TTL: 24h (продлевается при каждом входе)
//
// При повторном входе:
// 1. Вычисляем fingerprint
// 2. Проверяем Redis
// 3. Если есть и IP совпадает → слои 2-4 пропускаются
// 4. Если IP изменился → проходим верификацию заново
pub fn is_verified(redis: &Client, username: &str, ip: &str) -> bool {
let fp = compute_fingerprint(&get_secret(), username, ip);
let key = format!("rampart:verified:{}", fp);
match redis.get::<String>(&key) {
Ok(Some(data)) => {
let entry: VerifiedEntry = serde_json::from_str(&data).ok()?;
if entry.ip == ip {
// Продлеваем TTL
let _ = redis.expire(&key, 86400);
return true;
}
}
_ => {}
}
false
}
PoW Challenge (Layer 2)
// Hashcash-style proof of work
// Адаптировано из PowGo: +timestamp, +per-request challenge, dynamic difficulty
pub struct Challenge {
pub token: [u8; 16], // случайный per-request
pub timestamp: u64, // unix ms
pub difficulty: u8, // 4-12, динамический
pub allowed_hex: &'static str, // "012def" по умолчанию
}
pub struct Solution {
pub token: [u8; 16],
pub nonce: u64,
}
pub fn verify(challenge: &Challenge, solution: &Solution) -> bool {
// 1. Timestamp validity (max 30 seconds old)
let age = current_timestamp_ms() - challenge.timestamp;
if age > 30_000 { return false; }
// 2. Token match
if challenge.token != solution.token { return false; }
// 3. Hash verification
let mut data = [0u8; 32];
data[..16].copy_from_slice(&challenge.token);
data[16..24].copy_from_slice(&solution.nonce.to_le_bytes());
let hash = sha256(&data);
let hex = hex::encode(hash);
for i in 0..challenge.difficulty as usize {
let c = hex.as_bytes()[i] as char;
if !challenge.allowed_hex.contains(c) {
return false;
}
}
true
}
// Dynamic difficulty adjustment
pub fn get_difficulty(cps: u64, attack_mode: bool) -> u8 {
match (cps, attack_mode) {
(_, true) | (cps, _) if cps > 500 => 12,
(cps, _) if cps > 100 => 10,
(cps, _) if cps > 50 => 8,
_ => 4,
}
}
Матрица атак MHDDoS vs Rampart
Анализ MHDDoS — самый популярный DDoS тул на Python (25k+ stars).
| Метод атаки | Тип | Как работает | Блокируется слоем | Примечание |
|---|---|---|---|---|
| SYN | L4 RAW | SYN flood с подделкой source IP | Layer 1 (XDP SYN throttle) | Если XDP отключён — iptables SYN cookie |
| TCP | L4 | randbytes(1024) в TCP сокет | Layer 1 (conntrack) | Аномальный трафик, мало данных |
| UDP | L4 | randbytes(1024) через UDP | Layer 1 (UDP drop) | MC только TCP, UDP дропается |
| CPS | L4 | Открыть/закрыть TCP | Layer 1 (SYN throttle) + Layer 3 (rate limit) | 50+ conn/s → block |
| CONNECTION | L4 | Держать TCP открытым | Layer 1 (idle timeout) + Layer 3 (conntrack) | 30s idle → evict |
| MINECRAFT | L4 | Handshake + ping флуд | Layer 2 (PoW) + Layer 3 (rate limit) | PoW требует CPU |
| MCBOT | L4/L7 | Полная эмуляция игрока (login → чат) | Layer 4 (Physics) + Layer 6 (reputation) | Самый опасный для MC |
| ICMP | L4 RAW | ICMP echo flood | Layer 1 (ICMP rate-limit) | На уровне ядра |
| DNS/NTP/MEM | L4 AMP | Amplification через рефлекторы | Layer 1 (UDP drop) | UDP не на MC порты |
| GET/POST/HEAD | L7 | HTTP флуд | Layer 3 (rate limit) | 100 req/s → block |
| CFB | L7 | HTTP через cloudscraper (обходит CF) | Layer 3 (rate limit per IP) | Прокси не спасают — Rampart видит реальный IP |
| SLOW | L7 | Slowloris: медленные заголовки | Layer 3 (read timeout 10s) | Таймаут закрывает |
| BOT | L7 | Имитация Googlebot | Layer 6 (168h профиль) | Аномалия в час-слоте |
| BOMB | L7 | HTTP/2 через SOCKS5 прокси | Layer 6 (EWMA thresholds) | PPS аномалия |
| DGB | L7 | Обход DDoS-Guard | Layer 3 (HMAC verify) | После HMAC — невалидная подпись |
| APACHE | L7 | Range-атака (CVE-2011-3192) | Layer 3 (packet inspect) | Малый HTTP трафик |
Сводка
- Layer 1 (XDP) блокирует: SYN, UDP, ICMP, AMP, TCP flood
- Layer 2 (PoW) блокирует: MINECRAFT handshake flood
- Layer 3 (Core) блокирует: CPS, CONNECTION, HTTP flood, SLOW, APACHE
- Layer 4 (Physics) блокирует: MCBOT (неестественное движение)
- Layer 6 (Traffic Intel) блокирует: BOT, BOMB, аномалии по 168h профилю
- Не покрыто полностью: CFB через 10k+ уникальных IP (нужна репутация Layer 6)
Известные проблемы в других решениях
| Проблема | Где найдено | Наше решение |
|---|---|---|
| Fingerprint = hashCode + сдвиги, без соли | Sonar | HMAC-SHA256 с ротацией ключа |
| CAPTCHA проходима AI (3-4 символа, map colors) | Sonar #531 | PoW вместо/поверх CAPTCHA |
| 4x re-verification race | Sonar #611 | Idempotent finish, atomic state |
| KeepAlive ID plaintext | Sonar | Challenge-response с HMAC |
| QuietDecoderException как control flow | Sonar | Result<T, E> без исключений |
| checkY() fast-forward | LimboFilter | Строгий шаг: 1 tick за вызов |
| ignoredTicks не сбрасывается | LimboFilter | Сброс на валидном move |
| Memory leak MapData | LimboFilter #118 | Weak refs, explicit cleanup |
| Isolation Forest мёртвый код | AtomGuard | Реально используем или убираем |
| EWMA variance double-smoothing | AtomGuard | Правильная формула |
| Race в pipeline checks.clear/addAll | AtomGuard | Copy-on-write |
| SynFloodDetector при <15 IP отключается | AtomGuard | Per-IP fallback |
| AntiBot plugin пустой | Infrarust | Реализован с первого коммита |
| Rate limit disabled by default | Infrarust | Enabled по умолчанию |
| TCP handshake deadlock (pure ACK drop) | MC-XDP-eBPF | Не дропать pure ACK |
| Stale state on RST/FIN | MC-XDP-eBPF | Удалять entry на RST |
| Nonce replay | PowGo | Per-request challenge + timestamp |
| Static difficulty | PowGo | Dynamic по CPS |