Chapter 21. Secure Software Installation

The installation process is one of the most overlooked aspects of application security, and installation errors account for a sizable proportion of security patches. If you do a thorough job coding a network service that doesn’t contain buffer overflows and resists denial of service (DoS) attacks, you could be quite startled to find that your installation routine has turned your carefully crafted application into a local escalation of privilege attack.

The root of the problem is that much of the commonly used installation software available doesn’t have a clue about security settings; at least, that’s true at the time of this writing. Hopefully, this will change, but in the meantime, if you want to create a secure installation, you’re going to have to do some extra work. Even though the setup software might not be able to secure your application, it can invoke external processes. Either you can invoke your own application to create secure settings or, if you’re able to target Microsoft Windows 2000 (or later) or Microsoft Windows NT 4 with the Security Configuration Editor installed, you can leverage this handy tool to save you a lot of work.

I had the opportunity to deal with this problem in depth when I worked with the Internet Security Scanner while working at Internet Security Systems. Early in the process of porting the scanner from UNIX to Windows NT, I thought about how the application was quickly gathering quite a bit of information about how to break into many of the systems on our network. You definitely don’t want that sort of information to be trivially made available to anyone with access to the system. I then took a look at the registry keys where I was storing the configuration information and thought about how disastrous it would be if someone were to turn on all the DoS attacks or otherwise alter my configuration settings. By the time I was done, the scanner would verify that all the output and application directories were set to allow access only to administrators every time the scanner started, and we had also written applications to properly set access controls on both the file system and the registry. A network security auditing tool is an extreme example of a sensitive application, but I subsequently found a large number of security settings in the operating system itself that opened potential security holes by accepting the defaults. Everything I found ended up getting patched, and the default security settings in Windows 2000 were greatly improved when it shipped.

Principle of Least Privilege

The principle of least privilege states that you should give a user the ability to do what he needs to do and nothing more. Properly defining the boundary between your application binaries and user data will also make your application easier to secure, not to mention help you attain Windows 2000 (and later) compliance. So let’s think this through: who really needs to be able to overwrite your binaries? Typically, that would be administrators, and, if your application allows ordinary users to install a personal copy, creator-owner should have full control access. And who needs to be able to write data files to your installation directory? Hopefully, no one—any files written by an ordinary user ought to be kept in that user’s profile. If you do allow users to write their own files to a common directory, you need to be careful with the access rights.

Now consider configuration settings—I hope you’re storing per-user configuration settings under HKEY_CURRENT_USER, not HKEY_LOCAL_MACHINE. Apply the same logic to your configuration information that you would to the file system. Is this application sensitive enough that nonadministrators shouldn’t be changing the settings? Would any of the configuration settings possibly lead to escalation of privilege?

Let’s look at some real-world examples. The Systems Management Server (SMS) Remote Agent service runs under the local system context, but the folder that it is installed into allows everyone full control access by default. For full details (and a fix), see http://www.microsoft.com/technet/security/bulletin/fq00-012.asp. I’m aware of services shipped by other vendors that make the same mistake. In general, an application should never grant everyone write access, and if the application is meant to be primarily run by administrators or the local system account, only administrators should be able to change the executable.

The permissions on the AeDebug key under Windows NT 4.0 show another problem. AeDebug specifies the application that should be run if another application crashes. Although the binary that should have been run was safe on the file system, the configuration settings that pointed to it weren’t properly secured. (The details can be found at http://www.microsoft.com/TechNet/security/bulletin/fq00-008.asp.) What good does that do, you might ask. So what if I can crash one of my own applications—it will just run the debugger under my user context. What if there is an application that is running under the local system account that has a DoS vulnerability present? (This is a good example of why even application crashes can be very dangerous.) Now we can change the debugger, crash the application, and have local system executing the code of our choice!

A milder form of the same problem happened with the Simple Network Management Protocol (SNMP) parameters, detailed in http://www.microsoft.com/TechNet/security/bulletin/fq00-096.asp. SNMP—short for Security Not My Problem, according to a friend of mine—is an insecure protocol that is widely used for network management chores. SNMP bases access controls on a shared secret known as a community string. It isn’t a very good secret because it will be found on dozens of devices, and to make matters much worse, it’s transmitted almost completely in the clear (obfuscated with a programmer-unfriendly encoding scheme). Anyone with access to a network sniffer can capture a few of these packets, decode the information, and capture the community string. The problem with the permissions on the Parameters subkey for the SNMP service is that everyone has read permission by default (locally—you can’t get to it from the network). If a community string that has write access is present, an ordinary user can read it, send the system SNMP SET requests, and do things she should not. Even more severe examples of the same problem exist. Certain applications have been known to store passwords—sometimes only weakly encrypted or in the clear—in world-readable portions of the registry and even embedded in files. The important thing to remember is that some information shouldn’t be accessible to just anyone who logs on to the system. Think about whether your application has information like this, and make sure you protect it properly.

Another problem I sometimes see is information that has differing levels of security needs stored in the registry under the same key. Unlike the file system, you can’t set access controls on individual values under the same registry key. Consider a situation in which one value could have serious security implications (e.g., the user account the service uses) and there is another value that the user account running the application must be able to update. Now you have a problem. To properly secure the key, the key must be writable by only administrators. This implies your application must run as an administrator-level account. Now when your application gets compromised, the whole system gets compromised as well. If you try to run your application as an ordinary user, the attacker only needs to use the app to modify the security-sensitive value. The bottom line is to store only information with the same security needs in the same registry key. The same issue applies to a configuration file.

I was recently in a meeting with a group that wanted my advice on how to best secure portions of their application. I asked how they secured their files, and they replied, "We always write into the Program Files directory—it has good access controls by default." It’s true that Program Files has a reasonable set of permissions, so I asked what happened if the user chose to install somewhere else, say, off the root of a freshly formatted NTFS partition. They started looking worried, and rightfully so—their application would have ended up granting everyone full control. To avoid this situation, take control of your own access control lists.

Clean Up After Yourself!

A number of issues have cropped up where an installation program left files lying around with either clear-text passwords or obfuscated passwords. If your installation routine must deal with passwords or other very sensitive information, check to see whether this gets left in a file once setup is complete. One strategy is to use a custom setup application to handle passwords safely, and another is to use a postinstall step to clean up the files. A problem with this approach is that sometimes a setup will be aborted—sometimes with Task Manager if it is hung—and postinstallation steps won’t be completed. Leaving passwords lying around on the hard drive is a great way to end up with your very own CVE (Common Vulnerabilities and Exposures) entry!

Using the Security Configuration Editor

The Security Configuration Editor first shipped with service pack 4 for Windows NT 4 and is present by default on Windows 2000 and later. It consists of a pair of Microsoft Management Console (MMC) snap-ins and a command line application. Let’s say that you’ve thought carefully about how to secure your application and that your application installs in a single directory and creates one registry key under HKEY_LOCAL_MACHINESoftware. First start MMC and add the Security Templates and Security Configuration And Analysis snap-ins, as shown in Figure 21-1.

The Add/Remove Snap-In window, showing the Security Templates and the Security Configuration And Analysis snap-ins added to MMC.

Figure 21-1. The Add/Remove Snap-In window, showing the Security Templates and the Security Configuration And Analysis snap-ins added to MMC.

Next we need to create a custom security database and template. The tool won’t let you create a database without applying a template, so that’s the first step. Expand the Security Templates tree, right-click the %systemroot%SecurityTemplate, and choose New Template. Supply a name for this template. I named my new template null because it doesn’t set anything at all. Figure 21-2 shows the MMC console after the new template is created.

The MMC console, showing the null security template.

Figure 21-2. The MMC console, showing the null security template.

Next create a new configuration database. Right-click the Security Configuration And Analysis snap-in, and choose Open Database. Type in the name and path for the database you want to create. I used NewApp.sdb for this example. The Import Template dialog box, shown in Figure 21-3, will prompt you for a template to associate with the database. Choose the null template you just created.

The Import Template dialog box, where you can specify a template to associate with the database.

Figure 21-3. The Import Template dialog box, where you can specify a template to associate with the database.

Next create a template that defines the settings your application needs. Precreate the registry key and a directory that you can use to define settings on. Go back to MMC, as shown in Figure 21-4, right-click the Registry portion of the template, and choose Add Key.

The MMC console with the new template node expanded.

Figure 21-4. The MMC console with the new template node expanded.

Navigate the tree in the Select Registry Key dialog box until you locate your key, and set the permissions you’d like applied using the ACL editor tool. Now do the same thing with the File System folder. If you have individual files that need special permissions, you can set them here. Save your template, and close MMC so that it will release the database. If you open the template with Notepad, it will look like this:

[Unicode]
Unicode=yes
[Registry Values]
[Registry Keys]
"MACHINESOFTWARENewApp",0,"D:PAR(A;OICI;KA;;;BA)(A;CI;CCSWRC;;;WD) "
[File Security]
"E:NewApp",0,"D:AR(A;OICI;FA;;;BA)(A;OICI;0x1f00e9;;;W D)"
[Version]
signature="$CHICAGO$"
Revision=1

Edit any lines that point to the root of your installation directory (E:NewApp, in this example), and change them to %newapp_install%. Next compile and run the following code. This sample code is also available with the book’s sample files in the folder Secureco2Chapter21SecInstall.

/*
  This application takes a security template .inf file,
  substitutes a user-supplied directory for %newapp_install%,
  and writes it to a custom .inf file that you can apply
  to the directory your user chose.
*/

#define UNICODE
#include <windows.h>
#include <stdio.h>

/* 
  I really hate tracking all my code paths to make sure I
  don’t leak handles, so I write lots of classes like this.
*/
class SmartHandle
{
public:
    SmartHandle()
    {
        Handle = INVALID_HANDLE_VALUE;
    }

    ~SmartHandle()
    {
        if(IsValid())
        {
            CloseHandle(Handle);
        }
    }

    bool IsValid(void)
    {
        if(Handle != INVALID_HANDLE_VALUE &&
            Handle != NULL)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    HANDLE Handle;
};

/*
  Tired of having to convert arguments to UNICODE?
  Use wmain instead of main, and they’ll be passed in as UNICODE.
*/

int wmain(int argc, WCHAR* argv[])
{
    SmartHandle hInput;
    SmartHandle hOutput;
    SmartHandle hMap;
    WCHAR* pFile;
    WCHAR* pTmp;
    WCHAR* pLast;
    DWORD filesize;
    DWORD dirlen;

    if(argc != 4)
    {
        wprintf(L"Usage is %s [input file], argv[0]);
        wprintf(L" [output file] [install directory]
");
        return -1;
    }

    dirlen = wcslen(argv[3]);

    hInput.Handle = CreateFile(argv[1], 
                               GENERIC_READ, 
                               0,    //Don’t share the file.
                               NULL, //Don’t change the security.
                OPEN_EXISTING, //Fail if the file isn’t present.
                FILE_ATTRIBUTE_NORMAL, // Just a normal file
                NULL);         //No template

    if(!hInput.IsValid())
    {
        wprintf(L"Cannot open %s
", argv[1]);
        return -1;
    }

    DWORD highsize = 0;
    filesize = GetFileSize(hInput.Handle, &highsize);

    if(highsize != 0 || filesize == ~0)
    {
        //The file is bigger than 4 GB -  
        //what kind of .inf file is this???
    wprintf(L"%s is too large to map or size not found
", argv[1]);
        return -1;
    }

    /*
      Same as the previous function except that you always 
      create the file
    */
    hOutput.Handle = CreateFile(argv[2],
                 GENERIC_WRITE,
                 0,
                 NULL,
                 CREATE_ALWAYS,
                 FILE_ATTRIBUTE_NORMAL,
                 NULL);

    if(!hOutput.IsValid())
    {
        wprintf(L"Cannot open %s
", argv[2]);
        return -1;
    }

    //Now that we have the input and output files open,     
    //map a view of the input file.
    //Memory-mapped files are cool and make many tasks easier.

    hMap.Handle = CreateFileMapping(hInput.Handle, //Open file
                  NULL,          //No special security
                  PAGE_READONLY, //Read-only
                  0,             //Don’t specify max size
                  0,             //or min size - will be size of file.
                  NULL);         //We don’t need a name.


    if(!hMap.IsValid())
    {
        wprintf(L"Cannot map %s
", argv[1]);
        return -1;
    }

    //Start at the beginning of the file, and map the whole thing.
    pFile = (WCHAR*)MapViewOfFile(hMap.Handle, 
                                  FILE_MAP_READ, 0, 0, 0);

    if(pFile == NULL)
    {
        wprintf(L"Cannot map view of %s
", argv[1]);
        return -1;
    }

    //Now we’ve got a pointer to the whole file - 
    //let’s look for the string we want.

    pTmp = pLast = pFile;
    DWORD subst_len = wcslen(L"%newapp_install%");

    while(1)
    {
        DWORD written, bytes_out;

        pTmp = wcsstr(pLast, L"%newapp_install%");

        if(pTmp != NULL)
        {
            //Found the string.
            //How many bytes to write?

            bytes_out = (pTmp - pLast) * sizeof(WCHAR);

            if(!WriteFile(hOutput.Handle, pLast, bytes_out, 
                &written, NULL) || bytes_out != written )
            {
                wprintf(L"Cannot write to %s
", argv[2 ]);
                return -1;
            }

            //Now instead of %newapp_install%, print the actual dir.
            if(!WriteFile(hOutput.Handle, argv[3], 
                dirlen * sizeof(WCHAR), &written, NULL) ||
                dirlen * sizeof(WCHAR) != written)
            {
                wprintf(L"Cannot write to %s
", argv[2]);
                UnmapViewOfFile(pFile);
                return -1;
            }

            pTmp += subst_len;
            pLast = pTmp;
        }
        else
        {
            //Didn’t find the string -  write the rest of the file.
            bytes_out = (BYTE*)pFile + filesize - (BYTE*)pLast;

            if(!WriteFile(hOutput.Handle, pLast, bytes_out, 
                &written, NULL) || bytes_out != written)
            {
                wprintf(L"Cannot write to %s
", argv[2]);
                UnmapViewOfFile(pFile);
                return -1;
            }
            else
            {
                //We’re done.
                UnmapViewOfFile(pFile);
                break;
            }
        }
    }

    //All the rest of our handles close automagically.
    return 0;
}

Pretty cool, huh? I bet you thought I was going to do something lame like ask your users to edit the .inf file themselves. That wouldn’t do any good; users don’t do complicated steps, just like they usually don’t Read The Fine Manual. Now that you’ve taken your user-supplied directory path, simply run the following command:

[e:]secedit /configure /db NewApp.sdb /cfg out.inf /areas 
REGKEYS FILESTORE /verbose

Now your application installation will be done securely—the only step you have left is to verify that the permissions you set up were really what you wanted. You can also leave the Out.inf file in case the user wants to restore the application security settings to default. Once you’ve done the hard part (thinking) and set up the database and .inf files, the rest of it can easily run from within your installation scripts. Given my past experiences doing this the hard way for an application that had to support Windows NT 3.51 and 4, the time this approach will save you ought to be worth the price of this book!

Low-Level Security APIs

I’ve often had the luxury of being able to specify to the customer the version of the operating system that my application required. For many applications you don’t have that luxury, and you’re stuck supporting installations on several versions of the Windows NT family. The system APIs that are available vary quite a bit with operating system version. The only API calls we had available until Windows NT 4 were what are now considered the low-level API calls. Although you need to take a lot of care when using them, I still prefer to get as close to the operating system as I can if I’m going to manipulate a security descriptor directly. For example, AddAccessAllowedAce doesn’t correctly set the inheritance bits in the access control entry (ACE) header. If you build every field of the ACE by hand and then call AddAce, you’ll get exactly what you set. (There is an AddAccessAllowedAceEx function, which does allow you to properly set the ACE headers, but it is limited to Windows 2000 and later.)

Numerous texts and samples demonstrate writing to the low-level security APIs, including my article at http://www.windowsitsecurity.com/Articles/Index.cfm?ArticleID=9696. If you need to use the low-level API calls, I would urge you to test your code very carefully. A step that you should consider mandatory is using the user interface to set the discretionary access control list (DACL) to what you want and then either doing an extremely detailed dump of the security descriptor or saving it in binary format in self-relative form. Then set the DACL by using your code, and compare the two. If they don’t match perfectly, find out why. It is possible to create (and apply) a DACL that is full of errors to an object. Common errors include applying the ACEs in the wrong order and getting the ACE header flags wrong.

Using the Windows Installer

An explanation of how to use the Windows Installer is well beyond the scope of this book. If you need to understand the basics of how it is used, please refer to the Microsoft Platform SDK. The Platform SDK documentation also lists a number of security issues you should be concerned with, and because the SDK gets updated much more often than this book, I’d encourage you to read the section entitled "Guidelines for Authoring Secure Installations." That said, let’s take a look at some of the issues you’ll encounter:

  • Like any other software installation, you need to be concerned with installing applications under an administrator-level account into directories that could be modified by lower-level users. Unlike many installers, the Windows Installer provides you with a LockPermissions table that allows you to set access controls on files, directories, and registry keys.

  • An installation package contains a number of properties. Properties can be classified as private, public, or restricted public. If a user should be allowed to change a property, it must be classified as public, but if a package is run with elevated privileges, some settings may need to be set to restricted public. Never use properties for passwords or other sensitive information. The installer might write the property table into a log or the registry.

  • When using the installer to install a service, try to avoid specifying a particular user account. You’ll encounter problems with user-password pairs stored in the installation package and, as above, sensitive data could end up getting written into a log or the registry. In addition to these problems, the package will need to be updated every time the password changes. To make matters worse, installing a service under the same account on many machines makes them all dependent upon one another for their security.

  • Packages should be signed in order to verify that they have not been tampered with, and this should certainly be done with packages that install with elevated privileges. If an administrator needs to repackage the application, it can be resigned.

  • A package should be authored such that a failure to obtain needed resources does not cause the setup to fail in a way that would compromise security. For example, if an installation application running with elevated privileges was unable to locate resources, an Open dialog box used to find a resource could possibly be used to manipulate the file system inappropriately. Measures that will help prevent this problem include checking to be sure that a user has all required resources early in the install process and using source resiliency mechanisms in case a network install point is not available. For additional details on source resiliency, look in the Platform SDK documentation index.

  • A transform is used to customize the application for a given set of users. It’s generally best to use a secured transform. Secured transforms can be stored locally in an area where ordinary users cannot change them, or they can be stored at the source of the installation package.

  • Custom actions allow you to create installation routines that invoke external executables. Although it would be unusual for an application to need more than can be done within the Windows Installer, it’s nice to be able to extend the functionality. If an application runs with escalated privileges, custom actions run under the user context of the installing user unless the msidbCustomActionTypeNoImpersonate bit is set, and then only if the administrator has permitted the install.

Although creating a Windows Installer package might be a little extra work, Windows Installer makes it much easier to deploy your application in environments where the console user isn’t an administrator. Windows Installer is also one of the few installation mechanisms that allow defining custom access controls.

Summary

We’ve covered several of the potential mistakes that can be made during installation. Although creating installation routines isn’t glamorous, mistakes made during this stage can result in severe compromises. Plan on setting access controls for your sensitive data—don’t just rely on inheriting correct permissions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.87.161