HTTPs 302
Es posible hacer una redirección del protocolo HTTPs a HTTP?. Respuesta corta: NO y Respuesta larga: NO... pero hay un "escenario", que hoy intentaremos explicar. Pero, primero, veamos qué sucede cuando se establece una conexión HTTPs:
- El navegador del cliente intenta conectarse a una URL que usa HTTPs (puerto 443 por defecto).
- El cliente y el servidor inician un handshake SSL/TLS (una negociación para establecer un canal cifrado).
- Durante ese proceso, el servidor donde está alojada la URL envía un certificado digital, que el navegador del cliente valida para verificar que está hablando con el sitio correcto (por ejemplo, que el certificado sea válido para https://www.example.com).
- Solo si ese proceso es exitoso, se establece una conexión segura y recién ahí se transmiten los datos reales.
En realidad suceden más cosas, pero para qué alargar el asunto. Aquí lo importante es que todo está cifrado desde el principio y no hay manera de ver ni modificar el contenido.
Escenario
¿Y qué sucedería si tenemos una una red LAN centralizada y administrada por un servidor o router, actuando como Man-in-the-Middle MITM, y queremos bloquear https://www.facebook.com e inyectarle una redirección a una página de bloqueo personalizada?
- El navegador solicita el certificado SSL/TLS de facebook.
- El MITM interviene e inyecta una redirección de facebook hacia nuestra página de bloqueo.
- El navegador espera recibir el certificado legítimo de facebook y en su lugar recibe algo diferente y se rompe la conexión SSL/TLS y la página "se cae" con un mensaje de error similar al siguiente:
Esta es precisamente la razón por la cual no se pueden mostrar mensajes de advertencia, bloqueos u otro tipo de contenido personalizado cuando se trata del protocolo HTTPs.
A Long, Long Time Ago...
Antes de 2010, cuando aún no se había adoptado de forma masiva el protocolo HTTPs, la mayoría de los sitios en Internet utilizaban HTTP. En ese contexto, por ejemplo, con un proxy como Squid-Cache operando en una LAN centralizada, bastaba con definir una regla de bloqueo y agregar la cláusula deny_info para mostrar al cliente una página de error personalizada. Sin embargo, con la transición a HTTPs, esta cláusula ha perdido efectividad (solo sirve para HTTP), pero, existe otra alternativa.
Tomando nuevamente como ejemplo a Squid-Cache, este proxy ofrece la directiva SSL-Bump, que permite interceptar la conexión HTTPs, generar en tiempo real un certificado falso y entregárselo al cliente, actuando como intermediario (MITM). Esto permitiría redirigir o bloquear el acceso mostrando mensajes personalizados, pero el navegador lanza una alerta, que, en muchos casos, ni siquiera permite continuar la navegación, así se pulse el botón "Avanzado" para hacerlo:
![]() |
Alerta en el navegador por certificado SSL personalizado |
Como podemos observar, en la práctica, esta solución es casi inviable, debido a la gran diversidad de sistemas operativos y navegadores, así como a las crecientes restricciones de seguridad que dificultan la instalación y confianza en un certificado raíz personalizado en cada dispositivo, y, lo más importante, que este tipo de manipulación del tráfico puede violar políticas de privacidad o regulaciones legales, dependiendo del contexto y la jurisdicción.
Otras alternativas muy populares y menos invasivas son soluciones como Pi-hole y OpenDNS, que operan a nivel de resolución de nombres de dominio (DNS). Estas herramientas filtran las solicitudes, bloqueando dominios conocidos por alojar publicidad, rastreadores, malware, contenido no deseado, etc. Pi-hole suele implementarse en redes LAN como un servidor DNS interno, mientras que OpenDNS ofrece un servicio similar desde la nube. No obstante, este enfoque vuelve la navegación lenta a medida que las listas de bloqueo crecen en tamaño (cantidad de líneas de dominios a bloquear), y no permite inspeccionar el contenido de las conexiones cifradas, ni mucho menos realizar redirecciones a páginas personalizadas, ya que hacerlo requeriría romper el cifrado SSL, algo para lo cual no están diseñadas (OpenDNS tiene un certificado, pero ocurriría el mismo problema que con SSL-Bump).
En este punto ya deberíamos saber que la respuesta simple, como mencionamos al principio, es: No se puede redireccionar HTTPs a nuestro antojo. Pero esto no significa que nos quedemos cruzados de brazos. A continuación una solución a nuestro "escenario".
Principio de los portales cautivos
Antes de entrar en materia, primero hay que conocer el funcionamiento básico de los portales cautivos (hotspot). Un portal cautivo no intercepta el tráfico HTTPs (y por tanto no viola ninguna regulación). Lo que hace es evitar que el cliente navegue libremente hasta que pase por una página de autenticación o aviso.
Cuando un cliente se conecta por primera vez a la red (ya sea cableada, Wi-Fi pública, etc.), el sistema bloquea todo el tráfico saliente, excepto algunas excepciones mínimas, como DNS y HTTP. Si el cliente intenta abrir cualquier sitio web, se produce algo conocido en el argot popular como "connectivity check" (también recibe otros nombres más técnicos, como "captive portal detection", etc.).
Este "connectivity check", en el contexto de sistemas operativos y navegadores, es un mecanismo mediante el cual el dispositivo verifica si tiene acceso real a Internet y consiste en hacer una petición HTTP a una URL conocida, normalmente a un servidor controlado por el fabricante del sistema (como Google, Microsoft o Apple). Por ejemplo, al conectarse a una red, el sistema operativo o navegador puede hacer una solicitud a una URL específica, como:
- http://clients3.google.com/generate_204 (Android/Chrome)
- http://www.msftconnecttest.com/connecttest.txt (Windows)
- http://captive.apple.com (Apple)
- http://detectportal.firefox.com/success.txt (Firefox)
Si la respuesta es la esperada, por ejemplo, un código "HTTP 204 No Content" o un texto específico, el sistema concluye que hay conectividad a Internet.
Y sucede la magia
Estas peticiones de verificación no van por HTTPs, sino por HTTP, lo que significa que pueden ser interceptadas y redireccionadas automáticamente al portal cautivo y responde con una redirección HTTP 302.
Dicho de otro modo, no se redirige la petición HTTPs propiamente, sino que se espera a que el cliente active el mecanismo de verificación, que es por HTTP, y se intercepta para guiarlo al portal.
Y ¿cómo aplicamos estas técnicas en nuestro "escenario"?
Una vez entendido estos principios, lo siguiente es montar una página que muestre el aviso al cliente y configurarla para que sea servida tanto por HTTP como por HTTPs; aunque este último, como ya vimos, solo tendrá efecto si el cliente la abre directamente, ya que no hay redirección HTTPs. Para esto usaremos un servidor Linux, con apache2, iptables e ipset y... nada más.
Paso 1: Crear una página HTML
Primero creamos una página simple en HTML que será la advertencia o mensaje de restricción que verá el cliente. Luego, la alojamos en Apache2 (o en cualquier otro servidor HTTP que prefieran). A continuación un ejemplo (puede modificarla a su gusto):
#!/bin/bash mkdir -p /var/www/html/warning chown -R www-data:www-data /var/www/html/warning tee /var/www/html/warning/warning.html >/dev/null << EOL <!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <title>Acceso restringido | Restricted Access</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { background: #f2f5f8; font-family: "Segoe UI", Tahoma, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; } .container { background: white; padding: 40px; border-radius: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); text-align: center; max-width: 400px; width: 90%; } .container img { width: 80px; margin-bottom: 20px; } h1 { color: #333; margin-bottom: 10px; } p { color: #666; margin-bottom: 30px; } .quota { text-align: left; background: #f9fafc; border: 1px solid #ddd; padding: 15px; border-radius: 6px; font-size: 14px; } .quota li { margin-bottom: 8px; } .footer { font-size: 12px; color: #aaa; margin-top: 30px; } </style> </head> <body> <p style="display: none;">Success</p> <div class="container"> <svg width="80" height="80" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <path d="M50 10 L90 80 L10 80 Z" fill="none" stroke="#e53e3e" stroke-width="6" stroke-linejoin="round"/> <circle cx="50" cy="65" r="3" fill="#000"/> <line x1="50" y1="30" x2="50" y2="55" stroke="#000" stroke-width="6" stroke-linecap="round"/> </svg> <h1>Acceso restringido<br> Restricted Access</h1> </div> </body> </html> EOL
Paso 2: Configurar Apache para HTTP (y HTTPs como opcional, aunque no se necesita)
Ahora creamos dos VirtualHost en Apache2, uno que escuche en el puerto 18880 (HTTP) y otro en 18443 (HTTPs), ambos mostrando la misma página de advertencia (puede usar los puertos y path de su preferencia):
#!/bin/bash # defina la dirección IP del servidor server_ip="192.168.0.10" tee /etc/apache2/sites-available/warning.conf >/dev/null << EOL <VirtualHost *:18880> ServerAdmin webmaster@localhost DocumentRoot /var/www/html/warning <Directory /var/www/html/warning> DirectoryIndex warning.html Options -Indexes +FollowSymLinks AllowOverride None Require all granted </Directory> Alias /generate_204 /var/www/html/warning/warning.html Alias /connecttest.txt /var/www/html/warning/warning.html Alias /hotspot-detect.html /var/www/html/warning/warning.html Alias /check_network_status.txt /var/www/html/warning/warning.html Alias /success.txt /var/www/html/warning/warning.html Alias /ncsi.txt /var/www/html/warning/warning.html Alias /library/test/success.html /var/www/html/warning/warning.html RewriteEngine On RewriteCond %{REQUEST_URI} !^/warning\.html$ RewriteRule ^.*$ /warning.html [R=302,L] ErrorLog ${APACHE_LOG_DIR}/warning_error.log CustomLog ${APACHE_LOG_DIR}/warning_access.log combined </VirtualHost> EOL chmod 644 /etc/apache2/sites-available/warning.conf touch /var/log/apache2/{warning_access,warning_error}.log tee /etc/apache2/sites-available/warning-ssl.conf >/dev/null << EOL <VirtualHost *:18443> ServerAdmin webmaster@localhost DocumentRoot /var/www/html/warning SSLEngine on SSLCertificateFile /etc/ssl/certs/warning/cert.pem SSLCertificateKeyFile /etc/ssl/private/warning/key.pem <Directory /var/www/html/warning> DirectoryIndex warning.html Options -Indexes +FollowSymLinks AllowOverride None Require all granted </Directory> Alias /generate_204 /var/www/html/warning/warning.html Alias /connecttest.txt /var/www/html/warning/warning.html Alias /hotspot-detect.html /var/www/html/warning/warning.html Alias /check_network_status.txt /var/www/html/warning/warning.html Alias /success.txt /var/www/html/warning/warning.html Alias /ncsi.txt /var/www/html/warning/warning.html Alias /library/test/success.html /var/www/html/warning/warning.html RewriteEngine On RewriteCond %{REQUEST_URI} !^/warning\.html$ RewriteRule ^.*$ /warning.html [R=302,L] ErrorLog ${APACHE_LOG_DIR}/warning_ssl_error.log CustomLog ${APACHE_LOG_DIR}/warning_ssl_access.log combined </VirtualHost> EOL chmod 644 /etc/apache2/sites-available/warning-ssl.conf touch /var/log/apache2/{warning_ssl_access,warning_ssl_error}.log
Paso 3: Habilitar los puertos, certificado y módulos necesarios
Ahora levantamos los puertos a escuchar en
ports.conf
si no están ya definidos:#!/bin/bash server_ip="192.168.0.10" grep -q 'Listen 0.0.0.0:18880' /etc/apache2/ports.conf || echo 'Listen 0.0.0.0:18880' >> /etc/apache2/ports.conf grep -q 'Listen 0.0.0.0:18443' /etc/apache2/ports.conf || echo 'Listen 0.0.0.0:18443' >> /etc/apache2/ports.conf mkdir -p /etc/ssl/private/warning /etc/ssl/certs/warning cat > /tmp/warning_openssl.cnf <<EOF [req] distinguished_name = req_distinguished_name x509_extensions = v3_req prompt = no [req_distinguished_name] C = CO ST = Some-State L = City O = CaptivePortal CN = ${serverip} [v3_req] basicConstraints = critical,CA:FALSE subjectAltName = @alt_names [alt_names] IP.1 = ${serverip} EOF # SSL openssl req -x509 -nodes -newkey rsa:2048 -days 365 \ -keyout /etc/ssl/private/warning.key.pem \ -out /etc/ssl/certs/warning.cert.pem \ -config /tmp/warning_openssl.cnf \ -extensions v3_req \ > /dev/null 2> /tmp/openssl_error.log cat /tmp/warning_openssl.cnf rm -f /tmp/warning_openssl.cnf a2ensite -q warning.conf a2ensite -q warning-ssl.conf a2enmod ssl a2enmod rewrite update-ca-certificates -f systemctl daemon-reload systemctl restart apache2
Paso final: redirección y bloqueo con iptables + ipset
Una vez tengamos lista y funcionando la página que le vamos a mostrar al cliente, pasamos a configurar la redirección con ipset/iptables . Ejemplo:
#!/bin/bash # definimos la interfaz de red LAN lan=eth1 # definimos la dirección IP del servidor man-in-the-middle serverip="192.168.0.10" # ip a banear de ejemplo: all_bans="192.168.0.50" # Zero all packets and counters iptables -F iptables -X iptables -t nat -F iptables -t nat -X iptables -t mangle -F iptables -t mangle -X iptables -t raw -F iptables -t raw -X iptables -t security -F iptables -t security -X iptables -Z iptables -t nat -Z iptables -t mangle -Z # Políticas base iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -P OUTPUT ACCEPT # LOOPBACK iptables -A INPUT -i lo -j ACCEPT iptables -A OUTPUT -o lo -j ACCEPT # LOCALHOST iptables -t mangle -A PREROUTING -s 127.0.0.0/8 ! -i lo -j DROP # Disables packet forwarding (NAT) sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1 ### IPSET/IPTABLES FOR BANDATA echo "Running Ipset/Iptables Rules..." ipset -L bandata >/dev/null 2>&1 if [ $? -ne 0 ]; then ipset -! create bandata hash:net family inet hashsize 1024 maxelem 65536 else ipset -! flush bandata fi if [ -n "$all_bans" ]; then echo "$all_bans" | while read ip; do ipset -! add bandata "$ip" done echo "Applying iptables rules..." # Allow HTTP Virtualhost (18880/TCP) for bandata iptables -A INPUT -i $lan -m set --match-set bandata src -p tcp --dport 18880 -j ACCEPT iptables -A FORWARD -i $lan -m set --match-set bandata src -p tcp --dport 18880 -j ACCEPT # Allow HTTPs Virtualhost (18443/TCP) for bandata (Optional - descoméntelas si las necesita) #iptables -A INPUT -i $lan -m set --match-set bandata src -p tcp --dport 18443 -j ACCEPT #iptables -A FORWARD -i $lan -m set --match-set bandata src -p tcp --dport 18443 -j ACCEPT # Allow DNS (53/UDP) for bandata iptables -A INPUT -i $lan -m set --match-set bandata src -p udp --dport 53 -j ACCEPT iptables -A FORWARD -i $lan -m set --match-set bandata src -p udp --dport 53 -j ACCEPT # Redirect HTTP (80/TCP) for bandata iptables -t nat -A PREROUTING -i $lan -m set --match-set bandata src -p tcp --dport 80 -j DNAT --to-destination $serverip:18880 # Soft Drop: Block Squid Explicit/DoT/HTTPs iptables -I INPUT -i $lan -m set --match-set bandata src -p tcp -m multiport --dports 3128,853,443 -j DROP iptables -I FORWARD -i $lan -m set --match-set bandata src -p tcp -m multiport --dports 3128,853,443 -j DROP # Hard Drop: Deny all #iptables -I INPUT -i $lan -m set --match-set bandata src -j DROP #iptables -I FORWARD -i $lan -m set --match-set bandata src -j DROP echo "Done" else echo "No hay IPs en bandata" fi
Conclusión
No es necesario configurar un VirtualHost HTTPs ni instalar certificados. Los mecanismos de detección de portales cautivos en sistemas operativos modernos se basan exclusivamente en peticiones HTTP (puerto 80). Por tanto, basta con tener un servidor HTTP sencillo que responda en un puerto alternativo (como 18880), sin necesidad de manejar tráfico HTTPs. Tampoco es necesario redireccionar o interceptar tráfico HTTPs. Intentar redirigir HTTPs genera errores de conexión en los navegadores debido al cifrado. Basta con bloquearlo para forzar que el navegador intente una conexión HTTP, lo que activará la detección del portal cautivo (aunque no significa que descarte completamente el Virtualhost HTTPs. Puede ser útil para hacer otros tipos de forzado de conexiones, verificación de funcionalidad o servir otros tipos de páginas en localhost para ofrecerle a los clientes y por eso lo incluimos en la instalación).
Entonces, Los requisitos mínimos para que funcione el portal cautivo (o cualquier página que quiera ofrecerle a sus clientes) son:
- Permitir el acceso al puerto HTTP donde está el VirtualHost del portal cautivo.
- Permitir consultas DNS (puerto 53/UDP).
- Redirigir todo el tráfico HTTP (puerto 80) al puerto del VirtualHost HTTP.
- Bloquear todo el tráfico restante.
- La IP del cliente debe estar añadida al conjunto ipset que define los usuarios en cuarentena.
- Limpiar las reglas existentes al comienzo de la ejecución de iptables para evitar conflictos o comportamientos inesperados.
- El reenvío de paquetes (IP forwarding) debe estar habilitado para que funcione correctamente el redireccionamiento del tráfico.
Como vimos, no es posible redirigir el tráfico HTTPs debido a la naturaleza cifrada del protocolo, sin embargo, mediante el uso de técnicas similares a las empleadas por los portales cautivos, podemos abusar aprovecharnos de mecanismos como el llamado "connectivity check" (Captive Portal Detection) para mostrar advertencias o requisitos de acceso —ya sea un login, un aviso de términos, un formulario, página de bloqueo, etc.— antes de permitir la navegación completa.
Con unas pocas herramientas del kernel de Linux —como iptables, ipset y un servidor web básico— es posible implementar un sistema funcional que:
- Detecta clientes (no autenticados o bloqueados por el administrador).
- Los redirige a una página HTTP (de login, aceptación de términos, restricciones de acceso a URLs, etc.)
- Bloquea todo el tráfico restante hasta que se cumpla la condición definida por el administrador de sistemas o la empresa.
Esta solución no perfecta, ya que no funciona en entornos controlados por un proxy explícito, como Squid 3128, o un firewall, sin embargo si las conexiones son transparentes, sigue siendo una de las estrategias más efectivas y viables para controlar el acceso a redes locales, especialmente en entornos públicos o semi-controlados.
Si quiere ver este proceso en acción, visite el proyecto LightSquid, donde implementamos esta técnica bajo el nombre bandata.
Si quiere ver este proceso en acción, visite el proyecto LightSquid, donde implementamos esta técnica bajo el nombre bandata.
Post a Comment