Un error de inyección HTML almacenada en el tablón de anuncios de empleo RamenHire permitió que cualquier persona completara un formulario público y convirtiera los correos electrónicos de notificación del equipo en una plataforma de phishing.
Cómo se filtró el error
RamenHire funciona con Next.js y Supabase y permite que las startups publiquen ofertas de trabajo sin necesidad de iniciar sesión. Cada envío activa un correo electrónico al equipo de contratación interno. El cuerpo del correo se construye uniendo campos de formulario sin procesar —nombre de la empresa, descripción del puesto, nombre de contacto— directamente en una cadena HTML. No se realiza ningún escape o saneamiento antes de que la cadena llegue al servicio de correo.
Debido a que la plantilla trata la entrada del usuario como HTML seguro, un solicitante malintencionado puede inyectar un enlace falso de "haga clic aquí para verificar". La carga útil (payload) se almacena en el servidor como parte del anuncio de empleo, por lo que cada correo electrónico de notificación posterior lleva el código malicioso. Un atacante puede ocultar ese enlace junto al botón real de "Ver panel de control", engañando a un miembro del equipo para que revele sus credenciales o visite un sitio de phishing.
Por qué era importante
Los correos electrónicos van dirigidos al equipo central de contratación, personas que hacen clic en enlaces constantemente para avanzar a los candidatos a través del proceso de selección.
La brecha técnica
El escape de HTML no es un paso único. Dos contextos necesitan protección:
- Nodos de texto – contenido entre etiquetas. Convertir
<en<y>en>detiene las etiquetas, pero no impide que un atacante escape de un valor de atributo. - Valores de atributo – cadenas dentro de etiquetas como
href="…". Inyectar una comilla ("o') permite que un atacante cierre el atributo prematuramente e inserte su propio marcado.
El código original solo manejaba texto plano, dejando la inyección de atributos totalmente expuesta.
Solucionando el problema
El desarrollador añadió una pequeña función auxiliar escapeHtml y la aplicó a cada valor proporcionado por el usuario antes de la interpolación, ya sea que el valor termine en un nodo de texto o en un atributo.
Pasos de verificación
- Pruebas locales – Se introdujo un conjunto de cadenas de inyección en las funciones de la plantilla. Cada prueba mostró únicamente caracteres escapados, sin etiquetas ejecutables.
- Pruebas en producción – Tras implementar el parche, se envió una carga útil a través del sitio en vivo, superando el control de bots de Cloudflare Turnstile. El correo resultante llegó a la bandeja de entrada del desarrollador; el enlace malicioso y cualquier etiqueta de script aparecieron como texto ilegible, no como elementos en los que se pudiera hacer clic.
Una nota sobre Gmail: es posible que el servicio siga coloreando las URL en azul y las haga clicables, pero eso es una conveniencia del lado del cliente y no significa que la inyección HTML haya tenido éxito. La solución evita que la inyección cree botones reales o ejecute scripts.
A qué deben prestar atención los desarrolladores
- Nunca confíes en los datos de formularios públicos – Incluso los formularios que parecen inofensivos pueden producir una salida almacenada que se utiliza en otros lugares.
- Realiza el escape en el punto de uso – Aplica el escape sensible al contexto (texto frente a atributo) justo antes de la renderización, no antes en el flujo de trabajo.
- Realiza pruebas tanto en el lado del cliente como en el del servidor – Las pruebas unitarias detectan fallos obvios; una prueba manual de extremo a extremo a través de la interfaz de usuario en vivo confirma que las defensas se mantienen bajo tráfico real.
- Ten en cuenta las peculiaridades de los clientes de correo electrónico – Algunos clientes crean enlaces automáticos para las URL, lo que genera una falsa sensación de seguridad. Verifica que el HTML subyacente no contenga elementos activos.
Conclusión
Un solo descuido en el manejo de la entrada del usuario convirtió los correos electrónicos de notificación rutinarios en un vector de phishing. La introducción de una rutina integral de escape de HTML y la validación de la solución tanto de forma local como en producción restauraron la integridad de la bandeja de entrada. El episodio subraya una lección atemporal para cualquier aplicación web que genere HTML a partir de datos no confiables: el saneamiento adecuado es innegociable, e ignorarlo puede abrir una línea directa hacia las bandejas de entrada de tus usuarios.
