fix(security): CRLF injection en campos opcionales + XSS en rtbfUrl — VULN: Header injection

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 <noreply@anthropic.com>
This commit is contained in:
hacklab 2026-04-07 17:25:12 +02:00
parent 02360927ff
commit fa4a38bb9a
2 changed files with 15 additions and 6 deletions

View file

@ -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