miércoles, 28 de mayo de 2008

Creación de un CAPTCHA (o test de turing)

A esta altura, la mayoría de nosotros nos hemos encontrado en situaciones que cuando ingresamos información en un formulario para registrarnos en algún servicio -como por ejemplo crear una casilla de correo o descargar un archivo de servicios como rapidshare- el sitio nos pide que escribamos el texto de una imagen que vemos en pantalla.


Estos mecanismos de validación permiten -en teoría- distinguir entre humanos y computadores, y así prevenir abusos como por ejemplo la publicación de spams en los comentarios de blogs y la creación masiva de casillas de correo para spamming.

En los últimos años han proliferado diversos mecanismos de CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart), como por ejemplo resolver un problema matemático, indicar el animal que ves es una fotografía o -el más popular- identificar el texto que ves en una imágen.

Una de las desventajas de los CAPTCHAs es que tienen serias deficiencias de accesibilidad. Además existen algunos casos extremadamente molestos para el usuario como el siguiente ejemplo utilizado en Rapidshare:

Debido a que la mayoria de los desarrollos de software en que he participado son sistemas cerrados o que no son un potencial blanco de ataques de spamming, no me he visto en la necesidad de implementar mecanismos de CAPTCHA, sin embargo, haciendo algunos experimentos he llegado a implementar un sistema sencillo en PHP que les describo a continuación.


Implementación de CAPTCHA en PHP
En este ejercicio voy a generar un CAPTCHA que le presente al usuario una imagen con letras que el usuario deberá escribir en un formulario. Debido al uso de imágenes, me apoyaré en la biblioteca GD, por lo que para estos ejemplos PHP debe tener soporte para GD.

Mi objetivo es generar un captcha que nos brinde un nivel de seguridad mínima y en ningún caso desarrollar un sistema infalible. Detrás de la generación (y decodificación) de los captchas existen teorías de procesamiento de imágenes y reconocimiento de caracteres que no son parte de este artículo.

El modelo general consiste en construir un script PHP que retorne una imagen con un texto aleatorio. El texto original será almacenado en una variable de sesión. Al procesar el formulario se deberá comparar el texto ingresado por el usuario con el texto almacenado en la variable de sesión.


Versión 1
Mi primera versión consiste en una implementación básica sólo para desarrollar la funcionalidad mínima del sistema:
<?php

// Alto y ancho de la imagen
$width = 250;
$height = 60;
// Tipografia
$font = "arial.ttf";
// Nombre de la variable de sesion
$session_var = "captcha";




// Genero la imagen
$im = imagecreatetruecolor($width, $height);
$bg_color = imagecolorallocate($im, 255, 255, 255);
$fg_color = imagecolorallocate($im, 0, 0, 0);
imagefilledrectangle($im, 0, 0, $width, $height, $bg_color);

// Genero el texto
$text = "secret";
imagettftext($im, 20, 0, 11, 21, $fg_color, $font, $text);

// Guardo el texto en sesión
session_start();
$_SESSION[$session_var] = $text;


// Genero la imagen
header("Content-type: image/png");
imagepng($im);

// Limpieza
imagedestroy($im);

?>

Este script genera una imagen de 250x60 pixeles, con color de fondo blango y texto arial negro. El texto utilizado en la imagen se guarda en la variable de sesión captcha. El resultado de este script es la siguiente imagen:

En el script que procesa el formulario, basta comparar el valor de $_SESSION['captcha'] con el texto ingresado en el formulario para confirmar que el captcha haya sido ingresado satisfactoriamente. Para reducir la tasa de error, es posible quitar los eventuales espacios en blanco y convertir el texto a minúculas para hacer una comparación case-insensitive.

Ahora que contamos con el código base podemos continuar mejorando nuestro script.


Versión 2
Las mejoras que introduciremos en esta versión son las siguientes:
  1. Generación de un texto aleatorio.
  2. Generar texto con distintas tipografías.
  3. Agregar transformaciones simples al texto.
  4. Aumentar el tamaño y darle un color más amigable.

La generación de un texto aleatorio puede ser algo tan sencillo como extraer los primeros 6 caracteres de un valor MD5 como el siguiente: substr(md5(uniqid(),0,6) el cual genera un string con números y letras entre la "a" y la "f".

Sin embargo construiremos algo más elaborado donde podamos controlar los caracteres a generar e intentaremos que el texto sea medianamente pronunciable con el fin de ser más amigable con el usuario. Una manera simple de generar texto pronunciable es asegurándonos de tener suficientes vocales y quitando algunas letras poco usadas en el español como la "x" y la "k". Definí arbitrariamente la longitud del texto a 6 caracteres lo cual minimiza la generación de palabras mal vistas como "pene", "puta" o "malo" y da una cantidad de combinaciones de letras apropiada.

Para ello elaboré una función que retorna textos aleatorios intercalando una consonante y una vocal:
<?php

function getCaptchaText($length = 6) {
$consonants = "bcdfghjlmnpqrstvwyz";
$vocals = "aeiou";

$text = "";
$imax = $length/2;
for ($i=0; $i<$imax; $i++) {
$text .= substr($consonants, mt_rand(0, 18), 1);
$text .= substr($vocals, mt_rand(0, 4), 1);
}
return substr($text, 0, $length);
}

?>
Por lo tanto, en el script original reemplazamos:
$text = "secret";
por:
$text = getCaptchaText(6);
Con este cambio ahora generamos captchas como los siguientes:



Para hacer el captcha más complejo vamos a generar el texto con distintas tipografías, agregarle rotación independiente a cada letra y juntar (o condensar) las letras para que se produzca un traslape que hará más dificil su decodificación.

Extraje algunos archivos .ttf desde mi directorio C:\WINDOWS\Fonts y luego reemplacé:
$font = "arial.ttf";
por:
$fonts = array('timesbi.ttf', 'calibriz.ttf', 'cambriaz.ttf');
$font = $fonts[mt_rand(0, sizeof($fonts)-1)];
Aumentamos el tamaño, le agregamos color y ahora obtenemos las siguientes imagenes donde podemos apreciar el cambio de tipografía en cada ejecución:

Hasta este punto hemos generado un captcha básico que es extremadamente fácil de decodificar.

Para hacer la imágen mas compleja vamos a generar letra por letra. Cada letra tendrá una rotación distinta y acercaremos las letras para lograr un traslape.

Modificamos el código:
imagettftext($im, 20, 0, 11, 21, $fg_color, $font, $text);
por:
$x = 3;
for ($i=0; $i<=6; $i++) {
$angulo = rand(-12, 12);
$coords = imagettftext($im, 38, $angulo, $x, 47, $fg_color, $font, substr($text, $i, 1));
$x += ($coords[2]-$x) - 4;
}

Este código va generando letra por letra y en cada iteración va aumentando la coordenada X restandole algunos pixeles para lograr que el texto se traslape. La rotación de cada letra tendrá un valor comprendido entre el intervalo [-12,12].

Se deberá buscar el valor exacto de traslape con el fin de hacer difícil su decodificación, pero permitiendo que el texto sea legible. Un refinamiento de la generación de estos textos podría ser que cada letra se genere con una tipografía distinta.

Al aplicar estos cambios obtenemos las siguientes imagenes:


Una vez que juntemos todos estos cambios obtendremos el siguiente código:

<php


// Alto y ancho de la imagen
$width = 250;
$height = 65;
// Tipografia
$fonts = array('timesbi.ttf', 'calibriz.ttf', 'cambriaz.ttf');
// Nombre de la variable de sesion
$session_var = "captcha";
// Condensacion entre cada letra
$condensacion = 4;


// Genero la imagen
$im = imagecreatetruecolor($width, $height);
$bg_color = imagecolorallocate($im, 255, 255, 255);
$fg_color = imagecolorallocate($im, 33, 67, 165);
imagefilledrectangle($im, 0, 0, $width, $height, $bg_color);

// Genero el texto
$font = $fonts[mt_rand(0, sizeof($fonts)-1)];
$text = getCaptchaText(6);
$x = 3;
for ($i=0; $i<=6; $i++) {
$angulo = rand(-12, 12);
$coords = imagettftext($im, 38, $angulo, $x, 47, $fg_color, $font, substr($text, $i, 1));
$x += ($coords[2]-$x)-$condensacion;
}

// Guardo el texto en sesión
session_start();
$_SESSION[$session_var] = $text;

// Genero la imagen
header("Content-type: image/png");
imagepng($im);

// Limpieza
imagedestroy($im);

/**
* Retorna un texto aleatorio
*
* @param int $length Longitud del texto
* @return string Texto aleatorio
*/
function getCaptchaText($length = 6) {
$consonants = "bcdfghjlmnpqrstvwyz";
$vocals = "aeiou";
$text = "";
$imax = $length/2;
for ($i=0; $i<$imax; $i++) {
$text .= substr($consonants, mt_rand(0, 18), 1);
$text .= substr($vocals, mt_rand(0, 4), 1);
}
return substr($text, 0, $length);
}

?>



Versión 3
Para dificultar aún más la decodificación le aplicaremos a la imagen un filtro "wave" que genere ondulaciones al texto.

Adicionalmente para aumentar el "ruido" de la imagen la generaré como JPEG en vez de PNG.

Existen otras técnicas bastante eficientes para la mayoría de los casos que consiste en introducir imágenes de fondo y ruido a la imágen como por ejemplo líneas aleatorias o pintar pixeles al alzar. Sin embargo dichas técnicas disminuyen en gran medida la estética del captcha por lo que las descartaré para estos ejemplos.

Para la generación de ondas en la imágen, existen excelentes biblitecas gráficas que permiten hacer esto como por ejemplo ImageMagick. Sin embargo, para continuar con la simplicidad del script utilizaremos una combinación de funciones de GD y funciones trigonométricas. La función de a continuación está basada en un ejemplo publicado en el manual de PHP:

/**
* Filtro wave
*
* Obtenido desde ejemplo publicado en el manual de PHP:
* http://www.php.net/manual/en/function.imagecopy.php#72393
*
*/
function wave_region($img, $x, $y, $width, $height,$amplitude = 4.5,$period = 30) {
// Make a copy of the image twice the size
$mult = 2;
$img2 = imagecreatetruecolor($width * $mult, $height * $mult);
imagecopyresampled ($img2,$img,0,0,$x,$y,$width * $mult,$height * $mult,$width, $height);

// Wave it
$k = rand(-60,60);
for ($i = 0;$i < ($width * $mult);$i += 2) {
imagecopy($img2,$img2,
$x + $i - 2,$y + sin($k+$i / $period) * $amplitude,
$x + $i,$y,
2,($height * $mult));
}

// Resample it down again
imagecopyresampled ($img,$img2,$x,$y,0,0,$width, $height,$width * $mult,$height * $mult);
imagedestroy($img2);
}


Esta función la llamaremos inmediatamente después de generar el texto con los siguientes argumentos:
// Aplico filtros
wave_region($im, 0, 0, $width, $height, mt_rand(7, 22), mt_rand(25,40));
Una vez que apliquemos este cambio se generarán las siguientes imágenes de referencia:


Al juntar estos últimos cambios y ajustar el tamaño definitivo de la imágen obtendremos el siguiente código:

<php



// Alto y ancho de la imagen
$width = 180;
$height = 65;
// Tipografia
$fonts = array('timesbi.ttf', 'calibriz.ttf', 'cambriaz.ttf');
// Nombre de la variable de sesion
$session_var = "captcha";
// Condensacion entre cada letra
$condensacion = 4;



// Genero la imagen
$im = imagecreatetruecolor($width, $height);
$bg_color = imagecolorallocate($im, 255, 255, 255);
$fg_color = imagecolorallocate($im, 33, 67, 165);
imagefilledrectangle($im, 0, 0, $width, $height, $bg_color);

// Genero el texto
$font = $fonts[mt_rand(0, sizeof($fonts)-1)];
$text = getCaptchaText(6);

//$text = "captcha"; $font="cambriaz.ttf"; $angulo = 0;

$x = 3;
for ($i=0; $i<=6; $i++) {
$angulo = rand(-12, 12);
$coords = imagettftext($im, 38, $angulo, $x, 47, $fg_color, $font, substr($text, $i, 1));
$x += ($coords[2]-$x)-$condensacion;
}

// Aplico filtros
wave_region($im, 0, 0, $width, $height, mt_rand(7, 22), mt_rand(25,40));

// Guardo el texto en sesión
session_start();
$_SESSION[$session_var] = $text;

// Genero la imagen
header("Content-type: image/jpeg");
imagejpeg($im);

// Limpieza
imagedestroy($im);

/**
* Retorna un texto aleatorio
*
* @param int $length Longitud del texto
* @return string Texto aleatorio
*/
function getCaptchaText($length = 6) {
$consonants = "bcdfghjlmnpqrstvwyz";
$vocals = "aeiou";
$text = "";
$imax = $length/2;
for ($i=0; $i<$imax; $i++) {
$text .= substr($consonants, mt_rand(0, 18), 1);
$text .= substr($vocals, mt_rand(0, 4), 1);
}
return substr($text, 0, $length);
}

/**
* Filtro wave
*
* Obtenido desde ejemplo publicado en el manual de PHP:
* http://www.php.net/manual/en/function.imagecopy.php#72393
*
*/
function wave_region($img, $x, $y, $width, $height,$amplitude = 4.5,$period = 30) {
// Make a copy of the image twice the size
$mult = 2;
$img2 = imagecreatetruecolor($width * $mult, $height * $mult);
imagecopyresampled ($img2,$img,0,0,$x,$y,$width * $mult,$height * $mult,$width, $height);

// Wave it
$k = rand(-60,60);
for ($i = 0;$i < ($width * $mult);$i += 2) {
imagecopy($img2,$img2,
$x + $i - 2,$y + sin($k+$i / $period) * $amplitude,
$x + $i,$y,
2,($height * $mult));
}

// Resample it down again
imagecopyresampled ($img,$img2,$x,$y,0,0,$width, $height,$width * $mult,$height * $mult);
imagedestroy($img2);
}

?>

La evolución del captcha construido fue la siguiente:

Como mejoras a este captcha se podría realizar una seleccón mas exuastiva de las tipografías utilizadas, se le podría añadir una imagen de fondo aleatoria, lineas aleatorias o ruido.

En el siguiente artículo vamos a optimizar el código y aumentar la complejidad del captcha sin perder su estética (es decir no le incorporaré lineas aleatorias, imágenes de fondo, etc.) de una manera similar a los captchas utilizados por Google.

Actualizado el día domingo 1 de junio de 2008.

miércoles, 21 de mayo de 2008

Protección básica contra Cross-site scripting (XSS)

Los ataques Cross-site scripting (XSS) o de HTML injection pueden ser prevenidos de manera bastante simple tomando algunas medidas de seguridad básicas que debemos tener siempre presentes.

Este tipo de ataque consiste en incrustar código HTML en un sitio web con el objetivo de poder ejecutar código Javascript arbitraro o incluir código HTML con el fin de -por ejemplo- realizar ataques de phishing.

La preveción de este tipo de ataque se realiza filtrando la infomación que se ingresa y publica en nuestro sitio, por lo que tenemos dos puntos de control:
  • Filtrar datos ingresados
  • Escapar datos publicados en la WEB

Filtrar datos ingresados
La media de prevención más sencilla es impedir que los usuarios del sitio puedan publicar información, como por ejemplo cuando se hacen comentarios en un blog o en formularos de contacto.

En caso que el sitio deba permitir el ingreso de información, se debe definir si se aceptará código HTML. En caso que se opte por no aceptar código HTML será necesario filtrar todo el contenido recibido desde formularios para quitar eventuales tags HTML que ingrese el usuario. Esto se puede realizar con la siguiente función:
$comentario = strip_tags($comentario);
La función strip_tags() no realiza validación de que el código HTML esté bien formado, por lo que potencialmente se podría perder información -mal- ingresada por el usuario. Se puede realizar una validación más apropiada por medio de tidy.

En caso que sea necesario permitir el ingreso de código HTML, se deberán restringir los tags HTML que serán permitidos. Usualmente se permiten los tags de texto básico como <p>, <br>, <i>, <b>, <strong>, <em>, <blockquote> y tags de listas como <ul>, <ol> y <li>. Especialmente se debe evitar la inclusión de tags HTML que permitan la inclusión de contenidos externos como por ejemplo <script>, <frame>, <iframe>, <object> y <a>.

El filtrado de los tags HTML permitidos se puede realizar con el siguiente comando:
$comentario = strip_tags($comentario, "<p><br><strong><em>");
Sin embargo, nuevamente la función strip_tags() puede presentar problemas porque los tags admitidos podrían contener atributos peligrosos como onload y onmouseover. Por lo que se debería recurrir a una combinación de tidy y expresiones regulares.

Las tareas de filtrado se pueden enfrentar de dos maneras: Manteniendo una lista negra de tags y atributos no permitidos o manteniendo una lista blanca con los tags y atributos permitidos. En vista de evitar problemas de seguridad, es más recomendable mantener una lista blanca que contenga los elementos admitidos ya que es mucho menos grave restringir el ingreso de un dato inofensivo (falso positivo) que permitir el ingreso de un dato potencialmente peligroso.


Escapar datos ingresados
Como segunda medida de contención se puede agregar validación antes de publicar información en la WEB.

Se debe considerar que la información que ingresa a nuestras bases de datos (y archivos de caché) es introducida por diversos medios, como por ejemplo WebServices, migraciones de datos o SQL Injection, por lo tanto aunque se mantenga absoluta seguridad en nuestros formularios WEB, siempre existe la posibilidad de contar con datos peligrosos. Debido a esto, seimpre es recomendable filtrar la información que se publica en nuestro sitio.

En primer lugar debemos distinguir si la información que publicamos puede contener HTML o no.

En general la gran mayoría de la información que se publica en la web -como por ejemplo nombres, fechas, títulos- no contiene código HTML. En estos casos la información a imprimir en una página web debe ser escapada para reemplazar los caracteres especiales por sus entidades respectivas, esto se realiza con el siguiente comando:
echo htmlspecialchars($nombre);
Esta función tiene la ventaja que imprime correctamente textos como "Barnes & Noble" y "A < B < C" que en caso de no ser escapados no se visualizarían correctamente.

En el caso de publicar contenidos con HTML, se debe aplicar la misma función de filtrado que se utiliza al momento de ingresar datos.

Con estas simples medidas se aumenta en gran medida el nivel de seguridad ante ataques XSS.

lunes, 5 de mayo de 2008

Javascript dentro de HTML

Para incrustar código Javascript dentro de HTML se debe utilizar la siguiente etiqueta HTML:
<script type="text/javascript">
// Aquí va el código...
</script>
Sin embargo, en la actualidad es mejor no utilizar Javascript incrustado sino que más bien utilizar archivos Javascript externos. Esto permite separar el contenido de la programación y además permite una navegación más rápida ya que se pueden guardar en cache los archivos .js externos. El código para referenciar archivos Javascript es el siguiente:
<script type="text/javascript" src="filename.js"></script>
El sentido común nos dice que las referencias a archivos Javascript debieran realizarse en el encabezado del documeno HTML, es decir, dentro de los tags HEAD.

Es mejor hacer lo contrario: por un tema de performance se recomienda -en la medida de lo posible- poner dichas referencias al final del documento HTML ya que esto acelera el proceso de rendering del documento HTML.

Graceful degradation y Unobtrusive Javascript
Cuando se construyen sitios que utilicen Javascript es importante tener en mente mantener la semántica del sitio web y ser conscientes que no todos nuestros visitantes tendrán Javascript habilitado, por ejemplo bots de los motores de búsqeda, usuarios que han deshabilitado javascript por motivos de seguridad, usuarios de algunos dispositivos móviles, microformatos, herramientas semánticas como Yahoo pipes, clientes en modo de sólo texto o -el problema más común- usuarios que visitan nuestro sitio con un navegador que es pacialmente incompatible con nuestro sitio ya sea porque es un navegador antiguo o una versión más moderna. Todos estos usuarios debieran poder navegar por nuestro sitio sin mayores problemas.

El concepto de Unobtrusive Javascript consiste en seguir las buenas prácticas en la programación de Javascript y poder separar el comportamiento (código javascript) del contenido del sitio, es decir que el código Javascript sea lo menos invasivo posible.

El concepto de Graceful degradation consiste en que un sitio web tenga la capacidad de continuar operando con clientes incompatibles proveyendo un nivel de servicios reducido en vez de que este no pueda operar.

Por ejemplo, a continuación se muestran algunos links que hacen uso de javascript de manera incorrecta:
<a href="javascript:link('products')">Productos</a>

<a href="javascript:submitForm('form1')">Enviar formulario</a>

<a href="products.html" onclick="check()">Productos</a>
El primer ejemplo utiliza un llamado a la función link(). Este código no funcionará en navegadores sin soporte a Javascript y hace que sea muy dificil que un robot pueda navegar por el sitio para indexarlo, además atenta contra la usabilidad como por ejemplo hacer clic con el botón central del mouse para abrir el link en una nueva pestaña no funcionará. Este es el caso de Falabella donde no puedo abrir las fichas de productos en distintas pestañas para poder compararlos.

El segundo ejemplo realiza el envío de un formulario mediante código Javascript cuando lo semánticamente correcto habría sido utilizar el elemento HTML <input type="submit"> y en caso de requerir validación del formulario agregar el evento onsubmit al formulario.

El tercer ejemplo ejecuta una función de validación al momento de hacer clic en el link mediante el atributo onclick.

El código correcto para estos 3 ejemplos sería:
<a href="/show/products">Productos</a>

<input type="submmit" id="submitForm" value="Enviar formulario"/>
<a href="products.html" id="linkProducts">Productos</a>
En el primer ejemplo simplemente se reemplazó el código javascript por un link. Se pueden utilizar técnicas como mod_rewrite de Apache para mantener links amigables. En el segundo y tercer caso se utiliza HTML estándar y se referencian los elementos mediante el atributo id mediante el cual podrán ser manipulados por javascript.

Finalmente, agregamos los event handler mediante el siguiente código Javascript:
document.getElementById('linkProducts').onclick = check;
document.getElementById('form1').onsubmit = checkForm;
(NOTA: estos son ejemplos Javascript simplificados que contienen errores de malas práctias como por ejemplo definir directamente los eventos en vez de utilizar una función para agregar eventos en cascada)

sábado, 3 de mayo de 2008

SSH Tunneling

Un problema común
Uno de los grandes problema con que lidio lidiaba en los servidores de producción, es que comúnmente tienen reglas de firewall estrictas que sólo permiten el tráfico TCP/IP por el puerto del servicio que están ejecutando (servidor web, base de datos, FTP, etc.) y el puerto de SSH (22) con fines de administración.

Además en la mayoría de los casos el acceso SSH está limitado sólo desde algunos de los equipos de la red local y en ningún caso permiten el acceso SSH desde Internet.

Esta situación hace muy difícil los procesos de administración, actualización y solución de contingencias ya que la única manera de acceder al servidor era ir físicamente al datacenter o intermediar telefónicamente con un operador que con suerte sabe ejecutar los comandos de shell cd y ls.

Otro problema que se nos puede presentar es la necesidad de encriptar conexiones de protocolos inseguros, como por ejemplo una conexión FTP.


La solución
Una solución para poder acceder a sistemas remotos es estableciendo un Tunnleing SSH, el cual consiste en redirigir (forward) las conexiones TCP dirigidas hacia un puerto específico hacia otro host por medio de la conexión encriptada de SSH. No soporta redirección de paquetes UDP.

En los siguientes ejemplos utilizaremos como ejemplo las siguientes direcciones IP:
  • Servidor (con acceso restringido): 1.1.1.1
  • Equipo de administración (tiene acceso al servidor): 1.1.1.2
  • Equipo local (sin acceso al servidor): 1.1.1.3
El tunneling SSH tiene dos modalidades de redirección: remote port forwarding que permite redirigir conexiones TCP desde el equipo remoto y local port forwarding que permite redirigir las conexiones TCP desde el equipo local.

En shell, la sintaxis tradicional para establecer una conexión SSH es la siguiente:
ssh user@host
La sintaxis para establecer un tunneling consiste en indicar adicionalmente:
  1. El tipo de redirección: "R" para remote port forwarding y "L" para local port forwarding.
  2. El puerto que se redireccionará.
  3. El host y puerto hacia el cual se redigirán los paquetes TCP
Quedando en:
ssh user@host -R port:host:hostport
ssh user@host -L port:host:hostport
En los siguientes ejemplos se utilizará para redirigir conecciones SSH, pero esta técnica sirve para redireccionar cualquier servicio basado en TCP.


Local port forwarding
Si quisieramos acceder por SSH al servidor 1.1.1.1 desde nuestro equipo local 1.1.1.3, el cual no tiene acceso directo, podríamos establecer inicialmente una conexión hacia el equipo de administración 1.1.1.2 y desde ahí establecer un port forwarding hacia el servidor 1.1.1.1 tal como se muestra en el siguiente esquema:


Para eso, desde nuestro equipo local 1.1.1.1 ejecutamos el siguiente comando:
ssh user@1.1.1.2 -L 2222:1.1.1.1:22
Una vez conectado al equipo remoto 1.1.1.2 todas las conecciones desde nuestro equipo local al puerto 2222 serán redirigidas al puerto 22 del servidor 1.1.1.1, con lo cual hemos logrado tener acceso al servidor remoto mediante el siguiente comando:
ssh user@localhost -p 2222
Incluso desde otro equipo que tenga acceso a 1.1.1.3 podrá conectarse al servidor 1.1.1.1 mediante SSH:
ssh user@1.1.1.3 -p 2222


Remote port forwarding
Otra modalidad para acceder al servidor 1.1.1.1 es establecer la conexión opuesta desde el equipo de administración 1.1.1.2 hacia nuestro equipo local 1.1.1.3 como se ilustra a continuación:


En este caso desde el equipo 1.1.1.2 se establecerá un remote port forwarding mediante el siguiente comando:
ssh user@1.1.1.3 -R 3333:1.1.1.1:22
Una vez conectado al nuestro equipo 1.1.1.3 todas las conecciones desde nuestro equipo local al puerto 3333 serán redirigidas al puerto 22 del servidor 1.1.1.1, con lo cual hemos logrado tener acceso al servidor remoto mediante el siguiente comando ejecutado desde 1.1.1.3:
ssh user@localhost -p 3333


SSH desde Windows
Las técnicas de tunneling también están disponibles para Windows, por ejemplo se puede utilizar el programa gratuito putty. La configuración de tunneling se realiza en la siguiente ventana de configuración:




Aplicaciones
Esta técnica nos permite acceder a casi cualquier servicio remoto, como por ejemplo la base de datos del backend o a un sitio web disponible sólo para la intranet.

Además como la conexión se realiza sobre SSH, todas las comunicaciones se realizan mediante un canal encriptado, lo que nos permite dar una mayor seguridad a conexiones vía FTP o a una base de datos.

La técnica de port forwarding es bastante flexible y poderosa, por ejemplo en algunas situcaciones he llegado a realizar hasta 3 tunnelings anidados para lograr alcanzar servidores que están extremadamente resguardados.