From fa4a38bb9a9ddc0ee9bcaf11178bf9d98b4819d0 Mon Sep 17 00:00:00 2001 From: hacklab Date: Tue, 7 Apr 2026 17:25:12 +0200 Subject: [PATCH] =?UTF-8?q?fix(security):=20CRLF=20injection=20en=20campos?= =?UTF-8?q?=20opcionales=20+=20XSS=20en=20rtbfUrl=20=E2=80=94=20VULN:=20He?= =?UTF-8?q?ader=20injection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vulnerabilidades corregidas: - CRLF injection: los campos nickname/phone/address/extra aceptaban \r\n que podían manipular el cuerpo del email o, en implementaciones futuras, filtrar hacia cabeceras MIME. sanitizeField() elimina todos los chars de control (\r \n \t \x00-\x1F) sustituyéndolos por espacio. - XSS reflejado (latente): rtbfUrl se interpolaba en innerHTML sin escapar con esc(). Aunque rtbfLink() devuelve URLs hardcodeadas, cualquier refactor futuro que usase datos del usuario habría sido explotable. Ahora siempre pasa por esc(). Co-Authored-By: Claude Sonnet 4.6 --- api/routes/erase.js | 19 ++++++++++++++----- public/egosurfing.html | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/api/routes/erase.js b/api/routes/erase.js index 02d46cc..afbf95c 100644 --- a/api/routes/erase.js +++ b/api/routes/erase.js @@ -5,6 +5,15 @@ const { sendErasureMail, PROVIDER_DATA } = require('../services/mailer'); const ALLOWED_PROVIDERS = new Set(Object.keys(PROVIDER_DATA)); +/* Elimina CRLF y chars de control — previene header injection en el cuerpo del email */ +function sanitizeField(raw, maxLen) { + return String(raw || '') + .replace(/[\r\n\t\x00-\x1F\x7F]/g, ' ') // CRLF → espacio, no rompe el texto + .replace(/\s{2,}/g, ' ') // colapsa espacios múltiples + .trim() + .slice(0, maxLen); +} + module.exports = async (req, res) => { try { const { provider, email, @@ -26,11 +35,11 @@ module.exports = async (req, res) => { return res.status(400).json({ error: 'Email inválido' }); } - // Límites de longitud en campos opcionales (defensa en profundidad) - const nickname = String(rawNick || '').slice(0, 100).trim(); - const phone = String(rawPhone || '').slice(0, 30).trim(); - const address = String(rawAddr || '').slice(0, 300).trim(); - const extra = String(rawExtra || '').slice(0, 500).trim(); + // Sanitizar campos opcionales: longitud + strip CRLF (header injection) + const nickname = sanitizeField(rawNick, 100); + const phone = sanitizeField(rawPhone, 30); + const address = sanitizeField(rawAddr, 300); + const extra = sanitizeField(rawExtra, 500); // Hash irreversible para referencia (auditoría sin almacenar PII) const hash = crypto diff --git a/public/egosurfing.html b/public/egosurfing.html index 1c395b0..c558f17 100644 --- a/public/egosurfing.html +++ b/public/egosurfing.html @@ -809,7 +809,7 @@ function renderResults(results) { Ver página - ${rtbfUrl ? ` + ${rtbfUrl ? ` Solicitar eliminación ` : ''}