El protocolo
TELNET (TCP, puerto 23) permite utilizar una
máquina como terminal virtual de otra a través de la red, de forma que se crea
un canal virtual de comunicaciones similar - pero mucho más inseguro - a
utilizar una terminal físicamente conectada a un servidor; la idea es sencilla:
estamos accediendo remotamente en modo texto a un equipo - en principio potente
- igual que si estuviéramos utilizando su consola o una de sus terminales
físicas, lo que nos permite aprovechar toda su potencia de cálculo si necesidad
de desplazarnos hasta la ubicación de ese servidor, sino trabajando cómodamente
desde nuestro propio equipo.
TELNET es el clásico
servicio que hasta hace unos años no se solía deshabilitar nunca: no es habitual
adquirir una potente máquina corriendo Unix y permitir que sólo se trabaje en
ella desde su consola; lo más normal es que este servicio esté disponible para
que los usuarios puedan trabajar remotamente, al menos desde un conjunto de
máquinas determinado. Evidentemente, reducir al mínimo imprescindible el
conjunto de sistemas desde donde es posible la conexión es una primera medida de
seguridad; no obstante, no suele ser suficiente: recordemos que
TELNET no utiliza ningún tipo de cifrado, por lo que todo el
tráfico entre equipos se realiza en texto claro. Cualquier atacante con un
analizador de red (o un vulgar sniffer) puede capturar el login y
el password utilizados en una conexión; el sniffing siempre es
peligroso, pero más aún en sesiones TELNET en las que
transmitimos nombres de usuarios y contraseñas: estamos otorgando a cualquiera
que lea esos datos un acceso total a la máquina destino, bajo nuestra identidad.
Por tanto, es muy recomendable no utilizar TELNET para
conexiones remotas, sino sustituirlo por aplicaciones equivalentes pero que
utilicen cifrado para la transmisión de datos: SSH o
SSL-Telnet son las más comunes. En estos casos necesitamos además de la
parte cliente en nuestro equipo, la parte servidora en la máquina remota
escuchando en un puerto determinado.
Aparte del problema de los
atacantes esnifando claves, los demonios telnetd han sido también una
fuente clásica de problemas de programación (se puede encontrar un excelente
repaso a algunos de ellos en el capítulo 29 de [Ano97]);
básicamente, cualquier versión de este demonio que no esté actualizada es una
potencial fuente de problemas, por lo que conviene conseguir la última versión
de telnetd para nuestro Unix particular, especialmente si aún tenemos
una versión anterior a 1997. Otros problemas, como la posibilidad de que un
atacante consiga recuperar una sesión que no ha sido cerrada correctamente, el
uso de telnet para determinar qué puertos de un host están
abiertos, o la utilización del servicio telnet (junto a otros, como
FTP) para averiguar el clon de Unix concreto (versión de
kernel incluida) que un servidor utiliza, también han hecho famosa la
inseguridad de este servicio.
Antes hemos hablado de la configuración de
un entorno restringido para usuarios FTP invitados, que accedían
mediante su login y su contraseña pero que no veían la totalidad del
sistema de ficheros de nuestra máquina. Es posible - aunque ni de lejos tan
habitual - hacer algo parecido con ciertos usuarios interactivos, usuarios que
conectarán al sistema mediante telnet utilizando también su login
y su password, pero que no verán el sistema de ficheros completo: sólo la
parte que a nosotros nos interese (en principio).
Para que un usuario
acceda mediante telnet a un entorno restringido con chroot()
necesitamos en primer lugar un entorno parecido al que hemos visto antes: a
partir de su directorio $HOME, una serie de subdirectorios bin/,
lib/, etc/...Dentro de este último existirá al menos un
fichero group y otro passwd (igual que sucedía antes, no se
usan con propósitos de autenticación, por lo que no es necesario - ni
recomendable - que existan claves reales en ninguno de ellos). En el directorio
bin/ incluiremos los ejecutables que queremos que nuestro usuario pueda
ejecutar, y en lib/ (o usr/lib/) las librerías que necesiten;
si usamos el shellscript anterior - de nuevo, con alguna pequeña
modificación - para crear este entorno, en la variable $PROGS podemos
definir tales ejecutables para que automáticamente se copien junto a las
librerías necesarias en el directorio correspondiente:PROGS="/bin/ls /bin/sh"
Finalmente, en el archivo /etc/passwd real hemos de definir un
shell para el usuario como el siguiente:luisa:~# cat /home/toni/prog/shell.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#define SHELL "/bin/sh"
int main(){
struct passwd *entry=(struct passwd *)malloc(sizeof(struct passwd));
char *const ARGS[2]={SHELL,NULL};
while((entry=getpwent())->pw_uid!=getuid());
endpwent();
if(chdir(entry->pw_dir)<0) perror("chdir()");
if(chroot(entry->pw_dir)<0) perror("chroot()");
if(setuid(getuid())<0) perror("setuid()");
if(execvp(SHELL,ARGS)<0) perror("execvp()");
// No alcanzado
return(0);
}
luisa:~#
Este código, convenientemente compilado, será el shell real del
usuario restringido; como vemos, obtiene el directorio $HOME del mismo,
hace un chroot() a él, y ejecuta en este entorno el shell
secundario (bin/sh, que realmente será $HOME/bin/sh). Para que
el chroot() sea correcto el programa ha de estar setuidado bajo
la identidad de root (sólo el superusuario puede realizar esta
llamada), con los riesgos que esto implica; al contrario de lo que diría Knuth,
yo sólo defiendo que el código anterior funciona, no que sea correcto...o seguro
:)
Si tenemos que crear un entorno como este para usuarios interactivos
hemos de tener en cuenta ciertas medidas de seguridad relativas a los
ejecutables que situemos - o que permitamos situar - en dicho entorno. Para
empezar, hemos de evitar a toda costa los ejecutables setuidados, así
como las llamadas mknod(), chmod() o la propia
chroot(); además, no debe ser posible obtener privilegios de
administrador dentro del entorno restringido, ya que para el root estas
restricciones pierden su sentido: no tenemos más que pensar que si un usuario
con privilegios de root dentro del entorno es capaz de generar un
dispositivo que represente un disco duro, con algo tan sencillo como la utilidad
mknod, automáticamente accederá a la totalidad de ese disco, olvidando
ya el chroot() y la potencial protección que pueda ofrecernos. Algo
similar ocurre con la memoria del sistema, ciertos dispositivos físicos, o
estructuras de datos del núcleo: si esto es accesible desde el entorno
restringido, es muy probable que nuestra seguridad se vea rota tarde o temprano
(más bien temprano). Tampoco es aconsejable permitir la ejecución de
compiladores de C o de intérpretes de Perl.
Como hemos dicho, este tipo
de entornos es mucho menos habitual que los de FTP, aparte de
bastante más peligrosos. Una tarea tan habitual como cambiar la contraseña no es
posible - al menos de forma trivial - en este entorno (aunque podríamos
modificar el código anterior para que se ofrezca al usuario esta posibilidad
antes de situarlo en el entorno restringido). >Y que sucede si necesitamos
que el usuario acceda no a un sólo directorio, sino a dos? Las soluciones - al
menos las seguras - no son inmediatas.
© 2002 Antonio Villalón Huerta