Member of The Internet Defense League Últimos cambios
Últimos Cambios
Blog personal: El hilo del laberinto Geocaching

CHROOT y Seguridad

Última Actualización: 31 de Julio de 1.998 - Viernes

Artículo escrito el 05/Jun/98 y publicado en
Linux Actual, Julio 98, número 3, páginas 75-76

Este artículo toma como base otro documento escrito por mí. En la revista se suprimió una tabla y numerosas referencias al sistema operativo Solaris. Este documento es mi texto íntegro.


CHROOT Y SEGURIDAD

La informática, y la programación en particular, están muy lejos todavía de convertirse en ciencia. De momento apenas llegan a arte. Escribir programas seguros y a prueba de bomba no es una tarea sencilla. Requiere pericia, experiencia y una buena dosis de sana paranoia. Analizaremos con detalle el problema de escribir programas seguros en nuestro próximo número.

Este mes vamos a centrarnos, sin embargo, algo más del día a día: confinamiento de procesos mediante CHROOT.

"Chroot" es una forma de confinar procesos a una determinada zona del árbol de directorios, impidiendo que dichos procesos accedan a servicios o documentos fuera de su entorno. Ello resulta muy útil, ya que si el programa falla, su impacto será mucho más localizado que si se le permite acceder a todo el disco duro. Esto nos protege muchísimo no sólo ante fallos de programación o problemas de funcionamiento, sino también, incluso, ante ataques y violaciones de seguridad. Resulta una práctica casi imprescindible en un entorno abierto en donde se ejecutan servicios conectados a internet, o cuando no estamos seguros del todo respecto a la fiabilidad de un programa que vamos a probar.

En casi todos los UNIX, el "chroot" está accesible a través de dos vías: la línea de comandos y la propia llamada al sistema. Antes de continuar es aconsejable que el lector haga un "man -S8 chroot" y un "man -S2 chroot". La sintaxis es muy sencilla y, aparentemente, su uso también.

Pero las apariencias engañan, y la vida no es siempre tan fácil. En este artículo intentaremos desentrañar algunos de los misterios y mitos que rodean el "chroot", sus usos y sus limitaciones.

"Chroot", sea como comando o como llamada al sistema, sólo puede ser invocado por el superusuario, lo cual es comprensible dado el tremendo poder que supone. El "chroot" cambia la raíz del sistema de ficheros, desde el punto de vista del proceso lanzado y de sus hijos, y los nuevos procesos no pueden ampliar sus privilegios, ya que un nuevo "chroot" sería relativo al actual. Es decir, estaría contenido en su interior.

Todos los ficheros que se abran tras un "chroot" son relativos al nuevo directorio raíz, que será el especificado en dicho comando. Es decir, a todos los efectos el directorio "barra", cabeza del nuevo árbol de archivos, será el directorio en cuestión. Por lo tanto sólo será posible acceder a ficheros y comandos shell contenidos en el nuevo entorno. Esta comprobación sólo se efectúa al abrir ficheros; un fichero en uso antes del "chroot" no se verá afectado. Esto resulta muy útil, por ejemplo, para que los logs de un servidor se almacenen fuera de su alcance. Como contrapartida, claro, hay que tener cuidado con no filtrar ningún fichero abierto innecesario al entorno "chroot".

Es muy importante recordar que no se puede acceder a ningún fichero externo al "chroot". Y no basta con copiar los ejecutables que vamos a usar. Muchas veces dichos ejecutables necesitan de librerías dinámicas, con lo que es preciso o bien compilar el código indicando enlazado estático, o bien replicar las librerías dinámicas en el entorno "chroot". No sirve crear un enlace simbólico, ya que en el "chroot" la referencia indicada no tiene sentido (normalmente se apuntará a sí misma). Se pueden o bien copiar las librerías necesarias o bien, más recomendable, crearles un enlace "hard" si estamos trabajando dentro de la misma partición. Lo mismo se aplica a posibles dispositivos bajo "/dev", y similares. Si necesitamos crearlos dentro podemos usar el comando "mknod".

Saber qué librerías y ficheros necesita un proceso determinado no es inmediato. Las librerías, bajo Solaris, se pueden comprobar con un simple comando "pldd", y en Linux se puede emplear la interfaz "map" bajo "/proc", y el comando "find" para buscar el fichero referenciado por ese "inode". Otra posibilidad, más sencilla, consiste en emplear el comando "ldd" sobre el propio fichero del ejecutable. Bajo Solaris se permite repetir el proceso con cada librería que se liste, ya que pueden contener referencias a otras.

Conocer los ficheros abiertos a lo largo de la vida de un programa es más complicado, sobre todo cuando se manejan muchos y no se tienen abiertos más que por períodos muy cortos. Si un fichero permanece abierto durante todo el programa se puede localizar mediante la interfaz "fd" bajo "/proc", usando el comando "find" nuevamente para localizar el nombre del fichero dado su "inode". En Solaris se puede hacer lo mismo con el comando "pfiles". Algunos de los "fd" abiertos pueden corresponderse a ficheros que no existen realmente, como la entrada y salida estándares, pipes, etc.

Localizar ficheros abiertos apenas un instante es bastante más complejo. Lo normal en estos casos consiste en ejecutar el programa trazando las llamadas al sistema que realiza, entre ellas llamadas tales como "open". En Solaris se dispone de una utilidad sencilla llamada "truss" que las llamadas al sistema por parte de un proceso. Bajo Linux no conozco una utilidad así, aunque es sencillo ejecutar el proceso con el GDB (GNU Debugger) y poner un punto de ruptura a la entrada del "open".

Una vez que sabemos qué librerías, ficheros y dispositivos necesita un proceso podemos replicarlos dentro de la nueva estructura "chroot", ya sea copiándolos o bien creando un enlace "hard". Recordad que un enlace simbólico no sirve.

¿Cómo de seguro es un entorno "chroot"?. La respuesta no es sencilla. Bien configurado, un confinamiento de este tipo es muy útil, pero en la práctica una máquina en producción tiene tantos procesos abiertos y las interrelaciones entre los mismos es tan compleja que asegurarlo es muy difícil. En realidad nuestra obligación es construir toda una serie de barreras anti-intrusiones, como un tamiz. En ese sentido el "chroot" es una herramienta más, y muy útil. Debemos ser conscientes, sin embargo, de sus limitaciones; su misión consiste en confinar el sistema de ficheros, no en proporcionar una auténtica máquina virtual absolutamente segura.

Existen, no obstante, una serie de principios básicos, de obligado cumplimiento:

  1. Utilizar una versión actualizada del Kernel, sin bugs conocidos.

  2. Dado que la única forma de salir de un "chroot" es haciendo otro, y que esa llamada sólo es invocable por el superusuario, resulta más que conveniente que no se ejecute ningún proceso como "root". Hay que evitar los programas SUID siempre que sea posible; un compromiso en su seguridad puede ser catastrófico: acceso a la memoria del kernel, lectura directa del disco, etc. Debe ser imposible hacerse superusuario dentro del "chroot", ya que una vez alcanzado ese status romper el confinamiento resulta trivial.

  3. Evitar la importación de ficheros abiertos al "chroot".

  4. Es importante que el directorio actual del proceso esté dentro del "chroot" a la hora de hacer la llamada. En caso contrario romper el confinamiento es inmediato.

  5. Siguiendo las directivas proporcionadas a lo largo de este artículo, replicar en el "chroot" sólo los procesos, librerías y dispositivos necesarios para el correcto funcionamiento del programa. En Linux no dejar nunca accesible "/proc", ya que permite movernos sin ninguna restricción a cualquier directorio raíz de cualquier proceso, amen de muchas otras cosas inconfesables. Ello incluye el proceso "init" (el proceso uno), cuyo directorio raíz es el real del disco.

  6. Hay que prestar mucha atención a los procesos "explotables" desde dentro de un "chroot". Ello incluye ataques como el syslog flooding. No hay que dejar ningún fleco abierto, como AT o CRON. Y mucho menos si son SUID.

  7. Bajo ninguna circunstancia debe permitirse cargar módulos en el Kernel.

  8. Si estamos montando particiones remotas, y no hay otra opción, hagámoslas de "sólo lectura". En las que se pueda escribir, pongámoslas NOSUID.

En la figura se puede ver un caso práctico de creación de un entorno CHROOT para el FTP, configurando éste para que haga "chroot" y SETUID tras el "login". Es bastante sencillo de entender. Debe ejecutarse como "root" y desde el directorio raíz del usuario que estamos configurando, cuyo último segmento debe coincidir con el "login" del usuario. Por ejemplo, para el usuario "jcea", un path correcto podría ser "/export/home/usuarios/jcea".

El listado, naturalmente, no es para que lo utilicéis tal cual, sino para estudiarlo con detenimiento.

  • El directorio "/export/home/ftp/anonimo" contiene las librerías, ficheros, etc. comunes a todos los usuarios y que deben copiarse en el "chroot".

  • El fichero "actualizar_web" es nuestro fichero de bienvenida al FTP. En nuestro caso es un mensaje que explica dónde dejar los ficheros para el web del usuario, la sección privada, etc.

  • Las librerías que se vinculan mediante "ln" son comunes a todos los usuarios.

  • Se crea un fichero "passwd" y "group" fantasmas para que el comando "ls" funcione correctamente.

  • Dependiendo del sistema, puede no ser necesario duplicar dispositivos.

Otra posibilidad para el FTP sería tener un único directorio "chroot", con todo lo necesario duplicado allí, y, colgando de él, el directorio de cada usuario. Naturalmente el directorio "chroot" sería "no listable" para evitar curiosos (no nos interesa que nadie sepa qué usuarios tenemos, ¿no?) y cada directorio en particular sólo permitiría la entrada a su usuario, mediante los permisos apropiados. Yo he preferido independizarlo un poco más para posibilitar montar usuarios en diferentes particiones, etc. Esto va un poco al gusto de cada cual; la ocupación en disco es similar, ya que estamos creando vínculos "hard", no copiando todas las librerías cada vez.

Espero que este artículo os permita protegeros mejor y os solucione algún que otro problema. Se puede encontrar una versión previa de este documento en http://www.argo.es/~jcea/artic/chroot.htm.

Como regalito, una demostración de por qué no debe ser posible hacerse "root" dentro de un "chroot":

mkdir("xploit",S_IRUSR | S_IXUSR);
chroot("xploit");
chdir("..");

Librerías Compartidas

# ldd /usr/sbin/syslogd  (Linux)
          libc.so.5 => /lib/libc.so.5.3.12

# ldd /usr/sbin/syslogd  (Solaris)
        libnsl.so.1 =>   /usr/lib/libnsl.so.1
        libc.so.1 =>     /usr/lib/libc.so.1
        libdl.so.1 =>    /usr/lib/libdl.so.1
        libintl.so.1 =>  /usr/lib/libintl.so.1
        libmp.so.1 =>    /usr/lib/libmp.so.1
        libw.so.1 =>     /usr/lib/libw.so.1

FTP en CHROOT

#! /bin/sh
# Instala todos los directorios y ficheros necesarios dentro de
# una cuenta de usuario para que pueda utilizar el FTP restringido
# y así actualizar sus páginas WEB sin tener que depender de nosotros.
#
# Jcea - Jesús Cea Avión
# Versión 1, Revisión 1 - 05 Feb 97
#
# **** ATENCION ****
#
# Este script debe ser invocado desde la cuenta del usuario, ya que
# instala todo en el directorio actual.
#
#

origen="/export/home/ftp/anonimo"
usuario=`pwd | awk -F/ '{print $NF}'`
echo El directorio fuente de la configuración es $origen
echo El nombre de usuario es $usuario
echo

umask 666

temp=`pwd`
echo Creando estructura de directorios en $temp
ln $origen/../actualizar_web ./actualizar_web
mkdir dev
mkdir usr
mkdir etc
mkdir usr/bin
mkdir usr/lib
ln -s usr/bin bin

lib=$origen/usr/lib
cd usr/lib
temp=`pwd`
echo Creando enlaces a las librerías compartidas en $temp
ln $lib/ld.so.1 ld.so.1
ln $lib/libc.so.1 libc.so.1
ln $lib/libdl.so.1 libdl.so.1
ln $lib/libintl.so.1 libintl.so.1
ln $lib/libnsl.so.1 libnsl.so.1
ln $lib/libsocket.so.1 libsocket.so.1
ln $lib/libw.so.1 libw.so.1
ln $lib/nss_dns.so.1 nss_dns.so.1
ln $lib/nss_files.so.1 nss_files.so.1
ln $lib/nss_nis.so.1 nss_nis.so.1
ln $lib/nss_nisplus.so.1 nss_nisplus.so.1
ln $lib/straddr.so.2 straddr.so.2

bin=$origen/usr/bin
cd ../bin
temp=`pwd`
echo Creando enlace \"/bin/ls\" en $temp
ln $bin/ls ls

cd ../../etc
temp=`pwd`
umask 133
echo Creando "passwd" y "group" fantasma en $temp
cp /dev/null passwd
cp /dev/null group

dev=$origen/dev
cd ../dev
temp=`pwd`
echo Creando enlaces a dispositivos en $temp
ln $dev/tcp tcp
ln $dev/ticotsord ticotsord
ln $dev/udp udp
ln $dev/zero zero

cd ..
temp=`pwd`
echo Creando directorio raíz de páginas WEB en $temp
echo Cambiando su propietario y grupo
umask 066
mkdir publico
chown $usuario publico
chgrp usuarios publico

echo
echo ¡¡FIN!!



Python Zope ©1998 jcea@jcea.es

Más información sobre los OpenBadges

Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS