lalahop
Categorie: Sécurité

OpenWRT : sécuriser l’accès SSH

Table des matières

Dans ce billet, je vais vous parler de plusieurs méthodes pour améliorer la sécurité de votre accès SSH à votre routeur sous OpenWRT. Un deuxième objectif sera d'éviter au maximum les attaques par bruteforce. Bien qu'elles soient sans espoir avec un mot de passe bien choisi, elles consomment un peu des ressources (processeur et bande passante) et ça m'énerve. Nous verrons quatre méthodes au total. Il est bien entendu qu'il est inutile de combiner toutes les méthodes. Néanmoins, certaines peuvent être combinées (ex. : 3 et 4 ou 1 et 2 ou ...). Évidemment, tout ceci est inutile si vous n'utilisez pas SSH en dehors de votre LAN.

Un changement du port d'écoute et une règle iptables

J'ai déjà présenté une règle iptables permettant de limiter l’impact d'une attaque par bruteforce sur le démon SSH dans ce billet : Adieu DG834G, bonjour WRT54GL et OpenWRT

Pour rappel, voici ce que j'écrivais :
Je change le port sur lequel écoute dropbear (démon SSH). Pour cela, il faut éditer le fichier /etc/config/dropbear en changeant la ligne « Port ».

Ensuite, j’ouvre le port, dans iptables, pour qu’il soit accessible depuis internet et je protège le service contre une attaque par brute force. Pour faire cela, il faut éditer le fichier /etc/firewall.user avec vi, par exemple, et ajouter :

iptables -A input_wan -p tcp --dport 7523 -m state --state NEW -m recent --name ATTACKER_SSH --rsource --update --seconds 600 --hitcount 2 -j DROP
iptables -A input_wan -p tcp --dport 7523 -m state --state NEW -m recent --name ATTACKER_SSH --rsource --set
iptables -A input_wan -p tcp --dport 7523 -m state --state NEW -j ACCEPT

Notes :

  • 7523 est le port d'écoute choisi.
  • Ce code est inspiré de celui fourni sur le wiki officiel mais lui fonctionne, au moins 😉 .
  • Ce code nécessite que les packages iptables-mod-conntrack-extra et kmod-ipt-conntrack-extra soient installés. Ils ne le sont pas par défaut.

Explication rapide : L’attaquant sera donc bloqué pendant 10 minutes après 2 connexions (qui permettent chacune d’essayer 10 mots de passe). On ne peut pas plus réduire le nombre de connexions. Si vous mettez 1, vous ne devrez jamais vous tromper quand vous tapez le login. Car dans ce cas, vous devrez quitter la session et en recommencer une mais vous serez alors bloqué pendant 10 minutes. Évidemment, cela s’applique uniquement aux paquets venant d’internet, pas du LAN.

Malheureusement, il n'est pas toujours possible de changer le port d'écoute : certains réseaux vérifient les ports utilisés dans le trafic sortant, voir plus (reverse-proxy ...) et vous ne pourrez donc pas accéder à votre routeur depuis ces réseaux. Mais n'oubliez pas qu'il reste beaucoup de ports ouverts et qui ne sont que rarement contrôlés par de reverse proxy ou des règles qui matchent le contenu des paquets. À ce sujet, privilégiez les ports qui utilisent un protocole de chiffrement (ex : SSH, HTTPS, POPS, IMAPS, SMTPS, ports VPN, ports IPSEC, ...). De plus, Dropbear, le démon SSH utilisé par OpenWRT permet d'essayer 10 mots de passe par connexions. Ce qui fait qu'ici, un bot aura le droit d'essayer 20 mots de passe par session.

Bon, vous me direz que 20 essais toutes les 10 minutes, ce n'est rien comparé à un mot de passe de 12 caractères chiffres, lettre minuscules, majuscules et caractères spéciaux ne représentant pas une séquence connue (ex. : azertyuiop75) ni un mot du dictionnaire. Sachant que, d’après mes observations, les bot bloqués ne reviennent pas au bout de 10 minutes mais au bout de 24H, cela fait donc 20 essais de mot de passe par jour. Autant dire : rien. Et je ne parle pas des bots qui sont seulement capables de tester un mot de passe par connexion. Ceux-là essayent donc 2 mots de passe par jour. Néanmoins, comme je l'ai déjà dit ces tentatives, même vouées à l'échec, m'énervent.

Compiler dropbear pour n'autoriser que 3 essais de mot de passe par connexion

Édit du 25/08/2011 à 2h40 :
Attention : J'ai oublié de préciser que ce point vous concerne même si vous utilisez l'authentification à clé publique. En effet, même si vous n'avez pas besoin de limiter le nombre d'essais de mot de passe, il peut être intéressant de limiter la durée pendant laquelle l'authentification est possible, de limiter le nombre de d'utilisateurs non authentifiés simultanés ou bien encore de limiter la taille du binaire.
Fin de l'édit

Ici, les manières de procéder sont diverses. La plus adaptée serait d'utiliser le SDK d'OpenWRT puisque nous voulons compiler uniquement un seul paquet. Néanmoins, celui-ci n'est pas disponible pour Backfire 10.03. Certains internautes du forum officiel d'OpenWRT utilisent le SDK de la version Kamikaze au prix d'une petite bidouille d'opkg par la suite. Nous n'allons donc pas employer cette méthode mais nous allons utiliser buildroot, une série de scripts qui permet de compiler facilement un système embarqué avec Linux comme noyau et qui est utilisé par OpenWRT, pour compiler dropbear.

Pour rappel : le SDK ou l'image builder, sont des composants du buildroot. Ils peuvent donc être remplacés ou compilés par buildroot (voir make menuconfig).

Préparer votre GNU/Linux Debian Squeeze pour buildroot

Il faut installer un certain nombre de paquets :

# apt-get install asciidoc autoconf bison build-essential fastjar flex gawk gettext git-core intltool libextutils-autoinstall-perl libncurses5-dev libssl-dev libtool subversion zlib1g-dev

Source : OpenWrt Buildroot – Installation. Le reste des paquets seront installés via le jeu des dépendances.

C'est l'heure d'aller se chercher un premier café ! 😉

Récupérer les sources

Nous allons récupérer les sources stables de la version Backfire 10.03 :

~$ svn co svn://svn.openwrt.org/openwrt/tags/backfire_10.03

S'assurer que toutes les dépendances sont satisfaites

~$ cd backfire_10.03
~/backfire_10.03$ make defconfig
~/backfire_10.03$ make prereq

Modifier le Makefile de dropbear afin d'accroitre la sécurité

Ouvrez le Makefile spécifique à dropbear (./package/dropbear). Dans la rubrique "Build/Configure", constatez que certains fichiers sources sont modifiés avant compilation. Il s'agit notamment du fichier options.h que l'auteur de dropbear nous conseille (via le fichier README des sources) d'ailleurs de consulter afin de personnaliser notre installation. C'est ce que j'ai fait et je vous livre ici les modifications utiles que j’ai trouvées. Signalons au passage la présence de nombreux commentaires, dans le code, qui aide à faire des choix. Rien ne vous empêche donc de vérifier que je n'ai rien oublié 😉 .

Voici donc les lignes que nous allons rajouter dans la rubrique "Build/Configure du Makefile" :

$(SED) 's,^#define AUTH_TIMEOUT 300,#define AUTH_TIMEOUT 60,g' $(PKG_BUILD_DIR)/sysoptions.h
$(SED) 's,^#define MAX_UNAUTH_PER_IP 5,#define MAX_UNAUTH_PER_IP 2,g' $(PKG_BUILD_DIR)/options.h
$(SED) 's,^#define MAX_UNAUTH_CLIENTS 30,#define MAX_UNAUTH_CLIENTS 2,g' $(PKG_BUILD_DIR)/options.h
$(SED) 's,^#define MAX_AUTH_TRIES 10,#define MAX_AUTH_TRIES 3,g' $(PKG_BUILD_DIR)/options.h

Explications simplifiées :

  • La première ligne passe le délai pour s'authentifier de 5 minutes à 1 minute. Il ne faut tout de même pas 5 minutes pour taper un mot de passe !
  • La deuxième et la troisième ligne réduisent le nombre de clients non authentifiés qui peuvent se connecter simultanément.
  • La dernière ligne passe le nombre d'essais de mot de passe de 10 à 3 pour une session.

Évidemment, ces options, et notamment celle pour le nombre de clients non authentifiés simultanés, doivent être adapter à votre situation (nombre d'utilisateurs, ...).

Modifier le Makefile de dropbear afin d'optimiser l'espace occupé par le programme

Tant que nous y sommes, nous allons désactiver les fonctions dont nous ne nous servirons pas afin que le binaire généré occupe moins d'espace sur la flash du routeur. Par la même occasion, nous augmentons la sécurité du démon (moins de code donc moins de bugs/failles possibles).

Sachez que le développeur de dropbear donne des conseils pour que dropbear occupe moins d'espace dans le fichier SMALL des sources. Je vous cite le contenu de ce fichier, ci-dessous, pour information :

Tips for a small system:

If you only want server functionality (for example), compile with
make PROGRAMS=dropbear
rather than just
make dropbear
so that client functionality in shared portions of Dropbear won't be included.
The same applies if you are compiling just a client.

---

The following are set in options.h:

- You can safely disable blowfish and twofish ciphers, and MD5 hmac, without
affecting interoperability

- If you're compiling statically, you can turn off host lookups

- You can disable either password or public-key authentication, though note
that the IETF draft states that pubkey authentication is required.

- Similarly with DSS and RSA, you can disable one of these if you know that

all clients will be able to support a particular one. The IETF draft
states that DSS is required, however you may prefer to use RSA.
DON'T disable either of these on systems where you aren't 100% sure about
who will be connecting and what clients they will be using.

- Disabling the MOTD code and SFTP-SERVER may save a small amount of codesize

- You can disable x11, tcp and agent forwarding as desired. None of these are

essential, although agent-forwarding is often useful even on firewall boxes.

---

If you are compiling statically, you may want to disable zlib, as it will use
a few tens of kB of binary-size (./configure --disable-zlib).

You can create a combined binary, see the file MULTI, which will put all
the functions into one binary, avoiding repeated code.

If you're compiling with gcc, you might want to look at gcc's options for
stripping unused code. The relevant vars to set before configure are:

LDFLAGS=-Wl,--gc-sections
CFLAGS="-ffunction-sections -fdata-sections"

You can also experiment with optimisation flags such as -Os, note that in some
cases these flags actually seem to increase size, so experiment before
deciding.

Of course using small C libraries such as uClibc and dietlibc can also help.

If you have any queries, mail me and I'll see if I can help.

Nous allons suivre le dernier conseil : utiliser les options de gcc pour dégager le code inutilisé. Pour cela, ajoutez les deux lignes données en haut de la rubrique Build/Compile du Makefile de dropbear. Comme cela :

define Build/Compile
	LDFLAGS=-Wl,--gc-sections
	CFLAGS="-ffunction-sections -fdata-sections"	
	$(MAKE) -C $(PKG_BUILD_DIR) \
		$(TARGET_CONFIGURE_OPTS) \
		LD="$(TARGET_CC)" \
		PROGRAMS="dropbear dbclient dropbearkey scp" \
		MULTI=1 SCPPROGRESS=1

	$(MAKE) -C $(PKG_BUILD_DIR) \
		$(TARGET_CONFIGURE_OPTS) \
		LD="$(TARGET_CC)" \
		PROGRAMS="dropbearconvert"

endef

Nous allons maintenant suivre les conseils précédents en rajoutant les lignes suivantes dans la rubrique "Build/Configure" du Makefile :

$(SED) 's,^#define INETD_MODE,/* & */,g' $(PKG_BUILD_DIR)/options.h
$(SED) 's,^#define DO_MOTD,/* & */,g' $(PKG_BUILD_DIR)/options.h
$(SED) 's,^#define SFTPSERVER_PATH "/usr/libexec/sftp-server",/* & */,g' $(PKG_BUILD_DIR)/options.h
$(SED) 's,^#define ENABLE_X11FWD,/* & */,g' $(PKG_BUILD_DIR)/options.h
$(SED) 's,^#define DROPBEAR_BLOWFISH,/* & */,g' $(PKG_BUILD_DIR)/options.h
$(SED) 's,^#define DROPBEAR_TWOFISH256,/* & */,g' $(PKG_BUILD_DIR)/options.h
$(SED) 's,^#define DROPBEAR_TWOFISH128,/* & */,g' $(PKG_BUILD_DIR)/options.h
$(SED) 's,^#define DROPBEAR_3DES,/* & */,g' $(PKG_BUILD_DIR)/options.h
$(SED) 's,^#define DROPBEAR_AES128,/* & */,g' $(PKG_BUILD_DIR)/options.h
$(SED) 's,^#define DROPBEAR_MD5_HMAC,/* & */,g' $(PKG_BUILD_DIR)/options.h

Explications :

  • La première ligne demande à dropbear de ne pas être lancé par un quelconque démon réseau mais de toujours se lancer en standalone. Le mode inetd est inutile sur OpenWRT qui ne possède pas ce genre de démon.
  • La deuxième ligne désactive le message du jour. Le fichier /etc/banner nous suffira pour afficher un message.
  • La troisième ligne désactive le support d'un serveur SFTP. Nous avons déjà SCP pour copier des fichiers de manière sécurisée.
  • La quatrième ligne désactive le X11 forwarding. Par défaut, il n'y a pas d'environnement graphique accessible via SSH sur OpenWRT et nous n'en voulons pas. Autant gagner de la place en désactivant cette fonctionnalité.
  • Les autres lignes désactivent les algorithmes de chiffrage/hachage les plus faibles (bien qu'ils ne soient pas "non sécurisés"). Seuls l'algorithme de chiffrement symétrique AES 256 et les fonctions de hachage SHA1_HMAC et SHA1_96_HMAC resteront.

L'auteur de dropbear nous conseille aussi de désactiver le port forwarding et l'agent forwarding. Dans mon cas, ceux-ci me seront utiles donc je ne les désactive pas. Enfin, il nous conseille de désactiver l'authentification par mot de passe ou celle par clé publique. Vu le lien entre désactivation de telnet et changement du mot de passe de root sous OpenWRT, je ne préfère pas désactiver l'authentification par mot de passe dans le code mais plutôt dans le fichier de configuration de dropbear, une fois que j'aura importer ma clé publique dans le routeur. J'ai également conservé les deux algorithmes liés à l'authentification par clé publique, RSA et DSA car il m'arrive d'utiliser l'un ou l'autre.

Les autres options possibles (désactiver la résolution des IP en nom de domaine et créer un binaire qui combine le serveur et le client) sont déjà activées par OpenWRT : c'est l'objet des deux premières lignes de la rubrique Build/Configure.

Compiler le nouveau dropbear

Avant de compiler quoi que ce soit, fermez puis rouvrez votre session. Si vous ne le faites pas, vous aurez des erreurs sans queue ni tête (CDPATH ...). Ceci n'est à faire que la première fois, à la suite de l'installation des paquets prérequis. Certainement un problème de variables d'environnement mais je n'ai pas plus d'informations.

Quelles que soient les modifications que vous effectuez sur le Makefile, pensez à incrémenter la variable "PKG_RELEASE" de celui-ci avant de lancer la compilation. C'est une bonne pratique à prendre et ça évite des erreurs.

Vous ne pouvez pas compiler un seul paquet sans avoir compilé le toolchain d'abord. Nous allons utiliser la commande suivante afin d'avoir une interface semi-graphique nous permettant de régler les options de la compilation :

~/backfire_10.03$ make menuconfig

Dans "Target system", nous choisirons "Broadcom BCM947xx/953xx" qui correspond à la déclinaison brcm47xx. Vous pouvez choisir la déclinaison brcm2.4 ("Broadcom BCM947xx/953xx [2.4]"). Votre choix n'affectera que très peu la suite de ce billet, tout au plus quelques chemin/noms de fichiers qui changeront.

À noter que les binaires des deux branches (brcm47xx et brcm2.4) sont compatibles. En tout cas, c'est ce que j'ai pu constater.

Nous ne toucherons à rien dans "Target profile". Ni dans "Target images" ni dans" Global build settings" ni dans "Advanced configuration options" ni dans "Image configuration". Nous ne nous occuperons pas non plus des options qui permettent de compiler les trucs spécifiques (SDK, toolchain, image builder). Et dans les rubriques qui restent, nous décocherons tout (touche "n") sauf dropbear. Cela dans le but d’alléger le téléchargement et la compilation des sources.

Ensuite, lançons la compilation avec la commande :

~/backfire_10.03$ make

Si une erreur survient, vous pourrez tentez de debugger la compilation en la relançant avec le paramètre "V=99", comme cela :

~/backfire_10.03$ make V=99

Si vous compilez sur un processeur multicoeur, vous pouvez exploiter au mieux la puissance de celui-ci en utilisant le parametre "-j <nombre de coeur + 1>". Exemple pour un processeur quad core :

~/backfire_10.03$ make -j 5

Le système va télécharger les sources du toolchain et le compiler. Puis il téléchargera les sources de dropbear, les modifiera et les compilera. Enfin, il fabriquera le package ipkg de dropbear ainsi que les images des firmwares au format squashfs.

C'est le moment d'aller se chercher un deuxième café voir plus 😉 .

À la fin de la compilation, vous retrouverez le package de dropbear dans le dossier ./bin/brcm47xx/package.

Si jamais vous effectué des modifications, vous n'aurez pas besoin de tout recompiler : il vous suffira d'incrémenter la variable "PKG_RELEASE" présente dans le Makefile de dropbear et de lancer la commande suivante :

~/backfire_10.03$ make package/dropbear/compile

Les options précédemment évoquées ("-j" et "V=99" restent réutilisables).

Remplacer le binaire sur votre routeur

Ici, plusieurs méthodes sont encore possibles. Nous en exposerons deux.

Utiliser opkg

Il suffit de transférer le package sur le routeur avec la commande :

~/backfire_10.03$ scp ./bin/brcm47xx/packages/dropbear_0.52-5_brcm47xx.ipk root@192.168.1.1:/tmp

Puis de se connecter en SSH sur le routeur et d'y installer le package :

root@OpenWrt:~# opkg install /tmp/dropbear_0.52-5_brcm47xx.ipk

Vous n'avez plus qu'à relancer le serveur :

root@OpenWrt:~# /etc/init.d/dropbear restart

Cela devrait suffire mais, par prudence, je préfère rebooter le routeur.

Remplacer directement le binaire

Le package ipkg n'est qu'un fichier compressé avec gzip comme nous en informe la commande file :

file ./bin/brcm47xx/packages/dropbear_0.52-5_brcm47xx.ipk
./bin/brcm47xx/packages/dropbear_0.52-5_brcm47xx.ipk: gzip compressed data, from Unix, last modified: Sat Aug 13 15:34:30 2011

Il suffit donc de le décompresser :

~/backfire_10.03$ mkdir dropbear_uncompress && cd dropbear_uncompress
~/backfire_10.03/dropbear_uncompress$ tar -xf ../bin/brcm47xx/packages/dropbear_0.52-5_brcm47xx.ipk

Puis de décompresser le fichier data.tar.gz :

~/backfire_10.03/dropbear_uncompress$ tar -xf data.tar.gz

Votre binaire se trouve donc dans le dossier ~/backfire_10.03/dropbear_uncompress/usr/sbin. Il vous suffit de le transférer en utilisant SCP :

~/backfire_10.03/dropbear_uncompress$ scp ./usr/sbin/dropbear root@192.168.1.1:/tmp

Il ne vous reste plus qu'à remplacer le binaire en vous connectant par ssh :

root@OpenWrt:~# mv /tmp/dropbear /usr/sbin/dropbear
root@OpenWrt:~# chmod ugo+x /usr/sbin/dropbear

La deuxième ligne sert à rendre le binaire exécutable pour tout le monde. C'est les droits qui sont définis sur le fichier d'origine.

Vous n'avez plus qu'à relancer le serveur :

root@OpenWrt:~# /etc/init.d/dropbear restart

Cela devrait suffire mais, par prudence, je préfère rebooter le routeur.

Si vous venez de réussir votre première compilation croisée en suivant ces explications, félicitations 😀 .

Intégrer notre dropbear modifié dans une image de firmware au format squashfs (= créer un firmware personnalisé)

Cela permet de ne pas avoir à réinstaller votre version modifiée de dropbear (en supposant que vous ayez gardé le binaire que vous avez compilé) lorsque vous réinstallez le firmware suite à une erreur trop importante. Nous évoquerons ce point dans un prochain billet. Néanmoins, pour ceux qui veulent avoir un aperçu : Fonera 2 : Personnaliser, compiler, régénérer OpenWRT sur KubuntuBlog.

Néanmoins, la recompilation de dropbear n'est toujours pas suffisante. Combinée à la première solution, elle permet de réduire à 3 le nombre d'essais de mot de passe par connexion. Donc, 6 essais par jour pour les meilleurs bots (et toujours 2mdp/jour pour les plus mauvais). C'est toujours mieux que 20 mais ces 6 essais consomment toujours des ressources inutiles et m'énervent toujours.

Utiliser le port knocking

Présentation du concept

Encore un concept logiciel qui, à l’instar des sémaphores/jetons, prouve qu'on n’invente rien en informatique : on s'inspire de la réalité. Qui n'a jamais joué à l'espion avec un toc-toc secret pour rentrer dans le QG durant son enfance ? 😀 Comment ça je suis seul ? 😮 Menteurs !

Si vous ne savez pas ce qu'est le port knocking, voir : port knocking sur Wikipedia en.

Le port knocking est une technique que je connais depuis pas mal de temps mais que je n'avais jamais mis en pratique. Par contre, ce que je ne savais pas, c'est que le concept avait évolué pour être plus sécurisé : on a maintenant le Single Packet Authorization qui permet de transmettre la séquence de manière chiffrée. Cela empêche une attaque de l'homme du milieu qu'il est possible de faire avec un port knocking classique. A savoir aussi, une adaptation du port knocking peut également servir à dissimuler un point d’accès WiFi beaucoup plus efficacement que par le simple fait de cacher le SSID. Pour plus d'informations à ce sujet, voir : Sécuriser un réseau WiFi avec Wknock (Laurent Oudot - Blackhat 2005) - secuobs.com.

Bien que l'auteur de knockd s'en défendent, je vois le port knocking comme une sécurité par l'obscurité : on masque les ports en espérant que personne ne voit la porte d'entrée derrière le voile. C'est pour cela qu'il ne faut pas se reposer uniquement sur un logiciel de port knocking. Mais ces logiciels peuvent être utiles pour rajouter une couche de protection (et donc de failles (rappelez-vous : un logiciel sans bugs/failles est un logiciel insuffisamment testé)).

Quoi qu'il en soit, seuls les paquets wknock et knockd sont disponibles en package pré-compilés pour OpenWRT. Nous ne verrons donc pas de Single Packet Authorization à moins que quelqu'un compile le paquet pour OpenWRT, bien entendu. Pour ceux qui se le demande, knockd est évidemment sous licence libre (GNU GPL).

Édit du 25/08/2011 à 2h35 :
Attention : knockd a tendance à faire planter mon routeur lors de son démarrage. Cela arrive environ 3 fois sur 4. Cela peut être énervant donc je préfère vous prévenir. Je ne me suis pas penché sur l'origine de ce problème. Mais ne vous inquiétez pas, si vous utilisez la déclinaison brcm47xx, le watchdog se chargera de redémarrer le système pour arrêter le plantage. Mais cela prend un peu de temps (par défaut : 1 minute + le temps du boot).
Fin de l'édit

Installation

Vous devriez être habitué maintenant :

opkg update && opkg install knockd

Configuration de knockd

La configuration s'effectue depuis le fichier /etc/knockd.conf. Voici le contenu que je vous propose :

[options]
	UseSyslog
	interface	= eth0.1
 
[opencloseSSH]
	sequence		= 2222:tcp,3333:udp,4444:tcp
	seq_timeout		= 2
	tcpflags		= syn,ack
	command			= /usr/sbin/iptables -t filter -I INPUT -i eth0.1 -s %IP% -p tcp -m state --state NEW -m tcp --dport 22 --tcp-flags SYN,RST,ACK SYN -j ACCEPT
	cmd_timeout		= 5
	stop_command		= /usr/sbin/iptables -t filter -D INPUT -i eth0.1 -s %IP% -p tcp -m state --state NEW -m tcp --dport 22 --tcp-flags SYN,RST,ACK SYN -j ACCEPT

Explications :

  • UseSyslog permet d'envoyer le journal (les tentatives) au démon syslog. Cela permet donc de grouper le journal de knockd avec les autres journaux du système, ce qui les rend plus faciles à lire et à exporter. Si vous préférez écrire le journal de knockd dans un autre fichier, utilisez la directive "logfile". À noter que rien ne vous empêche d'utiliser les deux méthodes d'enregistrement du journal en même temps.
  • interface permet de spécifier l'interface sur laquelle knockd doit écouter. Par défaut, il tente d'écouter sur toutes les interfaces et bloque sur eth0 avec le message d'erreur suivant : "could not get IP address for eth0". Ce qui est normal puisque eth0 ne peut pas être utilisé directement mais par le biais d'eth0.1 ou de br-lan puisqu'elle fait partie d'un bridge.
  • [opencloseSSH] permet de définir un service. Mettez le nom que vous voulez.Évidemment, vous pouvez définir plusieurs services, avec des séquences différentes.
  • sequence permet de définir les ports auxquels nous frapperons et dans quel ordre nous les frapperons. Comme vous le constatez, vous pouvez mixer des ports tcp et udp. Vous pouvez également mettre le nombre de ports que vous souhaitez. Je vous conseille vivement de mettre une combinaison plus robuste que celle de cet exemple.
  • seq_timeout définit le délai après lequel la séquence est abandonnée si elle n’a pas été menée à terme. Vous devriez la régler en fonction de l'activité réseau de votre routeur. En effet, s’il y a trop d'activité, les paquets du knock pourraient être dispersés et traiter trop tard.
  • tcpflags permet de spécifier à knockd de ne prêter attention qu'aux paquets marqués avec les drapeaux définis.
  • command est la commande qui sera lancée lors de la réussite du knock. Ici, elle ouvre le port SSH, seulement pour l'IP ayant réussi le knock. Si votre routeur à une IP fixe sur l’interface WAN, vous pouvez rendre la règle plus spécifique en ajoutant le paramètre "-d <ip_wan>".
  • cmd_timeout est le temps après lequel le knock n'est plus considéré comme étant valide. Ici, il faut juste spécifier assez de temps pour lancer une connexion sur le port ouvert par le knock.
  • stop_command est, bien entendu, la commande qui sera exécutée dès que le knock ne sera plus valide. Ici, elle ferme le port.

Note : j'ai honteusement pompé et adapté ce fichier de configuration depuis le man knockd.

Configuration d'iptables

Regardons la règle qui nous ouvre le port SSH : elle accepte une nouvelle connexion dans l'état NEW, en provenance d'une machine bien définie, à destination du port tcp/22 du routeur à condition que le paquet provienne de l'interface WAN. Cela signifie que les paquets suivants seront ignorés et donc la connexion perdue. Évidemment, nous considérons que la politique par défaut de la chaine INPUT est DROP. De plus, après le timeout, knockd refermera cette porte d'entrée.

Il faut donc ajouter une règle dans la chaine INPUT qui autorise les connexions établies et à destination du port tcp/22 du routeur :

iptables -t filter -I INPUT -i eth0.1 -p tcp -m state --state ESTABLISHED -m tcp --dport 22 -j ACCEPT

Même combat si vous filtrer les sorties (ce qui est une bonne pratique) : il faudra créer, dans la chaine OUTPUT, une règle qui accepte les connexions établies provenant du port tcp/22 du routeur et qui sont à destination de l'interface WAN :

iptables -t filter -I OUTPUT -o eth0.1 -p tcp -m state --state ESTABLISHED -m tcp --sport 22 -j ACCEPT

Je suis au courant que mes règles ne respectent pas les chaînes introduites par OpenWRT. Mais, n'utilisant plus ces chaines, je vous mets les chaines par défaut plutôt que de vous imposer les miennes. Libre à vous de les remplacer pour convenir à votre configuration.

Script d'init

Nous allons créer un script d'init (sous /etc/init.d). Ces scripts sont toujours pratiques pour relancer rapidement un démon (/etc/init.d/knockd restart est quand même plus pratique que les commandes kill `pidof knockd|sed "s/$$//g"` + knockd -d & ou équivalents). OpenWRT n'en intègre pas pour l'instant pour knockd. Néanmoins, nous ne créerons pas de lien symbolique vers ce script dans le répertoire /etc/rc.d/ afin que knockd ne soit pas lancé deux fois de suite (voir ci-dessous).

Voilà le script que je vous propose (/etc/init.d/knockd) :

#!/bin/sh /etc/rc.common
 
START=70

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

DAEMON=/usr/sbin/knockd
NAME=knockd
DESC="Port knocking"
 
start() {

	echo "Starting $DESC: $NAME"
	logger Starting $DESC: $NAME
	`$DAEMON -d &`

}
 
stop() {
	echo "Stopping $NAME"
	logger Stopping $NAME
	kill `pidof $NAME|sed "s/$$//g"` > /dev/null 2>&1

}
 
restart() {
	echo "Restarting $DESC: $NAME... "
	stop
	sleep 2

	start
}

J'ai pompé et adapté ce script sur celui fourni par/pour dnscache.

Si vous avez configurer knockd pour utiliser syslogd, alors les commandes logger, qui, pour rappel, permettent d'enregistrer un message dans les logs du système, ne sont pas utiles puisque knockd écrira de lui-même des messages lors de ses démarrages/arrêts.

Il faudra penser à rendre ce script exécutable :

chmod ugo+x /etc/init.d/knockd

Démarrage automatique

Tout comme dnscache, knockd est inefficace après une déconnexion qui surviendrait sur l'interface WAN. Nous allons donc créer un script pour hotplugd. Pour ceux qui ne comprennent pas, voir le billet sus-linké.

C'est pour cela que l'on ne devait pas faire de lien symbolique vers /etc/init.d/knockd dans /etc/rc.d toute à l'heure : hotplud lancera knockd automatiquement au démarrage de la machine, pas besoin de le lancer une deuxième fois par erreur (voir le billet sus-linké pour des explications détaillées).

Voici donc le script pour hotplugd que je vous propose (/etc/hotplug.d/iface/30-dknock) :

#!/bin/sh
 
if [ "$INTERFACE" = "wan" ]

then
	KNOCKD_RUNNING=`ps  | grep knockd | grep -v grep`

 
	case "${ACTION:-ifup}" in
		ifup)
			[ -z "$KNOCKD_RUNNING" ] && /etc/init.d/knockd start
		;;

 
		ifdown)
			[ -n "$KNOCKD_RUNNING" ] && /etc/init.d/knockd stop
		;;

	esac
fi

Ouvrir le port ssh sur un client GNU/linux Debian

Il faut installer le logiciel knock :

# apt-get install knockd

Puis, lorsque vous voudrez vous connecter en SSH à votre routeur, il faudra exécuter la commande :

$ knock <séquence> && ssh root@OpenWRT

Par exemple, pour l'exemple configuré ci-dessus, il faudrait exécuter :

$ knock 2222 3333:udp 4444 && ssh root@OpenWRT

Pour aller plus loin

C'est ici que je m'arrête : je pense que la configuration actuelle de knockd couplée à l'utilisation d'une authentification par clé publique/privée suffira à protéger mon accès ssh. De plus knockd empêchera les attaques par bruteforce. Et si un jour la séquence est découverte, vous en serez informé dans les logs (= vous verrez les tentatives de connexions frauduleuses au démon SSH) et vous pourrez la changer.

Mais si vous voulez plus de sécurité, vous pouvez utiliser la fonctionnalité one time sequence de knockd. Il s'agit d'un fichier qui contient autant de séquences que vous voulez. Chaque fois qu'une séquence est validée, elle n'est plus acceptée. Ainsi, une séquence n'est valable qu'une seule fois (sauf exceptions). Voir l'exemple dans le man knockd. Pour vous simplifier l'utilisation de cette fonctionnalité, je vous conseille l'utilisation des knockd-utils (GNU GPL). Rien ne vous empêche dès lors d'imaginer une cron qui génère régulièrement un fichier de séquence sur l'ordinateur depuis lequel vous souhaitez accéder à votre routeur puis qui vous en stocke un exemplaire dans votre home et qui en upload une copie, via scp, sur votre routeur et relance le démon knockd pour qu'il prenne en compte ce fichier.

Rien ne vous empêche également de porter, sur OpenWRT, un démon qui fait de l'encrypted port knocking tels que fwknop (GNU GPL) ou cryptknock (flou sur la licence mais sources disponibles).

Authentification par clé publique/clé privée

C'est une fonctionnalité de base de SSH et elle permet d'éviter de la manière la plus simple du monde le problème du bruteforçage de votre mot de passe. On n'utilise plus un mot de passe mais une clé privée et une clé publique. Cette méthode permet, via les agent ssh de s'authentifier en toute sécurité sans
pour autant saisir un mot de passe à longueur de journée. Elle permet également l'automatisation de tâches (puisque la connexion ne requiert plus de mot de passe). On peut même faire de l'administration à travers plusieurs machines (voir : ssh agent forwarding).

Je ne vous expliquerai pas comment mettre en place une telle authentification puisque les gars d'openWRT l'ont déjà fait : Dropbear Public Key Authentication - OpenWRT Wiki. Si vous êtes un peu perdu, je vous conseille cette lecture : La connexion sécurisée à distance avec SSH - Site du Zéro.

Je préciserai juste que vous pouvez très bien utiliser des clés RSA d'une longueur supérieure à 1024 bits (ex. : 2048 bits). Il suffit d'utiliser un paramètre supplémentaire lors de la génération :

ssh-keygen -t rsa -b 2048

Les clés DSA doivent en revanche, avoir une longueur strictement égale à 1024 bits pour respecter le FIPS 186-2.

Si vous générer votre clé depuis puttygen, n'oubliez pas de copier le début (ex. : "ssh-rsa") sinon cela ne fonctionnera pas.

ÉDIT du 16/08/2011 à 14h50 :
Je précise également que si vous créez des comptes invités/non-root/sans droits d'administration, les clés publiques permettant de se connecter avec ces comptes devront se trouver dans le fichier ~/.ssh/authorized_keys de chacun. Exemple : vous créez un compte "toto" dont le home directory est /home/toto. La clé publique de toto devra donc se trouver dans le fichier /home/toto/.ssh/authorized_keys pour que l'authentification par clé publique soit acceptée. Source : Dropbear PublicKey Authentication for multiuser setup sur forum.openwrt.org.

Fin de l'édit

Ressources

Voici les sites sur lesquels j'ai honteusement appris, pompé, adapté mais que je n'ai pas encore cité dans ce billet :

Configuration rapide d’iptables pour les réseaux insecures

Table des matières

Nous allons voir, rapidement, comment configurer le noyau, avec iptables, lors de l'utilisation d'un réseau insecure. L'objectif premier est d'éviter la fuite de trafic réseau en dehors de notre tunnel SSH.

Savoir quels flux doivent circuler

En entrée

  • Tous les paquets appartenant à une connexion que nous avons ouverte doivent pouvoir entrer.
  • Tous les paquets relatifs à une connexion que nous avons ouverte doivent pouvoir entrer (c'est notamment le cas de certains messages d'erreur ICMP).
  • Facultatif : les paquets DHCP doivent pouvoir passer si vous utilisez ce protocole.
  • Tous les paquets en provenance de la boucle locale (127.0.0.1) doivent pouvoir passer (le port forwarding via SSH fait partit de cette catégorie dans le sens où SSH doit être autorisé à recevoir des paquets sur un port précis (6666 dans mon cas)).

En sortie

  • Tous les paquets à destination de la boucle locale (127.0.0.1) doivent pouvoir passer (là aussi, le port forwarding via SSH fait partit de cette catégorie dans le sens où les programmes doivent être autorisés à émettre en direction de ce port et SSH doit être autorisé à émettre depuis ce port vers les programmes).
  • Tous les paquets à destination du port tcp/443 (https) du serveur web hébergeant le portail captif doivent passer à condition que le programme soit cURL (afin de faire de la connexion automatique, nous y reviendrons dans un prochain billet).
  • Tous les paquets à destination du port 53 (tcp ou udp, voir billet précédent) du résolveur DNS du réseau insecure doivent passer à condition que le programme émetteur soit ssh ou scp (afin de résoudre le nom de domaine de votre serveur SSH).
  • Facultatif : les paquets DHCP doivent pouvoir passer si vous utilisez ce protocole.
  • Tous les paquets à destination du port tcp/22 sont autorisés si ils sont émis par ssh.

Comment savoir quel est le programme émetteur ?

Je suis certain que d'autres outils sont plus adaptés néanmoins je souhaite utiliser uniquement iptables dans le cas suivant.

La théorie

Iptables possède un module "owner" qui permet de réaliser un traitement sur un paquet selon l'uid ou le gid du programme qui a créé la socket. Sur les noyaux non SMP destinés aux systèmes n'ayant qu'un seul processeur avec un seul core, iptables propose d'autres options : traiter un paquet selon le pid du programme qui a ouvert la socket, le sid ou encore selon le chemin du programme sur le disque (cmd-owner).

Cette dernière option est celle qu'il nous faut. Mais comme je suis sur un noyau SMP, elle n'est pas présente. Il va donc falloir ruser.

Il faudrait que nos programmes aient un uid ou un gid unique qui permettrait de les reconnaitre ... bingo ! On va utiliser le setgid bit ! On aurait pu utiliser le setuid bit mais je pense que le gid bit aurai moins d'effets collatéraux.

Nous allons créer un groupe que nous appellerons "net". Nous allons ensuite modifier le groupe auquel appartient l'exécutable pour qu'il corresponde a "net". Puis nous activerons le setgid bit. Comme ça, le programme ne sera plus lancé avec le groupe de l'utilisateur qui le lancera mais bien avec le groupe "net". Iptables pourra donc reconnaitre ses sockets et laisser passer ses paquets.

Limites de cette méthode :

  • Elle est fastidieuse : pour chaque programme dont nous voulons qu'il accède à un service réseau, il faut changer son groupe d'appartenance et activer le setgid bit.
  • À chaque fois qu'un programme autorisé est mis à jour, le chown/chmod est perdu et le programme se retrouve donc bloqué par iptables. Il faut donc regarder attentivement son apt-get upgrade et s'en souvenir le jour où le programme ne marche plus.
  • Dans le cas où il y a plusieurs utilisateurs sur la machine, des utilisateurs peuvent laisser fuiter des informations en compilant un programme et en changeant le groupe d'appartenance et en leur activant le setgid bit. Pour ceux qui en doute, rappelons qu'il y a deux personnes autorisées à utiliser chmod et chown sur un fichier donné : root et le propriétaire du fichier. Il faut quand même relativiser ce point là.

La pratique

On créer le groupe :

sudo groupadd net

Pour chaque programme que vous voulez autoriser :
Il faut d'abord trouver le chemin vers l'exécutable de ce programme (ici scp) :

whereis -b scp

Ensuite, il faut changer le groupe du fichier :

sudo chown root:net /usr/bin/scp

Puis, il faut activer le setgid bit :

sudo chmod g+s /usr/bin/scp

Écrire les règles iptables correspondantes

En entrée

Tous les paquets appartenant à une connexion que nous avons ouverte doivent pouvoir entrer.
Tous les paquets relatifs à une connexion que nous avons ouverte doivent pouvoir entrer.

sudo iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

Facultatif : les paquets DHCP doivent pouvoir passer si vous utilisez ce protocole.

sudo iptables -A INPUT -p udp -m state --state NEW -m udp --sport 67 --dport 68 -j ACCEPT

Tous les paquets en provenance de la boucle locale (127.0.0.1) doivent pouvoir passer

sudo iptables -A INPUT -i lo -j ACCEPT

Tout le reste est ignoré :

sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP

En sortie

Tous les paquets à destination de la boucle locale (127.0.0.1) doivent pouvoir passer.

sudo iptables -A OUTPUT -d 127.0.0.1/32 -j ACCEPT

Tous les paquets à destination du port 443 (https) du serveur web (10.2.2.1) hébergeant le portail captif doivent passer à condition que le programme soit cURL.

sudo iptables -A OUTPUT -d 10.2.2.1/32 -p tcp -m tcp --dport 443 -m owner --gid-owner net -j ACCEPT

Tous les paquets à destination du port 53 du résolveur DNS (10.2.2.4) du réseau insecure doivent passer à condition que le programme émetteur soit ssh ou scp.

sudo iptables -A OUTPUT -d 10.2.2.4/32 -p udp -m udp --dport 53 -m owner --gid-owner net -j ACCEPT
sudo iptables -A OUTPUT -d 10.2.2.4/32 -p tcp -m tcp --dport 53 -m owner --gid-owner net -j ACCEPT

Tous les paquets à destination du port 22 sont autorisés si ils sont émis par ssh.

sudo iptables -A OUTPUT -p tcp -m tcp --dport 22 -m owner --gid-owner net -j ACCEPT

Facultatif : les paquets DHCP doivent pouvoir passer si vous utilisez ce protocole.

sudo iptables -A OUTPUT -p udp -m state --state NEW -m udp --sport 68 --dport 67 -j ACCEPT

Tout le reste est ignoré :

sudo iptables -P OUTPUT DROP

Conserver les règles

Pour sauvegarder les règles :

sudo iptables-save > reglesiptables

Voici ce que l'on obtient pour ce billet :

# Generated by iptables-save v1.4.12.2 on Tue Apr  3 16:52:20 2012
*filter
:INPUT DROP [36146:2386529]
:FORWARD DROP [0:0]
:OUTPUT DROP [224:17520]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p udp -m state --state NEW -m udp --sport 67 --dport 68 -j ACCEPT
-A OUTPUT -d 127.0.0.1/32 -j ACCEPT
-A OUTPUT -d 10.2.2.1/32 -p tcp -m tcp --dport 443 -m owner --gid-owner 1001 -j ACCEPT
-A OUTPUT -d 10.2.2.4/32 -p udp -m udp --dport 53 -m owner --gid-owner 1001 -j ACCEPT
-A OUTPUT -d 10.2.2.5/32 -p udp -m udp --dport 53 -m owner --gid-owner 1001 -j ACCEPT
-A OUTPUT -d 10.2.2.4/32 -p tcp -m tcp --dport 53 -m owner --gid-owner 1001 -j ACCEPT
-A OUTPUT -d 10.2.2.5/32 -p tcp -m tcp --dport 53 -m owner --gid-owner 1001 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 22 -m owner --gid-owner 1001 -j ACCEPT
-A OUTPUT -p udp -m state --state NEW -m udp --sport 68 --dport 67 -j ACCEPT
COMMIT
# Completed on Tue Apr  3 16:52:20 2012

Pour les restaurer avant de vous connecter physiquement au réseau insecure, il faudra taper :

sudo iptables-restore < reglesiptables

Charger les règles au boot

ÉDIT du 30/12/2013 à 12h30 : Cette partie est inutile : iptables-persistent est disponible dans les dépôts Debian et fait le job. Fin de l'édit

Cela peut être utile si vous passez plus de temps sur un réseau insecure que sur un réseau secure.

Copions les règles sauvegardées par iptables-save dans /etc/init.d :

sudo cp reglesiptables /etc/init.d/reglesiptables

Créons un script /etc/init.d/loadIptables :

sudo vi /etc/init.d/loadIptables

Donnons-lui le contenu suivant :

#!/bin/bash

### BEGIN INIT INFO
# Provides:          loadIptables
# Required-Start:    
# Required-Stop:     
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# X-Interactive:     false
# Short-Description: Start/stop iptables rules
### END INIT INFO

 
iptables-restore < /etc/init.d/reglesiptables

Donnons les droits d'exécution à ce script :

sudo chmod +x /etc/init.d/loadIptables

Demander l'exécution de ce script au boot :

sudo update-rc.d loadIptables defaults

Une autre manière de faire serait d'utiliser le fichier /etc/network/interfaces et l'option "pre-up". Exemple :

allow-hotplug eth0
iface eth0 inet dhcp
pre-up iptables-restore < /etc/network/reglesiptables

Lisez le man interfaces pour plus d'informations.

A propos d'IPv6

Si vous voulez être sûr qu'aucune fuite n'aura lieu, vous pouvez configurer le noyau dans ce sens. Le principe est le même que celui exposé ci-dessus à l'exceptions des commandes (iptables => ip6tables, iptables-save => ip6tables-save, etc.) et des IP, évidemment 😉 .

Exemple de fichiers ip6tables :

# Generated by ip6tables-save v1.4.12.2 on Tue Apr  3 16:56:30 2012
*filter
:INPUT DROP [16:1280]
:FORWARD DROP [0:0]
:OUTPUT DROP [18:1152]
COMMIT
# Completed on Tue Apr  3 16:56:30 2012

Notes

  • Certaines parties de ce billet sont inspirées (voir plus) de la documentation Ubuntu francophone : Iptables.
  • La configuration proposée ci-dessus n'est pas très agressive puisqu'en définitive tous les programmes qui feront partit du groupe "net" et qui auront le setgid bit d'activé, auront accès aux ports tcp/443, tcp/22, udp/53, tcp/53 même si ils n'en ont pas besoin. On aurait pu être plus agressif et créer un groupe pour cURL et un groupe pour ssh/scp afin que cURL n'ai accès qu'aux ports tcp/443, udp/53 et tcp/53 et ssh/scp uniquement aux ports tcp/22, udp/53 et tcp/53. Néanmoins cela complexifie les règles iptables pour un gain en sécurité pas franchement évident.
  • Certains d'entre vous remarquerons que les règles iptables sont incomplètes : il manque par exemple la gestion des messages d'erreur ICMP. Je sais que ce n'est pas l'idéal d'ignorer les paquets ICMP mais néanmoins je suis pas le seul puisqu'après vérification, la passerelle de mon réseau insecure bloque les paquets ICMP. Donc, sur le réseau, il ne reste plus que les paquets ICMP des machines faisant parties du réseau insecure, mais comme je ne communique jamais avec ces machines, j'ai peu de chance qu'elles m'envoient un message ICMP utile.

Proxychains : le retour

Table des matières

Dans un de mes billets sur proxychains, j'indiquais n'avoir jamais eu de soucis avec ce logiciel. Il a fallu que j'écrive ça pour en avoir. Je vais apporter une solution aux deux problèmes que j'ai rencontré. Je vous conseille de lire le billet précédent afin de comprendre ce dont je parle ici.

ERROR: ld.so: object 'libproxychains.so.3' from LD_PRELOAD cannot be preloaded: ignored.

Voici à quelle erreur je suis maintenant confronté. La solution exposée sur nombre de forums, a savoir créer un lien symbolique ne fonctionne pas chez moi.

En observant un peu, en lançant un programme proxychainé depuis un shell par exemple, on remarque que cette erreur apparaît lors de la résolution des noms de domaine. On se demande alors si l'erreur ne viendrait pas du script chargé de la résolution des noms de domaine, /usr/lib/proxychains3/proxyresolv. On l'ouvre et on remarque cette ligne :

export LD_PRELOAD=libproxychains.so.3

Si on en croit les forums qui conseillent de créer un lien symbolique, il faut créer le-dit lien car le chemin vers la libproxychains.so.3 serait codé en dur dans le code de proxychains et dans proxyresolv. C'est le déclic : et si nous mettons le chemin absolu vers la libproxychains.so.3 dans le script proxyresolv, cela corrige-t-il le problème ?

On cherche l'emplacement de la libproxychains.so.3 :

sudo find / -name "libproxychains.so.3"

On la trouve dans le répertoire /usr/lib, quelle surprise.

On modifie donc proxyresolv en remplaçant :

export LD_PRELOAD=libproxychains.so.3

par

export LD_PRELOAD=/usr/lib/libproxychains.so.3

Nous venons de résoudre le premier problème. Je ne sais pas expliquer pourquoi cela ne fonctionne plus subitement du jour au lendemain.

Les requêtes DNS ne sont plus proxychainées

En effet, je me suis aperçu que proxyresolv s'adresse bien au serveur définit dans sa variable "DNS_SERVER" mais sans passer par le tunnel SSH. Merci à mon netfilter bien configuré qui a évité les fuites vers le réseau local.

J'ai donc pensé à faire une redirection du port local tcp/53 vers le port tcp/53 de mon résolveur DNS. Cette redirection s'ajoute à ma redirection dynamique. Concrètement cela donne :

sudo ssh -ND6666 -L53:resolveurDNSDNS:53 login@server

Ensuite, il suffit de définir 127.0.0.1 comme "DNS_SERVER" dans le script proxyresolv et le problème sera résolu.

Pour ceux qui ne veulent pas lancer ssh avec les droits root, il y a un moyen de déporter les droits root sur un autre programme :

sudo apt-get install socat
ssh -ND6666 -L5300:resolveurDNS:53 login@server
sudo socat TCP4-LISTEN:53,reuseaddr,fork TCP4:127.0.0.1:5300

Explication : proxyresolv va envoyer sa requête sur le port tcp/53. socat va la relayer sur le port 5300. SSH va récupérer la requête, la faire transiter dans le tunnel et l'envoyer sur le port tcp/53 de la machine resolveurDNS.

Au lieu d'utiliser socat, on peut se la jouer netfilter ce qui évite de devoir créer des scripts pour lancer socat automatiquement et de donner des droits inutiles a ssh/socat :

ssh -ND6666 -L5300:serveurDNS:53 login@server
sudo iptables -t nat -A OUTPUT -s 127.0.0.1 -d 127.0.0.1 -p tcp --dport 53 -j REDIRECT --to-port 5300

Pour ceux qui se demande pourquoi définir 127.0.0.1 comme résolveur DNS dans proxyresolv et non pas dans /etc/resolv.conf : d'une part car cela empêcherait ssh de résoudre le nom de la machine avec laquelle il doit communiquer, dans le cas où votre serveur ssh a une ip dynamique. D'autre part, les autres applications fonctionnent très bien donc pourquoi les sanctionner ?

Pour ceux qui s'étonne de voir le protocole DNS agir sur le protocole TCP, sachez que cela avait été prévu par les RFC. Les administrateurs de réseaux qui bloquent le port tcp/53 ne sont donc pas consciencieux/informés. Néanmoins, une requête de résolution doit être effectuée sur TCP uniquement dans le cas où la réponse excède 512 octets (mais la taille autorisé via UDP a été augmentée dans le RFC 2671 afin de prendre en charge DNSSEC).

Nous faisons donc une entorse aux règles en faisant passer toutes nos requêtes, même celles qui n'excèdent pas la taille maximale, par le port tcp/53. Relativisons en disant que d'une part, comme SSH permet le port forwarding uniquement sur le protocole TCP, nous sommes obligé de passer via TCP à un moment donné. D'autre part, dés que l'on tente de masquer son trafic et/ou de contourner un firewall, on transgresse forcement les standards (on se souviens du TCP over TCP du billet précédent).

Pour ceux qui veulent étudier une solution alternative : Tunnelling UDP packets through SSH. Vous constaterez que cette méthode impose aussi un passage via TCP et qu'elle est plus complexe à mettre en place et à automatiser.

SSH : du port forwarding au VPN bon marché

Table des matières

Quand je suis sur un réseau insecure (wifi, dont la charte précise que des contrôle techniques de bon usage du réseau peuvent être effectués sans préavis, etc. ), je sécurise mes connexions grâce à SSH et à ses fonctionnalités de port forwarding / tunnels. Dans cet article, je souhaite vous faire un retour sur mes découvertes plus ou moins récentes.

Voici une page web qui explique clairement le principe du port forwarding avec SSH et la différence entre une redirection locale (ssh -L) et une redirection distante (ssh -R) : Tynsoe projects.

La redirection locale

Au commencement, j'utilisais une redirection locale afin de rediriger ma navigation internet sur un proxy Squid se trouvant sur un serveur "at home". Donc une bête ligne de commande comme celle-ci suffisait :

ssh -NL8080:localhost:3128 login@host

Ensuite, je configurais mon navigateur pour utiliser le proxy HTTP/HTTPS suivant :

  • Hôte : 127.0.0.1
  • Port : 6666

Et dans les rares cas ou j'avais besoin d'un autre service, je transférais un autre port en même temps :

ssh -NL8080:localhost:3128 -L2200:serveurSSH:22 login@host

Je pouvais alors surfer tranquillement et me connecter à la machine serveurSSH via l'adresse 127.0.0.1:2200

La redirection dynamique

Ensuite, j'ai voulu utiliser plus de services (mail, messagerie instantané, etc.) et j'ai donc trouvé les limites de la redirection locale de ports (transférer tous ces ports à la mano c'est pas top). De plus, mon serveur proxy était en panne. Mais j'ai découvert la redirection dynamique (ssh -D). Du coup, c'est mon fidèle WRT54GL qui me sert de proxy.

Maintenant, une simple ligne de commande suffit pour avoir accès à tous mes services :

ssh -ND 6666 login@host

Ensuite, il faut définir un proxy SOCKS dans les paramètres de chaque application dont vous avez besoin :

  • Hôte : 127.0.0.1
  • Port : 6666

Remplacez 6666 par le port que vous avez choisi lors de l'initialisation de la connexion SSH.

Se pose une première problématique : que faire si l'application ne possède pas une telle option ?
Se pose ensuite une seconde problématique : certains applications n'utilise pas le proxy pour faire transiter certaines informations. C'est typiquement le cas de la résolution d'un nom de domaine via le protocole DNS. A quoi peut bien servir un tunnel si le réseau insecure arrive encore à voir une partie de notre trafic réseau ?

Ces deux questions trouvent la même réponse : il faut utiliser un programme qui capture les "appels réseau" d'un programme et force la redirection de ceux-ci dans le tunnel SSH. Il en existe plusieurs : tsocks, torsocks, proxychains, etc. . Il fait néanmoins faire attention : certains laisse encore passer les fameuses requêtes DNS entre les mailles de leur filet. Personnellement, j'utilise proxychains et je n'ai jamais rencontré de problèmes.

EDIT 09/01/2011 19h35 : Il faut dire aussi que mon choix pour proxychains a été influencé par les différents inconvénients qui apparaissent sur les alternatives :

  • La version de tsocks disponible sur sourceforge laisse fuiter les résolutions DNS, à moins d'appliquer un patch. De plus, je rencontre beaucoup d'erreur de segmentation avec des applications courantes
  • Torsocks me sort une erreur de segmentation pour quelques uns de mes programmes. Néanmoins, il y a quand même des progrès, par rapport à tsocks, sur ce point là.
  • Dsocks me paraissait un peu "brouillon" avec son script python servant de forwardeur DNS. De plus, le script ne fonctionne pas et me lève des exceptions.
  • Socat est une alternative que je n'ai pas essayé mais qui me parait plus que correcte mais lourde à utiliser.

Le fichier de configuration (/etc/proxychains.conf) est d'une simplicité enfantine. Voici le mien (j'ai supprimé quelques commentaires mais je vous invite à les lire dans votre fichier) :

# Strict - Each connection will be done via chained proxies
# all proxies chained in the order as they appear in the list
# all proxies must be online to play in chain
# otherwise EINTR is returned to the app
strict_chain

# Quiet mode (no output from library)
quiet_mode

# Proxy DNS requests - no leak for DNS data
proxy_dns

[ProxyList]
socks5 127.0.0.1 6666

Il ne vous reste plus qu'à utiliser proxychains :

proxychains firefox
proxychains thunderbird
proxychains filezilla
proxychains pidgin
...
sudo proxychains apt-get update/install

Il ne vous reste plus qu'à modifier les raccourcis de votre menu (Gnome, KDE, etc.) afin d'inclure proxychains dans la commande à lancer pour les programmes dont vous souhaitez qu'il passent via le tunnel SSH.

A noter quand même que certains logiciels jouent le jeu et ne laisse pas filer les requêtes DNS, même sans utiliser proxychains. C'est le cas par exemple de Firefox, une fois la valeur "true" passée à l'option booléenne "network.proxy.socks_remote_dns" dans le about:config. Par contre, je n'ai pas constaté d'améliorations en faisant la même manipulation sous Thunderbird.

EDIT 09/01/2011 22h00 : Pour plus d'information sur le transfert des requêtes DNS via un proxy SOCKS, voir cet article : SSH, redirection dynamique, proxychains et DNS.

Vous comprenez que votre meilleur ami ici sera Tsark/Wireshark (ou n'importe quel autre programme d'analyse du trafic réseau), afin de tester qu'aucun flux transite en clair. Il apparait évident de procéder à ces tests avant d'aller dans le réseau insecure. Un iptables correctement paramétré permet également d'éviter les fuites causées par les démons ou par les programmes que l'utilisateur aurait oublié de proxyfier. Voir ce billet à ce sujet : Configuration rapide d’iptables pour les réseaux insecures.

ÉDIT 23/02/2011 22h45 :

Lorsque vous n'utilisez pas votre tunnel SSH, la connexion est fermée automatiquement. Pour maintenir la connexion, il suffit de se rappeler de la notion de keepalive.

Encore une fois, je ne réinventerai pas la roue puisqu'une recherche sur Google vous conduira sur ce blog : Aaron Toponce : Keeping Your SSH Connection Alive.

J'ajouterai cependant que vous pouvez définir le paramètre "ServerAliveInterval" dans le fichier /etc/ssh/ssh_config, ce qui évite de le répéter lors de chaque connexion. Personnellement, je définis un intervalle de 5 minutes soit 300 secondes : c'est amplement suffisant pour maintenir la connexion.

Si toute cette méthode vous semble encore trop contraignante, il vous reste encore la possibilité de vous monter un VPN avec OpenVPN. Je me suis penché sur cette alternative mais, malheureusement, mon WRT54GL n'a pas assez d'espace libre pour accueillir la libssl nécessaire au fonctionnement d'OpenVPN. Donc je n'ai pas creusé le sujet plus loin. Néanmoins, excellents tutoriel existent sur le net.

Un VPN bon marché avec SSH

Il y a quelques jours, je me disais qu'il devait y avoir un moyen de s'amuser avec SSH et les interfaces tun/tap. Je me tourne vers mon ami Google afin d'avoir une idée sur la marche à suivre. Je découvre alors que d'autres personnes ont eu mon idée depuis bien longtemps et notamment les développeurs d'openSSH qui permet, depuis sa version 4.3, de créer un tunnel de niveau 3 grâce aux interfaces virtuelles tun. Si j'étais un "artiste" ou un patent troll, je porterai plainte ... oser me voler mon idée que je n'avais pas à l'époque, c'est quand même scandaleux ! 😉 .

Voici les tutoriels qui m'ont éclairés sur le sujet :

J'ai évidemment voulu tester tout ça, en utilisant la méthode manuelle (sans ifup), avec une machine virtuelle, avant une mise en production sur mon WRT54GL. Il n'y a aucune difficulté et ça fonctionne très bien (avec la machine virtuelle).

Malheureusement, dropbear, le serveur ssh allégé fourni par défaut avec OpenWRT, ne supporte pas ce type de tunnel. Installer OpenSSH sur mon routeur pose le même problème que celui de l'installation d'OpenVPN : la nécessaire libssl est trop lourde.

Donc je suis contraint d'utiliser un tunnel dynamique pour sécuriser mes connexions depuis un réseau insecure. Pour l'instant, cela me convient. Mais il faudra quand même que je songe à greffer une carte mémoire à mon WRT54GL.

Une dernière chose : comme signalé dans les commentaires de Debian Administration ou sur la page de la Community Ubuntu Documentation, la méthode de VPN over SSH n'est pas la meilleure solution du fait que le protocole SSH repose sur TCP et que l'on va faire circuler des trames TCP dans ce tunnel. Cette solution est impeccable quand il n'y a pas de problèmes sur le(s) réseau(x) traversé(s), mais dans le cas d'une perte de paquet, on assiste à un effondrement interne de la connexion. C'est pour cela qu'il veut mieux utiliser un VPN basé sur OpenVPN, configuré en UDP, bien entendu.

Je vous invite à lire ces deux documents, le deuxième étant la traduction, en français, du premier, afin d'avoir plus d'informations sur le sujet :

Note : dans cet article, je me préoccupe des systèmes UNIX/UNIX-like. Mais sous Windows, PuTTY permet la redirection locale, distante et dynamique. Ensuite, il doit exister des équivalents de proxychains, torsocks. Enfin, cygwin doit permettre de monter un VPN over SSH mais je ne me suis pas renseigné sur ce point.