7.2.2.2.9. Multiplexación

Los puertos 80 y 443 son puertos a los que comúnmente todas las redes, por muy restrictivas que sean, dejan salir ya que de lo contrario ni siquiera se permitiría la navegación en ellas. Por esto motivo, puede darse el caso de que, para asegurarnos el acceso, tengamos interés es que nuestros servicios SSH o VPN sean accesibles a través de ellos, además del acceso al propio ;program:nginx.

Cómo multiplexemos dependerá del grado de evasión que deseemos:

  1. A través del puerto 80 podemos dar acceso, además de al servidor web, a SSH y VPN usando Websockets gracias a wstunnel. Con esta técnica puede llegarse a saber que tráfico se está enviando, pero, en principio, forma parte del estándar HTML5, usar Websockets para construir cualquier aplicación bidireccional, así que es probable que un proxy web no nos pusiera reparos.

  2. A través del puerto 443 tenemos varias posibilidades:

    1. Enviar SSH y VPN sin encapsular con SSL, lo cual hace distinguibles los tres protocolos y, en consecuencia, detectables por un proxy intermedio.

    2. Encapsular los tres con SSL y:

      • O distinguirlos gracias al SNI.

      • O desencapsularlos y distinguirlos después.

      Esta solución es indetectable.

    3. Como por el puerto 80, usar Websockets para tunelizar SSH y VPN. Esta solución también implica que se encapsulen toso los tráficos con SSL y, por tanto, la solución es también indetectable.

Un cuadro resumen con las alternativas posibles es el siguiente:

Puerto

Técnica

Detectable

Penalización

Herramientas

80

Websockets

Mínima

nginx+wstunnel

443

En crudo

Ninguna

sslh ó haproxy

SNI

No

Mucha

nginx ó haproxy

SSL

No

Mucha

haproxy ó nginx[1]

Websockets

Mucha

nginx+wstunnel

Advertencia

Si la VPN es Wireguard, que utiliza UDP, sólo es posible usar Websockets.

7.2.2.2.9.1. En crudo

nginx tiene dos limitaciones para discriminar tráfico en crudo, esto es, tráfico de distinto tipo dirigido al puerto 443:

  • Sólo es capaz de distinguir tráfico SSL, de aquel que no lo es. Por tanto, sólo podríamos añadir un tráfico extra al HTTPs.

  • Puede analizarse el protocolo (con $ssl_preread_protocol) sólo a partir de la versión 1.15.2 (o sea, no hasta Bullseye).

Ver también

El sitio oficial de nginx incluye un artículo que explica la técnica.

Para usar esta estrategia es más conveniente usar sslh o haproxy si se piensa combinar con otras.

7.2.2.2.9.2. Websockets

La solución más sencilla y, en principio, bastante eficaz es utilizar wstunnel y facilitar la conexión de SSH y VPN con Websockets. Para ello, debemos tenerlo arrancado en un puerto de la interfaz local, por ejemplo[2]:

# wstunnel -v --server ws://127.0.0.1:8080

Advertencia

Al no restringir con --restrictHost a ningún servicio, el túnel podrá usarlo cualquiera para tunelizar cualquier aplicación situada en cualquier otra máquina. Si por el contrario restringimos, no podremos usar la instancia para tunelizar dos o más servicios.

y preparar nginx para permitir el protocolo. Esto implica definir el fichero snippets/websocket.conf:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $x_forwarded_proto;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;

y una configuración del sitio ws.example.net, que será el que usemos en el cliente:

map $http_upgrade $connection_upgrade {
   default upgrade;
   '' close;
}

map $https $x_forwarded_proto {
   default http;
   on      https;
}

server {
   listen 80;
   listen 443 ssl;

   server_name ws.example.net;

   include snippets/snakeoil.conf;

   location / {
      proxy_pass http://127.0.0.1:8080;
      include snippets/websocket.conf;
   }
}

En la configuración aceptamos tanto conexiones cifradas (para lo cual en el cliente deberíamos usar el protocolo wss) como conexiones sin cifrar. En ambos casos, la parte servidor de wstunnel escucharía usando el protocolo ws, y es nginx el que se encarga del cifrado.

Nota

Si la conexión se hubiera identificado con una ruta, no habría más que haber cambiado la localización:

location ^~ /wireguard/ {
   proxy_pass http://127.0.0.1:8080;
   include snippets/websocket.conf;
}

7.2.2.2.9.3. SSL

Una alternativa a Websocket es cifrar el tráfico en el cliente (UDP por supuesto) con SSL y enviarlo al puerto 443 del servidor. Usado esta técnica, el tráfico es indistinguible de HTTPs y, en consecuencia, pasará cualquier restricción. Para discriminar en el servidor los tráficos hay dos alternativas:

  • Usar SNI y hacerlo reconocible, precisamente, por el nombre. Por ejemplo, el tráfico SSH puede ir dirigido a ssh.example.net, el VPN a vpn.example.net, y el HTTPs a cualquier otro nombre.

  • Descifrar el tráfico primero y analizar el tráfico, ya descifrado. Para esta estrategia deberíamos usar un proxy como haproxy.

Aunque la primera alternativa es posible con nginx, es necesario incluir un directiva proxy_timeout que indica el tiempo que se mantiene abierta la conexión sin que se envíen ni reciban datos. Este tiemmpo debería ser completamente distinto para conexiones HTTPs (de pocos segundos) que para SSH en que puede haber periodos muertos de tiempo. Desgraciadamente, como no puede usarse una variable, es imposible hacer una configuración adecuada. En cualquier caso, se relata la configuración.

# Añadido a nginx.conf

stream {
   map $ssl_server_name $backend {
      "ssh.example.net"    127.1.1.1:22;
      default              127.1.1.1:80;
   }

   server {
      listen 192.168.0.13:443 ssl;

      ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

      include "snippets/snakeoil.conf";

      proxy_connect_timeout 2s;
      proxy_timeout 5s;  # Este es el problema

      proxy_pass $backend;
   }
}

Y en 127.1.1.1:80 podría colocarse el sitio web.

Advertencia

Evite esta configuración: está incompleta (nginx debería ejecutarse, además, de modo transparente) y la temporización es inaceptable. Para implementar esta solución es mejor echar mano de haproxy.

Notas al pie