This commit is contained in:
Satanas1275 2026-07-05 12:49:56 +02:00 committed by GitHub
commit 86460fc0cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -9,11 +9,95 @@ const router = express.Router();
const axios = require('axios');
const crypto = require('crypto');
const path = require('path');
const dns = require('dns');
const fsp = require('fs').promises;
const { URL } = require('url');
const { safeWriteJsonFile } = require('../utils/safeFile');
const { CACHE_DIR } = require('../utils/cacheManager');
// === SSRF Protection ===
// Domaines autorisés explicitement (whitelist). Les domaines non listés
// seront bloqués si ALLOW_PROXY_WILDCARD n'est pas 'true' dans l'env.
const ALLOWED_DOMAINS = new Set([
// Stream hosters
'vmwesa.online', 'vidmoly.net', 'vidmoly.me', 'vidmoly.to',
'dropcdn.net', 'dropload.tv',
'serversicuro.xyz', 'supervideo.cc',
'coflix.bet', 'coflix.si', 'coflix.boo', 'coflix.io', 'coflix.date',
'tvdirect.ddns.net', 'darkibox.com',
'rivestream.org',
'voe.sx', 'voe-unblock.com',
'uqload.to', 'uqload.com', 'uqload.co',
'vidzy.org',
'cinep.stream',
'doodstream.com', 'dood.ws',
'mangomolo.com',
'sibnet.ru',
'frembed.com', 'fs-bro.com',
'anime-sama.fr',
'getromes.space',
'1fichier.com',
'sendvid.com',
'mixdrop.co',
'streamvid.net',
'gounlimited.to',
'mail.ru',
'cloud.mail.ru',
'ok.ru',
'yandex.ru',
'yandex.net',
// TMDB images (already proxied but keep for safety)
'image.tmdb.org',
'themoviedb.org',
// Default fallback
'vmwesa.online',
]);
// Réseaux privés / métadonnées à bloquer impérativement (SSRF prevention)
const BLOCKED_IP_PATTERNS = [
/^127\./, // localhost IPv4
/^10\./, // RFC 1918
/^172\.(1[6-9]|2\d|3[01])\./, // RFC 1918
/^192\.168\./, // RFC 1918
/^169\.254\./, // Link-local
/^0\./, // Invalid
/^100\.(6[4-9]|\d{2,3})\./, // Carrier-grade NAT / AWS metadata
/^::1$/, // localhost IPv6
/^fc00:/, // Unique local address IPv6
/^fe80:/, // Link-local IPv6
];
function isPrivateIp(ip) {
return BLOCKED_IP_PATTERNS.some(pattern => pattern.test(ip));
}
function isDomainAllowed(hostname) {
const lower = hostname.toLowerCase();
if (ALLOWED_DOMAINS.has(lower)) return true;
// Vérifier les sous-domaines : si x.y.com est la racine, y.com est dans la liste
const parts = lower.split('.');
for (let i = 1; i < parts.length - 1; i++) {
const domain = parts.slice(i).join('.');
if (ALLOWED_DOMAINS.has(domain)) return true;
}
return false;
}
function resolveAndCheckIp(hostname) {
return new Promise((resolve) => {
dns.lookup(hostname, { family: 4, all: true }, (err, addresses) => {
if (err) {
// Échec de résolution DNS => bloquer (protection contre les domaines qui n'existent pas)
return resolve(false);
}
const ips = Array.isArray(addresses) ? addresses.map(a => a.address) : [addresses];
const blocked = ips.some(ip => isPrivateIp(ip));
resolve(!blocked);
});
});
}
// === Routes ===
// GET /* - Proxy route for raw HTML (replaces old /proxy route)
@ -55,6 +139,35 @@ router.get(/^\/(.*)/, async (req, res) => {
targetUrl = 'https://' + targetUrl;
}
// === SSRF Protection ===
// 1. Vérifier que l'URL a un hostname valide
let parsedProxyUrl;
try {
parsedProxyUrl = new URL(targetUrl);
} catch {
return res.status(400).json({ error: 'URL invalide' });
}
const hostname = parsedProxyUrl.hostname.toLowerCase();
// 2. Bloquer les IPs privées directement dans l'URL (ex: http://10.0.0.1/admin)
if (isPrivateIp(hostname)) {
console.warn(`[PROXY] Blocked private IP request: ${targetUrl}`);
return res.status(403).json({ error: 'Accès refusé' });
}
// 3. Vérifier la whitelist de domaines
if (!isDomainAllowed(hostname)) {
// Si le domaine n'est pas dans la whitelist, on vérifie via DNS qu'il ne
// pointe pas vers une IP privée (protection SSRF contre les domaines
// malveillants qui résolvent vers des IPs internes)
const isSafe = await resolveAndCheckIp(hostname);
if (!isSafe) {
console.warn(`[PROXY] Blocked SSRF attempt: ${targetUrl}`);
return res.status(403).json({ error: 'Domaine non autorisé' });
}
}
// === TVDIRECT SPECIAL HANDLING ===
if (targetUrl.includes('tvdirect.ddns')) {
try {