HTTP CPD
Es posible hacer una redirección del protocolo HTTPs a HTTP?. Respuesta corta: NO y Respuesta larga: NO", pero hay un escenario muy interesante que hoy intentaremos explicar. Sin embargo, primero, veamos qué sucede cuando se establece una conexión HTTPs:
- El navegador del cliente intenta conectarse a la URL https://facebook.com (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, o sea, que el certificado sea válido para https://facebook.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:
|
| Error SSL/TLS facebook.com |
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" (a modo de recordatorio: mostrarle al cliente una página personalizada cuando intente acceder a un sitio https) y es HTTP CDP.
CDP¿Qué diablos es CDP?. Bueno, antes de entrar en materia, primero hay que conocer el funcionamiento básico de los portales cautivos.
Un portal cautivo es una parte integral de un sistema hotspot. Su función no es interceptar ni inspeccionar el contenido del tráfico, sino redirigir temporalmente las solicitudes iniciales del cliente hacia una página de autenticación o aviso. De este modo, el usuario no puede acceder libremente a Internet hasta completar el proceso de autenticación o aceptación.
Entonces, cuando un cliente se conecta por primera vez a la red LAN (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" y su nombre técnico es "Captive Portal Detection (CPD)".
Este CPD, 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 a quién le importa cómo funcionan y qué relación tiene con el "escenario"La razón de esta explicación es porque estas peticiones de verificación no van por HTTPs, sino por HTTP, lo que significa que pueden ser interceptadas y/o redireccionadas automáticamente al portal cautivo.
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 CPD, que es por HTTP, y se intercepta para guiarlo al portal. Y es aquí donde sucede la magia, ya que podemos usar este mecanismo para ofrecerle una página diferente al cliente.
Y ¿cómo aplicamos esta técnica en el "escenario"?Hay que aclarar que la única manera de que esto funcione es bloqueando al cliente, y mostrandole el portal. Por tanto, esto se puede usar cuando un cliente va a iniciar la navegación (al estilo de los portales cautivos) o para bloquear un infractor y mostrarle una página de advertencia, términos y condiciones, etc. Hay otros escenarios, pero estos dos son los más usados.
Una vez entendido estos principios, lo siguiente es montar una página que muestre la página que queremos mostrarle al cliente cuando intente acceder y configurarla para que sea servida por HTTP (Opcional: también 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 HTMLPrimero 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):
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>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>RESTRICTED ACCESS<br>ACCESO RESTRINGIDO</h1>
<p>You have exceeded your allotted data quota<br>Ha superado la cuota de datos asignada</p>
<div class="quota">
<ul>
<li><strong>Daily | Diario:</strong> <!-- bw-day -->1 GB<!-- /bw-day --></li>
<li><strong>Weekly | Semanal:</strong> <!-- bw-week -->5 GB<!-- /bw-week --></li>
<li><strong>Monthly | Mensual:</strong> <!-- bw-month -->20 GB<!-- /bw-month --></li>
</ul>
</div>
<div class="footer">
Access will be restored automatically when your quota renews.<br>
El acceso se restaurará automáticamente cuando se renueve su cuota.
</div>
</div>
</body>
</html>
Paso 2: Configurar Apache para HTTP (y HTTPs como opcional, aunque no se necesita)
Ahora creamos el VirtualHost en Apache2, que escuche en el puerto 18081 (HTTP), mostrando la página de advertencia (puede usar los puertos y path de su preferencia):
<VirtualHost *:18081>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/proxymon/warning
ServerName captive.portal
ServerAlias *
<Directory /var/www/proxymon/warning>
DirectoryIndex warning.html
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
# Windows (NCSI - MSFT Connect Test)
Alias /ncsi.txt /var/www/proxymon/warning/warning.html
Alias /connecttest.txt /var/www/proxymon/warning/warning.html
Alias /redirect /var/www/proxymon/warning/warning.html
Alias /msftconnecttest/redir.htm /var/www/proxymon/warning/warning.html
Alias /ncsi/redirect /var/www/proxymon/warning/warning.html
# Microsoft Edge (Chromium)
Alias /captiveportal/generate_204 /var/www/proxymon/warning/warning.html
Alias /edge-captiveportal/generate_204 /var/www/proxymon/warning/warning.html
# Android (Google Captive Portal Check)
Alias /generate_204 /var/www/proxymon/warning/warning.html
Alias /gen_204 /var/www/proxymon/warning/warning.html
Alias /mobile/status.php /var/www/proxymon/warning/warning.html
# Apple iOS - macOS
Alias /hotspot-detect.html /var/www/proxymon/warning/warning.html
Alias /library/test/success.html /var/www/proxymon/warning/warning.html
Alias /success.txt /var/www/proxymon/warning/warning.html
Alias /hotspot.txt /var/www/proxymon/warning/warning.html
# Firefox Captive Portal
Alias /captive-portal-success.html /var/www/proxymon/warning/warning.html
# GNOME / KDE (Linux)
Alias /nmcheck.txt /var/www/proxymon/warning/warning.html
Alias /check_network_status.txt /var/www/proxymon/warning/warning.html
Alias /conncheck.html /var/www/proxymon/warning/warning.html
Alias /check_network.txt /var/www/proxymon/warning/warning.html
# Extras
Alias /favicon.ico /var/www/proxymon/warning/warning.html
Alias /success.html /var/www/proxymon/warning/warning.html
Alias /success /var/www/proxymon/warning/warning.html
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/warning\.html$
RewriteCond %{REQUEST_URI} !^/$
RewriteRule ^.*$ /warning.html [L]
SetEnvIf Remote_Addr "127\.0\.0\.1" dontlog
SetEnvIf User-Agent ".*internal dummy connection.*" dontlog
ErrorLog ${APACHE_LOG_DIR}/warning_error.log
CustomLog ${APACHE_LOG_DIR}/warning_access.log combined env=!dontlog
</VirtualHost>
Paso 3: Habilitar los puertos, certificado y módulos necesarios
Ahora levantamos el puertos a escuchar en ports.conf si no está ya definido.
Una vez tengamos lista y funcionando la página que le vamos a mostrar al cliente, pasamos a configurar ipset/iptables. Ejemplo:
Importante:- Hemos usado reglas con "-I" (insert, para que se inserten al principio de iptables en el órden propuesto) asumiendo que tiene otras reglas iptables. Caso contrario, cambie "-I" por "-A".
- Sepa que estas reglas harán que la aparición del portal demore mucho tiempo. No las cambie a REJECT (
--reject-with tcp-reset,--reject-with icmp-port-unreachable, etc.) para acelerar el proceso, ya que no abrirá el portal. Sea paciente (en ocasiones hay que cerrar el navegador y volverlo a abrir). - Este script no incluye reglas por defecto, que debería considerar antes de ejecutarlo, tales como MASQUERADE, GLOBAL POLICIES, LOOPBACK, KERNEL RULES, NAT (FORWARDING), entre otras. Para mayor información, puede consultar tutoriales de reglas básicas de iptables o nuestro ejemplo en la cabecera de nuestro script.
#!/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"
### 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
# NAT
sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1
# Load IPs to bandata
all_bans=$(cat "$block_list_day" "$block_list_week" "$block_list_month" | $reorganize | uniq)
if [ -n "$all_bans" ]; then
for ip in $all_bans; do
ipset -exist add bandata "$ip"
done
echo ""
echo "FINAL BAN SUMMARY:"
ipset list bandata 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | $reorganize | while read ip; do
# Check which category this IP belongs to
if grep -qxF "$ip" "$block_list_day" 2>/dev/null; then
cat_type="DAILY"
elif grep -qxF "$ip" "$block_list_week" 2>/dev/null; then
cat_type="WEEKLY"
elif grep -qxF "$ip" "$block_list_month" 2>/dev/null; then
cat_type="MONTHLY"
else
cat_type="UNKNOWN"
fi
echo " $ip [$cat_type]"
done
else
echo "There are no IPs in bandata"
fi
echo ""
echo "Applying Iptables Rules..."
# Create dedicated chains if they don't exist
iptables -N BANDATA_FWD 2>/dev/null
iptables -N BANDATA_IN 2>/dev/null
# Jump into dedicated chains from FORWARD/INPUT position 1
iptables -C FORWARD -i "$lan" -j BANDATA_FWD 2>/dev/null || \
iptables -I FORWARD 1 -i "$lan" -j BANDATA_FWD
iptables -C INPUT -i "$lan" -j BANDATA_IN 2>/dev/null || \
iptables -I INPUT 1 -i "$lan" -j BANDATA_IN
# Populate BANDATA_FWD (order matters within this chain only)
iptables -C BANDATA_FWD -m set --match-set bandata src -p udp --dport 53 -j ACCEPT 2>/dev/null || \
iptables -A BANDATA_FWD -m set --match-set bandata src -p udp --dport 53 -j ACCEPT
iptables -C BANDATA_FWD -m set --match-set bandata src -p tcp --dport 80 -j ACCEPT 2>/dev/null || \
iptables -A BANDATA_FWD -m set --match-set bandata src -p tcp --dport 80 -j ACCEPT
iptables -C BANDATA_FWD -m set --match-set bandata src -j DROP 2>/dev/null || \
iptables -A BANDATA_FWD -m set --match-set bandata src -j DROP
# Populate BANDATA_IN
iptables -C BANDATA_IN -m set --match-set bandata src -p tcp --dport 18081 -j ACCEPT 2>/dev/null || \
iptables -A BANDATA_IN -m set --match-set bandata src -p tcp --dport 18081 -j ACCEPT
iptables -C BANDATA_IN -m set --match-set bandata src -j DROP 2>/dev/null || \
iptables -A BANDATA_IN -m set --match-set bandata src -j DROP
# NAT redirect (PREROUTING is unaffected by the chain restructure)
iptables -t nat -C PREROUTING -i "$lan" -m set --match-set bandata src -p tcp --dport 80 -j REDIRECT --to-port 18081 2>/dev/null || \
iptables -t nat -I PREROUTING 1 -i "$lan" -m set --match-set bandata src -p tcp --dport 80 -j REDIRECT --to-port 18081
echo "Done"
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 Captive Portal Detection (CPD) 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 es perfecta y puede no funcionar en entornos muy controlados, sin embargo, 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 Proxy Monitor, donde implementamos esta técnica bajo el nombre bandata.

Post a Comment