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 * Copyright (c) 2000 Dug Song * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern char *optarg; extern int optind; #include #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 et Dug Song . 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 et est disponible sur : http://www.groar.org/~ducamp/english.html#sec-trad