Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | /**
* Validation reseau — Bloque les IPs privees pour prevenir le SSRF.
*
* Utilise pour les URLs fournies par les utilisateurs (uptime monitors, webhooks)
* afin d'empecher le scan du reseau interne.
*
* @module utils/networkValidator
*/
const { URL } = require('node:url');
const dns = require('node:dns');
const { promisify } = require('node:util');
const dnsResolve = promisify(dns.resolve4);
// Plages d'IPs privees (RFC 1918 + loopback + link-local + metadata)
const PRIVATE_RANGES = [
/^127\./, // Loopback
/^10\./, // Classe A privee
/^172\.(1[6-9]|2\d|3[01])\./, // Classe B privee
/^192\.168\./, // Classe C privee
/^169\.254\./, // Link-local
/^0\./, // Reseau courant
/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./, // Shared address (CGN)
/^::1$/, // IPv6 loopback
/^f[cd]/i, // IPv6 unique local
/^fe80/i, // IPv6 link-local
];
// Hostnames bloques (cloud metadata endpoints)
const BLOCKED_HOSTS = new Set([
'localhost',
'metadata.google.internal',
'169.254.169.254', // AWS/GCP metadata
'metadata.internal',
]);
/**
* Verifie si une IP est privee.
* @param {string} ip
* @returns {boolean}
*/
function isPrivateIp(ip) {
return PRIVATE_RANGES.some((range) => range.test(ip));
}
/**
* Valide qu'une URL ne pointe pas vers une IP privee.
* Resout le DNS pour verifier l'IP reelle.
*
* @param {string} urlString — URL a verifier
* @returns {Promise<{ valid: boolean, reason?: string }>}
*/
async function validatePublicUrl(urlString) {
try {
const parsed = new URL(urlString);
const hostname = parsed.hostname;
// Bloquer les hostnames connus
if (BLOCKED_HOSTS.has(hostname.toLowerCase())) {
return { valid: false, reason: 'Acces au reseau interne interdit.' };
}
// Si c'est deja une IP, verifier directement
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)) {
if (isPrivateIp(hostname)) {
return { valid: false, reason: 'Les adresses IP privees ne sont pas autorisees.' };
}
return { valid: true };
}
// Resoudre le DNS et verifier
const ips = await dnsResolve(hostname);
for (const ip of ips) {
if (isPrivateIp(ip)) {
return { valid: false, reason: `Le domaine ${hostname} resout vers une IP privee (${ip}).` };
}
}
return { valid: true };
} catch (err) {
if (err.code === 'ENOTFOUND') {
return { valid: false, reason: 'Domaine introuvable.' };
}
return { valid: false, reason: `Erreur de validation : ${err.message}` };
}
}
module.exports = { validatePublicUrl, isPrivateIp };
|