OW-003-ssh-traffic-analysis-fr, révision 1 19 Mars 2001 (Note du traducteur : traduction achevée le 05 avril 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 (toujours en cours de travail), 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. Bien que l'article n'est pas encore publiquement accessible, les vendeurs travaillant à fixer ces problèmes peuvent contacter David Wagner ou Dawn Xiaodong Song pour obtenir une copie. 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. 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 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] == '\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; 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 -= 20 + 1; /* Assume several SSH-2 packets in this IP packet */ if (payload % 44 == 0) { session->line.input_count += payload / 44; /* One character per SSH-2 packet (typical) */ payload += payload / 44; payload %= 44; } 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 == 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?)" : ""); } if (session->state == 0 && session->protocol == 2 && (session->history.directions & 7) == 5) { if (plain_range->min == 4 + 9) { string_range = get_history(session, 1)->plain_range; if (string_range.min > 500 && string_range.min < 600) { session->state = 2; printf("+ %s -> %s: GUESS: DSA " "authentication accepted\n", s_saddr(ts), s_daddr(ts)); } else if (string_range.min > 42 + 9) { session->state = 2; printf("+ %s -> %s: GUESS: Password " "authentication accepted\n", s_saddr(ts), s_daddr(ts)); } } else if (plain_range->min > 12 + 9 && plain_range->min < 56 + 9) { string_range = get_history(session, 1)->plain_range; if (string_range.min > 500 && string_range.min < 600) printf("+ %s -> %s: GUESS: DSA " "authentication failed\n", s_saddr(ts), s_daddr(ts)); else if (string_range.min > 42 + 9) printf("+ %s -> %s: GUESS: Password " "authentication failed\n", s_saddr(ts), s_daddr(ts)); } } 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)); } 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); } 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_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