Parent : [[INDEX-CYBERSECU]]
Architecture de débridage torrent - Magnet vers téléchargement direct
Problème
Comment convertir un lien magnet en téléchargement direct HTTP ? L'archive Ygg contient un système complet de débridage torrent avec architecture distribuée, workers, caching, et seeding automatisé.
Contexte archive Ygg
- Projet :
01_SOURCE_CODE/02g_debrideur/ - Stack : Node.js + Express + MariaDB + Redis + BullMQ + qBittorrent-nox
- Fichiers : 20+ fichiers source (app.js, torrentWorker.js, qbittorrentService.js, etc.)
- Architecture : Load balancer (Nginx) → Web servers (Express) → Redis (queue/cache) → Workers → qBittorrent → Storage (NFS/S3)
Architecture complète
┌─────────────────┐
│ CLIENTS │ (Navigateurs Web)
└────────┬────────┘
│ HTTP/SSE
▼
┌─────────────────┐
│ LOAD BALANCER │ (Nginx)
└────────┬────────┘
│
┌────┴────┐
▼ ▼
┌────────┐ ┌────────┐
│ Express│ │ Express│ (Web Servers)
└───┬────┘ └───┬────┘
│ │
└────┬─────┘
│
┌────┴────┬────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ Redis │ │ MariaDB│ │qBittor.│
│ Queue │ │ (DB) │ │ (API) │
└───┬────┘ └────────┘ └────────┘
│ BullMQ Jobs
▼
┌─────────────────┐
│ WORKERS │ (Torrent Download Workers)
└────────┬────────┘
│
▼
┌─────────────────┐
│ STORAGE (NFS) │ /storage/downloads
└─────────────────┘
Flux de téléchargement
- Soumission : L'utilisateur soumet un lien magnet via le formulaire web
- Parsing : Extraction de l'infoHash (hex 40 caractères ou base32 32 caractères)
- Vérification cache : Le système vérifie si le torrent existe déjà en base
- Queue : Si nouveau, le job est ajouté à la queue BullMQ Redis
- Téléchargement : Le worker ajoute le torrent à qBittorrent via API
- Progression : Les mises à jour sont publiées via Redis Pub/Sub → SSE → client
- Complétion : Les fichiers sont disponibles au téléchargement direct
- Seeding : Le torrent continue à seeder selon les limites configurées (ratio/temps)
- Cleanup : Les torrents seedés sont supprimés automatiquement
Pattern protectif — Détection de systèmes de débridage
1. Signatures d'un service de débridage
# detect_debrid_service.py — Détecter les signaux d'un service de débridage
import re
class DebridDetector:
SIGNALS = {
# URLs de téléchargement direct avec tokens courts
'direct_download_tokens': re.compile(r'/dl/[a-zA-Z0-9_-]{10,32}'),
# Endpoints de soumission magnet
'magnet_submission': re.compile(r'/(add|submit|download)\?magnet='),
# APIs torrent
'torrent_api': re.compile(r'/api/v\d+/(torrent|magnet|debrid)'),
# Headers de réponse suspects
'debrid_headers': ['X-Debrid-Cache', 'X-Torrent-Hash', 'X-Seed-Time'],
# Ports qBittorrent exposés
'qbittorrent_port': 8080,
# URLs de tracking de progression SSE
'sse_progress': re.compile(r'/sse/(torrent|progress|download)/'),
}
def analyze_response(self, response):
signals = []
# Vérifier les headers
for header in self.SIGNALS['debrid_headers']:
if header in response.headers:
signals.append(f"Header debrid: {header}")
# Vérifier le contenu
body = response.text
if 'magnet:?' in body and 'download' in body:
signals.append("Formulaire magnet détecté")
if 'sse' in body and 'torrent' in body:
signals.append("Streaming progression torrent")
return signals
2. Protection contre l'abus d'un service de débridage
# anti_debrid_abuse.py — Rate limiting et validation
from flask import Flask, request, abort
import redis
import hashlib
app = Flask(__name__)
redis_client = redis.Redis()
# Rate limiting par IP
DOWNLOAD_RATE_LIMIT = 10 # 10 téléchargements / heure
MAGNET_RATE_LIMIT = 5 # 5 magnets / heure
@app.route('/download/<token>')
def download(token):
ip = request.remote_addr
# Vérifier le token
if not validate_token(token):
abort(404)
# Rate limit
key = f"dl:{ip}:{token}"
count = redis_client.incr(key)
redis_client.expire(key, 3600)
if count > DOWNLOAD_RATE_LIMIT:
abort(429, 'Too many downloads')
return send_file(token)
@app.route('/add', methods=['POST'])
def add_magnet():
ip = request.remote_addr
magnet = request.form.get('magnet')
# Validation magnet
if not magnet.startswith('magnet:?'):
abort(400)
info_hash = extract_info_hash(magnet)
if not info_hash or len(info_hash) != 40:
abort(400)
# Rate limit par infoHash
hash_key = f"magnet:{ip}:{info_hash}"
hash_count = redis_client.incr(hash_key)
redis_client.expire(hash_key, 3600)
if hash_count > MAGNET_RATE_LIMIT:
abort(429, 'Too many magnets')
# Blacklist de torrents connus (DMCA, malware)
if info_hash in BLACKLIST:
abort(403, 'Torrent blacklisted')
return process_magnet(magnet)
3. Parsing sécurisé de liens magnet
// magnetParser.js — Parsing et validation sécurisée
function parseMagnet(magnetUri) {
if (!magnetUri || typeof magnetUri !== 'string') {
return null;
}
// Vérifier le schéma
if (!magnetUri.startsWith('magnet:?')) {
return null;
}
// Vérifier la longueur max (prévention DoS)
if (magnetUri.length > 2048) {
return null;
}
const result = {
infoHash: null,
name: null,
announce: [],
size: null
};
try {
const params = magnetUri.slice(8).split('&');
for (const param of params) {
const [key, value] = param.split('=');
const decodedValue = decodeURIComponent(value || '');
if (key === 'xt' && decodedValue.startsWith('urn:btih:')) {
let hash = decodedValue.slice(9);
// Validation hex (40 caractères)
if (hash.length === 40 && /^[a-fA-F0-9]+$/.test(hash)) {
result.infoHash = hash.toLowerCase();
}
// Validation base32 (32 caractères)
else if (hash.length === 32 && /^[A-Z2-7]+$/.test(hash)) {
result.infoHash = base32ToHex(hash);
}
} else if (key === 'dn') {
// Validation nom (pas de path traversal)
if (!decodedValue.includes('..') && !decodedValue.includes('/')) {
result.name = decodedValue;
}
} else if (key === 'tr') {
// Validation URL tracker
if (isValidTrackerUrl(decodedValue)) {
result.announce.push(decodedValue);
}
}
}
} catch (e) {
return null;
}
return result.infoHash ? result : null;
}
Stack technique identifiée
| Composant | Technologie | Rôle |
|---|---|---|
| Backend | Node.js + Express | Serveur web et API |
| Frontend | EJS + Tailwind CSS + Alpine.js | Interface utilisateur |
| Base de données | MariaDB | Stockage persistant |
| Cache/Queue | Redis + BullMQ | File d'attente et cache |
| Client Torrent | qBittorrent-nox | Téléchargement et seeding |
| Temps réel | SSE + Redis Pub/Sub | Mises à jour en direct |
| Auth | bcryptjs + express-session | Authentification utilisateurs |
Checklist de validation
- Parsing magnet avec validation stricte (hex/base32)
- Rate limiting par IP + infoHash
- Blacklist de torrents DMCA/malware
- Tokens de téléchargement temporaires (24h max)
- Nettoyage automatique des fichiers après expiration
- Seeding limité (ratio + temps max)
- Isolation des workers (pas d'accès direct au storage)
- Validation des chemins de fichiers (pas de path traversal)
- Headers de sécurité (CSP, HSTS, X-Frame-Options)
- Audit des téléchargements (qui, quoi, quand)
Anti-patterns associés
KNOW-ANT-007— Hardcoded secrets in .env files
Comment se prot[ée]ger
- Validation stricte des URLs soumises (sch[ée]ma, domaine, pas d'internal)
- Rate limiting par utilisateur + par IP
- Isolation r[ée]seau du worker debrid (pas d'acc[è]s interne)
- Logging et audit des t[ée]l[ée]chargements
- Scan antivirus des fichiers t[ée]l[ée]charg[ée]s
Références
- Archive Ygg :
01_SOURCE_CODE/02g_debrideur/ - BitTorrent Protocol : BEP-9 (Magnet Links)
- qBittorrent Web API : https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)