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