Windows Access Control List (ACL) 11

 

 

Windows Privileges

 

A privilege is the right of an account, such as a user or group account, to perform various system-related operations on the local computer, such as shutting down the system, loading device drivers, or changing the system time.  Privileges differ from access rights in two ways:

 

  1. Privileges control access to system resources and system-related tasks, whereas access rights control access to securable objects.
  2. A system administrator assigns privileges to user and group accounts, whereas the system grants or denies access to a securable object based on the access rights granted in the ACEs of the object's DACL.

 

Each system has an account database that stores the privileges held by user and group accounts.  When a user logs on, the system produces an access token that contains a list of the user's privileges, including those granted to the user or to groups to which the user belongs.  Note that the privileges apply only to the local computer; a domain account can have different privileges on different computers. For example, the user Rights assignments on the local computer can be seen through the Local Security Settings (Administrative Tools > Local Security Policy) snap-in. Keep in mind that for Windows server there are another two security policies: Domain Security Policy and DC Security Policy. We can just enable and disable the Windows Rights.

 

The User Rights Assignment of the Local Security Settings

 

 

 

When the user tries to perform a privileged operation, the system checks the user's access token to determine whether the user holds the necessary privileges, and if so, it checks whether the privileges are enabled.  If the user fails these tests, the system does not perform the operation.  To determine the privileges held in an access token, call the GetTokenInformation() function, which also indicates which privileges are enabled.  Most privileges are disabled by default.  The Windows API defines a set of string constants, such as SE_ASSIGNPRIMARYTOKEN_NAME, to identify the various privileges.  These constants are the same on all systems and are defined in winnt.h.  However, the functions those get and adjust the privileges in an access token use the LUID type to identify privileges.  The LUID values for a privilege can differ from one computer to another and from one boot to another on the same computer. To get the current LUID that corresponds to one of the string constants, use the LookupPrivilegeValue() function.  Use the LookupPrivilegeName() function to convert a LUID to its corresponding string constant.  The system provides a set of display names that describe each of the privileges.  These are useful when you need to display a description of a privilege to the user.  You can use the LookupPrivilegeDisplayName() function to retrieve a description string that corresponds to the string constant for a privilege.  For example, on systems that use U.S. English, the display name for the SE_SYSTEMTIME_NAME privilege is Change the system time.  You can use the PrivilegeCheck() function to determine whether an access token holds a specified set of privileges.  This is useful primarily to server applications that are impersonating a client.

A system administrator can use administrative tools, such as User Manager, to add or remove privileges for user and group accounts.  Administrators can programmatically use the LSA functions to work with privileges.  The LsaAddAccountRights() and LsaRemoveAccountRights() functions add or remove privileges from an account.  The LsaEnumerateAccountRights() function enumerates the privileges held by a specified account.  The LsaEnumerateAccountsWithUserRight() function enumerates the accounts that hold a specified privilege.  The information for LUID structure is given in the following Table.  Do not manipulate LUID directly.  Applications should use functions and structures to manipulate LUID values.

 

Running with Special Privileges

 

Some functions require special privileges to run correctly.  In some cases, the function can only be run by certain users or by members of certain groups.  The most common requirement is that the user be a local administrator.  Other functions require the user's account to have specific privileges enabled.  To reduce the possibility of unauthorized code being able to get control, the system should run with the least privilege necessary.  Applications that need to call functions that require special privileges can leave the system open to attack by hackers.  Such applications should be designed to run for short periods of time and should inform the user of the security implications involved.

 

Running with Administrator Privileges

 

The first step in establishing which type of account your application needs to run under is to examine what resources the application will use and what privileged APIs it will call.  You may find that the applications, or large parts of it, do not require administrator privileges.  You can provide the privileges your application needs with less exposure to malicious attack by using one of the following approaches:

 

  1. Run under an account with less privilege.  One way to do this is to use PrivilegeCheck() to determine what privileges are enabled in a token.  If the available privileges are not adequate for the current operation, you can disable that code and ask the user to logon to an account with administrator privileges.
  2. Break into a separate application functions that require administrator permissions.  You can provide for the user a shortcut that executes the RunAs command.  Programmatically, you can configure the RunAs command under the AppId registry key for your application.
  3. Authenticate the user by calling CredUIPromptForCredentials() (GUI) or CredUICmdLinePromptForCredentials() (command line) to obtain user name and password.
  4. Impersonate the user.  A process that starts under a highly privileged account like System can impersonate a user account by calling ImpersonateLoggedOnUser() or similar Impersonate functions, thus reducing privilege level.  However, if a call to RevertToSelf() is injected into the thread, the process returns to the original System privileges.

 

If you have determined that your application must run under an account with administrator privileges and that an administrator password must be stored in the software system.

 

Asking the User for Credentials

 

Your application may need to prompt the user for user name and password information to avoid storing an administrator password or to verify that the token holds the appropriate privileges.  However, simply prompting for credentials may train users to supply those to any random, unidentified dialog box that appears on the screen.  The following procedure is recommended to reduce that training effect.

 

Acquiring user credentials

 

The recommended steps to properly acquire user credentials:

 

  1. Inform the user, by using a message that is clearly part of your application, that they will see a dialog box that requests their user name and password.  You can also use the CREDUI_INFO structure on the call to CredUIPromptForCredentials() to convey identifying data or a message.
  2. Call CredUIPromptForCredentials().  Note that the maximum number of characters specified for user name and password information includes the terminating null character.
  3. Call CredUIParseUserName() and CredUIConfirmCredentials() to verify that you obtained appropriate credentials.

 

The following code snippet shows how to call CredUIPromptForCredentials() to ask the user for a user name and password.  It begins by filling in a CREDUI_INFO structure with information about what prompts to use. Next, the code fills two buffers with zeros.  This is done to ensure that no information gets passed to the function that might reveal an old user name or password to the user.  The call to CredUIPromptForCredentials() brings up the dialog box.  For security reasons, this example uses the CREDUI_FLAGS_DO_NOT_PERSIST flag to prevent the operating system from storing the password because it might then be exposed.  If there are no errors, CredUIPromptForCredentials() fills in the pszName and pszPwd variables and returns zero.  When the application has finished using the credentials, it should put zeros in the buffers to prevent the information from being accidentally revealed.

 

#include <windows.h>

#include <wincred.h>

 

CREDUI_INFO cui;

WCHAR pszName[CREDUI_MAX_USERNAME_LENGTH+1];

WCHAR pszPwd[CREDUI_MAX_PASSWORD_LENGTH+1];

BOOL fSave;

DWORD dwErr;

 

cui.cbSize = sizeof(CREDUI_INFO);

cui.hwndParent = NULL;

//  Ensure that MessageText and CaptionText identify what credentials to use and which application requires them.

cui.pszMessageText = LEnter administrator account information;

cui.pszCaptionText = LCredUITest;

cui.hbmBanner = NULL;

fSave = FALSE;

SecureZeroMemory(pszName, sizeof(pszName));

SecureZeroMemory(pszPwd, sizeof(pszPwd));

dwErr = CredUIPromptForCredentials(

                 &cui,                        // CREDUI_INFO structure

                 LTheServer,                          // Target for credentials, usually a server

                 NULL,                        // Reserved

                 0,                           // Reason

                 pszName,                     // User name

                 CREDUI_MAX_USERNAME_LENGTH+1,// Max number of char for user name

                 pszPwd,                      // Password

                 CREDUI_MAX_PASSWORD_LENGTH+1,// Max number of char for password

                 &fSave,                      // State of save check box

                 CREDUI_FLAGS_GENERIC_CREDENTIALS |  // flags

                 CREDUI_FLAGS_ALWAYS_SHOW_UI |

                 CREDUI_FLAGS_DO_NOT_PERSIST); 

 

if(!dwErr)

{

    //  TODO: Put code that uses the credentials here.

    //  When you have finished using the credentials, erase them from memory.

    SecureZeroMemory(pszName, sizeof(pszName));

    SecureZeroMemory(pszPwd, sizeof(pszPwd));

}

 

Changing Privileges in a Token

 

You can change the privileges in either a primary or an impersonation token in two ways:

 

  1. Enable or disable privileges by using the AdjustTokenPrivileges() function.
  2. Restrict or remove privileges by using the CreateRestrictedToken() function.

 

AdjustTokenPrivileges() cannot add or remove privileges from the token.  It can only enable existing privileges that are currently disabled or disable existing privileges that are currently enabled.  CreateRestrictedToken() has more extensive capabilities as follows:

 

  1. Removing a privilege.  Note that removing a privilege is not the same as disabling one.  After a privilege is removed from a token, it cannot be put back.
  2. Attaching the deny-only attribute to SIDs in the token.  This has the effect of disallowing specific groups or accounts, for example, denying the Everyone group delete access to a particular file.
  3. Specifying a list of restricting SIDs in the token.

 

Enabling and Disabling Privileges

 

Enabling a privilege in an access token allows the process to perform system-level actions that it could not previously.  Your application should thoroughly verify that the privilege is appropriate to the type of account, especially for the following powerful privileges:

 

Privilege constant/string

Display name

SE_ASSIGNPRIMARYTOKEN_NAME (SeAssignPrimaryTokenPrivilege)

Replace a process level token.

SE_BACKUP_NAME (SeBackupPrivilege)

Backup files and directories.

SE_DEBUG_NAME (SeDebugPrivilege)

Debug programs.

SE_INCREASE_QUOTA_NAME (SeIncreaseQuotaPrivilege)

Adjust memory quotas for a process.

SE_TCB_NAME (SeTchPrivilege)

Act as part of the operating system.

 

Table 13

 

Before enabling any of these potentially dangerous privileges, determine that functions or operations in your code actually require the privileges.  For example, very few functions in the operating system actually require the SeTchPrivilege.

 

 

 

< Windows ACL 10 | Windows Access Control List (ACL) Main | Win32 Programming | Windows ACL 12 >