|
|
 | |  |  | chrooter un compte avec OpenSSH |  |
by Denis Ducamp (23/04/2001)
Cet article montre un test d'utilisation de chroot (mise en cage) des accès
avec OpenSSH en version 2.5.2p2 . Ceci peut être utile pour ne donner à un
utilisateur l'accès qu'à un environnement restreint ou plus restrictivement
de ne lui permettre que la publication de données.
OpenSSH est une version libre (sources et utilisation) des version 1.5 et
2.0 du protocole SSH. Cette mise en oeuvre est disponible sur
http://www.openssh.com/ .
Le patch ci-dessous, testé sous Linux et NetBSD (par Stéphane Aubert), est
une version mise à jour du patch de Ricardo Cerqueira disponible dans le
répertoire contrib de la distribution portable de OpenSSH.
Le principe est extrêmement simple : il faut changer le répertoire principal
de l'utilisateur de telle sorte que le démon sshd comprenne qu'il lui faut
chrooter l'utilisateur. Pour ceci, l'entrée suivante dans le fichier
/etc/passwd :
ducamp:x:1000:1000:Denis Ducamp,,,:/home/ducamp:/bin/bash
peut être modifiée comme ceci :
ducamp:x:1000:1000:Denis Ducamp,,,:/home/./ducamp:/bin/bash
ou comme cela :
ducamp:x:1000:1000:Denis Ducamp,,,:/home/ducamp/./:/bin/bash
Dans le premier cas (/home/./ducamp) le serveur sshd comprend que
l'utilisateur doit être chrooté dans le répertoire /home et que le
répertoire principal de l'utilisateur, relativement au sommet de la cage,
est /ducamp .
Dans le second cas (/home/ducamp/./) le serveur sshd comprend que
l'utilisateur doit être chrooté dans le répertoire /home/ducamp et que le
répertoire principal de l'utilisateur, relativement au sommet de la cage,
est / .
Il faut ensuite peupler la cage avec quelques exécutables indispensables...
Dans le cas de l'accès à un shell, il faut recopier dans la cage le shell et
les bibliothèques nécessaires à celui-ci :
# ldd /home/ducamp/bin/bash
libtermcap.so.2 => /lib/libtermcap.so.2 (0x4001c000)
libdl.so.2 => /lib/libdl.so.2 (0x40020000)
libc.so.6 => /lib/libc.so.6 (0x40023000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
# mkdir -p $CAGE/bin $CAGE/lib
# cp /bin/bash $CAGE/bin
# cp /lib/libtermcap.so.2 /lib/libdl.so.2 /lib/libc.so.6 /lib/ld-linux.so.2 $CAGE/bin
Attention, sur certains systèmes (Linux libc5, *BSD), le programme de
chargement des bibliothèques n'est pas montré par ldd. Pour savoir où il est
localisé, il suffit d'utiliser la commande suivante :
# strings ...bin/bash | egrep /ld.elf.so
ce qui peut donner par exemples les résultats suivants :
/usr/libexec/ld.elf_so
ou /usr/libexec/ld-elf.so.1
Pour tester, il est possible de lancer un serveur sshd sur un autre port :
# ./sshd -p 2222
ou pour avoir un maximum d'informations de débogage sous les systèmes Linux :
# strace -f ./sshd -d -p 2222 > strace.txt 2>&1
et sous les systèmes BSD :
# ktrace -i sshd -p 2222 -d
# kdump
puis de s'y connecter :
$ ssh localhost -x -p 2222
Exemple de session dans le cas /home/./ducamp , l'utilisateur étant chrooté
dans /home :
bash-2.04$ pwd
/ducamp
bash-2.04$ echo /bin/* /lib/*
/bin/bash /lib/ld-linux.so.2 /lib/libc.so.6 /lib/libcrypt.so.1
/lib/libdl.so.2 /lib/libnsl.so.1 /lib/libtermcap.so.2 /lib/libutil.so.1
bash-2.04$ echo $$
17776
Pour vérifier, il est possible de visualiser dans une autre session les
caractéristiques du shell ainsi obtenu. Sous Linux il est possible
d'utiliser ainsi /proc :
# ls -l /proc/17776/{cwd,exe,root}
lrwx------ 1 ducamp ducamp 0 Apr 20 17:09 /proc/17776/cwd -> /home/ducamp
lrwx------ 1 ducamp ducamp 0 Apr 20 17:09 /proc/17776/exe -> /home/bin/bash
lrwx------ 1 ducamp ducamp 0 Apr 20 17:09 /proc/17776/root -> /home
Le shell exécuté est correctement chrooté dans /home , ce shell est
/home/bin/bash (/bin/bash par rapport à la racine de la cage qui est ici
/home) et son répertoire actuel est /home/ducamp (/ducamp par rapport à
la racine de la cage).
De façon générale, lsof peut être utilisé ainsi :
# lsof -p 17776
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
bash 17776 ducamp cwd DIR 3,9 4096 32163 /home/ducamp
bash 17776 ducamp rtd DIR 3,9 4096 2 /home
bash 17776 ducamp txt REG 3,9 477756 212928 /home/bin/bash
bash 17776 ducamp mem REG 3,9 424087 212932 /home/lib/ld-linux.so.2
bash 17776 ducamp mem REG 3,9 14831 212929 /home/lib/libtermcap.so.2
bash 17776 ducamp mem REG 3,9 62615 212930 /home/lib/libdl.so.2
bash 17776 ducamp mem REG 3,9 4746015 212931 /home/lib/libc.so.6
bash 17776 ducamp 0u CHR 136,30 32 /dev/pts/30
bash 17776 ducamp 1u CHR 136,30 32 /dev/pts/30
bash 17776 ducamp 2u CHR 136,30 32 /dev/pts/30
bash 17776 ducamp 255u CHR 136,30 32 /dev/pts/30
Ici aussi il est possible de voir les mêmes données sur les lignes où FD est
égal à cwd (répertoire courant), rtd (répertoire racine) et txt (programme
exécuté).
Dans le cas de l'accès au serveur via sftp (protocole dédié au transfert de
fichiers via ssh), il faut recopier dans la cage le module sftp-server et
les bibliothèques nécessaires à celui-ci :
# ldd /usr/local/libexec/sftp-server
libz.so.1 => /usr/lib/libz.so.1 (0x4001c000)
libnsl.so.1 => /lib/libnsl.so.1 (0x4002b000)
libutil.so.1 => /lib/libutil.so.1 (0x40040000)
libcrypto.so.0 => /usr/lib/libcrypto.so.0 (0x40044000)
libcrypt.so.1 => /lib/libcrypt.so.1 (0x40104000)
libc.so.6 => /lib/libc.so.6 (0x40131000)
libdl.so.2 => /lib/libdl.so.2 (0x4023f000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
# mkdir -p $CAGE/usr/local/libexec $CAGE/usr/lib
# cp /usr/local/libexec/sftp-server $CAGE/usr/local/libexec
# cp /lib/libnsl.so.1 /lib/libutil.so.1 /lib/libcrypt.so.1 $CAGE/lib
# cp /usr/lib/libz.so.1 /usr/lib/libcrypto.so.0 $CAGE/usr/lib
Un serveur sshd pourra être lancé sur un autre port, puis la commande
suivante lancée pour vérifier le bon fonctionnement :
$ sftp -o 'port 2222' localhost
Voici un exemple de session :
sftp> pwd
Remote working directory: /ducamp
sftp> dir /lib
drwxr-xr-x 2 root root 4096 Apr 20 14:06 .
drwxr-xr-x 12 root root 4096 Apr 20 15:05 ..
-rwxr-xr-x 1 root root 14831 Aug 21 1999 libtermcap.so.2
-rwxr-xr-x 1 root root 62615 Feb 19 06:57 libdl.so.2
-rwxr-xr-x 1 root root 4746015 Feb 19 06:58 libc.so.6
-rwxr-xr-x 1 root root 424087 Feb 19 06:58 ld-linux.so.2
-rwxr-xr-x 1 root root 71527 Feb 19 06:57 libcrypt.so.1
-rwxr-xr-x 1 root root 42626 Feb 19 06:58 libutil.so.1
-rwxr-xr-x 1 root root 350956 Feb 19 06:58 libnsl.so.1
Et voici une vérification rapide des propriétés du module sftp-server
exécuté :
$ ps ax|grep [s]ftp-server
18995 ? S 0:00 /usr/local/libexec/sftp-server
$ ls -l /proc/18995/{cwd,exe,root}
lrwx------ 1 ducamp ducamp 0 Apr 20 18:25 /proc/18995/cwd -> /home/ducamp
lrwx------ 1 ducamp ducamp 0 Apr 20 18:25 /proc/18995/exe -> /home/usr/local/libexec/sftp-server
lrwx------ 1 ducamp ducamp 0 Apr 20 18:25 /proc/18995/root -> /home
Puis en utilisant lsof :
# lsof -p 18995
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
sftp-serv 18995 ducamp cwd DIR 3,9 4096 32163 /home/ducamp
sftp-serv 18995 ducamp rtd DIR 3,9 4096 2 /home
sftp-serv 18995 ducamp txt REG 3,9 23752 213116 /home/usr/local/libexec/sftp-server
sftp-serv 18995 ducamp mem REG 3,9 424087 212932 /home/lib/ld-linux.so.2
sftp-serv 18995 ducamp mem REG 3,9 62776 61294 /home/usr/lib/libz.so.1
sftp-serv 18995 ducamp mem REG 3,9 350956 213163 /home/lib/libnsl.so.1
sftp-serv 18995 ducamp mem REG 3,9 42626 213121 /home/lib/libutil.so.1
sftp-serv 18995 ducamp mem REG 3,9 796880 61295 /home/usr/lib/libcrypto.so.0
sftp-serv 18995 ducamp mem REG 3,9 71527 213119 /home/lib/libcrypt.so.1
sftp-serv 18995 ducamp mem REG 3,9 4746015 212931 /home/lib/libc.so.6
sftp-serv 18995 ducamp mem REG 3,9 62615 212930 /home/lib/libdl.so.2
sftp-serv 18995 ducamp 0u unix 0xcbbf4000 46713 socket
sftp-serv 18995 ducamp 1u unix 0xcbbf4000 46713 socket
sftp-serv 18995 ducamp 2u unix 0xc9c9fc40 46715 socket
sftp-serv 18995 ducamp 3u unix 0xcbbf4000 46713 socket
sftp-serv 18995 ducamp 4u unix 0xcbbf4000 46713 socket
Remarque : même pour sftp, il est nécessaire que le shell de l'utilisateur
soit présent dans la cage et qu'il puisse être exécuté. En effet, les
commandes passées par l'utilisateur sont exécutées par l'option -c du shell
de l'utilisateur.
Afin de rajouter le support de scp et rsync, il est nécessaire de recopier
les exécutables scp et rsync dans la cage ainsi que les bibliothèques dont
ils ont besoin. La remarque pour sftp s'applique également ici : le shell de
l'utilisateur doit être présent et exécutable.
Maintenant pour restreindre le compte à certaines commandes, il est
nécessaire de générer un couple de clés et de limiter l'usage d'une clé
publique à un commande précise.
La génération d'un couple de clés est faisable par les commandes :
$ ssh-keygen -t rsa
ou $ ssh-keygen -t dsa
générant respectivement des clés rsa et dsa pour SSHv2.
La clé publique du client (id_rsa.pub ou id_dsa.pub) doit être copiée dans
le fichier ~/.ssh/authorized_keys2 côté serveur et il suffit d'insérer
l'option suivante au début de la ligne :
command="/usr/local/libexec/sftp-server"
Ici le serveur ssh exécutera systématiquement la commande
/usr/local/libexec/sftp-server quelque soit la commande demandée par le
client. Si le client se connecte grâce à sftp alors tout marche bien puisque
dans ce cas la commande sftp-server est lancée par le serveur sshd. Si le
client se connecte simplement en ssh, la commande sftp-server sera lancée
côté serveur et le client ne peut pas se retrouver avec un shell.
Pour rsync la situation est plus complexe car le "serveur" rsync lancé par
le client prend des options qui dépendent des options utilisées par le
client. Ainsi il est nécessaire de décider d'une ligne de commande rsync qui
sera exécutée et de se tenir à celle-ci. Par exemple pour synchroniser un
serveur web personnel, la commande utilisateur est :
rsync -avr --delete --rsh=ssh public_html/ perso@web:public_html/
et la commande à mettre dans le fichier .ssh/authorized_keys2 est :
command="/usr/bin/rsync --server -vlogDtpr --delete . public_html/"
Dans certains cas où la clé privée ne peut être chiffrée, car elle devra
être utilisée par des scripts automatiques, il est possible de limiter sur
le serveur le droit d'utilisation de la clé par adresses IP. Pour cela, il
faut insérer l'option suivante au début de la ligne de la clé publique :
from="liste d'adresses"
Cette liste d'adresses, séparées par des virgules, peut être composée
d'adresses IP et de noms entièrement qualifiés. Les caractères génériques
'*' et '?' peuvent être utilisés.
Il vous est maintenant possible de créer un compte ssh chrooté. Ce compte
peut être utilisé, par exemple, pour ne permettre d'accéder qu'à un petit
nombre de commandes qui auront été recopiées dans la cage. Ce compte peut
également être beaucoup plus restrictif en n'acceptant qu'une commande
déterminée, comme sftp-server ou rsync, ce qui peut permettre par exemple de
publier des données.
Faites attention toutefois à ce que dans certains cas l'utilisateur ne
puisse pas modifier certains fichiers tels que les exécutables et les
fichiers de configuration (authorized_keys par exemple). Afin de faire cela
complètement, OpenSSH a aujourd'hui besoin de quelques patches en plus qui
seront publiés dans un autre tips...
------------ chroot.diff ------------
diff -u openssh-2.5.2p2.orig/session.c openssh-2.5.2p2/session.c
--- openssh-2.5.2p2.orig/session.c Thu Mar 22 01:58:27 2001
+++ openssh-2.5.2p2/session.c Mon Apr 23 15:39:56 2001
@@ -93,6 +93,8 @@
# include <uinfo.h>
#endif
+#define CHROOT
+
/* types */
#define TTYSZ 64
@@ -1012,6 +1014,10 @@
extern char **environ;
struct stat st;
char *argv[10];
+#ifdef CHROOT
+ char *user_dir;
+ char *new_root;
+#endif /* CHROOT */
int do_xauth = s->auth_proto != NULL && s->auth_data != NULL;
#ifdef WITH_IRIX_PROJECT
prid_t projid;
@@ -1064,6 +1070,26 @@
set_limits_from_userattr(pw->pw_name);
# endif /* HAVE_GETUSERATTR */
# ifdef HAVE_LOGIN_CAP
+
+#ifdef CHROOT
+ user_dir = xstrdup(pw->pw_dir);
+ new_root = user_dir + 1;
+
+ while((new_root = strchr(new_root, '.')) != NULL) {
+ new_root--;
+ if(strncmp(new_root, "/./", 3) == 0) {
+ *new_root = '\0';
+ new_root += 2;
+
+ if(chroot(user_dir) != 0)
+ fatal("Couldn't chroot to user directory %s", user_dir);
+ pw->pw_dir = new_root;
+ break;
+ }
+ new_root += 2;
+ }
+#endif /* CHROOT */
+
if (setusercontext(lc, pw, pw->pw_uid,
(LOGIN_SETALL & ~LOGIN_SETPATH)) < 0) {
perror("unable to set user context");
@@ -1085,6 +1111,26 @@
if (setlogin(pw->pw_name) < 0)
error("setlogin failed: %s", strerror(errno));
+
+#ifdef CHROOT
+ user_dir = xstrdup(pw->pw_dir);
+ new_root = user_dir + 1;
+
+ while((new_root = strchr(new_root, '.')) != NULL) {
+ new_root--;
+ if(strncmp(new_root, "/./", 3) == 0) {
+ *new_root = '\0';
+ new_root += 2;
+
+ if(chroot(user_dir) != 0)
+ fatal("Couldn't chroot to user directory %s", user_dir);
+ pw->pw_dir = new_root;
+ break;
+ }
+ new_root += 2;
+ }
+#endif /* CHROOT */
+
if (setgid(pw->pw_gid) < 0) {
perror("setgid");
exit(1);
------------ chroot.diff ------------
|