OW-003-ssh-traffic-analysis-fr, révision 2
Sortie : 19 Mars 2001
(Note du traducteur : traduction achevée le 05 avril 2001)
Mise à jour : 6 Août 2001
(Note du traducteur : traduction achevée le 24 août 2001)

 Analyse Passive du Trafic SSH (Shell Sécurisé)
 ----------------------------------------------

Cet avis démontre plusieurs faiblesses dans la mise en oeuvre des protocoles
SSH (Shell Sécurisé). Quand elles sont exploitées, elle laissent l'attaquant
obtenir des informations sensibles en écoutant passivement les sessions SSH
chiffrées. L'information peut être utilisée ultérieurement pour accélérer
les attaques en force brute sur les mots de passe, en incluant le mot de
passe initial de connexion et d'autres mots de passe apparaissant dans les
sessions SSH interactives, tels que ceux utilisés avec su(1) et les mots de
passe "enable" de l'IOS Cisco.

Toutes les attaques décrites dans cet avis requièrent la possibilité
d'écouter (renifler) le trafic réseau entre un ou plusieurs serveurs et
clients SSH.

Des informations de correction, des patchs pour réduire l'impact de
l'analyse de trafic et un outil pour démontrer les attaques sont fournis.


 Impact
 ------

La version 1 du protocole SSH, à moins que sa mise en oeuvre ne prenne des
précautions spéciales pour éviter ceci, expose les longueurs exactes des
mots de passe de connexion utilisées avec l'authentification par mot de
passe. Le protocole SSH-2 ne révèle pas autant d'information, mais un
intervalle de longueurs possible de mot de passe peut toujours être
déterminé.

Des faiblesses additionnelles rend possible de détecter quand un mot de passe
est entré dans une session SSH interactive, et de découvrir même plus
d'informations à propos de ces mots de passe, en incluant leurs longueurs
exactes (avec les deux versions de protocoles) et les informations de
temporisation. Ces dernières exposent la probabilité de caractères possibles
dans chaque position du mot de passe.

Toutes ces informations peuvent être envoyées dans un cracker de mots de
passe pour une augmentation de vitesse significative due à un espace de clé
réduit et d'autres optimisations, en incluant l'attaque des mots de passe
utilisateur dans l'ordre croissant de complexité estimée.

Additionnellement, notre outil d'analyse du trafic SSH est capable de
détecter l'utilisation de l'authentification RSA ou DSA, et dans le cas de
RSA et des mises en oeuvre des serveurs SSH dérivés de SSH 1.2.x, le nombre
d'options du fichier authorized_keys. Ce dernier est possible grâce aux
paquets de débogage envoyés par ces mises en oeuvre. Si une session SSH avec
authentification RSA mais aucune option authorized_keys n'est vue, un
attaquant peut déduire que la machine cliente a la clé privée suffisante
pour obtenir l'accès total au shell du serveur. Si la session est
automatique, la clé privée doit être enregistrée en clair.

Finalement, il est possible de déterminer les longueurs des commandes
shell, et dans quelques cas, les commandes elles même (depuis une petite
liste des plus communes) dans une session interactive (ce qui n'est pas un
problème de sécurité dans la plupart des circonstances).

Il devrait être noté que, malgré leurs simplicités, les attaques d'analyse
de trafic telles que celles présentées dans cet avis n'ont pas été beaucoup
recherchées. Nous espérons que des attaques similaires sont possibles contre
la plupart des autres protocoles de connexion à distance "sécurisés"
(chiffrés). Nous espérons aussi que d'autres attaques d'analyse de trafic
sur SSH soient découvertes. En particulier, il devrait y avoir des motifs
reconnaissables dans les connexions X11 qui ont été redirigées dans SSH,
mais celles-ci sont hors de portée de cet avis.


 Les vulnérabilités de l'authentification par mot de passe
 ---------------------------------------------------------

Lors de l'encapsulation de données en clair dans un paquet du protocole SSH,
les données sont rembourrées à la prochaine limite de 8 octets (ou quelque
soit la taille du bloc de l'algorithme de chiffrement, avec SSH-2),
chiffrées, et envoyées en même temps que le champ longueur du texte en clair.
SSH-1 envoie ce champ en clair.

Comme résultat, un attaquant écoutant passivement une session SSH est
capable de détecter la quantité de texte en clair envoyé dans chaque paquet
-- exacte pour SSH-1, ou un intervalle de longueurs possibles pour SSH-2.

Puisque le mot de passe de connexion est envoyé dans un paquet du protocole
SSH-1 sans aucune précaution spéciale, un attaquant peut déterminer la
longueur exacte du mot de passe.

Avec SSH-2, d'autres informations (incluant le nom d'utilisateur) sont
transmises dans le même paquet et la longueur du texte en clair est
chiffrée, donc seulement un intervalle de possibles longueurs de mots de
passe peut être déterminé.

Heureusement, grâce à l'utilisation des chaînes C dans la plupart des mises
en oeuvre des serveurs SSH-1, il est normalement possible pour un client SSH
d'ajouter suffisamment de caractères NUL de rembourrage juste pour les mots
de passe sans changement au protocole. Nous recommandons que les mises en
oeuvres futures de serveurs SSH-1 autorisent ce rembourrage, même dans les
cas où les interfaces systèmes sous-jacentes n'impliquent pas
nécessairement cela.

Un contournement alternatif, proposé par Simon Tatham, est d'envoyer une
séquence de messages SSH-1 contenant des chaînes de longueurs croissantes.
Exactement un de ces messages est SSH_MSG_PASSWORD et contient la chaîne du
mot de passe. Tous les autres sont des SSH_MSG_IGNORE. Il est important que
le nombre de messages envoyés reste constant et soit suffisant pour couvrir
le plus long mot de passe que nous espérons voir. Pour transmettre de façon
sûre des mots de passe jusqu'à 32 caractères, 1088 octets de messages SSH-1
sont nécessaires, ce qui devrait toujours tenir dans un segment TCP. Cette
approche a l'avantage qu'aucune supposition à propos de la mise en oeuvre du
serveur SSH-1 n'est faite (autre qu'il mette correctement en oeuvre le
protocole; quelques mises en oeuvre sont connues pour avoir des problèmes
dans le support de SSH_MSG_IGNORE).

Le protocole SSH-2 permet une solution (proposée indépendamment par plusieurs
auteurs de mises en oeuvre SSH-2) avec moins de surcharge, et sans dépendance
d'artifices de mises en oeuvre du protocole. Une paire de messages SSH-2,
SSH_MSG_USERAUTH_REQUEST et SSH_MSG_IGNORE, peut être construit tels que
leur longueur combinée reste constante. Les messages peuvent être envoyés à
la couche transport en une fois.


 Les faiblesses des session interactives
 ---------------------------------------

Avec les sessions shell interactives, les caractères entrés sont normalement
répétés par le système distant, ce qui résulte usuellement dans un paquet
écho par le serveur pour chaque caractère rentré. Toutefois, si une
application désactive l'écho des caractères rentrés, tel que pour l'entrée
d'un mot de passe, les paquets commencent à n'aller que dans une seule
direction -- vers le serveur. Notre simple outil d'analyse de trafic est
capable de détecter ceci facilement et fiablement.

Une fois qu'un attaquant sait que la victime est en train de rentrer un mot
de passe, tout ce qu'il a besoin de faire est de compter les paquets qui
n'ont pas généré de paquet réponse de la part du serveur. Dans le cas de
SSH-1, la somme des tailles des textes en clair donne la longueur exacte du
mot de passe, avec tous les caractères backspace. Avec SSH-2, l'attaquant
doit assumer que chaque paquet contient seulement un caractère du mot de
passe, ce qui est typiquement le cas.

Les délais entre les paquets donnent à l'attaquant des informations
additionnelles sur la probabilité de caractères possibles dans chaque
position du mot de passe. Par exemple, si le délai avant un caractère est
plus important que la plupart des autres délais, c'est probablement que le
caractère requière plus d'une frappe de touche pour être entré.

En tapant des commandes dans un shell en ligne de commandes dans SSH, chaque
caractère génère un petit paquet écho depuis le serveur. Toutefois, une fois
la commande entrée entièrement, un paquet plus large -- contenant le prompt
shell et probablement la sortie de la commande -- est envoyé par le serveur.

En comptant les petits paquets (ou les longueurs des textes en clair dans
les paquets envoyés au serveur, dans le cas de SSH-1), l'attaquant peut
déduire la longueur de chaque commande shell. Pour rendre la détection plus
fiable avec SSH-1, il est habituellement possible de détecter les backspace
en assumant qu'ils produisent une réponse de 3 caractères (^H, espace, ^H).

Une fois encore, les délais peuvent être utilisés -- cette fois-ci pour
déduire les commandes shell habituellement rentrées, depuis une petite
liste de commandes courantes.

La solution partielle que nous proposons est de modifier les serveurs SSH
afin qu'ils simulent des paquets écho quand la répétition du terminal est
désactivée par une application. Le type de message SSH_MSG_IGNORE peut être
utilisé pour assurer que le client ne traite vraiment pas les contenus de
ces faux paquets. Ainsi, aucun changement dans le protocole n'est requis.

Il est important de noter que cette solution partielle peut ne vaincre que
la manière la plus générique de deviner qu'un mot de passe est entré. Dans
beaucoup de cas il est possible de faire la même chose par d'autres moyens,
en incluant l'écoute d'autres trafics réseau en relation et des évènements
locaux au système du serveur SSH.

Résoudre les vulnérabilités de l'analyse de trafic non reliées à
l'information du mot de passe devrait accroître la surcharge du protocole de
façon significative, et donc ne semble pas pratique pour de nombreuses
utilisation courantes de SSH.


 Compression
 -----------

L'utilisation de la compression rend de nombreuses attaques d'analyse de
trafic décrites ci-dessus moins fiables de façon significative. C'est parce
que la même quantité de texte en clair ne résulte plus dans la même quantité
de données transmises. Les tailles de paquets sont plutôt rendus
"aléatoires".

Toutefois, il est probable que la compression permet également encore
d'autres type d'attaques d'analyse de trafic, puisque les changements de
taille de paquet dus à la compression ne sont pas vraiment aléatoires -- ils
dépendent du contenu du paquet de texte en clair.

Nous sommes déjà conscients d'une attaque pratiquable qui est possible grâce
à la compression. Avec SSH-2, le message SSH_MSG_USERAUTH_REQUEST est
transmis après que la compression soit négociée. Si elle est activée, la
taille du segment TCP résultant dépendra de l'entropie du mot de passe en
clair. Si un message SSH_MSG_IGNORE est utilisé pour rembourrer le mot de
passe comme nous avons proposé, la compression peut battre quelques
bénéfices que ceci a pu fournir. Ce cas de problème peut être résolu en
transmettant les messages SSH_MSG_USERAUTH_REQUEST et SSH_MSG_IGNORE non
compressés. Toutefois, ceci n'est pas trivial à mettre en oeuvre si une
bibliothèque générique de compression est utilisée.


 Travaux apparentés
 ------------------

Plusieurs des attaques soulignées dans cet avis étaient également
indépendamment découvertes par l'auteur de l'article suivant, qui en décrit
quelques uns avec plus de détails :

	Dawn Xiaodong Song, David Wagner, Xuqing Tian:
	``Timing Analysis of Keystrokes and Timing Attacks on SSH.''


En particulier, ils révèlent que les temps entre les frappes de touches
divulguent environ 1 bit d'information par paire de caractères, et décrivent
un système d'attaque, Herbivore, qui essaye d'apprendre le mot de passe des
utilisateurs en écoutant les sessions SSH. Herbivore a démontré réduire
l'espace de recherche pour les mots de passe de 8 caractères choisis
aléatoirement par un facteur de 50.


 Réparations
 -----------

Plusieurs mises en oeuvre SSH ont été modifiées pour inclure des réparation
qui réduisent l'impact de quelques attaques d'analyse de trafic décrites
dans cet avis. Il est important de comprendre que ces réparations ne sont
par aucun moyen une solution complète à l'analyse de trafic -- seulement un
remède simple pour les vulnérabilités les plus pressantes décrites
ci-dessus.

OpenSSH :

Les réparations ont été initialement appliquées à OpenSSH à partir de la
version 2.5.0. OpenSSH 2.5.2 contient les versions les plus complètes des
réparations et résout certains problèmes d'interopérabilité associés avec
les versions plus récentes.

PuTTY :

PuTTY 0.52 inclura des défenses contre la découverte de la longueur ou de
l'entropie des mots de passe de connexion, pour SSH-1 and SSH-2.

TTSSH:

TTSSH 1.5.4 fournit quelques protections contre l'analyse du trafic en
rembourrant avec des NUL le mot de passe de connection initial transmis.

Cisco :

Cisco a inclus des contremesures contre les attaques d'analyse de trafic SSH
dans les versions récentes de ses logiciels IOS et CatOS supportant SSH.
Leur avis de sécurité a été posté sur :

	http://www.cisco.com/warp/public/707/SSH-multiple-pub.html

SSH 1.2.x :

Les utilisateurs de SSH 1.2.x peuvent utiliser ce patch non officiel (ce
patch est contre la version 1.2.27, mais s'applique à la version 1.2.31
également). Merci de noter qu'un serveur SSH avec ce patch appliqué
n'interopèrera plus avec les clients en versions 1.2.18 à 1.2.22 (inclus).

--- ssh-1.2.27.orig/sshconnect.c	Wed May 12 15:19:29 1999
+++ ssh-1.2.27/sshconnect.c	Tue Feb 20 08:38:57 2001
@@ -1258,6 +1258,18 @@
     fatal("write: %.100s", strerror(errno));
 }

+void ssh_put_password(char *password)
+{
+  int size;
+  char *padded;
+
+  size = (strlen(password) + (1 + (32 - 1))) & ~(32 - 1);
+  strncpy(padded = xmalloc(size), password, size);
+  packet_put_string(padded, size);
+  memset(padded, 0, size);
+  xfree(padded);
+}
+
 /* Starts a dialog with the server, and authenticates the current user on the
    server.  This does not need any extra privileges.  The basic connection
    to the server must already have been established before this is called.
@@ -1753,7 +1765,7 @@
         /* Asks for password */
         password = read_passphrase(pw->pw_uid, prompt, 0);
         packet_start(SSH_CMSG_AUTH_TIS_RESPONSE);
-        packet_put_string(password, strlen(password));
+        ssh_put_password(password);
         memset(password, 0, strlen(password));
         xfree(password);
         packet_send();
@@ -1791,7 +1803,7 @@
         {
           password = read_passphrase(pw->pw_uid, prompt, 0);
           packet_start(SSH_CMSG_AUTH_PASSWORD);
-          packet_put_string(password, strlen(password));
+          ssh_put_password(password);
           memset(password, 0, strlen(password));
           xfree(password);
           packet_send();
--- ssh-1.2.27.orig/serverloop.c	Wed May 12 15:19:28 1999
+++ ssh-1.2.27/serverloop.c	Tue Feb 20 08:38:56 2001
@@ -522,6 +522,9 @@
 void process_output(fd_set *writeset)
 {
   int len;
+#ifdef USING_TERMIOS
+  struct termios tio;
+#endif

   /* Write buffered data to program stdin. */
   if (fdin != -1 && FD_ISSET(fdin, writeset))
@@ -543,7 +546,18 @@
         }
       else
         {
-          /* Successful write.  Consume the data from the buffer. */
+          /* Successful write. */
+#ifdef USING_TERMIOS
+          if (tcgetattr(fdin, &tio) == 0 &&
+              !(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) {
+            /* Simulate echo to reduce the impact of traffic analysis. */
+            packet_start(SSH_MSG_IGNORE);
+            memset(buffer_ptr(&stdin_buffer), 0, len);
+            packet_put_string(buffer_ptr(&stdin_buffer), len);
+            packet_send();
+          }
+#endif
+          /* Consume the data from the buffer. */
           buffer_consume(&stdin_buffer, len);
           /* Update the count of bytes written to the program. */
           stdin_bytes += len;


 SSHOW, l'outil d'analyse de trafic SSH
 --------------------------------------

Nous avons développé un outil d'analyse de trafic SSH, qui peut être utilisé
pour démontrer beaucoup des faiblesses décrites dans cet avis. Le source de
la version initiale de l'outil est inclus ci-dessous. Les versions futures
seront maintenues comme partie du package dsniff de Dug Song, accessible sur
:

	http://www.monkey.org/~dugsong/dsniff/

Les bibliothèques de réseau IP requises par SSHOW peuvent être obtenues sur
:

	http://www.tcpdump.org/release/
	http://www.packetfactory.net/Projects/Libnet/
	http://www.packetfactory.net/Projects/Libnids/

<++> sshow.c
/*
 * SSHOW.
 *
 * Copyright (c) 2000-2001 Solar Designer <solar@openwall.com>
 * Copyright (c) 2000 Dug Song <dugsong@monkey.org>
 *
 * You're allowed to do whatever you like with this software (including
 * re-distribution in source and/or binary form, with or without
 * modification), provided that credit is given where it is due and any
 * modified versions are marked as such.  There's absolutely no warranty.
 *
 * Note that you don't have to re-distribute modified versions of this
 * software under these same relaxed terms.  In particular, you're free to
 * place them under (L)GPL, thus disallowing re-distribution of further
 * modifications in binary-only form.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/times.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

extern char *optarg;
extern int optind;

#include <nids.h>

#if !defined(NIDS_MAJOR) || (NIDS_MAJOR == 1 && NIDS_MINOR < 15)
#error This code requires libnids 1.15+
#endif

#define HISTORY_SIZE			16

typedef struct {
	unsigned int min, max;
} range;

typedef struct {
	int direction;			/* 0 for client to server */
	clock_t timestamp;		/* timestamp of this packet */
	unsigned int cipher_size;	/* ciphertext size */
	range plain_range;		/* possible plaintext sizes */
} record;

struct history {
	record packets[HISTORY_SIZE];	/* recent packets (circular list) */
	int index;			/* next (free) index into packets[] */
	unsigned int directions;	/* recent directions (bitmask) */
	clock_t timestamps[2];		/* last timestamps in each direction */
};

struct line {
	int input_count;		/* input packets (client to server) */
	int input_size;			/* input size (estimated) */
	int input_last;			/* last input packet size */
	int echo_count;			/* echo packets (server to client) */
};

struct session {
	int protocol;		/* -1 not SSH, 0 unknown, 1 or 2 once known */
	int state;		/* 1 after username, 2 after authentication */
	int compressed;		/* whether compression is known to be used */
	struct history history;	/* session history */
	struct line line;	/* current command line */
};

static int debug = 0;

static clock_t now;

static clock_t add_history(struct session *session, int direction,
	unsigned int cipher_size, range *plain_range)
{
	record *current;
	clock_t delay;

	current = &session->history.packets[session->history.index++];
	session->history.index %= HISTORY_SIZE;

	current->direction = direction;
	current->timestamp = now;
	current->cipher_size = cipher_size;
	current->plain_range = *plain_range;

	session->history.directions <<= 1;
	session->history.directions |= direction;

	delay = now - session->history.timestamps[direction];
	session->history.timestamps[direction] = now;

	return delay;
}

static record *get_history(struct session *session, int age)
{
	int index;

	index = session->history.index + (HISTORY_SIZE - 1) - age;
	index %= HISTORY_SIZE;

	return &session->history.packets[index];
}

static char *s_saddr(struct tcp_stream *ts)
{
	static char output[32];

	snprintf(output, sizeof(output), "%s:%u",
		inet_ntoa(*((struct in_addr *)&ts->addr.saddr)),
		ts->addr.source);
	return output;
}

static char *s_daddr(struct tcp_stream *ts)
{
	static char output[32];

	snprintf(output, sizeof(output), "%s:%u",
		inet_ntoa(*((struct in_addr *)&ts->addr.daddr)),
		ts->addr.dest);
	return output;
}

static char *s_range(range *range)
{
	static char output[32];

	snprintf(output, sizeof(output),
		range->min == range->max ? "%u" : "%u to %u",
		range->min, range->max);
	return output;
}

static void print_data(struct half_stream *stream, unsigned int count)
{
	unsigned int i;
	int printable;

	printable = 1;
	for (i = 0; i < count; i++) {
		printf("%02x%c", (int)(unsigned char)stream->data[i],
			i < count - 1 && i % 24 != 23
			? ' ' : '\n');
		printable &=
			isprint(stream->data[i]) ||
			stream->data[i] == '\r' || stream->data[i] == '\n';
	}
	if (printable && count >= 4 && !memcmp(stream->data, "SSH-", 4))
		fwrite(stream->data, count, 1, stdout);
}

static unsigned int ssh1_plain_size(struct half_stream *stream)
{
	if (stream->count_new < 4) return 0;

	return (unsigned int)(unsigned char)stream->data[3] |
		((unsigned int)(unsigned char)stream->data[2] << 8) |
		((unsigned int)(unsigned char)stream->data[1] << 16) |
		((unsigned int)(unsigned char)stream->data[0] << 24);
}

static unsigned int ssh1_cipher_size(struct half_stream *stream)
{
	return 4 + ((ssh1_plain_size(stream) + 8) & ~7);
}

static range *ssh1_plain_range(struct half_stream *stream)
{
	static range output;

	output.min = output.max = ssh1_plain_size(stream) - 5;
	return &output;
}

static range *ssh2_plain_range(struct half_stream *stream)
{
	static range output;

	output.max = stream->count_new - 16;
	/* Assume min padding + 8-byte cipher blocksize */
	output.min = output.max - 7;
	if ((int)output.min < 0) output.min = 0;
	return &output;
}

static void client_to_server(struct tcp_stream *ts, struct session *session,
	unsigned int cipher_size, range *plain_range)
{
	clock_t delay;
	int payload, magic;

	delay = add_history(session, 0, cipher_size, plain_range);

	if (debug)
		printf("- %s -> %s: DATA (%s bytes, %.2f seconds)\n",
			s_saddr(ts), s_daddr(ts), s_range(plain_range),
			(float)delay / CLK_TCK);
	if (debug > 1)
		print_data(&ts->server, cipher_size);

	payload = plain_range->min;
	if (session->state == 2 && payload > 0) {
		session->line.input_count++;
		session->line.input_last = payload;
		if (session->protocol == 1)
			payload -= 4;
		else {
			payload -= 16 + 1;
			/* Handle different versions and cipher block sizes */
			for (magic = 40; magic <= 52; magic += 4)
			/* Assume several SSH-2 packets in this IP packet */
			if ((payload - (magic - 40)) % magic == 0) {
				payload -= magic - 40;
				/* One character per SSH-2 packet (typical) */
				payload /= magic;
				session->line.input_count += payload;
				break;
			}
			payload++;
		}
		if (payload <= 0) {
			if (payload < 0 && !session->compressed &&
			    session->protocol == 1) {
				session->compressed = 1;
				printf("+ %s -> %s: Compression detected, "
					"guesses will be much less reliable\n",
					s_saddr(ts), s_daddr(ts));
			}
			payload = 1;
		}
		session->line.input_size += payload;
	}
}

static void server_to_client(struct tcp_stream *ts, struct session *session,
	unsigned int cipher_size, range *plain_range)
{
	clock_t delay;
	int skip;
	range string_range;

	delay = add_history(session, 1, cipher_size, plain_range);

	if (debug)
		printf("- %s <- %s: DATA (%s bytes, %.2f seconds)\n",
			s_saddr(ts), s_daddr(ts), s_range(plain_range),
			(float)delay / CLK_TCK);
	if (debug > 1)
		print_data(&ts->client, cipher_size);

/*
 * Some of the checks may want to skip over multiple server responses.
 * For example, there's a debugging packet sent for every option found
 * in authorized_keys, but we can't use those packets in our pattern.
 */
	skip = 0;
	while (((session->history.directions >> skip) & 3) == 3)
		if (++skip > HISTORY_SIZE - 5) break;

	if (session->state == 0 &&
	    session->protocol == 1 &&
	    ((session->history.directions >> skip) & 7) == 5 &&
	    plain_range->min == 0 &&
	    get_history(session, skip + 1)->plain_range.min > 4 &&
	    get_history(session, skip + 2)->plain_range.min == 0) {
		session->state = 1;
		string_range = get_history(session, skip + 1)->plain_range;
		string_range.min -= 4; string_range.max -= 4;
		printf("+ %s -> %s: GUESS: Username length is %s\n",
			s_saddr(ts), s_daddr(ts), s_range(&string_range));
		return;
	}

	if (session->state == 0 &&
	    session->protocol == 2 &&
	    (session->history.directions & 7) == 5) {
		if (plain_range->min == 9 || plain_range->min == 4 + 9) {
			string_range = get_history(session, 1)->plain_range;

			if (string_range.min > 490 && string_range.min < 600) {
				session->state = 2;
				printf("+ %s -> %s: GUESS: DSA "
					"authentication accepted\n",
					s_saddr(ts), s_daddr(ts));
				return;
			} else
			if (string_range.min > 42 + 9) {
				session->state = 2;
				printf("+ %s -> %s: GUESS: Password "
					"authentication accepted\n",
					s_saddr(ts), s_daddr(ts));
				return;
			}
		} else
		if (plain_range->min > 12 + 9 && plain_range->min < 56 + 9) {
			string_range = get_history(session, 1)->plain_range;

			if (string_range.min > 490 && string_range.min < 600) {
				printf("+ %s -> %s: GUESS: DSA "
					"authentication failed\n",
					s_saddr(ts), s_daddr(ts));
				return;
			} else if (string_range.min > 42 + 9) {
				printf("+ %s -> %s: GUESS: Password "
					"authentication failed\n",
					s_saddr(ts), s_daddr(ts));
				return;
			}
		}
	}

	if (session->state == 1 &&
	    session->protocol == 1 &&
	    (session->history.directions & 3) == 1 &&
	    plain_range->min == 0 &&
	    get_history(session, 1)->plain_range.min == 130) {
		printf("+ %s -> %s: GUESS: RSA authentication refused\n",
			s_saddr(ts), s_daddr(ts));
		return;
	}

	if (session->state == 1 &&
	    session->protocol == 1 &&
	    skip >= 1 &&
	    ((session->history.directions >> (skip - 1)) & 037) == 013 &&
	    plain_range->min == 0 &&
	    get_history(session, skip - 1 + 2)->plain_range.min == 16 &&
	    get_history(session, skip - 1 + 3)->plain_range.min == 130 &&
	    get_history(session, skip - 1 + 4)->plain_range.min == 130) {
		char *what;

		switch (get_history(session, 1)->plain_range.min - 4) {
		case 28:
			/* "RSA authentication accepted." */
			session->state = 2;
			if (skip > 1 && (what = alloca(64))) {
				snprintf(what, 64,
				    "accepted (%d+ authorized_keys option%s)",
				    skip - 1, skip - 1 == 1 ? "" : "s");
				break;
			}
			what = "accepted";
			break;

		case 47:
			/* "Wrong response to RSA authentication challenge." */
			what = "failed";
			break;

		default:
			what = "???";
		}

		printf("+ %s -> %s: GUESS: RSA authentication %s\n",
			s_saddr(ts), s_daddr(ts), what);
		return;
	}

	if (session->state == 1 &&
#ifdef USE_TIMING
	    now - get_history(session, 2)->timestamp >= CLK_TCK &&
#endif
	    session->protocol == 1 &&
	    (session->history.directions & 7) == 5 &&
	    plain_range->min == 0 &&
	    get_history(session, 1)->plain_range.min > 4 &&
	    get_history(session, 2)->plain_range.min == 0) {
		session->state = 2;
		string_range = get_history(session, 1)->plain_range;
		string_range.min -= 4; string_range.max -= 4;
		printf("+ %s -> %s: GUESS: Password authentication, "
			"password length %s %s%s\n",
			s_saddr(ts), s_daddr(ts),
			string_range.min == 32 ? "appears to be" : "is",
			s_range(&string_range),
			string_range.min == 32 ? " (padded?)" : "");
		return;
	}

	if (session->state == 2) {
		session->line.echo_count++;

		/* Check for backspace */
		if (session->protocol == 1 && !session->compressed &&
		    plain_range->min == 4 + 3 &&
		    session->line.input_size >= 2)
			session->line.input_size -= 2;

		if (plain_range->min > 4 + session->line.input_last &&
		    session->line.input_count >= 2 &&
		    session->line.input_size >= 2) {
			int size;
			char *what;

			size = session->line.input_size;
			if (session->line.echo_count + 1 >=
			    session->line.input_count &&
			    size <= (session->line.input_count << 2) &&
			    size < 0x100)
				what = "(command) line";
			else
			if (session->line.echo_count <= 2 &&
			    size <= (session->line.input_count << 1) &&
			    size >= 2 + 1 && size <= 40 + 1)
				what = "password";
			else
				what = NULL;

			if (debug)
				printf("- %s -> %s: sent %d packets "
					"(%d characters), seen %d replies\n",
					s_saddr(ts), s_daddr(ts),
					session->line.input_count, size,
					session->line.echo_count);

			if (what)
				printf("+ %s -> %s: GUESS: "
					"a %s of %d character%s\n",
					s_saddr(ts), s_daddr(ts),
					what, size - 1, size == 2 ? "" : "s");
		}

		if (plain_range->min <= 0 ||
		    plain_range->min > 4 + session->line.input_last ||
		    session->line.input_last >= 0x100) {
			session->line.input_count = 0;
			session->line.input_size = 0;
			session->line.echo_count = 0;
		}
	}
}

static void process_data(struct tcp_stream *ts, struct session *session)
{
	unsigned int have, need;
	char *lf;

	if (session->protocol < 0) return;

	if (ts->client.count_new &&
	    (have = ts->client.count - ts->client.offset)) {
		switch (session->protocol) {
		case 1:
			if (have < (need = ssh1_cipher_size(&ts->client))) {
				if (debug)
					printf("- %s <- %s: got %u of "
						"%u bytes\n", s_saddr(ts),
						s_daddr(ts), have, need);
				nids_discard(ts, 0);
				return;
			}
			if (have != need && debug)
				printf("- %s <- %s: left %u bytes\n",
					s_saddr(ts), s_daddr(ts),
					have - need);
			nids_discard(ts, need);
			server_to_client(ts, session, need,
				ssh1_plain_range(&ts->client));
			return;

		case 2:
			server_to_client(ts, session, have,
				ssh2_plain_range(&ts->client));
			return;

		default:
			break;
		}
	}

	if (ts->server.count_new &&
	    (have = ts->server.count - ts->server.offset)) {
		if (!session->protocol) {
			lf = (char *)memchr(ts->server.data, '\n', have);
			if (have < 7 || (!lf && have < 0x100)) {
				nids_discard(ts, 0);
				return;
			}
			if (lf && !memcmp(ts->server.data, "SSH-", 4))
				session->protocol = ts->server.data[4] - '0';
			/* some clients announce SSH-1.99 instead of SSH-2.0 */
			if (session->protocol == 1 &&
			    ts->server.data[5] == '.' &&
			    ts->server.data[6] == '9')
				session->protocol = 2;
			if (session->protocol != 1 && session->protocol != 2) {
				session->protocol = -1;
				if (debug)
					printf("- %s -> %s: not SSH\n",
						s_saddr(ts), s_daddr(ts));
				return;
			}
			need = lf - ts->server.data + 1;
			nids_discard(ts, need);
			printf("+ %s -> %s: SSH protocol %d\n",
				s_saddr(ts), s_daddr(ts), session->protocol);
			if (debug)
				print_data(&ts->server, have);
			return;
		}

		switch (session->protocol) {
		case 1:
			if (have < (need = ssh1_cipher_size(&ts->server))) {
				if (debug)
					printf("- %s -> %s: got %u of "
						"%u bytes\n", s_saddr(ts),
						s_daddr(ts), have, need);
				nids_discard(ts, 0);
				return;
			}
			if (have != need && debug)
				printf("- %s -> %s: left %u bytes\n",
					s_saddr(ts), s_daddr(ts),
					have - need);
			nids_discard(ts, need);
			client_to_server(ts, session, need,
				ssh1_plain_range(&ts->server));
			return;

		case 2:
			client_to_server(ts, session, have,
				ssh2_plain_range(&ts->server));
		}
	}
}

static void process_event(struct tcp_stream *ts, struct session **session)
{
	struct tms buf;
	char *what;

	now = times(&buf);

	what = NULL;
	switch (ts->nids_state) {
	case NIDS_JUST_EST:
		ts->client.collect = 1;
		ts->server.collect = 1;
		if (debug)
			printf("- %s -> %s: ESTABLISHED\n",
				s_saddr(ts), s_daddr(ts));
		if (!(*session = calloc(1, sizeof(**session)))) {
			errno = ENOMEM;
			perror("calloc");
			exit(1);
		}
		(*session)->history.timestamps[0] = now;
		(*session)->history.timestamps[1] = now;
		return;

	case NIDS_CLOSE:
		what = "CLOSED";

	case NIDS_RESET:
		if (!what) what = "RESET";

	case NIDS_TIMED_OUT:
		if (!what) what = "TIMED OUT";
		if ((*session)->protocol > 0)
			printf("+ %s -- %s: %s\n",
				s_saddr(ts), s_daddr(ts), what);
		else if (debug)
			printf("- %s -- %s: %s\n",
				s_saddr(ts), s_daddr(ts), what);
		free(*session);
		return;

	case NIDS_DATA:
		process_data(ts, *session);
		return;
	}
}

static void dummy_syslog(int type, int errnum, struct ip *iph, void *data)
{
}

static void cleanup(int signum)
{
	exit(0);	/* Just so that atexit(3) jobs are called */
}

static void usage(void)
{
	fprintf(stderr, "Usage: sshow [-d] [-i interface]\n");
	exit(1);
}

int main(int argc, char *argv[])
{
	int c;

	while ((c = getopt(argc, argv, "di:h?")) != -1) {
		switch (c) {
		case 'd':
			debug++;
			break;
		case 'i':
			nids_params.device = optarg;
			break;
		default:
			usage();
			break;
		}
	}
	argc -= optind;
	argv += optind;

	if (argc != 0) usage();

	signal(SIGTERM, cleanup);
	signal(SIGINT, cleanup);
	signal(SIGHUP, cleanup);

	setlinebuf(stdout);

	nids_params.syslog = dummy_syslog;
	nids_params.scan_num_hosts = 0;
	nids_params.pcap_filter = "tcp";
	nids_params.one_loop_less = 1;
	if (!nids_init()) {
		fprintf(stderr, "nids_init: %s\n", nids_errbuf);
		return 1;
	}

	nids_register_tcp(process_event);
	nids_run();

	return 0;
}
<-->
<++> Makefile
CC = gcc
LD = gcc
RM = rm -f
CFLAGS = -c -Wall -O2 -fomit-frame-pointer -I/usr/local/include
LDFLAGS = -s
LIBS = -L/usr/local/lib -lnids -lnet -lpcap

PROJ = sshow
OBJS = sshow.o

all: $(PROJ)

sshow: $(OBJS)
	$(LD) $(LDFLAGS) $(OBJS) $(LIBS) -o sshow

.c.o:
	$(CC) $(CFLAGS) $*.c

clean:
	$(RM) $(PROJ) $(OBJS)
<-->


 Crédits et informations de contact
 ----------------------------------

Cet avis, l'outil de trafic SSHOW, et le patch non officiel pour SSH 1.2.x
ont été écrits par Solar Designer <solar@openwall.com> et Dug Song
<dugsong@monkey.org>. Nous aimerions remercier les auteurs de mises en
oeuvre SSH, spécialement Markus Friedl et Theo de Raadt (d'OpenSSH), Simon
Tatham (PuTTY), et Niels Möller (LSH) pour l'amélioration de nos
contre-mesures initiales contre l'analyse de trafic SSH. Nous aimerions
également remercier David Wagner et Dawn Xiaodong Song de l'Université de
Californie, Berkeley et Ariel Futoransky de CORE-SDI pour des discussions
perspicaces.

Les versions mises à jour de celui-ci et des autres avis Openwall seront
accessibles sur :

	http://www.openwall.com/advisories/

Note du traducteur : cette traduction française (non officielle) a été
réalisée par Denis Ducamp <Denis.Ducamp@groar.org> et est disponible sur :

	http://www.groar.org/~ducamp/english.html#sec-trad