Implementación de medidas de mitigación y seguridad ante caídas de servicios por escaneos automatizados de internet (APORTE UNAM)

Ante los inconvenientes ocasionados por el alto volumen de consultas automatizadas realizadas por bots sobre el Portal Público de Compras y Contrataciones de SIU-Diaguita, se evaluaron distintas alternativas para mitigar este comportamiento. Como resultado de este análisis, se diseñó e implementó una arquitectura y configuración del mecanismo de seguridad basado en Google reCAPTCHA v2.

El objetivo de esta implementación fue reducir el impacto de consultas automatizadas realizadas por bots sobre el Portal, preservando la disponibilidad del servicio para los usuarios legítimos.

Nuestros entornos se encuentran desplegados sobre infraestructura en Docker, por eso se optó por una solución desacoplada del código fuente de la aplicación. Esto permitió implementar la validación mediante un servicio adicional integrado a nivel de infraestructura, aprovechando los mecanismos de proxy reverso y middleware de Traefik, sin necesidad de realizar modificaciones sobre el portal ni afectar futuras actualizaciones del sistema.

1. Arquitectura General

La solución utiliza una arquitectura stateless (sin estado) en la capa de red/proxy reverso. En lugar de modificar el código interno del Portal de SIU-Diaguita, se utiliza el middleware forwardAuth de Traefik.

2. Configuración en la Infraestructura (Docker Compose y Traefik)

La integración está configurada en el despliegue mediante el archivo yaml

A. Servicio captcha

Se define un contenedor basado en PHP que contiene la lógica de validación:

  captcha:
    image: <registro-docker>/diaguita/captcha:<version>
    environment:
      - RECAPTCHA_ENABLED=true
      - TOBA_SALT=<clave_firmado_cookie>
      - PORTAL_COOKIE_LIFESPAN_SECONDS=86400
      - RECAPTCHA_SITE_KEY=<tu_recaptcha_site_key>
      - RECAPTCHA_SECRET_KEY=<tu_recaptcha_secret_key>
    deploy:
      labels:
        - "traefik.enable=true"
        - "traefik.http.services.<entorno>_diaguita_captcha.loadbalancer.server.port=80"
    networks:
      internal:
      traefik-public:

B. Middleware forwardAuth en el Servicio portal

El portal utiliza etiquetas de Traefik para indicar que todas las solicitudes dirigidas a la web del portal deben pasar por el filtro del contenedor de Captcha:

       # 1. Vincular el router al middleware de Captcha
        - "traefik.http.routers.<entorno>_portal_diaguita.middlewares=<entorno>_portal_diaguita_captcha"
        
        # 2. Definición del middleware ForwardAuth
        - "traefik.http.middlewares.<entorno>_portal_diaguita_captcha.forwardauth.address=http://<entorno>_diaguita_captcha/verify.php"
        - "traefik.http.middlewares.<entorno>_portal_diaguita_captcha.forwardauth.trustForwardHeader=true"
        - "traefik.http.middlewares.<entorno>_portal_diaguita_captcha.forwardauth.authResponseHeaders=Set-Cookie"

Nota: La propiedad forwardauth. es crucial. Le indica a Traefik que, cuando el servicio de captcha responda exitosamente y adjunte un encabezado Set-Cookie, ese encabezado debe ser propagado hacia el cliente final (el navegador) para almacenar la sesión de verificación.

C. Exclusión del Tráfico de la API REST

Para evitar romper integraciones automatizadas legítimas (ej. sistemas externos que consumen datos del portal), el tráfico con destino al prefijo /rest/v1 tiene un enrutador dedicado sin el middleware de Captcha:

        - "traefik.http.routers.<entorno>_portal_diaguita_rest.entrypoints=web"
        - "traefik.http.routers.<entorno>_portal_diaguita_rest.rule=Host(`<host_portal_diaguita>`) && PathPrefix(`/rest/v1`)"
        - "traefik.http.routers.<entorno>_portal_diaguita_rest.service=<entorno>_portal_diaguita"

3.Funcionamiento de la Capa de Verificación (verify.php)

La lógica interna reside en verify.php. El script realiza las siguientes operaciones secuenciales:

A. Variables de Entorno del Sistema

El comportamiento se parametriza dinámicamente con las siguientes variables en los archivos .env:

  • RECAPTCHA_ENABLED: (Booleano) Permite desactivar globalmente la verificación (por
    ejemplo, en entornos de desarrollo o pruebas automatizadas).

  • TOBA_SALT: Clave criptográfica utilizada como semilla para firmar las cookies.

  • PORTAL_COOKIE_LIFESPAN_SECONDS 86400 segundos = 24 horas).

  • RECAPTCHA_SITE_KEY: Clave pública de Google reCAPTCHA.

  • RECAPTCHA_SECRET_KEY: Clave privada para verificar la validez del token en el backend.

B. Firma de Cookies mediante HMAC-SHA256

Para evitar que un atacante falsifique la cookie de validación y salte el captcha, el script utiliza criptografía simétrica:

  1. Generación de la Cookie: Cuando el captcha se resuelve con éxito, se toma la hora actual
    (timestamp) y se genera una firma: hash=hash_hmac(′sha256′,timest,TOBA_SALT)

    La cookie resultante se compone de timestamp|hash y se envía bajo el nombre diaguita_portal_captcha_auth con banderas de seguridad (HttpOnly, SameSite=Lax, y Secure según el protocolo original).

  2. Validación: Para cada nueva solicitud, el script divide la cookie, calcula si el timestamp no
    ha expirado y vuelve a generar el HMAC esperado para compararlo con el valor recibido mediante la función segura contra ataques de temporización hash_equals.

C. Redirección Limpia

Al verificar un captcha resuelto en una URL del tipo portal.diaguita.uunn.edu.ar/?,
el script:

  1. Remueve el parámetro g-recaptcha-response.

  2. Preserva los demás parámetros de búsqueda originales (para mantener la
    navegación a enlaces profundos intacta).

  3. Responde con un código HTTP 302 Found redirigiendo al cliente a la URL limpia e inyectando la cookie de seguridad en el mismo paso.

D. Interfaz Visual Autoincluida (Base64)

Si el cliente no está validado, el script responde con HTTP 401 Unauthorized.
Como la petición es interceptada por Traefik, el HTML generado por verify.php es
el que recibe el navegador del usuario.

Para evitar problemas de carga de imágenes corporativas antes de que el
usuario esté verificado, el logo de la institución (unam.png) se procesa de forma preventiva y se inyecta directamente en el HTML
usando codificación Base64 (Data URI)
:

$ruta_imagen = './unam.png';

$src_base64 = '';if (file_exists($ruta_imagen) && is_readable($ruta_imagen)) {$contenido_binario = file_get_contents($ruta_imagen);$src_base64 = ‘data:image/png;base64,’ . base64_encode($contenido_binario); }

4. Resumen de Flujo de Trabajo ante Nuevas Peticiones

  • Petición normal a la API:
    Accede directo (/rest/v1/* no pasa por captcha).

  • Solicitud de usuario con cookie de validación vigente: verify.php responde 200
    OK instantáneamente (sobrecarga mínima, proceso 100% en memoria del contenedor
    captcha).

  • Petición de Navegador sin Cookie (o Expirada):
    Recibe el formulario de validación reCAPTCHA con código 401.
    Una vez resuelto, se le otorga la cookie con vigencia de 24 horas y se le redirige a su destino original de forma transparente.

5. Construcción de la Imagen Docker Captcha

La imagen de Docker del servicio de captcha se construye sobre un entorno ligero de PHP y se distribuye a través del registro de contenedores de la institución.

A. Dockerfile (captcha/Dockerfile)

La imagen utiliza como base php:8.2-alpine para minimizar el tamaño y consumo de recursos. Se copian la lógica de verificación (verify.php) y el logo institucional (unam.png)
al directorio de trabajo /var/www/html, exponiendo el puerto 80 mediante el servidor de desarrollo integrado de PHP:

FROM php:8.2-alpine 
COPY verify.php /var/www/html/verify.php
COPY unam.png /var/www/html/unam.png
WORKDIR /var/www/html
EXPOSE 80
CMD ["php", "-S", "0.0.0.0:80", "-t", "/var/www/html"]

B. Construcción Local

Para construir la imagen localmente desde la raíz del repositorio, se ejecutó el siguiente comando:

docker build -t /diaguita/captcha:local -f captcha/Dockerfile captcha 

La implementación de Google reCAPTCHA v2 mediante el mecanismo ForwardAuth de Traefik permitió incorporar una capa de protección contra accesos automatizados. La solución resulta transparente para los usuarios legítimos, preserva la compatibilidad con futuras actualizaciones del sistema y mantiene operativas las integraciones externas mediante la exclusión de los servicios REST.