A privilege is a right assigned to a trustee that has system-wide ramifications. (In contrast, an access right assigned to a trustee defines access allowed to a specific object in the system.) Examples of privileges include the rights to log on locally, back up files and directories, and shut down the system.
Another way of looking at a privilege is as a way to secure a system function. For example, certain system functions, such as LogonUser and InitiateSystemShutdown, require that the calling software have the proper privileges or else the function will fail.
Privileges have two textual representations you should be aware of. These representations are the friendly name that shows up in the user interface for Windows and the programmatic name used by your software. Each of the programmatic names is defined in a header file in the SDK.
Table 9-10 lists all the privileges available in Windows 2000 at the time of this book's printing. A partial list is given in the Platform SDK documentation.
Table 9-10. Privileges and account rights
Programmatic Name | Display Name | Header File |
---|---|---|
SeAssignPrimaryTokenPrivilege SE_ASSIGNPRIMARYTOKEN_NAME | Replace a process level token | WinNT.h |
SeAuditPrivilege SE_AUDIT_NAME | Generate security audits | WinNT.h |
SeBackupPrivilege SE_BACKUP_NAME | Back up files and directories | WinNT.h |
SeChangeNotifyPrivilege SE_CHANGE_NOTIFY_NAME | Bypass traverse checking | WinNT.h |
SeCreatePagefilePrivilege SE_CREATE_PAGEFILE_NAME | Create a pagefile | WinNT.h |
SeCreatePermanentPrivilege SE_CREATE_PERMANENT_NAME | Create permanent shared objects | WinNT.h |
SeCreateTokenPrivilege SE_CREATE_TOKEN_NAME | Create a token object | WinNT.h |
SeDebugPrivilege SE_DEBUG_NAME | Debug programs | WinNT.h |
SeEnableDelegationPrivilege SE_ENABLE_DELEGATION_NAME | Enable computer and user accounts to be trusted for delegation | WinNT.h |
SeIncreaseBasePriorityPrivilege SE_INC_BASE_PRIORITY_NAME | Increase scheduling priority | WinNT.h |
SeIncreaseQuotaPrivilege SE_INCREASE_QUOTA_NAME | Increase quotas | WinNT.h |
SeLoadDriverPrivilege SE_LOAD_DRIVER_NAME | Load and unload device drivers | WinNT.h |
SeLockMemoryPrivilege SE_LOCK_MEMORY_NAME | Lock pages in memory | WinNT.h |
SeMachineAccountPrivilege SE_MACHINE_ACCOUNT_NAME | Add workstations to domain | WinNT.h |
SeProfileSingleProcessPrivilege SE_PROF_SINGLE_PROCESS_NAME | Profile single process | WinNT.h |
SeRemoteShutdownPrivilege SE_REMOTE_SHUTDOWN_NAME | Force shutdown from a remote system | WinNT.h |
SeRestorePrivilege SE_RESTORE_NAME | Restore files and directories | WinNT.h |
SeSecurityPrivilege SE_SECURITY_NAME | Manage auditing and security log | WinNT.h |
SeShutdownPrivilege SE_SHUTDOWN_NAME | Shut down the system | WinNT.h |
SeSyncAgentPrivilege SE_SYNC_AGENT_NAME | Synchronize directory service data | WinNT.h |
SeSystemEnvironmentPrivilege SE_SYSTEM_ENVIRONMENT_NAME | Modify firmware environment values | WinNT.h |
SeSystemProfilePrivilege SE_SYSTEM_PROFILE_NAME | Profile system performance | WinNT.h |
SeSystemtimePrivilege SE_SYSTEMTIME_NAME | Change the system time | WinNT.h |
SeTakeOwnershipPrivilege SE_TAKE_OWNERSHIP_NAME | Take ownership of files or other objects | WinNT.h |
SeTcbPrivilege SE_TCB_NAME | Act as part of the operating system | WinNT.h |
SeUndockPrivilege SE_UNDOCK_NAME | Remove computer from docking station | WinNT.h |
SeUnsolicitedInputPrivilege SE_UNSOLICITED_INPUT_NAME | Receive unsolicited device input | WinNT.h |
SeBatchLogonRight SE_BATCH_LOGON_NAME | Log on as a batch job | NTSecAPI.h |
SeDenyBatchLogonRight SE_DENY_BATCH_LOGON_NAME | Deny logon as a batch job | NTSecAPI.h |
SeDenyInteractiveLogonRight SE_DENY_INTERACTIVE_LOGON_NAME | Deny logon locally | NTSecAPI.h |
SeDenyNetworkLogonRight SE_DENY_NETWORK_LOGON_NAME | Deny access to this computer from the network | NTSecAPI.h |
SeDenyServiceLogonRight SE_DENY_SERVICE_LOGON_NAME | Deny logon as a service | NTSecAPI.h |
SeInteractiveLogonRight SE_INTERACTIVE_LOGON_NAME | Log on locally | NTSecAPI.h |
SeNetworkLogonRight SE_NETWORK_LOGON_NAME | Access this computer from the network | NTSecAPI.h |
SeServiceLogonRight SE_SERVICE_LOGON_NAME | Log on as a service | NTSecAPI.h |
The system actually distinguishes between the privilege and its close cousin, the account right, but they are assigned and revoked identically. In Table 9-10, the privileges are defined in WinNT.h and the account rights are defined in NTSecAPI.h.
Now that you have some familiarity with what privileges are and how the system represents them, let's dive into assigning and revoking privileges on a system.
To assign privileges programmatically, you need to become familiar with a set of functions known as the LSA functions. LSA stands for Local Security Authority, which handles user logon and authentication on the local machine. The LSA functions can be used to do a number of tasks, of which we will only be concerned with privilege assignment in this chapter.The first step in using the LSA functions is to retrieve a handle to a policy object. A policy object represents the system on which you will be performing account management functions. To obtain a handle to a policy object, you must call LsaOpenPolicy.
NTSTATUS LsaOpenPolicy( PLSA_UNICODE_STRING plsastrSystemName, PLSA_OBJECT_ATTRIBUTES pObjectAttributes, ACCESS_MASK DesiredAccess, PLSA_HANDLE pPolicyHandle); |
This function might look a bit strange at first, because the data types aren't commonly used in other Win32 functions. So let's examine them one by one.
The plsastrSystemName parameter is a pointer to an LSA_UNICODE_STRING structure, which is defined as follows:
typedef struct _LSA_UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } LSA_UNICODE_STRING |
Like the Net functions, the LSA functions deal only with strings in Unicode format. However, unlike the Net functions, the LSA functions require that all strings be managed in terms of the LSA_UNICODE_STRING structure, which is simply a string length, a buffer length, and a pointer to a buffer.
NOTE
It is important to remember that the Length (indicating the length of the string) and MaximumLength (indicating the length of the buffer) members are stored in terms of bytes, not characters.
The string pointed to by the Buffer member of the LSA_UNICODE_STRING structure is not explicitly defined to be zero-terminated. Although you can choose to end a string with a zero, you must be sure not to include the null termination in your calculation of the length of the string. Also keep in mind that when the system returns an LSA_UNICODE_STRING structure to your software, it cannot count on zero termination of the string pointed to by the Buffer member.
Dealing with the LSA_UNICODE_STRING type can be a little awkward because it is length-delimited rather than zero-terminated. Because of this, I think that the LSA_UNICODE_STRING virtually begs to be wrapped in a simple C++ class. I've made a class called CLSAStr, which does this, and use it liberally in the TrusteeMan sample application described later in this chapter.
To call LsaOpenPolicy, initialize an instance of the LSA_UNICODE_STRING structure and point it to a Unicode string containing the name of the system for which you wish to manipulate privileges. Passing NULL as the plsastrSystemName parameter indicates the local machine.
The pObjectAttributes parameter points to an instance of the LSA_OBJECT_ATTRIBUTES structure, which is unused at this time and should have each of its members initialized to 0.
The DesiredAccess parameter is declared as type ACCESS_MASK, which maps to a 32-bit unsigned integer. If you will be viewing privilege information on your system, combine POLICY_VIEW_LOCAL_INFORMATION and POLICY_LOOKUP_NAMES as in the example that follows this discussion. If you will be setting privilege information, you should also combine the POLICY_CREATE_ACCOUNT access flag.
The final parameter of LsaOpenPolicy requires that you pass the address of a variable of type LSA_HANDLE. Upon success, the system will place the handle to the open LSA policy object in this variable. Once you have finished with the policy object, you should pass this handle to the LsaClose function:
NTSTATUS LsaClose(LSA_HANDLE hObjectHandle); |
All LSA functions return a value of type NTSTATUS, and you should pass this value to LsaNtStatusToWinError:
ULONG LsaNtStatusToWinError(NTSTATUS Status); |
The LsaNtStatusToWinError function converts the NT status code to a familiar Win32 error code just like those returned from GetLastError. After conversion, a successful call to LsaOpenPolicy returns a value of ERROR_SUCCESS.
The following code shows the proper way to open an LSA policy object for a system on the network whose name is "JasonsComputer":
LSA_OBJECT_ATTRIBUTES lsaOA = { 0 }; LSA_UNICODE_STRING lsastrComputer = { 0 }; LSA_HANDLE hPolicy = NULL; // Computer Name WCHAR* pstrName = L"JasonsComputer"; // Set the size of the useless LSA_OBJECT_ATTRIBUTES structure lsaOA.Length = sizeof(lsaOA); // Fill in the members of the LSA_UNICODE_STRING structure lsastrComputer.Length = (USHORT) (lstrlen(pstrName) * sizeof(WCHAR)); lsastrComputer.MaximumLength = lsastrComputer.Length + sizeof(WCHAR); lsastrComputer.Buffer = pstrName; // Retrieve the policy handle NTSTATUS ntStatus = LsaOpenPolicy(&lsastrComputer, &lsaOA, POLICY_VIEW_LOCAL_INFORMATION | POLICY_LOOKUP_NAMES | POLICY_CREATE_ACCOUNT, &hPolicy); ULONG lErr = LsaNtStatusToWinError(ntStatus); if (lErr != ERROR_SUCCESS){ // Handle error } |
Now that you know how to obtain an open handle to an LSA policy object, you are ready to begin managing the privileges on the system.
There are two ways to enumerate the privileges on a system: either obtain a list of the privileges held by a specific trustee, or request a list of trustees that hold a specific privilege.
Let's start by discussing how to obtain a list of privileges held by a specific trustee of the system. To enumerate the privileges, use the LsaEnumerateAccountRights function:
NTSTATUS LsaEnumerateAccountRights( LSA_HANDLE hPolicy, PSID psidTrustee, PLSA_UNICODE_STRING* pplsastrUserRights, PULONG plCountOfRights); |
You must pass an open policy object created with POLICY_LOOKUP_NAMES access as the hPolicy parameter of LSAEnumerateAccountRights. The psidTrustee parameter is a pointer to the SID for the trustee for which you want to enumerate privileges. You can use LookupAccountName (discussed earlier in the section "Understanding SIDs") to obtain a SID from a trustee's account name. This trustee can be a user account, group account, or computer account.
You should pass the address of a variable of type LSA_UNICODE_STRING as the pplsastrUserRights parameter of LsaEnumerateAccountRights. The system generates an array of LSA_UNICODE_STRING structures, allocates a buffer to hold the array, and then places a pointer to the buffer in the variable pointed to by the pplsastrUserRights parameter. The system then returns the number of privileges in the array in the ULONG variable pointed to by plCountOfRights.
Because the system has allocated a buffer on your behalf, you must free the buffer when you are finished with it. Passing a pointer to the buffer to the LsaFreeMemory function does this:
NTSTATUS LsaFreeMemory(PVOID pvBuffer); |
If LsaEnumerateAccountRights succeeds, the translated status code will be ERROR_SUCCESS. If the account has no privileges assigned to it, the translated error code will be ERROR_FILE_NOT_FOUND. The elements of the array returned by LsaEnumerateAccountRights are of type LSA_UNICODE_STRING (defined earlier in the section "The LSA Functions"). Each element will point to a buffer containing a Unicode string representation of an account right, including values such as SeDebugPrivilege and SeEnableDelegationPrivilege. You can refer back to Table 9-10 for a list of all possible returned privileges.
Although it is not directly related to enumerating privileges, the LookupPrivilegeDisplayName function should be mentioned. This function translates a programmatic privilege name, such as SeTcbPrivilege, into its friendly display name, which in this case would be "Act as part of the operating system".
BOOL LookupPrivilegeDisplayName( PCTSTR pszSystemName, PCTSTR pszName, PTSTR pszDisplayName, PDWORD cbDisplayName, PDWORD pLanguageId); |
This function takes the system name and the programmatic name for the privilege, and then returns the friendly name in a buffer that you supply.
NOTE
LookupPrivilegeDisplayName will not return the display name for an account right. It works only for privileges. Although our discussion here does not distinguish between these two types of account right, this topic is discussed more fully in Chapter 11.You can determine whether an account right is a privilege by referring to Table 9-10. If the right in question is defined in the WinNT.h header file, it is a privilege. If it is defined in the NTSecAPI.h header file, it is only an account right, not an actual privilege.
The following sample function shows how to list all the privileges held by a trustee. It takes an LSA policy handle and a PSID as its parameters.
BOOL PrintTrusteePrivs(LSA_HANDLE hPolicy, PSID psid) { BOOL fSuccess = FALSE; WCHAR szTempPrivBuf[256]; WCHAR szPrivDispBuf[1024]; PLSA_UNICODE_STRING plsastrPrivs = NULL; __try { // Retrieve the array of privileges for the given SID ULONG lCount = 0; NTSTATUS ntStatus = LsaEnumerateAccountRights(hPolicy, psid, &plsastrPrivs, &lCount); ULONG lErr = LsaNtStatusToWinError(ntStatus); if (lErr != ERROR_SUCCESS) { plsastrPrivs = NULL; __leave; } ULONG lDispLen = 0; ULONG lDispLang = 0; for (ULONG lIndex = 0; lIndex < lCount; lIndex++) { // Assure zero termination lstrcpyn(szTempPrivBuf, plsastrPrivs[lIndex].Buffer, plsastrPrivs[lIndex].Length); szTempPrivBuf[plsastrPrivs[lIndex].Length] = 0; wprintf(L"Programmatic Name: %s\n", szTempPrivBuf); // Translate to Display Name lDispLen = 1024; // Size of static Display buffer if (LookupPrivilegeDisplayName(NULL, szTempPrivBuf, szPrivDispBuf, &lDispLen, &lDispLang)) wprintf(L"Display Name: %s\n\n", szPrivDispBuf); } fSuccess = TRUE; } __finally { if (plsastrPrivs) LsaFreeMemory(plsastrPrivs); } return(fSuccess); } |
Now for the second way to retrieve privilege information for a Windows 2000 system: request a list of trustees that hold a specific privilege by calling LsaEnumerateAccountsWithUserRight.
NTSTATUS LsaEnumerateAccountsWithUserRight( LSA_HANDLE hPolicy, PLSA_UNICODE_STRING plsastrUserRight, PVOID* ppvEnumerationBuffer, PULONG CountReturned); |
This function requires a handle to an LSA policy object much like LsaEnumerateAccountRights does. However, instead of filling an LSA_UNICODE_STRING structure with the name of a trustee, you should pass a pointer to an LSA_UNICODE_STRING structure complete with the programmatic name of a privilege or an account right.
The system returns trustee information to your software by allocating a buffer and returning its pointer via the ppvEnumerationBuffer parameter. Although this parameter is defined as a pointer to a PVOID, you should pass the address of a pointer to a variable of type PLSA_ENUMERATION_INFORMATION because the system will return the information as an array of LSA_ENUMERATION_INFORMATION structures. It would have been nice if the developers of Windows had defined LsaEnumerateAccountsWithUserRight to take a pointer to the correct type, but they chose instead to require a pointer to a PVOID, so you will have to cast this parameter.
The LSA_ENUMERATION_INFORMATION structure is actually very simple and contains only a single member—a pointer to a SID:
typedef struct _LSA_ENUMERATION_INFORMATION { PSID Sid; } LSA_ENUMERATION_INFORMATION; |
The number of elements in the array is returned in the variable pointed to by CountReturned, which is the final parameter of LsaEnumerateAccountsWithUserRight. The SIDs in the returned array can be used to generate a trustee name by passing the SID to LookupAccountSid, discussed earlier in this chapter. When you are finished with the buffer returned by LsaEnumerateAccountsWithUserRight, you should pass its pointer to LsaFreeMemory.
It is uncommon to have to create software solely to enumerate the privileges given to a trustee account. However, just about any software that creates trustee accounts will need to assign (and perhaps remove) privileges from an account. Fortunately, the LSA functions provide two simple functions to perform these tasks. The first, LsaAddAccountRights, is used to grant privileges to a trustee:
NTSTATUS LsaAddAccountRights( LSA_HANDLE hPolicy, PSID psidTrustee, PLSA_UNICODE_STRING plsastrUserRights, ULONG lCountOfRights); |
This function is easy to use. You must pass the handle to an open LSA policy object, as well as a pointer to the SID for the trustee whose privileges you wish to modify. (You can obtain a SID for a user or group account by calling LookupAccountName, as discussed earlier in this chapter.)
Before calling LsaAddAccountRights, however, you should build an array of LSA_UNICODE_STRING structures, each with the name of a privilege you want the trustee to hold. Pass a pointer to this array as the plsastrUserRights parameter of LsaAddAccountRights. Finally, pass the number of elements in the array as the final parameter, lCountOfRights.
The LsaAddAccountRights function ignores any account rights or privileges already held by the trustee. It fails, however, if any of the rights in the array are not valid names for the system.
The following code provides an example of how to assign the SeInteractiveLogonRight account right and the SeTcbPrivilege privilege to a trustee account whose SID is pointed to by the variable psid.
LSA_UNICODE_STRING lsastrPrivs[2] = { 0 }; lsastrPrivs[0].Buffer = SE_INTERACTIVE_LOGON_NAME; lsastrPrivs[0].Length = lstrlen(lsastrPrivs[0].Buffer) * sizeof(WCHAR); lsastrPrivs[0].MaximumLength = lsastrPrivs[0].Length + sizeof(WCHAR); lsastrPrivs[1].Buffer = SE_TCB_NAME; lsastrPrivs[1].Length = lstrlen(lsastrPrivs[1].Buffer) * sizeof(WCHAR); lsastrPrivs[1].MaximumLength = lsastrPrivs[1].Length + sizeof(WCHAR); NTSTATUS ntStatus = LsaAddAccountRights(hPolicy, psid, lsastrPrivs, 2); ULONG lErr = LsaNtStatusToWinError(ntStatus); |
The function used to remove privileges from a trustee account is similar to LsaAddAccountRights. It is called LsaRemoveAccountRights and is prototyped as follows:
NTSTATUS LsaRemoveAccountRights( LSA_HANDLE hPolicy, PSID psidTrustee, BOOLEAN fAllRights, PLSA_UNICODE_STRING plsastrUserRights, ULONG lCountOfRights); |
This function is the same as LsaAddAccountRights except that it removes the list of rights from the trustee indicated by psidTrustee and has one extra parameter, the Boolean fAllRights.
The fAllRights parameter allows you to remove all privileges from a trustee without building a list of the privileges held by the trustee. If you pass TRUE for this parameter, the plsastrUserRights and lCountOfRights parameters should be NULL and 0, respectively. If you pass FALSE for the fAllRights parameter, LsRemoveAccountRights is used the same way as LsaAddAccountRights.
Before wrapping up our discussion of LsaRemoveAccountRights, I should point out one detail about removing all rights from an account. The Platform SDK documentation states that if you pass TRUE for the fAllRights parameter, the system will remove all privileges from the account and then delete the account from the system. However, this is true only from a certain perspective. Each system running Windows 2000 must maintain a local database of account privilege entries (technically implemented internally as a local account) even if the trustee account lives on another machine such as a domain controller. The LsaRemoveAccountRights function will not delete a trustee account (even if the account is local) from the system that hosts the account; it deletes only the account privilege entries from its local database.