
Hoy quiero compartir el análisis de una vulnerabilidad real que encontré durante mi participación en un programa público de Bug Bounty en BugCrowd(Por cierto también mi primer reporte). Por razones de confidencialidad y ética, he anonimizado todos los detalles específicos como nombres de la organización, URLs y valores de parámetros, pero la lógica y la técnica de explotación son 100% reales.
El objetivo de este post es mostrar un patrón de vulnerabilidad común y cómo, con un poco de curiosidad, podemos descubrir fallos de seguridad significativos.
El Escenario: Una Plataforma de Gestión de Datos
El objetivo era una plataforma web compleja que permitía a los usuarios buscar, filtrar y exportar grandes volúmenes de datos. Una de sus funcionalidades clave era un «Exportador de Datos Personalizado», donde podías aplicar varios filtros para luego descargar los resultados.
Paso 1: Explorando la Funcionalidad de Exportación
El flujo de trabajo era sencillo:
- Navegar a la página principal de la aplicación en https://gestion-datos.ejemplo.com/app/.
- Hacer clic en un botón para acceder a la herramienta de exportación, como «Exportar Resultados».
Esto nos llevaba a una nueva página donde podíamos construir nuestra consulta. La URL de esta página ya nos daba pistas interesantes:
Aquí vemos varios parámetros que controlan la configuración de la página, como appId y, el más interesante para nosotros, filterName.
Paso 2: La Pista Clave – La Función «Compartir URL»
Dentro de esta página de exportación, después de aplicar algunos filtros, la aplicación generaba una «URL para Compartir». Esta URL contenía todos los filtros que habías seleccionado, permitiéndote enviar la consulta exacta a un colega.
Al observar el código fuente de la página, descubrí una función en JavaScript llamada buildSaveURL() que se encargaba de leer los filtros de la página y construir dinámicamente esta URL.
function generarURLParaGuardar() {
let urlGenerada = "";
$("#filterGroups input:not(.hidden):not(:button)").each(function () {
let idElemento = $(this).attr("id");
let esAutogenerado = idElemento.includes("autogen");
let esGrupoFiltro = idElemento.includes("filterGroup1");
if (!esAutogenerado && !esGrupoFiltro) {
urlGenerada += idElemento + "=" + $(this).val() + "&";
}
});
let camposSalidaSeleccionados = $("#outputFields").val();
if (camposSalidaSeleccionados) {
urlGenerada += "outputFields=" + camposSalidaSeleccionados;
}
}
Mi hipótesis fue inmediata: si la URL se construye dinámicamente con parámetros que yo controlo desde la URL inicial, ¿qué pasa si inyecto código en uno de esos parámetros?
Paso 3: La Explotación – Creando el Payload
El parámetro filterName parecía el candidato perfecto. El valor de este parámetro se reflejaba en el JavaScript de la página. Específicamente, parecía que se insertaba dentro de una cadena de texto, algo así:
// Código original (supuesto)
var searchFilterName = 'DefaultFilter'; // <- El valor del parámetro 'filterName' se inserta aquíPara explotar esto, necesitamos «escapar» de esa cadena de texto y ejecutar nuestro propio código. Un payload clásico para esto es:
valor_original’;alert(‘XSS PoC by [Zedeq]’)//
Analicemos este payload:
- ‘: Cierra la comilla simple de la cadena de texto original.
- ;: Termina la declaración de la variable en JavaScript.
- alert(‘XSS PoC by [TuAlias]’): Nuestro código malicioso. Ejecuta una alerta en el navegador.
- //: Convierte el resto de la línea original en un comentario, evitando errores de sintaxis.
Ahora, construimos la URL maliciosa modificando el parámetro filterName:
URL Original:
…/exportar?appId=WebApp1&filterName=DefaultFilter&coreId=search-core
URL Maliciosa:
…/exportar?appId=WebApp1&filterName=**DefaultFilter’;alert(‘XSS-PoC’)//**&coreId=search-core
Paso 4: La Prueba de Concepto (PoC)
Al abrir la URL maliciosa en el navegador, ¡éxito! Inmediatamente apareció una ventana de alerta, confirmando la ejecución de nuestro código JavaScript. La vulnerabilidad de Cross-Site Scripting (XSS) Reflejado estaba confirmada.

Análisis del Código Vulnerable
Revisando el fichero JavaScript, encontramos la función buildSaveURL() y confirmamos nuestras sospechas. El código era similar a esto:
function buildSaveURL() {
// ... otro código ...
var appName = 'WebApp1';
var solrCoreName = 'search-core';
// ¡Línea vulnerable!
// El valor de 'filterName' viene directamente de la URL sin sanitizar.
var searchFilterName = '{{ valor_del_parametro_filterName }}';
// ... más código que usa la variable searchFilterName ...
var myShareUrl = 'https://gestion-datos.ejemplo.com/app/exportar' +
'?appName=' + appName +
'&searchFilterName=' + searchFilterName + // <-- Aquí se usa la variable contaminada
'&solrCoreName=' + solrCoreName;
$("#shareUrl").val(myShareUrl);
}El problema es que el valor del parámetro se inserta directamente en el script sin ningún tipo de validación o codificación. El navegador no tiene forma de distinguir entre el código legítimo y nuestro código inyectado.
Impacto y Mitigación
Un XSS Reflejado como este puede ser usado para:
- Robar cookies de sesión y suplantar la identidad de la víctima.
- Redirigir a los usuarios a sitios de phishing.
- Modificar el contenido de la página para engañar al usuario.
- Realizar acciones en la plataforma en nombre del usuario.
La solución correcta es siempre codificar la salida. El servidor o el framework del lado del cliente debe escapar cualquier dato que vaya a ser insertado en un contexto de JavaScript para que sea tratado como un literal de cadena y no como código ejecutable.
Conclusión
Este caso es un recordatorio clásico de un principio fundamental de la seguridad: nunca confíes en la entrada del usuario. Cualquier dato que provenga de una fuente externa, como un parámetro de URL, debe ser rigurosamente validado y sanitizado antes de ser procesado o reflejado en la página.
¡Espero que este análisis les haya sido útil! ¡HappyHacking!

