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:
parent
02360927ff
commit
fa4a38bb9a
2 changed files with 15 additions and 6 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -809,7 +809,7 @@ function renderResults(results) {
|
|||
<a class="result-action result-action--visit" href="${esc(r.url)}" target="_blank" rel="noopener noreferrer">
|
||||
Ver página
|
||||
</a>
|
||||
${rtbfUrl ? `<a class="result-action result-action--rtbf" href="${rtbfUrl}" target="_blank" rel="noopener noreferrer">
|
||||
${rtbfUrl ? `<a class="result-action result-action--rtbf" href="${esc(rtbfUrl)}" target="_blank" rel="noopener noreferrer">
|
||||
Solicitar eliminación
|
||||
</a>` : ''}
|
||||
<a class="result-action result-action--gdpr" href="plantillas.html" target="_blank">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue