Development Impacts of Security Changes in Windows Server 2003


Michael Howard
Secure Windows Initiative

June 16, 2003

Summary: Michael Howard outlines some of the security changes made to Microsoft Windows Server 2003 to reduce its attack surface, and discusses how these changes impact the software developer. (9 printed pages)

Note The security changes discussed in this article are in no particular order.

Is That Service You Rely on Running?

One of the first things we did when developing Microsoft Windows Server™ 2003 was to turn off less-commonly used services. If a service has a security bug, and it's not running by the default, then the exploit potential is dramatically reduced. The prime example is Internet Information Services (IIS). Many Web application applications build on IIS and their setup tools expect the Web server to be there—but guess what—by default it's not. You should determine your operating system dependencies, and then update your setup tool to inform the user if a dependency is not running. A good example is NetDDE. If your application uses NetDDE you'll need to (a) start the service because it's disabled by default or (b) migrate your application to a more modern protocol like, say, sockets!

IIS 6.0 Runs No User Code as SYSTEM

By default, all worker processes run as a Network Service. Once again, it's an in-depth defense mechanism. Some ISAPI applications perform the following steps:

  1. Call RevertToSelf, expecting the resulting process identity to be SYSTEM.
  2. Do some tasks that only SYSTEM can perform.
  3. Impersonate the user again.

This scenario does not work on IIS 6.0 because step 2 fails since user code runs in much lower privilege than SYSTEM. So how do you fix this? The only truly secure way to fix it is to change the way your ISAPI works. Frankly, you shouldn't process user's requests as SYSTEM, just in case your code is compromised. If you need to perform some request in elevated privilege, then create a service that runs with the required privileges and passes the request to the service over a pipe or socket. Another way to remedy the issue is to run the worker process as SYSTEM. However, this second solution is a kludge, and it's not the best scenario.

Impersonation Functions May Fail

I already discussed this in detail in the March 2003 article Impersonation Issues, but it's worth repeating. In Windows Server 2003 impersonation is a privilege granted only to admins, service accounts and COM+ processes running as specific identities. If your application calls any impersonation function, including SetThreadToken, it may fail unless it has the SeImpersonatePrivilege privilege. In short, if your code does fail, the token returned to you will be an identify token, and not an impersonation function. Refer to my previous article for more information and remedies.

Creating Various Global Objects May Fail

Starting with Windows Server 2003, the creation of some global objects, such as file mapping will fail unless the calling process has the SeCreateGlobalPrivilege privilege enabled. Note that the privilege check is limited to the creating of said objects, and does not apply to opening existing ones. For example, the following code will fail on Windows Server 2003 unless the process account has this privilege:

0, 0xFFFF,"Global\\MyMapping");

By default this privilege is assigned to all services and administrators. This privilege also applies when creating symbolic links in the object manager. The way around this is to either grant the account in question this privilege, or do not make the object name global. Of course, the latter may not be possible.

DLL Search Order Has Changed

No longer is the current directory searched first when loading DLLs! This change was also made in Windows XP SP1. The default behavior now is to look in all the system locations first, then the current directory, and finally any user-defined paths. This will have an impact on your code if you install a DLL in the application's directory because Windows Server 2003 no longer loads the 'local' DLL if a DLL of the same name is in the system directory. A common example is if an application won't run with a specific version of a DLL, an older version is installed that does work in the application directory. This scenario will fail in Windows Server 2003.

The reason this change was made was to mitigate some kinds of trojaning attacks. An attacker may be able to sneak a bad DLL into your application directory or a directory that has files associated with your application. The DLL search order change removes this attack vector.

The SetDllDirectory function, also available in Windows XP SP1, modifies the search path used to locate DLLs for the application and affects all subsequent calls to the LoadLibrary and LoadLibraryEx functions by the application.

Implications of Security Enhanced Internet Explorer

During the Windows Server 2003 security audit in December 2002, we asked the Internet Explorer team to evaluate the potential threats (I don't mean bugs or vulnerabilities—I mean avenues of attack and damage potential) when an administrator is browsing untrusted Internet-based Web sites from a computer acting as a domain controller. This led us to disable all mobile code capabilities in the browser including VBScript, JScript, ActiveX controls, Java, and .NET Framework components. The way the mobile code functionality is disabled is by setting very tight zone security policy and setting strict enhanced security settings. I'm not going to outline all the settings as they are available in the article Internet Explorer Enhanced Security Configuration. You should print this document out and read it a couple of times.

So what does this mean for you? That's a complex question because it depends on what you're doing. If you write Web server code that creates Web pages containing mobile code, then the client may not work correctly. But this is no different from a security-conscious user disabling, say, scripting.

In short, you should thoroughly test any application you use that relies on Internet Explorer technologies. If you find problems, the easiest fix is to change the browser settings to a more permissive setting, but I don't think that's necessarily the correct thing to do for your customers. Rather, I would recommend you add the Web server name to the Trusted Sites zone. Please don't ask the administrator to loosen the settings unless there is a really, really good reason.

Secure Root ACLs

We changed the ACLs on the file system root to be much more secure than Everyone (Full Control). The ACL is now:

Admin, SYS, Creator – Full Control
Everyone – Read/Execute
Users – Read/Execute
   Create Folders/Append Data (this and sub folders)
   Create Files/Write Data (sub folders)

If you want to understand why this change has been made, read Windows 2000 Default Permissions Could Allow Trojan Horse Program. Essentially, this means that your application cannot write to C:\ unless the user is an administrator. So, unless you have good reason to write to C:\ don't do it.

Secure Share ACLs

This is a favorite change of mine. Shares are no longer Everyone: Full Control, but rather Everyone: Read. This was changed as a direct consequence of the Nimda worm that wrote its payload to network shares on other computers. Of course, if you have good ACLs on the files and directories, then the ACL on the share is moot, but this is a defense in depth measure.

Tighter ACLs on Event Logs

We also tightened up the ACLs on the event logs to restrict what accounts can read and write to the logs. Better still, the security of each log is configured locally through the values in the following registry key:


For example, the Application log Security Descriptor is configured through the following registry value:


And the System log Security Descriptor is configured through the following:


The Security Descriptor for each log is specified by using Security Descriptor Definition Language (SDDL) syntax. The following is an example from the Application event log:


Quick, what is this ACL in English? You can read about SDDL at This string means:

Entry Meaning
O:BA Object owner is Built-in Admin (BA).
G:SY Primary group is System (SY).
D: This is a DACL, rather than an audit entry or SACL.
(D;;0xf0007;;;AN) Deny Anonymous (AN) all access.
(D;;0xf0007;;;BG) Deny Built-in Guests (BG) all access.
(A;;0xf0005;;;SY) Allow System Read and Clear, including DELETE, READ_CONTROL, WRITE_DAC, and WRITE_OWNER (indicated by the 0xf0000).
(A;;0x7;;;BA) Allow Built-in Admin READ, WRITE and CLEAR.
(A;;0x7;;;SO) Allow Server Operators READ, WRITE and CLEAR.
(A;;0x3;;;IU) Allow Interactive Users READ and WRITE.
(A;;0x3;;;SU) Allow Service accounts READ and WRITE.
(A;;0x3;;;S-1-5-3) Allow Batch accounts (S-1-5-3) READ and WRITE.

The specific event log access mask bits are:

0x0001 ELF_LOGFILE_READ Permission to read log files.
0x0002 ELF_LOGFILE_WRITE Permission to write log files.
0x0004 ELF_LOGFILE_CLEAR Permission to clear log files.

The only time you should see a failure in your application when writing to the event log is because of an ACL issue. Please do not relax the ACL too much. Add your own ACE to the SDDL string and then restart the Event Log service. For example, if your process runs under an account MyAccount, that has the SID S-1-5-21-853885456-2109860151-3743179773-1190, and you want the process to write to the Application log, simply add this string to the SDDL string in the registry:

(A;;0x2;;; S-1-5-21-853885456-2109860151-3743179773-1190)

Restricting Remote Execution of Console Applications

To help mitigate bad guys calling into cmd.exe (and other tools) remotely, we changed the ACL on some critical console applications. Compare the Windows® 2000 ACL on cmd.exe with the ACL on Windows Server 2003:

Windows 2000
Administrators (Full Control)
System (Full Control)
Users (Read & Execute)

Windows Server 2003
Administrators (Full Control)
System (Full Control)
Interactive (Read & Execute)
Services (Read & Execute)

Now, this probably won't affect you, but you should be aware of the ACL change just in case.

More Restrictions on Anonymous Users

This should not affect too many applications, unless it makes anonymous connections to Windows Server 2003 computers to gather information. Essentially, we removed the Everyone SID out of the anonymous token. Now you must explicitly grant anonymous access through Policy settings. To do so, load the Group Policy Object snap-in, and navigate to Computer Configuration -> Windows Settings -> Security Settings -> Local Policies -> Security Options.

In-Memory Encryption

Okay, this is hardly a security change that may cause your application to fail, but it's something you should be aware of, and frankly, it's one of my pet peeve features.

We added two new functions to Windows Server 2003 that allow you to encrypt and decrypt secret data, such as passwords and cryptographic keys, in memory. This reduces the potential data exposure. The new functions are CryptProtectMemory and CryptUnprotectMemory. Note this functionality is also available in Windows 2000 SP3 and Windows XP using the RtlEncryptMemory and RtlDecryptMemory functions, but they have no associated import library. They are available as resources named SystemFunction040 and SystemFunction041 in Advapi32.dll. You must use the LoadLibrary and GetProcAddress functions to dynamically link to Advapi32.dll.

The following C++ class code shows how to access the functions from Windows 2000 and Windows XP:

#include "windows.h"
#include "strsafe.h" 

#   define RTL_ENCRYPT_MEMORY_SIZE           16
#   define RTL_ENCRYPT_OPTION_SAME_PROCESS         0x00
#   define RTL_ENCRYPT_OPTION_SAME_LOGON           0x02

class CMemCrypt {
public : 

   CMemCrypt() {
      char szDir[MAX_PATH+1];
      UINT cchDir = sizeof(szDir)/sizeof(szDir[0]);
      if (GetSystemDirectory(szDir,cchDir) == 0)
         throw GetLastError();

      if (FAILED(StringCchCat(szDir,cchDir,"\\advapi32.dll")))

      m_hMod = LoadLibrary(szDir);
      if (NULL == m_hMod) 
         throw GetLastError();

      m_pEM = (EM)(GetProcAddress(m_hMod,"SystemFunction040"));
      m_pDM = (DM)(GetProcAddress(m_hMod,"SystemFunction041"));

        if (!m_pEM || !m_pDM) 
         throw GetLastError();

   virtual ~CMemCrypt() {
      if (m_hMod)

      m_pEM = m_pDM = NULL;
      m_hMod = NULL;

   DWORD Encrypt(PVOID pMem, ULONG cbMem, ULONG flags) {
      return (m_pEM)(pMem,cbMem,flags);

   DWORD Decrypt(PVOID pMem, ULONG cbMem, ULONG flags) {
      return (m_pDM)(pMem,cbMem,flags);

private :
   HMODULE m_hMod;

   typedef LONG (__stdcall *EM)(PVOID,ULONG,ULONG);
   typedef LONG (__stdcall *DM)(PVOID,ULONG,ULONG);
   EM m_pEM;
   DM m_pDM;

// example test code
int main(int argc, char* argv[]) {
   char p[16+1];
   p[16] = 0;

   CMemCrypt m;
   if (m.Encrypt(p,16,0))

   return 0;

A good starting point for more information regarding these functions is


In short, you should actively test your applications on a clean install of Windows Server 2003 to verify if any of the security changes affect your application. You can troubleshoot many security failures using the event log. In the case of ACL issues this will involve setting an audit ACE on the resource in question, and auditing for object access. For privilege issues, make sure you're auditing for failed privilege use.

Spot the Security Flaw

A bunch of people worked out my last error—it was a simple SQL injection attack. You can piggyback other SQL statements in the Id parameter, and thanks to the wonders of string concatenation, end up with a SQL query that does more than you expected, such as deleting database files, adding new user accounts, gathering credit card details, and so on. The remedy is to use SQL parameters, not string concatenation.

There are also a couple of other flaws people didn't pick up on, including connecting as the sysadmin account. Since when did you need to be sysadmin to query a table? Also, the code embeds a difficult-to-guess password in the code. Don't do that, ever! Finally, if the code fails for some reason, the code in exception handler tells the attacker exactly what failed.

Okay, onto the next error. What's up with this C# code?

// Restrict access to only non-FILE requests
// For example HTTP:// is valid, FILE:// is not
if (string.Compare("FILE",0,url.ToUpper(),0,4) != 0)

I'll give a hint in a week or so. You'll probably need it!

Michael Howard is a Senior Security Program Manager in the Secure Windows Initiative group at Microsoft and is the coauthor of Writing Secure Code, now in its second edition, and the main author of Designing Secure Web-based Applications for Windows 2000. His main focus in life is making sure people design, build, test, and document nothing short of a secure system. His favorite line is "One person's feature is another's exploit."